From 98acb33018ba7492e4fb6262528762b642f4fdde Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:48:06 +0200 Subject: [PATCH 01/21] Merge Nukkit PM1E --- .github/CONTRIBUTING.md | 107 - README.md | 26 +- build.gradle.kts | 22 +- gradle/libs.versions.toml | 9 + src/main/java/cn/nukkit/Achievement.java | 47 +- .../java/cn/nukkit/AdventureSettings.java | 143 +- src/main/java/cn/nukkit/IPlayer.java | 44 +- .../java/cn/nukkit/InterruptibleThread.java | 7 +- src/main/java/cn/nukkit/Nukkit.java | 81 +- src/main/java/cn/nukkit/OfflinePlayer.java | 13 +- src/main/java/cn/nukkit/Player.java | 6190 +++++++++++------ src/main/java/cn/nukkit/PlayerFood.java | 82 +- src/main/java/cn/nukkit/Server.java | 1736 +++-- src/main/java/cn/nukkit/api/API.java | 130 - src/main/java/cn/nukkit/block/Block.java | 767 +- .../nukkit/block/BlockAcaciaSignStanding.java | 45 + .../cn/nukkit/block/BlockAcaciaWallSign.java | 40 + src/main/java/cn/nukkit/block/BlockAir.java | 4 +- src/main/java/cn/nukkit/block/BlockAllow.java | 41 + .../java/cn/nukkit/block/BlockAmethyst.java | 46 + .../cn/nukkit/block/BlockAmethystBud.java | 111 + .../nukkit/block/BlockAmethystBudLarge.java | 37 + .../nukkit/block/BlockAmethystBudMedium.java | 37 + .../nukkit/block/BlockAmethystBudSmall.java | 37 + .../cn/nukkit/block/BlockAmethystCluster.java | 80 + .../cn/nukkit/block/BlockAncientDebris.java | 46 + src/main/java/cn/nukkit/block/BlockAnvil.java | 51 +- .../java/cn/nukkit/block/BlockAzalea.java | 100 + .../cn/nukkit/block/BlockAzaleaFlowering.java | 17 + .../cn/nukkit/block/BlockAzaleaLeaves.java | 93 + .../block/BlockAzaleaLeavesFlowered.java | 22 + .../java/cn/nukkit/block/BlockBamboo.java | 318 + .../cn/nukkit/block/BlockBambooSapling.java | 172 + .../java/cn/nukkit/block/BlockBanner.java | 28 +- .../java/cn/nukkit/block/BlockBarrel.java | 171 + .../java/cn/nukkit/block/BlockBarrier.java | 57 + .../java/cn/nukkit/block/BlockBasalt.java | 97 + .../cn/nukkit/block/BlockBasaltSmooth.java | 60 + .../java/cn/nukkit/block/BlockBeacon.java | 50 +- src/main/java/cn/nukkit/block/BlockBed.java | 192 +- .../java/cn/nukkit/block/BlockBedrock.java | 5 +- .../nukkit/block/BlockBedrockInvisible.java | 11 +- .../java/cn/nukkit/block/BlockBeeNest.java | 55 + .../java/cn/nukkit/block/BlockBeehive.java | 77 + .../java/cn/nukkit/block/BlockBeetroot.java | 6 +- src/main/java/cn/nukkit/block/BlockBell.java | 401 ++ .../nukkit/block/BlockBirchSignStanding.java | 40 + .../cn/nukkit/block/BlockBirchWallSign.java | 40 + .../java/cn/nukkit/block/BlockBlackstone.java | 57 + .../nukkit/block/BlockBlackstoneGilded.java | 87 + .../nukkit/block/BlockBlackstonePolished.java | 19 + .../BlockBlackstonePolishedChiseled.java | 14 + .../cn/nukkit/block/BlockBlackstoneWall.java | 22 + .../cn/nukkit/block/BlockBlastFurnace.java | 27 + .../cn/nukkit/block/BlockBlastFurnaceLit.java | 87 + .../java/cn/nukkit/block/BlockBlueIce.java | 34 + src/main/java/cn/nukkit/block/BlockBone.java | 16 +- .../java/cn/nukkit/block/BlockBookshelf.java | 13 +- .../java/cn/nukkit/block/BlockBorder.java | 41 + .../cn/nukkit/block/BlockBrewingStand.java | 74 +- .../java/cn/nukkit/block/BlockBricks.java | 13 +- .../block/BlockBricksBlackstonePolished.java | 14 + .../BlockBricksBlackstonePolishedCracked.java | 14 + .../cn/nukkit/block/BlockBricksDeepslate.java | 50 + .../block/BlockBricksDeepslateCracked.java | 18 + .../cn/nukkit/block/BlockBricksEndStone.java | 5 +- .../cn/nukkit/block/BlockBricksNether.java | 5 +- .../block/BlockBricksNetherChiseled.java | 14 + .../block/BlockBricksNetherCracked.java | 14 + .../cn/nukkit/block/BlockBricksRedNether.java | 6 +- .../cn/nukkit/block/BlockBricksStone.java | 7 +- .../cn/nukkit/block/BlockBubbleColumn.java | 180 + .../cn/nukkit/block/BlockBuddingAmethyst.java | 96 + .../java/cn/nukkit/block/BlockButton.java | 40 +- .../cn/nukkit/block/BlockButtonAcacia.java | 22 + .../cn/nukkit/block/BlockButtonBirch.java | 22 + .../cn/nukkit/block/BlockButtonCrimson.java | 22 + .../cn/nukkit/block/BlockButtonDarkOak.java | 22 + .../cn/nukkit/block/BlockButtonJungle.java | 22 + .../block/BlockButtonPolishedBlackstone.java | 22 + .../cn/nukkit/block/BlockButtonSpruce.java | 22 + .../cn/nukkit/block/BlockButtonStone.java | 2 +- .../cn/nukkit/block/BlockButtonWarped.java | 22 + .../java/cn/nukkit/block/BlockCactus.java | 52 +- src/main/java/cn/nukkit/block/BlockCake.java | 31 +- .../java/cn/nukkit/block/BlockCalcite.java | 46 + .../java/cn/nukkit/block/BlockCampfire.java | 262 + .../cn/nukkit/block/BlockCampfireSoul.java | 41 + .../java/cn/nukkit/block/BlockCarpet.java | 23 +- .../java/cn/nukkit/block/BlockCarrot.java | 10 +- .../nukkit/block/BlockCartographyTable.java | 42 + .../java/cn/nukkit/block/BlockCauldron.java | 312 +- .../cn/nukkit/block/BlockCauldronLava.java | 113 + .../java/cn/nukkit/block/BlockCaveVines.java | 238 + .../block/BlockCaveVinesBerriesBody.java | 34 + .../block/BlockCaveVinesBerriesHead.java | 34 + src/main/java/cn/nukkit/block/BlockChain.java | 112 + src/main/java/cn/nukkit/block/BlockChest.java | 53 +- .../cn/nukkit/block/BlockChorusFlower.java | 215 +- .../cn/nukkit/block/BlockChorusPlant.java | 72 +- src/main/java/cn/nukkit/block/BlockClay.java | 10 +- src/main/java/cn/nukkit/block/BlockCoal.java | 4 +- .../cn/nukkit/block/BlockCobblestone.java | 7 +- .../java/cn/nukkit/block/BlockCobweb.java | 25 +- src/main/java/cn/nukkit/block/BlockCocoa.java | 123 +- .../cn/nukkit/block/BlockCommandBlock.java | 41 + .../nukkit/block/BlockCommandBlockChain.java | 41 + .../block/BlockCommandBlockRepeating.java | 41 + .../java/cn/nukkit/block/BlockComposter.java | 214 + .../cn/nukkit/block/BlockConcretePowder.java | 21 +- .../java/cn/nukkit/block/BlockConduit.java | 59 + .../java/cn/nukkit/block/BlockCopper.java | 31 + .../java/cn/nukkit/block/BlockCopperBase.java | 98 + .../java/cn/nukkit/block/BlockCopperCut.java | 51 + .../nukkit/block/BlockCopperCutExposed.java | 31 + .../block/BlockCopperCutExposedWaxed.java | 23 + .../nukkit/block/BlockCopperCutOxidized.java | 31 + .../block/BlockCopperCutOxidizedWaxed.java | 23 + .../cn/nukkit/block/BlockCopperCutWaxed.java | 23 + .../nukkit/block/BlockCopperCutWeathered.java | 31 + .../block/BlockCopperCutWeatheredWaxed.java | 23 + .../cn/nukkit/block/BlockCopperExposed.java | 31 + .../nukkit/block/BlockCopperExposedWaxed.java | 23 + .../cn/nukkit/block/BlockCopperOxidized.java | 31 + .../block/BlockCopperOxidizedWaxed.java | 23 + .../cn/nukkit/block/BlockCopperWaxed.java | 23 + .../cn/nukkit/block/BlockCopperWeathered.java | 31 + .../block/BlockCopperWeatheredWaxed.java | 23 + src/main/java/cn/nukkit/block/BlockCoral.java | 98 + .../java/cn/nukkit/block/BlockCoralBlock.java | 123 + .../java/cn/nukkit/block/BlockCoralFan.java | 194 + .../cn/nukkit/block/BlockCoralFanDead.java | 48 + .../cn/nukkit/block/BlockCoralFanHang.java | 83 + .../cn/nukkit/block/BlockCoralFanHang2.java | 26 + .../cn/nukkit/block/BlockCoralFanHang3.java | 22 + .../cn/nukkit/block/BlockCraftingTable.java | 37 +- .../cn/nukkit/block/BlockCrimsonDoor.java | 29 + .../cn/nukkit/block/BlockCrimsonFungus.java | 97 + .../cn/nukkit/block/BlockCrimsonNylium.java | 25 + .../cn/nukkit/block/BlockCrimsonPlanks.java | 45 + .../cn/nukkit/block/BlockCrimsonRoots.java | 39 + .../cn/nukkit/block/BlockCrimsonSign.java | 40 + .../cn/nukkit/block/BlockCrimsonStairs.java | 39 + .../cn/nukkit/block/BlockCrimsonStem.java | 51 + .../cn/nukkit/block/BlockCrimsonTrapdoor.java | 30 + .../cn/nukkit/block/BlockCrimsonWallSign.java | 29 + src/main/java/cn/nukkit/block/BlockCrops.java | 17 +- .../java/cn/nukkit/block/BlockDandelion.java | 1 + .../block/BlockDarkOakSignStanding.java | 40 + .../cn/nukkit/block/BlockDarkOakWallSign.java | 40 + .../nukkit/block/BlockDaylightDetector.java | 61 +- .../block/BlockDaylightDetectorInverted.java | 20 +- .../java/cn/nukkit/block/BlockDeadBush.java | 18 +- .../java/cn/nukkit/block/BlockDeepslate.java | 106 + .../nukkit/block/BlockDeepslateChiseled.java | 17 + .../nukkit/block/BlockDeepslateCobbled.java | 40 + .../nukkit/block/BlockDeepslatePolished.java | 18 + src/main/java/cn/nukkit/block/BlockDeny.java | 41 + .../java/cn/nukkit/block/BlockDiamond.java | 5 +- src/main/java/cn/nukkit/block/BlockDirt.java | 37 +- .../java/cn/nukkit/block/BlockDirtRooted.java | 81 + .../java/cn/nukkit/block/BlockDispenser.java | 163 +- src/main/java/cn/nukkit/block/BlockDoor.java | 110 +- .../java/cn/nukkit/block/BlockDoorAcacia.java | 3 +- .../java/cn/nukkit/block/BlockDoorBirch.java | 3 +- .../cn/nukkit/block/BlockDoorDarkOak.java | 3 +- .../java/cn/nukkit/block/BlockDoorIron.java | 12 +- .../java/cn/nukkit/block/BlockDoorJungle.java | 3 +- .../java/cn/nukkit/block/BlockDoorSpruce.java | 3 +- .../java/cn/nukkit/block/BlockDoorWood.java | 5 +- .../nukkit/block/BlockDoubleMudBrickSlab.java | 31 + .../cn/nukkit/block/BlockDoublePlant.java | 63 +- .../java/cn/nukkit/block/BlockDoubleSlab.java | 72 +- .../cn/nukkit/block/BlockDoubleSlabBase.java | 46 + .../block/BlockDoubleSlabBlackstone.java | 60 + .../BlockDoubleSlabBlackstonePolished.java | 60 + ...lockDoubleSlabBrickBlackstonePolished.java | 32 + .../block/BlockDoubleSlabBrickDeepslate.java | 65 + .../block/BlockDoubleSlabCopperBase.java | 88 + .../block/BlockDoubleSlabCopperCut.java | 68 + .../BlockDoubleSlabCopperCutExposed.java | 35 + .../BlockDoubleSlabCopperCutExposedWaxed.java | 27 + .../BlockDoubleSlabCopperCutOxidized.java | 35 + ...BlockDoubleSlabCopperCutOxidizedWaxed.java | 27 + .../block/BlockDoubleSlabCopperCutWaxed.java | 27 + .../BlockDoubleSlabCopperCutWeathered.java | 35 + ...lockDoubleSlabCopperCutWeatheredWaxed.java | 27 + .../nukkit/block/BlockDoubleSlabCrimson.java | 65 + .../BlockDoubleSlabDeepslateCobbled.java | 32 + .../BlockDoubleSlabDeepslatePolished.java | 65 + .../block/BlockDoubleSlabRedSandstone.java | 52 +- .../cn/nukkit/block/BlockDoubleSlabStone.java | 64 +- .../nukkit/block/BlockDoubleSlabStone3.java | 79 + .../nukkit/block/BlockDoubleSlabStone4.java | 89 + .../block/BlockDoubleSlabTileDeepslate.java | 65 + .../nukkit/block/BlockDoubleSlabWarped.java | 65 + .../cn/nukkit/block/BlockDoubleSlabWood.java | 50 +- .../java/cn/nukkit/block/BlockDragonEgg.java | 31 +- .../cn/nukkit/block/BlockDriedKelpBlock.java | 40 + .../cn/nukkit/block/BlockDripleafBig.java | 287 + .../cn/nukkit/block/BlockDripleafSmall.java | 197 + .../java/cn/nukkit/block/BlockDripstone.java | 46 + .../java/cn/nukkit/block/BlockDropper.java | 242 + .../java/cn/nukkit/block/BlockEmerald.java | 7 +- .../cn/nukkit/block/BlockEnchantingTable.java | 36 +- .../java/cn/nukkit/block/BlockEndGateway.java | 12 +- .../java/cn/nukkit/block/BlockEndPortal.java | 11 +- .../cn/nukkit/block/BlockEndPortalFrame.java | 124 +- .../java/cn/nukkit/block/BlockEndRod.java | 21 +- .../java/cn/nukkit/block/BlockEndStone.java | 5 +- .../java/cn/nukkit/block/BlockEnderChest.java | 45 +- .../java/cn/nukkit/block/BlockFallable.java | 11 +- .../cn/nukkit/block/BlockFallableMeta.java | 54 + .../java/cn/nukkit/block/BlockFarmland.java | 30 +- src/main/java/cn/nukkit/block/BlockFence.java | 22 +- .../cn/nukkit/block/BlockFenceCrimson.java | 40 + .../java/cn/nukkit/block/BlockFenceGate.java | 37 +- .../cn/nukkit/block/BlockFenceGateAcacia.java | 3 +- .../cn/nukkit/block/BlockFenceGateBirch.java | 3 +- .../nukkit/block/BlockFenceGateCrimson.java | 30 + .../nukkit/block/BlockFenceGateDarkOak.java | 3 +- .../cn/nukkit/block/BlockFenceGateJungle.java | 3 +- .../cn/nukkit/block/BlockFenceGateSpruce.java | 3 +- .../cn/nukkit/block/BlockFenceGateWarped.java | 30 + .../nukkit/block/BlockFenceNetherBrick.java | 7 +- .../cn/nukkit/block/BlockFenceWarped.java | 40 + src/main/java/cn/nukkit/block/BlockFire.java | 239 +- .../cn/nukkit/block/BlockFletchingTable.java | 42 + .../java/cn/nukkit/block/BlockFlowable.java | 7 +- .../java/cn/nukkit/block/BlockFlower.java | 64 +- .../java/cn/nukkit/block/BlockFlowerPot.java | 124 +- .../java/cn/nukkit/block/BlockFungus.java | 89 + .../java/cn/nukkit/block/BlockFurnace.java | 11 +- .../cn/nukkit/block/BlockFurnaceBurning.java | 37 +- src/main/java/cn/nukkit/block/BlockGlass.java | 5 +- .../java/cn/nukkit/block/BlockGlassPane.java | 8 +- .../nukkit/block/BlockGlassPaneStained.java | 11 +- .../cn/nukkit/block/BlockGlassStained.java | 11 +- .../cn/nukkit/block/BlockGlassTinted.java | 30 + .../java/cn/nukkit/block/BlockGlowLichen.java | 204 + .../java/cn/nukkit/block/BlockGlowstone.java | 17 +- src/main/java/cn/nukkit/block/BlockGold.java | 8 +- src/main/java/cn/nukkit/block/BlockGrass.java | 37 +- .../java/cn/nukkit/block/BlockGrassPath.java | 45 +- .../java/cn/nukkit/block/BlockGravel.java | 51 +- .../java/cn/nukkit/block/BlockGrindstone.java | 135 + .../java/cn/nukkit/block/BlockHayBale.java | 30 +- .../java/cn/nukkit/block/BlockHoneyBlock.java | 56 + .../cn/nukkit/block/BlockHoneycombBlock.java | 37 + .../java/cn/nukkit/block/BlockHopper.java | 27 +- .../nukkit/block/BlockHugeMushroomBrown.java | 21 +- .../cn/nukkit/block/BlockHugeMushroomRed.java | 17 +- .../cn/nukkit/block/BlockHyphaeCrimson.java | 44 + .../block/BlockHyphaeStrippedCrimson.java | 34 + .../block/BlockHyphaeStrippedWarped.java | 34 + .../cn/nukkit/block/BlockHyphaeWarped.java | 43 + src/main/java/cn/nukkit/block/BlockID.java | 357 +- src/main/java/cn/nukkit/block/BlockIce.java | 14 +- .../java/cn/nukkit/block/BlockIceFrosted.java | 125 + .../java/cn/nukkit/block/BlockIcePacked.java | 35 +- .../nukkit/block/BlockInfestedDeepslate.java | 44 + .../java/cn/nukkit/block/BlockInfoUpdate.java | 26 + .../cn/nukkit/block/BlockInfoUpdate2.java | 21 + src/main/java/cn/nukkit/block/BlockIron.java | 8 +- .../java/cn/nukkit/block/BlockIronBars.java | 12 +- .../java/cn/nukkit/block/BlockItemFrame.java | 62 +- .../cn/nukkit/block/BlockItemFrameGlow.java | 29 + .../java/cn/nukkit/block/BlockJigsaw.java | 79 + .../java/cn/nukkit/block/BlockJukebox.java | 58 +- .../nukkit/block/BlockJungleSignStanding.java | 40 + .../cn/nukkit/block/BlockJungleWallSign.java | 40 + src/main/java/cn/nukkit/block/BlockKelp.java | 254 + .../java/cn/nukkit/block/BlockLadder.java | 58 +- .../java/cn/nukkit/block/BlockLantern.java | 168 + src/main/java/cn/nukkit/block/BlockLapis.java | 7 +- src/main/java/cn/nukkit/block/BlockLava.java | 123 +- .../java/cn/nukkit/block/BlockLavaStill.java | 14 +- src/main/java/cn/nukkit/block/BlockLayer.java | 6 + .../java/cn/nukkit/block/BlockLeaves.java | 56 +- .../java/cn/nukkit/block/BlockLeaves2.java | 14 +- .../java/cn/nukkit/block/BlockLectern.java | 171 + src/main/java/cn/nukkit/block/BlockLever.java | 43 +- .../java/cn/nukkit/block/BlockLightBlock.java | 75 + .../cn/nukkit/block/BlockLightningRod.java | 87 + .../java/cn/nukkit/block/BlockLiquid.java | 177 +- .../java/cn/nukkit/block/BlockLodestone.java | 36 + src/main/java/cn/nukkit/block/BlockLoom.java | 76 + src/main/java/cn/nukkit/block/BlockMagma.java | 46 +- src/main/java/cn/nukkit/block/BlockMelon.java | 25 +- src/main/java/cn/nukkit/block/BlockMeta.java | 6 +- .../java/cn/nukkit/block/BlockMobSpawner.java | 40 +- .../java/cn/nukkit/block/BlockMonsterEgg.java | 5 +- src/main/java/cn/nukkit/block/BlockMoss.java | 112 + .../java/cn/nukkit/block/BlockMossCarpet.java | 90 + .../java/cn/nukkit/block/BlockMossStone.java | 5 +- src/main/java/cn/nukkit/block/BlockMud.java | 39 + .../java/cn/nukkit/block/BlockMudBrick.java | 27 + .../cn/nukkit/block/BlockMudBrickSlab.java | 22 + .../cn/nukkit/block/BlockMudBrickStairs.java | 32 + .../cn/nukkit/block/BlockMudBrickWall.java | 22 + .../java/cn/nukkit/block/BlockMushroom.java | 26 +- .../cn/nukkit/block/BlockMushroomBrown.java | 2 +- .../cn/nukkit/block/BlockMushroomRed.java | 2 +- .../java/cn/nukkit/block/BlockMycelium.java | 45 +- .../cn/nukkit/block/BlockNetherBrick.java | 5 +- .../cn/nukkit/block/BlockNetherPortal.java | 249 +- .../cn/nukkit/block/BlockNetherReactor.java | 44 + .../cn/nukkit/block/BlockNetherSprouts.java | 54 + .../java/cn/nukkit/block/BlockNetherWart.java | 21 +- .../cn/nukkit/block/BlockNetherWartBlock.java | 11 +- .../cn/nukkit/block/BlockNetheriteBlock.java | 45 + .../java/cn/nukkit/block/BlockNetherrack.java | 6 +- .../java/cn/nukkit/block/BlockNoteblock.java | 25 +- .../java/cn/nukkit/block/BlockNylium.java | 59 + .../java/cn/nukkit/block/BlockObserver.java | 115 +- .../java/cn/nukkit/block/BlockObsidian.java | 7 +- .../cn/nukkit/block/BlockObsidianCrying.java | 63 + .../cn/nukkit/block/BlockObsidianGlowing.java | 40 +- src/main/java/cn/nukkit/block/BlockOre.java | 82 + .../java/cn/nukkit/block/BlockOreCoal.java | 32 +- .../nukkit/block/BlockOreCoalDeepslate.java | 41 + .../java/cn/nukkit/block/BlockOreCopper.java | 36 + .../nukkit/block/BlockOreCopperDeepslate.java | 30 + .../java/cn/nukkit/block/BlockOreDiamond.java | 25 +- .../block/BlockOreDiamondDeepslate.java | 47 + .../java/cn/nukkit/block/BlockOreEmerald.java | 22 +- .../block/BlockOreEmeraldDeepslate.java | 47 + .../java/cn/nukkit/block/BlockOreGold.java | 40 +- .../nukkit/block/BlockOreGoldDeepslate.java | 41 + .../cn/nukkit/block/BlockOreGoldNether.java | 90 + .../java/cn/nukkit/block/BlockOreIron.java | 41 +- .../nukkit/block/BlockOreIronDeepslate.java | 35 + .../java/cn/nukkit/block/BlockOreLapis.java | 30 +- .../nukkit/block/BlockOreLapisDeepslate.java | 41 + .../java/cn/nukkit/block/BlockOreQuartz.java | 22 +- .../cn/nukkit/block/BlockOreRedstone.java | 29 +- .../block/BlockOreRedstoneDeepslate.java | 53 + .../BlockOreRedstoneDeepslateGlowing.java | 47 + .../nukkit/block/BlockOreRedstoneGlowing.java | 17 +- .../java/cn/nukkit/block/BlockPackedMud.java | 27 + .../java/cn/nukkit/block/BlockPiston.java | 5 + .../java/cn/nukkit/block/BlockPistonBase.java | 216 +- .../cn/nukkit/block/BlockPistonExtension.java | 36 + .../java/cn/nukkit/block/BlockPistonHead.java | 20 +- .../nukkit/block/BlockPistonHeadSticky.java | 22 + .../cn/nukkit/block/BlockPistonSticky.java | 5 + .../java/cn/nukkit/block/BlockPlanks.java | 10 +- .../java/cn/nukkit/block/BlockPodzol.java | 27 +- .../nukkit/block/BlockPointedDripstone.java | 288 + .../cn/nukkit/block/BlockPolishedBasalt.java | 22 + .../BlockPolishedBlackstoneBrickWall.java | 22 + .../block/BlockPolishedBlackstoneWall.java | 22 + .../java/cn/nukkit/block/BlockPotato.java | 21 +- .../java/cn/nukkit/block/BlockPowderSnow.java | 46 + .../block/BlockPressurePlateAcacia.java | 29 + .../nukkit/block/BlockPressurePlateBase.java | 28 +- .../nukkit/block/BlockPressurePlateBirch.java | 29 + .../block/BlockPressurePlateCrimson.java | 22 + .../block/BlockPressurePlateDarkOak.java | 29 + .../block/BlockPressurePlateJungle.java | 29 + .../BlockPressurePlatePolishedBlackstone.java | 22 + .../block/BlockPressurePlateSpruce.java | 29 + .../nukkit/block/BlockPressurePlateStone.java | 2 +- .../block/BlockPressurePlateWarped.java | 23 + .../java/cn/nukkit/block/BlockPrismarine.java | 12 +- .../java/cn/nukkit/block/BlockPumpkin.java | 38 +- .../cn/nukkit/block/BlockPumpkinCarved.java | 26 + .../java/cn/nukkit/block/BlockPumpkinLit.java | 5 + .../java/cn/nukkit/block/BlockPurpur.java | 26 +- .../java/cn/nukkit/block/BlockQuartz.java | 25 +- .../cn/nukkit/block/BlockQuartzBricks.java | 22 + src/main/java/cn/nukkit/block/BlockRail.java | 88 +- .../cn/nukkit/block/BlockRailActivator.java | 27 +- .../cn/nukkit/block/BlockRailDetector.java | 32 +- .../cn/nukkit/block/BlockRailPowered.java | 28 +- .../java/cn/nukkit/block/BlockRawCopper.java | 17 + .../java/cn/nukkit/block/BlockRawGold.java | 24 + .../java/cn/nukkit/block/BlockRawIron.java | 17 + .../cn/nukkit/block/BlockRawOreVariant.java | 50 + .../cn/nukkit/block/BlockRedSandstone.java | 10 +- .../java/cn/nukkit/block/BlockRedstone.java | 40 +- .../nukkit/block/BlockRedstoneComparator.java | 35 +- .../cn/nukkit/block/BlockRedstoneDiode.java | 44 +- .../cn/nukkit/block/BlockRedstoneLamp.java | 13 +- .../cn/nukkit/block/BlockRedstoneLampLit.java | 12 +- .../block/BlockRedstoneRepeaterPowered.java | 7 +- .../block/BlockRedstoneRepeaterUnpowered.java | 9 +- .../cn/nukkit/block/BlockRedstoneTorch.java | 35 +- .../nukkit/block/BlockRedstoneTorchUnlit.java | 22 +- .../cn/nukkit/block/BlockRedstoneWire.java | 105 +- .../cn/nukkit/block/BlockRespawnAnchor.java | 151 + src/main/java/cn/nukkit/block/BlockRoots.java | 53 + .../cn/nukkit/block/BlockRootsHanging.java | 28 + src/main/java/cn/nukkit/block/BlockSand.java | 52 +- .../java/cn/nukkit/block/BlockSandstone.java | 7 +- .../java/cn/nukkit/block/BlockSapling.java | 188 +- .../cn/nukkit/block/BlockScaffolding.java | 256 + .../java/cn/nukkit/block/BlockSeaLantern.java | 15 +- .../java/cn/nukkit/block/BlockSeaPickle.java | 186 + .../java/cn/nukkit/block/BlockSeagrass.java | 178 + .../cn/nukkit/block/BlockShroomlight.java | 46 + .../java/cn/nukkit/block/BlockShulkerBox.java | 142 +- .../java/cn/nukkit/block/BlockSignPost.java | 38 +- src/main/java/cn/nukkit/block/BlockSkull.java | 28 +- src/main/java/cn/nukkit/block/BlockSlab.java | 84 +- .../cn/nukkit/block/BlockSlabBlackstone.java | 70 + .../block/BlockSlabBlackstonePolished.java | 74 + .../BlockSlabBrickBlackstonePolished.java | 27 + .../nukkit/block/BlockSlabBrickDeepslate.java | 56 + .../cn/nukkit/block/BlockSlabCopperBase.java | 91 + .../cn/nukkit/block/BlockSlabCopperCut.java | 62 + .../block/BlockSlabCopperCutExposed.java | 34 + .../block/BlockSlabCopperCutExposedWaxed.java | 22 + .../block/BlockSlabCopperCutOxidized.java | 34 + .../BlockSlabCopperCutOxidizedWaxed.java | 22 + .../nukkit/block/BlockSlabCopperCutWaxed.java | 22 + .../block/BlockSlabCopperCutWeathered.java | 34 + .../BlockSlabCopperCutWeatheredWaxed.java | 22 + .../cn/nukkit/block/BlockSlabCrimson.java | 64 + .../block/BlockSlabDeepslateCobbled.java | 22 + .../block/BlockSlabDeepslatePolished.java | 55 + .../nukkit/block/BlockSlabRedSandstone.java | 35 +- .../java/cn/nukkit/block/BlockSlabStone.java | 16 +- .../java/cn/nukkit/block/BlockSlabStone3.java | 72 + .../java/cn/nukkit/block/BlockSlabStone4.java | 64 + .../nukkit/block/BlockSlabTileDeepslate.java | 55 + .../java/cn/nukkit/block/BlockSlabWarped.java | 62 + .../java/cn/nukkit/block/BlockSlabWood.java | 7 +- src/main/java/cn/nukkit/block/BlockSlime.java | 5 +- .../cn/nukkit/block/BlockSmithingTable.java | 60 + .../java/cn/nukkit/block/BlockSmoker.java | 27 + .../java/cn/nukkit/block/BlockSmokerLit.java | 87 + .../cn/nukkit/block/BlockSmoothStone.java | 36 + src/main/java/cn/nukkit/block/BlockSnow.java | 24 +- .../java/cn/nukkit/block/BlockSnowLayer.java | 44 +- src/main/java/cn/nukkit/block/BlockSolid.java | 7 +- .../java/cn/nukkit/block/BlockSolidMeta.java | 6 +- .../java/cn/nukkit/block/BlockSoulFire.java | 42 + .../cn/nukkit/block/BlockSoulLantern.java | 35 + .../java/cn/nukkit/block/BlockSoulSand.java | 26 +- .../java/cn/nukkit/block/BlockSoulSoil.java | 53 + .../java/cn/nukkit/block/BlockSoulTorch.java | 27 + .../java/cn/nukkit/block/BlockSponge.java | 44 +- .../cn/nukkit/block/BlockSporeBlossom.java | 75 + .../nukkit/block/BlockSpruceSignStanding.java | 40 + .../cn/nukkit/block/BlockSpruceWallSign.java | 40 + .../java/cn/nukkit/block/BlockStairs.java | 49 +- .../cn/nukkit/block/BlockStairsAcacia.java | 3 +- .../cn/nukkit/block/BlockStairsAndesite.java | 44 + .../block/BlockStairsAndesitePolished.java | 22 + .../cn/nukkit/block/BlockStairsBirch.java | 1 - .../nukkit/block/BlockStairsBlackstone.java | 50 + .../block/BlockStairsBlackstonePolished.java | 22 + .../cn/nukkit/block/BlockStairsBrick.java | 9 +- .../BlockStairsBrickBlackstonePolished.java | 22 + .../block/BlockStairsBrickDeepslate.java | 55 + .../nukkit/block/BlockStairsCobblestone.java | 7 +- .../nukkit/block/BlockStairsCopperBase.java | 86 + .../cn/nukkit/block/BlockStairsCopperCut.java | 59 + .../block/BlockStairsCopperCutExposed.java | 30 + .../BlockStairsCopperCutExposedWaxed.java | 22 + .../block/BlockStairsCopperCutOxidized.java | 31 + .../BlockStairsCopperCutOxidizedWaxed.java | 24 + .../block/BlockStairsCopperCutWaxed.java | 23 + .../block/BlockStairsCopperCutWeathered.java | 30 + .../BlockStairsCopperCutWeatheredWaxed.java | 22 + .../cn/nukkit/block/BlockStairsDarkOak.java | 1 - .../block/BlockStairsDarkPrismarine.java | 50 + .../block/BlockStairsDeepslateCobbled.java | 55 + .../block/BlockStairsDeepslatePolished.java | 55 + .../cn/nukkit/block/BlockStairsDiorite.java | 50 + .../block/BlockStairsDioritePolished.java | 22 + .../cn/nukkit/block/BlockStairsEndBrick.java | 44 + .../cn/nukkit/block/BlockStairsGranite.java | 50 + .../block/BlockStairsGranitePolished.java | 22 + .../cn/nukkit/block/BlockStairsJungle.java | 1 - .../block/BlockStairsMossyCobblestone.java | 22 + .../block/BlockStairsMossyStoneBrick.java | 22 + .../nukkit/block/BlockStairsNetherBrick.java | 1 + .../nukkit/block/BlockStairsPrismarine.java | 50 + .../block/BlockStairsPrismarineBrick.java | 29 + .../cn/nukkit/block/BlockStairsPurpur.java | 5 + .../cn/nukkit/block/BlockStairsQuartz.java | 5 +- .../block/BlockStairsRedNetherBrick.java | 22 + .../nukkit/block/BlockStairsRedSandstone.java | 4 +- .../cn/nukkit/block/BlockStairsSandstone.java | 1 + .../nukkit/block/BlockStairsSmoothQuartz.java | 22 + .../block/BlockStairsSmoothRedSandstone.java | 30 + .../block/BlockStairsSmoothSandstone.java | 30 + .../cn/nukkit/block/BlockStairsSpruce.java | 1 - .../cn/nukkit/block/BlockStairsStone.java | 44 + .../nukkit/block/BlockStairsStoneBrick.java | 1 + .../block/BlockStairsTileDeepslate.java | 55 + .../java/cn/nukkit/block/BlockStairsWood.java | 4 +- src/main/java/cn/nukkit/block/BlockStem.java | 73 + .../java/cn/nukkit/block/BlockStemMelon.java | 25 +- .../cn/nukkit/block/BlockStemPumpkin.java | 25 +- .../cn/nukkit/block/BlockStemStripped.java | 30 + src/main/java/cn/nukkit/block/BlockStone.java | 43 +- .../cn/nukkit/block/BlockStonecutter.java | 105 +- .../nukkit/block/BlockStonecutterBlock.java | 64 + .../block/BlockStrippedCrimsonStem.java | 39 + .../nukkit/block/BlockStrippedWarpedStem.java | 39 + .../cn/nukkit/block/BlockStructureBlock.java | 47 + .../java/cn/nukkit/block/BlockSugarcane.java | 34 +- .../cn/nukkit/block/BlockSweetBerryBush.java | 206 + src/main/java/cn/nukkit/block/BlockTNT.java | 41 +- .../java/cn/nukkit/block/BlockTallGrass.java | 38 +- .../java/cn/nukkit/block/BlockTarget.java | 109 + .../java/cn/nukkit/block/BlockTerracotta.java | 8 +- .../nukkit/block/BlockTerracottaGlazed.java | 22 +- .../block/BlockTerracottaGlazedLime.java | 4 +- .../nukkit/block/BlockTerracottaStained.java | 3 +- src/main/java/cn/nukkit/block/BlockThin.java | 57 +- .../cn/nukkit/block/BlockTilesDeepslate.java | 52 + .../block/BlockTilesDeepslateCracked.java | 18 + src/main/java/cn/nukkit/block/BlockTorch.java | 21 +- .../cn/nukkit/block/BlockTransparent.java | 3 +- .../cn/nukkit/block/BlockTransparentMeta.java | 3 +- .../java/cn/nukkit/block/BlockTrapdoor.java | 47 +- .../cn/nukkit/block/BlockTrapdoorAcacia.java | 41 + .../cn/nukkit/block/BlockTrapdoorBirch.java | 36 + .../cn/nukkit/block/BlockTrapdoorDarkOak.java | 36 + .../cn/nukkit/block/BlockTrapdoorJungle.java | 36 + .../cn/nukkit/block/BlockTrapdoorSpruce.java | 36 + .../cn/nukkit/block/BlockTrappedChest.java | 13 +- .../java/cn/nukkit/block/BlockTripWire.java | 38 +- .../cn/nukkit/block/BlockTripWireHook.java | 39 +- src/main/java/cn/nukkit/block/BlockTuff.java | 51 + .../java/cn/nukkit/block/BlockTurtleEgg.java | 111 + src/main/java/cn/nukkit/block/BlockTypes.java | 677 ++ .../nukkit/block/BlockUndyedShulkerBox.java | 146 +- .../java/cn/nukkit/block/BlockUnknown.java | 7 +- src/main/java/cn/nukkit/block/BlockVine.java | 70 +- .../cn/nukkit/block/BlockVinesNether.java | 398 ++ .../cn/nukkit/block/BlockVinesTwisting.java | 50 + src/main/java/cn/nukkit/block/BlockWall.java | 10 +- .../java/cn/nukkit/block/BlockWallBanner.java | 3 - .../nukkit/block/BlockWallBrickDeepslate.java | 22 + .../block/BlockWallDeepslateCobbled.java | 22 + .../block/BlockWallDeepslatePolished.java | 22 + .../java/cn/nukkit/block/BlockWallSign.java | 13 +- .../nukkit/block/BlockWallTileDeepslate.java | 22 + .../java/cn/nukkit/block/BlockWarpedDoor.java | 29 + .../cn/nukkit/block/BlockWarpedFungus.java | 99 + .../cn/nukkit/block/BlockWarpedNylium.java | 21 + .../cn/nukkit/block/BlockWarpedPlanks.java | 55 + .../cn/nukkit/block/BlockWarpedRoots.java | 39 + .../java/cn/nukkit/block/BlockWarpedSign.java | 40 + .../cn/nukkit/block/BlockWarpedStairs.java | 39 + .../java/cn/nukkit/block/BlockWarpedStem.java | 44 + .../cn/nukkit/block/BlockWarpedTrapdoor.java | 30 + .../cn/nukkit/block/BlockWarpedWallSign.java | 29 + .../cn/nukkit/block/BlockWarpedWartBlock.java | 35 + src/main/java/cn/nukkit/block/BlockWater.java | 42 +- .../java/cn/nukkit/block/BlockWaterLily.java | 26 +- .../java/cn/nukkit/block/BlockWaterStill.java | 5 +- .../cn/nukkit/block/BlockWeepingVines.java | 49 + .../BlockWeightedPressurePlateHeavy.java | 4 +- .../BlockWeightedPressurePlateLight.java | 4 +- src/main/java/cn/nukkit/block/BlockWheat.java | 16 +- .../java/cn/nukkit/block/BlockWitherRose.java | 75 + src/main/java/cn/nukkit/block/BlockWood.java | 74 +- src/main/java/cn/nukkit/block/BlockWood2.java | 52 +- .../java/cn/nukkit/block/BlockWoodBark.java | 75 + .../cn/nukkit/block/BlockWoodStripped.java | 51 + .../nukkit/block/BlockWoodStrippedAcacia.java | 29 + .../nukkit/block/BlockWoodStrippedBirch.java | 29 + .../block/BlockWoodStrippedDarkOak.java | 29 + .../nukkit/block/BlockWoodStrippedJungle.java | 29 + .../cn/nukkit/block/BlockWoodStrippedOak.java | 28 + .../nukkit/block/BlockWoodStrippedSpruce.java | 28 + src/main/java/cn/nukkit/block/Blocks.java | 609 ++ src/main/java/cn/nukkit/block/Oxidizable.java | 118 + src/main/java/cn/nukkit/block/Waxable.java | 40 + .../block/properties/BlockNotImplemented.java | 32 + .../properties/BlockPropertiesHelper.java | 26 + .../nukkit/block/properties/DripleafTilt.java | 19 + .../block/properties/DripstoneThickness.java | 9 + .../block/properties/OxidizationLevel.java | 8 + .../block/properties/VanillaProperties.java | 20 + .../cn/nukkit/blockentity/BlockEntity.java | 101 +- .../nukkit/blockentity/BlockEntityBanner.java | 9 +- .../nukkit/blockentity/BlockEntityBarrel.java | 169 + .../nukkit/blockentity/BlockEntityBeacon.java | 110 +- .../cn/nukkit/blockentity/BlockEntityBed.java | 2 +- .../nukkit/blockentity/BlockEntityBell.java | 149 + .../blockentity/BlockEntityBlastFurnace.java | 149 + .../blockentity/BlockEntityBrewingStand.java | 210 +- .../blockentity/BlockEntityCampfire.java | 229 + .../blockentity/BlockEntityCauldron.java | 75 +- .../nukkit/blockentity/BlockEntityChest.java | 69 +- .../blockentity/BlockEntityComparator.java | 10 +- .../blockentity/BlockEntityContainer.java | 16 +- .../blockentity/BlockEntityDispenser.java | 166 + .../blockentity/BlockEntityDropper.java | 166 + .../blockentity/BlockEntityEnchantTable.java | 9 +- .../blockentity/BlockEntityEnderChest.java | 2 +- .../blockentity/BlockEntityFlowerPot.java | 26 +- .../blockentity/BlockEntityFurnace.java | 155 +- .../nukkit/blockentity/BlockEntityHopper.java | 265 +- .../blockentity/BlockEntityItemFrame.java | 69 +- .../blockentity/BlockEntityJukebox.java | 4 +- .../blockentity/BlockEntityLectern.java | 133 + .../blockentity/BlockEntityMovingBlock.java | 4 +- .../nukkit/blockentity/BlockEntityMusic.java | 5 +- .../blockentity/BlockEntityNameable.java | 4 - .../blockentity/BlockEntityPistonArm.java | 60 +- .../blockentity/BlockEntityShulkerBox.java | 56 +- .../nukkit/blockentity/BlockEntitySign.java | 53 +- .../nukkit/blockentity/BlockEntitySkull.java | 3 +- .../nukkit/blockentity/BlockEntitySmoker.java | 144 + .../blockentity/BlockEntitySpawnable.java | 19 +- .../blockentity/BlockEntitySpawner.java | 26 + .../PersistentDataContainerBlockEntity.java | 70 + src/main/java/cn/nukkit/command/Command.java | 40 +- .../cn/nukkit/command/CommandExecutor.java | 20 +- .../java/cn/nukkit/command/CommandMap.java | 3 +- .../java/cn/nukkit/command/CommandSender.java | 22 +- .../nukkit/command/ConsoleCommandSender.java | 3 +- .../nukkit/command/FormattedCommandAlias.java | 11 +- .../java/cn/nukkit/command/PluginCommand.java | 4 +- .../command/PluginIdentifiableCommand.java | 2 +- .../command/RemoteConsoleCommandSender.java | 7 +- .../cn/nukkit/command/SimpleCommandMap.java | 68 +- .../cn/nukkit/command/data/CommandArgs.java | 1 - .../command/data/CommandDataVersions.java | 1 - .../cn/nukkit/command/data/CommandEnum.java | 8 +- .../cn/nukkit/command/data/CommandInput.java | 1 - .../cn/nukkit/command/data/CommandOutput.java | 1 - .../nukkit/command/data/CommandOverload.java | 1 - .../nukkit/command/data/CommandParamType.java | 3 +- .../nukkit/command/data/CommandParameter.java | 53 +- .../nukkit/command/data/args/CommandArg.java | 15 - .../data/args/CommandArgBlockVector.java | 35 - .../command/data/args/CommandArgRules.java | 21 - .../nukkit/command/defaults/BanCommand.java | 10 +- .../nukkit/command/defaults/BanIpCommand.java | 37 +- .../command/defaults/BanListCommand.java | 4 +- .../nukkit/command/defaults/ClearCommand.java | 22 +- .../command/defaults/ConvertCommand.java | 80 + .../command/defaults/DebugPasteCommand.java | 90 - .../defaults/DefaultGamemodeCommand.java | 18 +- .../nukkit/command/defaults/DeopCommand.java | 14 +- .../command/defaults/DifficultyCommand.java | 10 +- .../command/defaults/EffectCommand.java | 32 +- .../command/defaults/EnchantCommand.java | 24 +- .../command/defaults/GamemodeCommand.java | 8 +- .../command/defaults/GameruleCommand.java | 19 +- .../defaults/GarbageCollectorCommand.java | 4 +- .../nukkit/command/defaults/GiveCommand.java | 61 +- .../nukkit/command/defaults/HelpCommand.java | 20 +- .../nukkit/command/defaults/KickCommand.java | 10 +- .../nukkit/command/defaults/KillCommand.java | 58 +- .../nukkit/command/defaults/ListCommand.java | 3 +- .../cn/nukkit/command/defaults/MeCommand.java | 6 +- .../cn/nukkit/command/defaults/OpCommand.java | 15 +- .../command/defaults/PardonCommand.java | 4 +- .../command/defaults/PardonIpCommand.java | 5 +- .../command/defaults/ParticleCommand.java | 34 +- .../command/defaults/PlaySoundCommand.java | 78 + .../command/defaults/PluginsCommand.java | 13 +- .../command/defaults/ReloadCommand.java | 2 +- .../command/defaults/SaveOffCommand.java | 5 + .../command/defaults/SaveOnCommand.java | 5 + .../nukkit/command/defaults/SayCommand.java | 5 +- .../nukkit/command/defaults/SeedCommand.java | 2 +- .../defaults/SetWorldSpawnCommand.java | 7 +- .../command/defaults/SpawnpointCommand.java | 9 +- .../command/defaults/StatusCommand.java | 35 +- .../nukkit/command/defaults/StopCommand.java | 2 +- .../command/defaults/SummonCommand.java | 75 + .../command/defaults/TeleportCommand.java | 35 +- .../nukkit/command/defaults/TellCommand.java | 21 +- .../nukkit/command/defaults/TimeCommand.java | 5 +- .../command/defaults/TimingsCommand.java | 74 - .../nukkit/command/defaults/TitleCommand.java | 48 +- .../command/defaults/VanillaCommand.java | 2 +- .../command/defaults/VersionCommand.java | 6 +- .../command/defaults/WeatherCommand.java | 3 +- .../command/defaults/WhitelistCommand.java | 14 +- .../cn/nukkit/command/defaults/XpCommand.java | 13 +- .../nukkit/command/simple/SimpleCommand.java | 6 +- .../java/cn/nukkit/console/NukkitConsole.java | 21 +- .../console/NukkitConsoleCompleter.java | 7 +- .../customblock/CustomBlockDefinition.java | 13 + .../customblock/CustomBlockManager.java | 316 + .../nukkit/customblock/CustomBlockState.java | 22 + .../cn/nukkit/customblock/GsonNBTMapper.java | 186 + .../comparator/AlphabetPaletteComparator.java | 18 + .../comparator/HashedPaletteComparator.java | 37 + .../customblock/container/BlockContainer.java | 16 + .../container/BlockContainerFactory.java | 16 + .../container/BlockStorageContainer.java | 78 + .../customblock/container/CustomBlock.java | 29 + .../container/CustomBlockMeta.java | 57 + .../properties/BlockProperties.java | 420 ++ .../customblock/properties/BlockProperty.java | 333 + .../properties/BlockPropertyUtils.java | 68 + .../properties/BooleanBlockProperty.java | 147 + .../properties/EnumBlockProperty.java | 201 + .../properties/IntBlockProperty.java | 154 + .../properties/RegisteredBlockProperty.java | 28 + .../properties/UnsignedIntBlockProperty.java | 155 + .../BlockPropertyNotFoundException.java | 28 + .../InvalidBlockPropertyException.java | 37 + .../InvalidBlockPropertyMetaException.java | 46 + ...lockPropertyPersistenceValueException.java | 47 + .../InvalidBlockPropertyValueException.java | 49 + .../customblock/util/BlockPropertyDumper.java | 79 + .../dispenser/BoatDispenseBehavior.java | 36 + .../dispenser/BucketDispenseBehavior.java | 31 +- .../dispenser/ChestBoatDispenseBehavior.java | 56 + .../dispenser/DefaultDispenseBehavior.java | 32 +- .../cn/nukkit/dispenser/DispenseBehavior.java | 4 +- .../dispenser/DispenseBehaviorRegister.java | 93 +- .../nukkit/dispenser/DyeDispenseBehavior.java | 26 + .../EmptyBucketDispenseBehavior.java | 15 - .../dispenser/FireChargeDispenseBehavior.java | 29 + .../dispenser/FireworksDispenseBehavior.java | 23 + .../FlintAndSteelDispenseBehavior.java | 29 + .../dispenser/MinecartDispenseBehavior.java | 62 + .../dispenser/ProjectileDispenseBehavior.java | 57 +- .../dispenser/ShearsDispenseBehaviour.java | 35 + .../dispenser/ShulkerBoxDispenseBehavior.java | 40 + .../dispenser/SpawnEggDispenseBehavior.java | 30 + .../nukkit/dispenser/TNTDispenseBehavior.java | 22 + .../UndyedShulkerBoxDispenseBehavior.java | 40 + src/main/java/cn/nukkit/entity/Attribute.java | 50 +- .../java/cn/nukkit/entity/BaseEntity.java | 99 + src/main/java/cn/nukkit/entity/Entity.java | 1138 +-- .../java/cn/nukkit/entity/EntityAgeable.java | 7 +- .../cn/nukkit/entity/EntityArthropod.java | 2 +- .../java/cn/nukkit/entity/EntityBoss.java | 4 + .../cn/nukkit/entity/EntityControllable.java | 11 + .../java/cn/nukkit/entity/EntityCreature.java | 13 +- .../cn/nukkit/entity/EntityDamageable.java | 2 +- .../cn/nukkit/entity/EntityExplosive.java | 1 - .../java/cn/nukkit/entity/EntityFlying.java | 12 + .../java/cn/nukkit/entity/EntityHanging.java | 43 +- .../java/cn/nukkit/entity/EntityHuman.java | 109 +- .../cn/nukkit/entity/EntityHumanType.java | 125 +- .../cn/nukkit/entity/EntityInteractable.java | 2 - .../java/cn/nukkit/entity/EntityJumping.java | 11 + .../java/cn/nukkit/entity/EntityLiving.java | 306 +- .../java/cn/nukkit/entity/EntityOwnable.java | 3 +- .../java/cn/nukkit/entity/EntityRideable.java | 2 +- .../java/cn/nukkit/entity/EntitySmite.java | 2 +- .../java/cn/nukkit/entity/EntitySwimming.java | 11 + .../java/cn/nukkit/entity/EntityTameable.java | 28 + .../java/cn/nukkit/entity/EntityWalking.java | 11 + .../cn/nukkit/entity/custom/CustomEntity.java | 14 + .../entity/custom/EntityDefinition.java | 80 + .../nukkit/entity/custom/EntityManager.java | 97 + .../cn/nukkit/entity/data/ByteEntityData.java | 3 +- .../cn/nukkit/entity/data/EntityData.java | 5 +- .../cn/nukkit/entity/data/EntityMetadata.java | 23 +- .../nukkit/entity/data/FloatEntityData.java | 4 +- .../cn/nukkit/entity/data/IntEntityData.java | 3 +- .../entity/data/IntPositionEntityData.java | 3 +- .../cn/nukkit/entity/data/LongEntityData.java | 3 +- .../cn/nukkit/entity/data/NBTEntityData.java | 11 +- .../nukkit/entity/data/ShortEntityData.java | 3 +- src/main/java/cn/nukkit/entity/data/Skin.java | 85 +- .../nukkit/entity/data/StringEntityData.java | 3 +- .../entity/data/Vector3fEntityData.java | 3 +- .../entity/item/EntityAreaEffectCloud.java | 407 ++ .../nukkit/entity/item/EntityArmorStand.java | 378 + .../cn/nukkit/entity/item/EntityBoat.java | 165 +- .../nukkit/entity/item/EntityChestBoat.java | 143 + .../nukkit/entity/item/EntityEndCrystal.java | 111 +- .../nukkit/entity/item/EntityExpBottle.java | 35 +- .../entity/item/EntityFallingBlock.java | 73 +- .../cn/nukkit/entity/item/EntityFirework.java | 100 +- .../nukkit/entity/item/EntityFishingHook.java | 600 +- .../cn/nukkit/entity/item/EntityItem.java | 222 +- .../entity/item/EntityMinecartAbstract.java | 204 +- .../entity/item/EntityMinecartChest.java | 62 +- .../entity/item/EntityMinecartEmpty.java | 13 +- .../entity/item/EntityMinecartHopper.java | 217 +- .../nukkit/entity/item/EntityMinecartTNT.java | 39 +- .../cn/nukkit/entity/item/EntityPainting.java | 29 +- .../cn/nukkit/entity/item/EntityPotion.java | 51 +- .../entity/item/EntityPotionLingering.java | 56 + .../nukkit/entity/item/EntityPrimedTNT.java | 34 +- .../cn/nukkit/entity/item/EntityVehicle.java | 33 +- .../cn/nukkit/entity/item/EntityXPOrb.java | 206 +- .../cn/nukkit/entity/mob/EntityBlaze.java | 33 +- .../nukkit/entity/mob/EntityCaveSpider.java | 46 +- .../cn/nukkit/entity/mob/EntityCreeper.java | 106 +- .../cn/nukkit/entity/mob/EntityDrowned.java | 39 +- .../entity/mob/EntityElderGuardian.java | 54 +- .../nukkit/entity/mob/EntityEnderDragon.java | 46 +- .../cn/nukkit/entity/mob/EntityEnderman.java | 32 +- .../cn/nukkit/entity/mob/EntityEndermite.java | 25 +- .../cn/nukkit/entity/mob/EntityEvoker.java | 28 +- .../cn/nukkit/entity/mob/EntityFlyingMob.java | 12 + .../cn/nukkit/entity/mob/EntityGhast.java | 46 +- .../cn/nukkit/entity/mob/EntityGuardian.java | 34 +- .../cn/nukkit/entity/mob/EntityHoglin.java | 30 +- .../java/cn/nukkit/entity/mob/EntityHusk.java | 32 +- .../nukkit/entity/mob/EntityJumpingMob.java | 12 + .../cn/nukkit/entity/mob/EntityMagmaCube.java | 41 +- .../java/cn/nukkit/entity/mob/EntityMob.java | 15 +- .../cn/nukkit/entity/mob/EntityPhantom.java | 29 +- .../cn/nukkit/entity/mob/EntityPiglin.java | 14 +- .../nukkit/entity/mob/EntityPiglinBrute.java | 25 +- .../cn/nukkit/entity/mob/EntityPillager.java | 39 +- .../cn/nukkit/entity/mob/EntityRavager.java | 14 +- .../cn/nukkit/entity/mob/EntityShulker.java | 34 +- .../nukkit/entity/mob/EntitySilverfish.java | 21 +- .../cn/nukkit/entity/mob/EntitySkeleton.java | 63 +- .../cn/nukkit/entity/mob/EntitySlime.java | 46 +- .../cn/nukkit/entity/mob/EntitySnowGolem.java | 34 +- .../cn/nukkit/entity/mob/EntitySpider.java | 41 +- .../cn/nukkit/entity/mob/EntityStray.java | 68 +- .../nukkit/entity/mob/EntitySwimmingMob.java | 12 + .../nukkit/entity/mob/EntityTameableMob.java | 11 + .../java/cn/nukkit/entity/mob/EntityVex.java | 21 +- .../nukkit/entity/mob/EntityVindicator.java | 24 +- .../nukkit/entity/mob/EntityWalkingMob.java | 12 + .../cn/nukkit/entity/mob/EntityWarden.java | 14 +- .../cn/nukkit/entity/mob/EntityWitch.java | 64 +- .../cn/nukkit/entity/mob/EntityWither.java | 65 +- .../entity/mob/EntityWitherSkeleton.java | 74 +- .../cn/nukkit/entity/mob/EntityZoglin.java | 16 +- .../cn/nukkit/entity/mob/EntityZombie.java | 25 +- .../nukkit/entity/mob/EntityZombiePigman.java | 49 +- .../entity/mob/EntityZombieVillager.java | 23 +- .../entity/mob/EntityZombieVillagerV1.java | 28 +- .../cn/nukkit/entity/passive/EntityAllay.java | 14 +- .../nukkit/entity/passive/EntityAnimal.java | 26 +- .../nukkit/entity/passive/EntityAxolotl.java | 20 +- .../cn/nukkit/entity/passive/EntityBat.java | 12 +- .../cn/nukkit/entity/passive/EntityBee.java | 27 +- .../cn/nukkit/entity/passive/EntityCamel.java | 46 + .../cn/nukkit/entity/passive/EntityCat.java | 28 +- .../nukkit/entity/passive/EntityChicken.java | 57 +- .../cn/nukkit/entity/passive/EntityCod.java | 23 +- .../cn/nukkit/entity/passive/EntityCow.java | 41 +- .../nukkit/entity/passive/EntityDolphin.java | 19 +- .../nukkit/entity/passive/EntityDonkey.java | 21 +- .../cn/nukkit/entity/passive/EntityFish.java | 40 + .../entity/passive/EntityFlyingAnimal.java | 12 + .../cn/nukkit/entity/passive/EntityFox.java | 17 +- .../cn/nukkit/entity/passive/EntityFrog.java | 15 +- .../entity/passive/EntityGlowSquid.java | 23 +- .../cn/nukkit/entity/passive/EntityGoat.java | 31 +- .../cn/nukkit/entity/passive/EntityHorse.java | 46 +- .../entity/passive/EntityHorseBase.java | 16 + .../entity/passive/EntityIronGolem.java | 94 + .../entity/passive/EntityJumpingAnimal.java | 12 + .../cn/nukkit/entity/passive/EntityLlama.java | 42 +- .../entity/passive/EntityMooshroom.java | 67 +- .../cn/nukkit/entity/passive/EntityMule.java | 29 +- .../cn/nukkit/entity/passive/EntityNPC.java | 1 - .../nukkit/entity/passive/EntityOcelot.java | 31 +- .../cn/nukkit/entity/passive/EntityPanda.java | 10 +- .../nukkit/entity/passive/EntityParrot.java | 42 +- .../cn/nukkit/entity/passive/EntityPig.java | 81 +- .../entity/passive/EntityPolarBear.java | 31 +- .../entity/passive/EntityPufferfish.java | 69 +- .../nukkit/entity/passive/EntityRabbit.java | 40 +- .../nukkit/entity/passive/EntitySalmon.java | 23 +- .../cn/nukkit/entity/passive/EntitySheep.java | 128 +- .../entity/passive/EntitySkeletonHorse.java | 36 +- .../cn/nukkit/entity/passive/EntitySquid.java | 34 +- .../nukkit/entity/passive/EntityStrider.java | 28 +- .../nukkit/entity/passive/EntityTadpole.java | 19 +- .../nukkit/entity/passive/EntityTameable.java | 110 - .../entity/passive/EntityTameableAnimal.java | 11 + .../entity/passive/EntityTropicalFish.java | 68 +- .../nukkit/entity/passive/EntityTurtle.java | 17 +- .../nukkit/entity/passive/EntityVillager.java | 37 +- .../entity/passive/EntityVillagerV1.java | 158 +- .../entity/passive/EntityWalkingAnimal.java | 12 + .../entity/passive/EntityWanderingTrader.java | 24 +- .../entity/passive/EntityWaterAnimal.java | 16 +- .../cn/nukkit/entity/passive/EntityWolf.java | 33 +- .../entity/passive/EntityZombieHorse.java | 38 +- .../nukkit/entity/projectile/EntityArrow.java | 108 +- .../nukkit/entity/projectile/EntityEgg.java | 24 +- .../entity/projectile/EntityEnderEye.java | 50 + .../entity/projectile/EntityEnderPearl.java | 55 +- .../entity/projectile/EntityProjectile.java | 165 +- .../entity/projectile/EntitySnowball.java | 23 +- .../projectile/EntityThrownTrident.java | 257 +- .../entity/weather/EntityLightning.java | 18 +- .../entity/weather/EntityLightningStrike.java | 1 - src/main/java/cn/nukkit/event/Event.java | 19 +- .../java/cn/nukkit/event/EventHandler.java | 27 +- .../java/cn/nukkit/event/EventPriority.java | 4 +- .../java/cn/nukkit/event/HandlerList.java | 21 +- src/main/java/cn/nukkit/event/Listener.java | 29 +- .../nukkit/event/block/AnvilDamageEvent.java | 10 +- .../cn/nukkit/event/block/BellRingEvent.java | 46 + .../nukkit/event/block/BlockBreakEvent.java | 48 +- .../cn/nukkit/event/block/BlockBurnEvent.java | 9 +- .../cn/nukkit/event/block/BlockEvent.java | 9 +- .../nukkit/event/block/BlockExplodeEvent.java | 55 + .../cn/nukkit/event/block/BlockFadeEvent.java | 11 + .../cn/nukkit/event/block/BlockFallEvent.java | 7 + .../cn/nukkit/event/block/BlockFormEvent.java | 12 +- .../nukkit/event/block/BlockFromToEvent.java | 8 + .../cn/nukkit/event/block/BlockGrowEvent.java | 10 +- .../nukkit/event/block/BlockHarvestEvent.java | 41 + .../nukkit/event/block/BlockIgniteEvent.java | 10 + .../event/block/BlockPistonChangeEvent.java | 13 +- .../nukkit/event/block/BlockPistonEvent.java | 53 + .../nukkit/event/block/BlockPlaceEvent.java | 12 +- .../event/block/BlockRedstoneEvent.java | 13 +- .../nukkit/event/block/BlockSpreadEvent.java | 10 +- .../nukkit/event/block/BlockUpdateEvent.java | 9 +- .../event/block/CampfireSmeltEvent.java | 52 + .../event/block/ComposterEmptyEvent.java | 73 + .../event/block/ComposterFillEvent.java | 49 + .../nukkit/event/block/DoorToggleEvent.java | 9 +- .../event/block/ItemFrameDropItemEvent.java | 10 +- .../nukkit/event/block/LeavesDecayEvent.java | 9 +- .../nukkit/event/block/LiquidFlowEvent.java | 10 +- .../nukkit/event/block/SignChangeEvent.java | 12 +- .../nukkit/event/block/WaterFrostEvent.java | 25 + .../event/entity/CreatureSpawnEvent.java | 12 +- .../event/entity/CreeperPowerEvent.java | 8 +- .../entity/EndermanBlockPickUpEvent.java | 31 + .../event/entity/EntityArmorChangeEvent.java | 2 +- .../event/entity/EntityBlockChangeEvent.java | 4 - .../entity/EntityCombustByBlockEvent.java | 2 +- .../entity/EntityCombustByEntityEvent.java | 2 +- .../event/entity/EntityCombustEvent.java | 2 +- .../entity/EntityDamageBlockedEvent.java | 56 + .../entity/EntityDamageByBlockEvent.java | 3 +- .../EntityDamageByChildEntityEvent.java | 8 +- .../entity/EntityDamageByEntityEvent.java | 2 +- .../event/entity/EntityDamageEvent.java | 49 +- .../nukkit/event/entity/EntityDeathEvent.java | 2 +- .../event/entity/EntityDespawnEvent.java | 8 +- .../cn/nukkit/event/entity/EntityEvent.java | 2 +- .../event/entity/EntityExplodeEvent.java | 3 +- .../entity/EntityExplosionPrimeEvent.java | 4 - .../event/entity/EntityInteractEvent.java | 2 +- .../entity/EntityInventoryChangeEvent.java | 2 +- .../event/entity/EntityLevelChangeEvent.java | 2 +- .../event/entity/EntityMotionEvent.java | 2 +- .../event/entity/EntityRegainHealthEvent.java | 2 +- .../event/entity/EntityShootBowEvent.java | 6 +- .../nukkit/event/entity/EntitySpawnEvent.java | 8 +- .../event/entity/EntityTeleportEvent.java | 3 +- .../event/entity/EntityVehicleEnterEvent.java | 8 +- .../event/entity/EntityVehicleExitEvent.java | 8 +- .../event/entity/ExplosionPrimeEvent.java | 5 +- .../nukkit/event/entity/ItemDespawnEvent.java | 2 +- .../nukkit/event/entity/ItemSpawnEvent.java | 2 +- .../event/entity/ProjectileHitEvent.java | 3 +- .../event/inventory/CraftItemEvent.java | 7 +- .../event/inventory/EnchantItemEvent.java | 1 + .../event/inventory/FurnaceBurnEvent.java | 2 +- .../event/inventory/FurnaceSmeltEvent.java | 2 +- .../event/inventory/InventoryClickEvent.java | 2 +- .../event/inventory/InventoryCloseEvent.java | 2 +- .../event/inventory/InventoryEvent.java | 3 +- .../event/inventory/InventoryOpenEvent.java | 2 +- .../inventory/InventoryPickupArrowEvent.java | 2 +- .../inventory/InventoryPickupItemEvent.java | 2 +- .../inventory/InventoryTransactionEvent.java | 2 +- .../nukkit/event/inventory/LoomItemEvent.java | 33 + .../event/inventory/RepairItemEvent.java | 8 +- .../event/inventory/SmithItemEvent.java | 45 + .../cn/nukkit/event/level/ChunkEvent.java | 2 +- .../cn/nukkit/event/level/ChunkLoadEvent.java | 2 +- .../event/level/ChunkPopulateEvent.java | 3 +- .../nukkit/event/level/ChunkUnloadEvent.java | 3 +- .../cn/nukkit/event/level/LevelEvent.java | 2 +- .../cn/nukkit/event/level/LevelInitEvent.java | 3 +- .../cn/nukkit/event/level/LevelLoadEvent.java | 3 +- .../cn/nukkit/event/level/LevelSaveEvent.java | 3 +- .../nukkit/event/level/LevelUnloadEvent.java | 3 +- .../event/level/NetherPortalSpawnEvent.java | 26 + .../nukkit/event/level/SpawnChangeEvent.java | 2 +- .../event/level/StructureGrowEvent.java | 4 +- .../event/level/ThunderChangeEvent.java | 3 +- .../event/level/WeatherChangeEvent.java | 3 +- .../cn/nukkit/event/level/WeatherEvent.java | 2 +- .../event/player/CraftingTableOpenEvent.java | 26 + .../player/PlayerAsyncPreLoginEvent.java | 4 +- .../event/player/PlayerBedEnterEvent.java | 15 + .../event/player/PlayerBucketEmptyEvent.java | 6 +- .../event/player/PlayerBucketFillEvent.java | 1 - .../event/player/PlayerChangeSkinEvent.java | 3 +- .../nukkit/event/player/PlayerChatEvent.java | 1 - .../event/player/PlayerCreationEvent.java | 2 +- .../nukkit/event/player/PlayerDeathEvent.java | 4 + .../event/player/PlayerEatFoodEvent.java | 1 - .../cn/nukkit/event/player/PlayerEvent.java | 3 +- .../player/PlayerExperienceChangeEvent.java | 2 +- .../player/PlayerFormRespondedEvent.java | 3 - .../event/player/PlayerInitializedEvent.java | 17 + .../event/player/PlayerInteractEvent.java | 2 +- .../event/player/PlayerInvalidMoveEvent.java | 1 - .../event/player/PlayerItemHeldEvent.java | 4 +- .../nukkit/event/player/PlayerJumpEvent.java | 3 +- .../nukkit/event/player/PlayerKickEvent.java | 12 +- .../player/PlayerLocallyInitializedEvent.java | 3 +- .../player/PlayerMapInfoRequestEvent.java | 2 +- .../event/player/PlayerMessageEvent.java | 1 - .../nukkit/event/player/PlayerMoveEvent.java | 5 - .../nukkit/event/player/PlayerQuitEvent.java | 2 +- .../event/player/PlayerRespawnEvent.java | 5 +- .../player/PlayerSettingsRespondedEvent.java | 12 - .../event/player/PlayerTeleportEvent.java | 5 +- .../event/player/PlayerToggleCrawlEvent.java | 25 + .../event/player/PlayerToggleGlideEvent.java | 1 - .../event/player/PlayerToggleSneakEvent.java | 1 - .../player/PlayerToggleSpinAttackEvent.java | 28 + .../event/player/PlayerToggleSprintEvent.java | 1 - .../event/plugin/PluginDisableEvent.java | 2 +- .../event/plugin/PluginEnableEvent.java | 2 +- .../cn/nukkit/event/plugin/PluginEvent.java | 2 +- .../cn/nukkit/event/potion/PotionEvent.java | 1 - .../event/redstone/RedstoneUpdateEvent.java | 6 +- .../event/server/BatchPacketsEvent.java | 7 +- .../event/server/DataPacketReceiveEvent.java | 2 +- .../event/server/DataPacketSendEvent.java | 2 +- .../server/PlayerDataSerializeEvent.java | 7 +- .../event/server/QueryRegenerateEvent.java | 158 +- .../server/RemoteServerCommandEvent.java | 1 + .../event/server/ServerCommandEvent.java | 2 +- .../cn/nukkit/event/server/ServerEvent.java | 2 +- .../nukkit/event/server/ServerStopEvent.java | 2 +- .../vehicle/EntityEnterVehicleEvent.java | 8 +- .../event/vehicle/EntityExitVehicleEvent.java | 8 +- .../event/vehicle/VehicleCreateEvent.java | 6 +- .../event/vehicle/VehicleDamageEvent.java | 8 +- .../event/vehicle/VehicleDestroyEvent.java | 8 +- .../cn/nukkit/event/vehicle/VehicleEvent.java | 10 +- .../event/vehicle/VehicleMoveEvent.java | 4 +- .../event/vehicle/VehicleUpdateEvent.java | 5 +- .../event/weather/LightningStrikeEvent.java | 3 +- .../java/cn/nukkit/form/element/Element.java | 1 - .../cn/nukkit/form/element/ElementButton.java | 1 - .../nukkit/form/element/ElementDropdown.java | 5 +- .../cn/nukkit/form/element/ElementInput.java | 3 +- .../cn/nukkit/form/element/ElementLabel.java | 3 +- .../cn/nukkit/form/element/ElementSlider.java | 7 +- .../form/element/ElementStepSlider.java | 5 +- .../cn/nukkit/form/element/ElementToggle.java | 3 +- .../form/handler/FormResponseHandler.java | 3 +- .../cn/nukkit/form/response/FormResponse.java | 1 - .../form/response/FormResponseData.java | 1 - .../form/response/FormResponseModal.java | 1 - .../form/response/FormResponseSimple.java | 1 - .../cn/nukkit/form/window/FormWindow.java | 3 +- .../nukkit/form/window/FormWindowCustom.java | 10 +- .../nukkit/form/window/FormWindowModal.java | 4 +- .../nukkit/form/window/FormWindowSimple.java | 5 +- .../cn/nukkit/inventory/AnvilInventory.java | 14 +- .../cn/nukkit/inventory/BarrelInventory.java | 68 + .../cn/nukkit/inventory/BaseInventory.java | 74 +- .../cn/nukkit/inventory/BeaconInventory.java | 26 +- .../cn/nukkit/inventory/BigCraftingGrid.java | 1 + .../inventory/BlastFurnaceInventory.java | 15 + .../cn/nukkit/inventory/BrewingInventory.java | 68 +- .../cn/nukkit/inventory/BrewingRecipe.java | 1 - .../nukkit/inventory/CampfireInventory.java | 113 + .../cn/nukkit/inventory/CampfireRecipe.java | 38 + .../nukkit/inventory/ChestBoatInventory.java | 44 + .../cn/nukkit/inventory/ChestInventory.java | 16 +- .../nukkit/inventory/ContainerInventory.java | 3 +- .../cn/nukkit/inventory/ContainerRecipe.java | 1 + .../cn/nukkit/inventory/CraftingGrid.java | 2 +- .../cn/nukkit/inventory/CraftingManager.java | 246 +- .../cn/nukkit/inventory/CraftingRecipe.java | 2 +- .../cn/nukkit/inventory/CustomInventory.java | 3 +- .../nukkit/inventory/DispenserInventory.java | 15 + .../inventory/DoubleChestInventory.java | 74 +- .../cn/nukkit/inventory/DropperInventory.java | 15 + .../cn/nukkit/inventory/EnchantInventory.java | 16 +- .../inventory/EntityArmorInventory.java | 137 + .../inventory/EntityEquipmentInventory.java | 80 + .../cn/nukkit/inventory/FakeBlockMenu.java | 2 +- .../inventory/FakeBlockUIComponent.java | 19 +- src/main/java/cn/nukkit/inventory/Fuel.java | 57 +- .../cn/nukkit/inventory/FurnaceInventory.java | 6 +- .../cn/nukkit/inventory/FurnaceRecipe.java | 2 +- .../java/cn/nukkit/inventory/Inventory.java | 12 +- .../cn/nukkit/inventory/InventoryHolder.java | 2 +- .../cn/nukkit/inventory/InventoryType.java | 45 +- .../cn/nukkit/inventory/LoomInventory.java | 36 + .../java/cn/nukkit/inventory/MultiRecipe.java | 7 + .../inventory/PlayerCursorInventory.java | 1 + .../inventory/PlayerEnderChestInventory.java | 19 +- .../cn/nukkit/inventory/PlayerInventory.java | 143 +- .../inventory/PlayerOffhandInventory.java | 34 +- .../nukkit/inventory/PlayerUIComponent.java | 7 +- .../nukkit/inventory/PlayerUIInventory.java | 13 +- src/main/java/cn/nukkit/inventory/Recipe.java | 2 +- .../java/cn/nukkit/inventory/RecipeType.java | 7 +- .../cn/nukkit/inventory/ShapedRecipe.java | 43 +- .../cn/nukkit/inventory/ShapelessRecipe.java | 28 +- .../nukkit/inventory/ShulkerBoxInventory.java | 13 +- .../nukkit/inventory/SmithingInventory.java | 87 + .../cn/nukkit/inventory/SmithingRecipe.java | 150 + .../cn/nukkit/inventory/SmokerInventory.java | 15 + .../cn/nukkit/inventory/TradeInventory.java | 53 + .../inventory/TradeInventoryRecipe.java | 114 + .../transaction/CraftingTransaction.java | 76 +- .../transaction/EnchantTransaction.java | 10 +- .../transaction/InventoryTransaction.java | 61 +- .../transaction/LoomTransaction.java | 96 + .../transaction/RepairItemTransaction.java | 8 +- .../transaction/SmithingTransaction.java | 120 + .../action/CraftingTakeResultAction.java | 2 - .../CraftingTransferMaterialAction.java | 6 +- .../action/CreativeInventoryAction.java | 2 - .../transaction/action/DropItemAction.java | 6 +- .../transaction/action/EnchantingAction.java | 6 +- .../transaction/action/InventoryAction.java | 34 +- .../transaction/action/LoomItemAction.java | 44 + .../transaction/action/RepairItemAction.java | 5 +- .../transaction/action/SlotChangeAction.java | 54 +- .../action/SmithingItemAction.java | 42 + .../transaction/data/UseItemOnEntityData.java | 1 - src/main/java/cn/nukkit/item/Item.java | 378 +- .../cn/nukkit/item/ItemAmethystShard.java | 16 + src/main/java/cn/nukkit/item/ItemApple.java | 2 +- .../java/cn/nukkit/item/ItemAppleGold.java | 2 +- .../nukkit/item/ItemAppleGoldEnchanted.java | 3 +- src/main/java/cn/nukkit/item/ItemArmor.java | 11 +- .../java/cn/nukkit/item/ItemArmorStand.java | 72 + src/main/java/cn/nukkit/item/ItemArrow.java | 89 +- .../java/cn/nukkit/item/ItemAxeDiamond.java | 2 +- src/main/java/cn/nukkit/item/ItemAxeGold.java | 4 +- src/main/java/cn/nukkit/item/ItemAxeIron.java | 2 +- .../java/cn/nukkit/item/ItemAxeStone.java | 2 +- src/main/java/cn/nukkit/item/ItemAxeWood.java | 2 +- src/main/java/cn/nukkit/item/ItemBanner.java | 9 +- src/main/java/cn/nukkit/item/ItemBed.java | 5 +- src/main/java/cn/nukkit/item/ItemBeefRaw.java | 2 +- .../java/cn/nukkit/item/ItemBeetroot.java | 3 +- .../java/cn/nukkit/item/ItemBeetrootSoup.java | 2 +- .../java/cn/nukkit/item/ItemBlazePowder.java | 7 +- .../java/cn/nukkit/item/ItemBlazeRod.java | 1 - src/main/java/cn/nukkit/item/ItemBlock.java | 30 +- src/main/java/cn/nukkit/item/ItemBoat.java | 19 +- src/main/java/cn/nukkit/item/ItemBone.java | 2 +- src/main/java/cn/nukkit/item/ItemBook.java | 2 +- .../cn/nukkit/item/ItemBookEnchanted.java | 2 +- .../java/cn/nukkit/item/ItemBookWritable.java | 2 +- .../java/cn/nukkit/item/ItemBookWritten.java | 2 +- .../java/cn/nukkit/item/ItemBootsChain.java | 2 +- .../java/cn/nukkit/item/ItemBootsDiamond.java | 2 +- .../java/cn/nukkit/item/ItemBootsGold.java | 4 +- .../java/cn/nukkit/item/ItemBootsIron.java | 2 +- .../java/cn/nukkit/item/ItemBootsLeather.java | 2 +- .../cn/nukkit/item/ItemBootsNetherite.java | 2 +- src/main/java/cn/nukkit/item/ItemBow.java | 103 +- src/main/java/cn/nukkit/item/ItemBowl.java | 2 +- src/main/java/cn/nukkit/item/ItemBread.java | 2 +- .../java/cn/nukkit/item/ItemBrewingStand.java | 4 +- src/main/java/cn/nukkit/item/ItemBrick.java | 2 +- src/main/java/cn/nukkit/item/ItemBucket.java | 117 +- src/main/java/cn/nukkit/item/ItemCactus.java | 20 + src/main/java/cn/nukkit/item/ItemCake.java | 5 +- .../java/cn/nukkit/item/ItemCampfire.java | 19 + .../java/cn/nukkit/item/ItemCampfireSoul.java | 19 + src/main/java/cn/nukkit/item/ItemCarrot.java | 6 +- .../java/cn/nukkit/item/ItemCarrotGolden.java | 1 + .../java/cn/nukkit/item/ItemCauldron.java | 5 +- src/main/java/cn/nukkit/item/ItemChain.java | 20 + .../cn/nukkit/item/ItemChestBoatAcacia.java | 64 + .../cn/nukkit/item/ItemChestBoatBirch.java | 64 + .../cn/nukkit/item/ItemChestBoatDarkOak.java | 64 + .../cn/nukkit/item/ItemChestBoatJungle.java | 64 + .../cn/nukkit/item/ItemChestBoatMangrove.java | 64 + .../java/cn/nukkit/item/ItemChestBoatOak.java | 64 + .../cn/nukkit/item/ItemChestBoatSpruce.java | 64 + .../cn/nukkit/item/ItemChestplateChain.java | 2 +- .../cn/nukkit/item/ItemChestplateDiamond.java | 2 +- .../cn/nukkit/item/ItemChestplateGold.java | 4 +- .../cn/nukkit/item/ItemChestplateIron.java | 2 +- .../cn/nukkit/item/ItemChestplateLeather.java | 2 +- .../nukkit/item/ItemChestplateNetherite.java | 2 +- .../cn/nukkit/item/ItemChickenCooked.java | 2 +- .../java/cn/nukkit/item/ItemChickenRaw.java | 2 +- .../java/cn/nukkit/item/ItemChorusFruit.java | 12 +- src/main/java/cn/nukkit/item/ItemClay.java | 2 +- src/main/java/cn/nukkit/item/ItemClock.java | 2 +- .../java/cn/nukkit/item/ItemClownfish.java | 2 +- src/main/java/cn/nukkit/item/ItemCoal.java | 2 +- .../java/cn/nukkit/item/ItemColorArmor.java | 1 - src/main/java/cn/nukkit/item/ItemCompass.java | 2 +- src/main/java/cn/nukkit/item/ItemCookie.java | 2 +- .../java/cn/nukkit/item/ItemCopperRaw.java | 16 + .../java/cn/nukkit/item/ItemCrossbow.java | 222 +- src/main/java/cn/nukkit/item/ItemDiamond.java | 2 +- .../cn/nukkit/item/ItemDiscFragment5.java | 16 + .../java/cn/nukkit/item/ItemDoorAcacia.java | 5 +- .../java/cn/nukkit/item/ItemDoorBirch.java | 5 +- .../java/cn/nukkit/item/ItemDoorCrimson.java | 19 + .../java/cn/nukkit/item/ItemDoorDarkOak.java | 5 +- .../java/cn/nukkit/item/ItemDoorIron.java | 6 +- .../java/cn/nukkit/item/ItemDoorJungle.java | 5 +- .../java/cn/nukkit/item/ItemDoorSpruce.java | 5 +- .../java/cn/nukkit/item/ItemDoorWarped.java | 19 + .../java/cn/nukkit/item/ItemDoorWood.java | 6 +- .../java/cn/nukkit/item/ItemDriedKelp.java | 8 +- src/main/java/cn/nukkit/item/ItemDye.java | 2 +- .../java/cn/nukkit/item/ItemEchoShard.java | 16 + src/main/java/cn/nukkit/item/ItemEdible.java | 30 +- src/main/java/cn/nukkit/item/ItemEgg.java | 8 +- src/main/java/cn/nukkit/item/ItemElytra.java | 3 +- src/main/java/cn/nukkit/item/ItemEmerald.java | 2 +- .../java/cn/nukkit/item/ItemEmptyMap.java | 29 + .../java/cn/nukkit/item/ItemEndCrystal.java | 28 +- .../java/cn/nukkit/item/ItemEnderEye.java | 12 +- .../java/cn/nukkit/item/ItemExpBottle.java | 1 - src/main/java/cn/nukkit/item/ItemFeather.java | 2 +- .../java/cn/nukkit/item/ItemFireCharge.java | 36 +- .../java/cn/nukkit/item/ItemFirework.java | 50 +- src/main/java/cn/nukkit/item/ItemFish.java | 3 +- .../java/cn/nukkit/item/ItemFishCooked.java | 3 +- .../java/cn/nukkit/item/ItemFishingRod.java | 21 +- src/main/java/cn/nukkit/item/ItemFlint.java | 2 +- .../java/cn/nukkit/item/ItemFlintSteel.java | 30 +- .../java/cn/nukkit/item/ItemFlowerPot.java | 1 - .../java/cn/nukkit/item/ItemGhastTear.java | 6 +- .../java/cn/nukkit/item/ItemGlassBottle.java | 2 +- .../java/cn/nukkit/item/ItemGlowBerries.java | 19 + .../cn/nukkit/item/ItemGlowstoneDust.java | 2 +- .../java/cn/nukkit/item/ItemGoatHorn.java | 63 + src/main/java/cn/nukkit/item/ItemGoldRaw.java | 16 + .../java/cn/nukkit/item/ItemGunpowder.java | 2 +- .../java/cn/nukkit/item/ItemHelmetChain.java | 4 +- .../cn/nukkit/item/ItemHelmetDiamond.java | 2 +- .../java/cn/nukkit/item/ItemHelmetGold.java | 4 +- .../java/cn/nukkit/item/ItemHelmetIron.java | 2 +- .../cn/nukkit/item/ItemHelmetLeather.java | 2 +- .../cn/nukkit/item/ItemHelmetNetherite.java | 2 +- .../java/cn/nukkit/item/ItemHoeDiamond.java | 2 +- src/main/java/cn/nukkit/item/ItemHoeGold.java | 4 +- src/main/java/cn/nukkit/item/ItemHoeIron.java | 2 +- .../java/cn/nukkit/item/ItemHoeNetherite.java | 5 - .../java/cn/nukkit/item/ItemHoeStone.java | 2 +- src/main/java/cn/nukkit/item/ItemHoeWood.java | 2 +- .../java/cn/nukkit/item/ItemHoneyBottle.java | 24 +- .../java/cn/nukkit/item/ItemHoneycomb.java | 3 +- src/main/java/cn/nukkit/item/ItemHopper.java | 3 +- .../cn/nukkit/item/ItemHorseArmorDiamond.java | 1 + .../cn/nukkit/item/ItemHorseArmorGold.java | 3 +- .../cn/nukkit/item/ItemHorseArmorIron.java | 1 + .../cn/nukkit/item/ItemHorseArmorLeather.java | 1 + src/main/java/cn/nukkit/item/ItemID.java | 72 +- .../java/cn/nukkit/item/ItemIngotCopper.java | 16 + .../java/cn/nukkit/item/ItemIngotGold.java | 2 +- .../java/cn/nukkit/item/ItemIngotIron.java | 2 +- src/main/java/cn/nukkit/item/ItemIronRaw.java | 16 + .../java/cn/nukkit/item/ItemItemFrame.java | 3 +- .../cn/nukkit/item/ItemItemFrameGlow.java | 20 + src/main/java/cn/nukkit/item/ItemKelp.java | 19 + src/main/java/cn/nukkit/item/ItemLadder.java | 20 + src/main/java/cn/nukkit/item/ItemLeather.java | 2 +- .../cn/nukkit/item/ItemLeggingsChain.java | 2 +- .../cn/nukkit/item/ItemLeggingsDiamond.java | 2 +- .../java/cn/nukkit/item/ItemLeggingsGold.java | 4 +- .../java/cn/nukkit/item/ItemLeggingsIron.java | 2 +- .../cn/nukkit/item/ItemLeggingsLeather.java | 2 +- .../cn/nukkit/item/ItemLeggingsNetherite.java | 2 +- .../cn/nukkit/item/ItemLodestoneCompass.java | 16 + .../java/cn/nukkit/item/ItemMagmaCream.java | 6 +- src/main/java/cn/nukkit/item/ItemMap.java | 77 +- src/main/java/cn/nukkit/item/ItemMelon.java | 2 +- .../cn/nukkit/item/ItemMelonGlistering.java | 4 +- .../java/cn/nukkit/item/ItemMinecart.java | 19 +- .../cn/nukkit/item/ItemMinecartChest.java | 39 +- .../cn/nukkit/item/ItemMinecartHopper.java | 39 +- .../java/cn/nukkit/item/ItemMinecartTNT.java | 17 +- .../java/cn/nukkit/item/ItemMushroomStew.java | 2 +- src/main/java/cn/nukkit/item/ItemNameTag.java | 3 +- .../java/cn/nukkit/item/ItemNetherBrick.java | 2 +- .../cn/nukkit/item/ItemNetherSprouts.java | 20 + .../java/cn/nukkit/item/ItemNetherWart.java | 4 +- .../java/cn/nukkit/item/ItemNuggetGold.java | 2 +- .../java/cn/nukkit/item/ItemPainting.java | 29 +- src/main/java/cn/nukkit/item/ItemPaper.java | 2 +- .../cn/nukkit/item/ItemPickaxeDiamond.java | 2 +- .../java/cn/nukkit/item/ItemPickaxeGold.java | 4 +- .../java/cn/nukkit/item/ItemPickaxeIron.java | 2 +- .../java/cn/nukkit/item/ItemPickaxeStone.java | 2 +- .../java/cn/nukkit/item/ItemPickaxeWood.java | 2 +- .../cn/nukkit/item/ItemPorkchopCooked.java | 2 +- .../java/cn/nukkit/item/ItemPorkchopRaw.java | 2 +- src/main/java/cn/nukkit/item/ItemPotato.java | 5 +- .../java/cn/nukkit/item/ItemPotatoBaked.java | 3 +- src/main/java/cn/nukkit/item/ItemPotion.java | 8 +- .../cn/nukkit/item/ItemPotionLingering.java | 28 +- .../java/cn/nukkit/item/ItemPotionSplash.java | 2 +- .../java/cn/nukkit/item/ItemPufferfish.java | 1 - .../java/cn/nukkit/item/ItemPumpkinPie.java | 2 +- src/main/java/cn/nukkit/item/ItemQuartz.java | 2 +- .../java/cn/nukkit/item/ItemRabbitRaw.java | 2 +- src/main/java/cn/nukkit/item/ItemRail.java | 20 + src/main/java/cn/nukkit/item/ItemRecord.java | 4 + .../java/cn/nukkit/item/ItemRecord11.java | 5 + .../java/cn/nukkit/item/ItemRecord13.java | 5 + src/main/java/cn/nukkit/item/ItemRecord5.java | 5 + .../java/cn/nukkit/item/ItemRecordBlocks.java | 5 + .../java/cn/nukkit/item/ItemRecordCat.java | 5 + .../java/cn/nukkit/item/ItemRecordChirp.java | 5 + .../java/cn/nukkit/item/ItemRecordFar.java | 5 + .../java/cn/nukkit/item/ItemRecordMall.java | 5 + .../cn/nukkit/item/ItemRecordMellohi.java | 5 + .../cn/nukkit/item/ItemRecordOtherside.java | 5 + .../cn/nukkit/item/ItemRecordPigstep.java | 8 +- .../java/cn/nukkit/item/ItemRecordRelic.java | 5 + .../java/cn/nukkit/item/ItemRecordStal.java | 5 + .../java/cn/nukkit/item/ItemRecordStrad.java | 5 + .../java/cn/nukkit/item/ItemRecordWait.java | 5 + .../java/cn/nukkit/item/ItemRecordWard.java | 5 + .../cn/nukkit/item/ItemRecoveryCompass.java | 16 + .../java/cn/nukkit/item/ItemRedstone.java | 6 +- .../nukkit/item/ItemRedstoneComparator.java | 3 +- .../cn/nukkit/item/ItemRedstoneRepeater.java | 3 +- .../java/cn/nukkit/item/ItemRottenFlesh.java | 1 - src/main/java/cn/nukkit/item/ItemSaddle.java | 5 +- src/main/java/cn/nukkit/item/ItemSalmon.java | 1 - .../java/cn/nukkit/item/ItemSalmonCooked.java | 1 - .../cn/nukkit/item/ItemSeedsBeetroot.java | 5 +- .../java/cn/nukkit/item/ItemSeedsMelon.java | 5 +- .../java/cn/nukkit/item/ItemSeedsPumpkin.java | 5 +- .../java/cn/nukkit/item/ItemSeedsWheat.java | 7 +- src/main/java/cn/nukkit/item/ItemShears.java | 2 +- src/main/java/cn/nukkit/item/ItemShield.java | 11 +- .../cn/nukkit/item/ItemShovelDiamond.java | 2 +- .../java/cn/nukkit/item/ItemShovelGold.java | 9 +- .../java/cn/nukkit/item/ItemShovelIron.java | 2 +- .../java/cn/nukkit/item/ItemShovelStone.java | 2 +- .../java/cn/nukkit/item/ItemShovelWood.java | 7 +- src/main/java/cn/nukkit/item/ItemSign.java | 5 +- .../java/cn/nukkit/item/ItemSignAcacia.java | 29 + .../java/cn/nukkit/item/ItemSignBirch.java | 29 + .../java/cn/nukkit/item/ItemSignCrimson.java | 25 + .../java/cn/nukkit/item/ItemSignDarkOak.java | 28 + .../java/cn/nukkit/item/ItemSignJungle.java | 29 + .../java/cn/nukkit/item/ItemSignSpruce.java | 29 + .../java/cn/nukkit/item/ItemSignWarped.java | 25 + src/main/java/cn/nukkit/item/ItemSkull.java | 24 + .../java/cn/nukkit/item/ItemSlimeball.java | 2 +- .../java/cn/nukkit/item/ItemSnowball.java | 2 +- .../java/cn/nukkit/item/ItemSpawnEgg.java | 38 +- .../java/cn/nukkit/item/ItemSpiderEye.java | 1 + .../nukkit/item/ItemSpiderEyeFermented.java | 6 +- src/main/java/cn/nukkit/item/ItemSteak.java | 2 +- src/main/java/cn/nukkit/item/ItemStick.java | 2 +- src/main/java/cn/nukkit/item/ItemString.java | 5 +- src/main/java/cn/nukkit/item/ItemSugar.java | 2 +- .../java/cn/nukkit/item/ItemSugarcane.java | 7 +- .../java/cn/nukkit/item/ItemSweetBerries.java | 3 + .../java/cn/nukkit/item/ItemSwordDiamond.java | 2 +- .../java/cn/nukkit/item/ItemSwordGold.java | 4 +- .../java/cn/nukkit/item/ItemSwordIron.java | 2 +- .../java/cn/nukkit/item/ItemSwordStone.java | 2 +- .../java/cn/nukkit/item/ItemSwordWood.java | 2 +- src/main/java/cn/nukkit/item/ItemTool.java | 53 +- src/main/java/cn/nukkit/item/ItemTotem.java | 2 +- src/main/java/cn/nukkit/item/ItemTrident.java | 16 +- .../java/cn/nukkit/item/ItemTurtleShell.java | 8 - src/main/java/cn/nukkit/item/ItemTypes.java | 364 + .../nukkit/item/ItemWarpedFungusOnAStick.java | 3 - src/main/java/cn/nukkit/item/ItemWheat.java | 2 +- .../java/cn/nukkit/item/ProjectileItem.java | 52 +- .../cn/nukkit/item/RuntimeItemMapping.java | 106 +- .../java/cn/nukkit/item/RuntimeItems.java | 116 +- .../nukkit/item/enchantment/Enchantment.java | 26 +- .../enchantment/EnchantmentBindingCurse.java | 40 +- .../enchantment/EnchantmentDurability.java | 10 +- .../enchantment/EnchantmentEfficiency.java | 3 +- .../item/enchantment/EnchantmentEntry.java | 1 - .../enchantment/EnchantmentFireAspect.java | 3 +- .../enchantment/EnchantmentFrostWalker.java | 45 +- .../enchantment/EnchantmentKnockback.java | 3 +- .../item/enchantment/EnchantmentList.java | 1 - .../item/enchantment/EnchantmentLure.java | 3 +- .../item/enchantment/EnchantmentMending.java | 6 + .../enchantment/EnchantmentSilkTouch.java | 3 +- .../item/enchantment/EnchantmentThorns.java | 9 +- .../item/enchantment/EnchantmentType.java | 16 +- .../EnchantmentVanishingCurse.java | 33 +- .../enchantment/EnchantmentWaterBreath.java | 3 +- .../enchantment/EnchantmentWaterWalker.java | 3 +- .../enchantment/EnchantmentWaterWorker.java | 3 +- .../item/enchantment/bow/EnchantmentBow.java | 3 +- .../enchantment/bow/EnchantmentBowFlame.java | 2 +- .../bow/EnchantmentBowInfinity.java | 3 +- .../bow/EnchantmentBowKnockback.java | 4 +- .../enchantment/bow/EnchantmentBowPower.java | 3 +- .../enchantment/damage/EnchantmentDamage.java | 5 +- .../damage/EnchantmentDamageAll.java | 4 +- .../damage/EnchantmentDamageArthropods.java | 10 +- .../damage/EnchantmentDamageSmite.java | 6 +- .../enchantment/loot/EnchantmentLoot.java | 2 +- .../loot/EnchantmentLootDigging.java | 3 +- .../loot/EnchantmentLootFishing.java | 3 +- .../loot/EnchantmentLootWeapon.java | 3 +- .../protection/EnchantmentProtection.java | 2 +- .../protection/EnchantmentProtectionAll.java | 4 +- .../EnchantmentProtectionExplosion.java | 4 +- .../protection/EnchantmentProtectionFall.java | 2 +- .../protection/EnchantmentProtectionFire.java | 4 +- .../EnchantmentProtectionProjectile.java | 2 +- .../trident/EnchantmentTrident.java | 1 + .../trident/EnchantmentTridentChanneling.java | 19 +- .../trident/EnchantmentTridentImpaling.java | 69 +- .../trident/EnchantmentTridentLoyalty.java | 39 +- .../trident/EnchantmentTridentRiptide.java | 39 +- src/main/java/cn/nukkit/item/food/Food.java | 48 +- .../cn/nukkit/item/food/FoodChorusFruit.java | 40 +- .../cn/nukkit/item/food/FoodEffective.java | 9 +- .../nukkit/item/food/FoodEffectiveInBow.java | 18 + .../java/cn/nukkit/item/food/FoodInBowl.java | 5 +- .../java/cn/nukkit/item/food/FoodMilk.java | 5 +- .../item/randomitem/ConstantItemSelector.java | 10 +- .../cn/nukkit/item/randomitem/Fishing.java | 21 +- .../cn/nukkit/item/randomitem/RandomItem.java | 4 +- src/main/java/cn/nukkit/lang/BaseLang.java | 45 +- .../java/cn/nukkit/lang/TextContainer.java | 5 +- .../cn/nukkit/lang/TranslationContainer.java | 2 +- .../java/cn/nukkit/level/AsyncChunkData.java | 20 + .../cn/nukkit/level/AsyncChunkThread.java | 41 + .../java/cn/nukkit/level/BlockPalette.java | 104 + .../java/cn/nukkit/level/ChunkLoader.java | 2 +- .../java/cn/nukkit/level/ChunkManager.java | 39 +- .../java/cn/nukkit/level/ChunkPosition.java | 4 +- .../java/cn/nukkit/level/DimensionData.java | 18 + .../java/cn/nukkit/level/DimensionEnum.java | 1 + src/main/java/cn/nukkit/level/EnumLevel.java | 80 +- src/main/java/cn/nukkit/level/Explosion.java | 325 +- src/main/java/cn/nukkit/level/GameRule.java | 21 +- src/main/java/cn/nukkit/level/GameRules.java | 45 +- .../cn/nukkit/level/GlobalBlockPalette.java | 88 +- src/main/java/cn/nukkit/level/Level.java | 2402 ++++--- .../nukkit/level/LevelProviderConverter.java | 141 - .../cn/nukkit/level/ListChunkManager.java | 56 +- src/main/java/cn/nukkit/level/Location.java | 45 +- .../cn/nukkit/level/MovingObjectPosition.java | 2 +- .../java/cn/nukkit/level/ParticleEffect.java | 1 + src/main/java/cn/nukkit/level/Position.java | 13 +- src/main/java/cn/nukkit/level/Sound.java | 1 + .../java/cn/nukkit/level/biome/Biome.java | 30 +- .../cn/nukkit/level/biome/BiomeSelector.java | 96 +- .../java/cn/nukkit/level/biome/EnumBiome.java | 28 +- .../cn/nukkit/level/biome/impl/EndBiome.java | 20 + .../cn/nukkit/level/biome/impl/HellBiome.java | 20 +- .../level/biome/impl/beach/BeachBiome.java | 8 +- .../biome/impl/beach/ColdBeachBiome.java | 6 +- .../level/biome/impl/desert/DesertBiome.java | 2 +- .../level/biome/impl/desert/DesertMBiome.java | 2 +- .../impl/extremehills/ExtremeHillsBiome.java | 2 +- .../extremehills/ExtremeHillsEdgeBiome.java | 2 +- .../impl/extremehills/ExtremeHillsMBiome.java | 9 +- .../extremehills/ExtremeHillsPlusBiome.java | 2 +- .../extremehills/ExtremeHillsPlusMBiome.java | 7 +- .../impl/extremehills/StoneBeachBiome.java | 1 + .../biome/impl/forest/FlowerForestBiome.java | 5 +- .../level/biome/impl/forest/ForestBiome.java | 15 +- .../biome/impl/forest/ForestHillsBiome.java | 2 +- .../biome/impl/iceplains/IcePlainsBiome.java | 5 +- .../impl/iceplains/IcePlainsSpikesBiome.java | 9 +- .../level/biome/impl/jungle/JungleBiome.java | 12 +- .../biome/impl/jungle/JungleEdgeBiome.java | 3 - .../biome/impl/jungle/JungleEdgeMBiome.java | 4 - .../level/biome/impl/mesa/MesaBiome.java | 43 +- .../level/biome/impl/mesa/MesaBryceBiome.java | 5 +- .../biome/impl/mesa/MesaPlateauFBiome.java | 3 +- .../biome/impl/mesa/MesaPlateauFMBiome.java | 4 - .../biome/impl/mesa/MesaPlateauMBiome.java | 5 +- .../impl/mushroom/MushroomIslandBiome.java | 3 +- .../biome/impl/nether/BasaltDeltasBiome.java | 43 + .../biome/impl/nether/CrimsonForestBiome.java | 36 + .../impl/nether/SoulSandValleyBiome.java | 39 + .../biome/impl/nether/WarpedForestBiome.java | 36 + .../biome/impl/ocean/DeepOceanBiome.java | 2 +- .../biome/impl/ocean/FrozenOceanBiome.java | 12 +- .../level/biome/impl/ocean/OceanBiome.java | 49 +- .../level/biome/impl/plains/PlainsBiome.java | 11 +- .../impl/plains/SunflowerPlainsBiome.java | 14 +- .../biome/impl/river/FrozenRiverBiome.java | 12 +- .../level/biome/impl/river/RiverBiome.java | 44 +- .../impl/roofedforest/RoofedForestBiome.java | 16 +- .../biome/impl/savanna/SavannaBiome.java | 5 + .../biome/impl/savanna/SavannaMBiome.java | 5 + .../impl/savanna/SavannaPlateauBiome.java | 5 + .../impl/savanna/SavannaPlateauMBiome.java | 2 - .../level/biome/impl/swamp/SwampBiome.java | 19 +- .../biome/impl/swamp/SwamplandMBiome.java | 7 +- .../biome/impl/taiga/ColdTaigaBiome.java | 9 +- .../biome/impl/taiga/ColdTaigaHillsBiome.java | 2 +- .../biome/impl/taiga/ColdTaigaMBiome.java | 6 +- .../impl/taiga/MegaSpruceTaigaBiome.java | 22 +- .../biome/impl/taiga/MegaTaigaBiome.java | 26 +- .../biome/impl/taiga/MegaTaigaHillsBiome.java | 2 +- .../level/biome/impl/taiga/TaigaBiome.java | 15 +- .../level/biome/impl/taiga/TaigaMBiome.java | 3 +- .../nukkit/level/biome/type/CoveredBiome.java | 12 +- .../nukkit/level/biome/type/GrassyBiome.java | 8 +- .../nukkit/level/biome/type/SandyBiome.java | 8 +- .../nukkit/level/biome/type/SnowyBiome.java | 14 +- .../nukkit/level/biome/type/WateryBiome.java | 7 +- .../java/cn/nukkit/level/format/Chunk.java | 7 +- .../cn/nukkit/level/format/ChunkSection.java | 52 +- .../cn/nukkit/level/format/FullChunk.java | 94 +- .../cn/nukkit/level/format/LevelProvider.java | 6 +- .../level/format/LevelProviderManager.java | 7 +- .../cn/nukkit/level/format/anvil/Anvil.java | 54 +- .../cn/nukkit/level/format/anvil/Chunk.java | 161 +- .../level/format/anvil/ChunkSection.java | 127 +- .../level/format/anvil/RegionLoader.java | 79 +- .../format/anvil/palette/BiomePalette.java | 8 +- .../format/anvil/palette/BitArray256.java | 13 +- .../format/anvil/palette/BitArray4096.java | 163 - .../anvil/palette/BlockDataPalette.java | 221 - .../format/anvil/palette/BytePalette.java | 93 - .../format/anvil/palette/CharPalette.java | 93 - .../format/anvil/palette/IntPalette.java | 8 +- .../level/format/anvil/util/BlockStorage.java | 71 +- .../level/format/anvil/util/NibbleArray.java | 35 +- .../generic/Anvil2LevelDBConverter.java | 237 + .../level/format/generic/BaseChunk.java | 143 +- .../level/format/generic/BaseFullChunk.java | 211 +- .../format/generic/BaseLevelProvider.java | 76 +- .../format/generic/BaseRegionLoader.java | 22 +- .../level/format/generic/ChunkConverter.java | 91 - .../format/generic/EmptyChunkSection.java | 52 +- .../generic/serializer/NetworkChunkData.java | 13 + .../serializer/NetworkChunkSerializer.java | 77 +- .../format/leveldb/BlockStateMapping.java | 284 + .../cn/nukkit/level/format/leveldb/Chunk.java | 537 -- .../format/leveldb/LegacyStateMapper.java | 11 + .../nukkit/level/format/leveldb/LevelDB.java | 560 -- .../format/leveldb/LevelDBConstants.java | 25 + .../level/format/leveldb/LevelDBKey.java | 83 + .../level/format/leveldb/LevelDBProvider.java | 741 ++ .../format/leveldb/NukkitLegacyMapper.java | 59 + .../level/format/leveldb/key/BaseKey.java | 39 - .../level/format/leveldb/key/EntitiesKey.java | 15 - .../format/leveldb/key/ExtraDataKey.java | 15 - .../level/format/leveldb/key/FlagsKey.java | 15 - .../level/format/leveldb/key/TerrainKey.java | 15 - .../level/format/leveldb/key/TicksKey.java | 15 - .../level/format/leveldb/key/TilesKey.java | 15 - .../level/format/leveldb/key/VersionKey.java | 15 - .../serializer/BlockEntitySerializer.java | 64 + .../leveldb/serializer/ChunkDataLoader.java | 9 + .../serializer/ChunkSectionSerializer.java | 12 + .../serializer/ChunkSectionSerializerV1.java | 23 + .../serializer/ChunkSectionSerializerV7.java | 41 + .../serializer/ChunkSectionSerializerV8.java | 30 + .../serializer/ChunkSectionSerializerV9.java | 32 + .../serializer/ChunkSectionSerializers.java | 38 + .../leveldb/serializer/ChunkSerializer.java | 13 + .../leveldb/serializer/ChunkSerializerV3.java | 114 + .../leveldb/serializer/ChunkSerializers.java | 57 + .../leveldb/serializer/Data2dSerializer.java | 45 + .../leveldb/serializer/Data3dSerializer.java | 90 + .../leveldb/serializer/EntitySerializer.java | 88 + .../leveldb/structure/BlockStateSnapshot.java | 51 + .../leveldb/structure/ChunkBuilder.java | 171 + .../leveldb/structure/LevelDBChunk.java | 481 ++ .../structure/LevelDBChunkSection.java | 357 + .../leveldb/structure/StateBlockStorage.java | 277 + .../updater/BlockStateUpdaterChunker.java | 86 + .../updater/BlockStateUpdaterVanilla.java | 55 + .../nukkit/level/format/mcregion/Chunk.java | 538 -- .../level/format/mcregion/McRegion.java | 220 - .../level/format/mcregion/RegionLoader.java | 322 - .../java/cn/nukkit/level/generator/End.java | 71 + .../java/cn/nukkit/level/generator/Flat.java | 49 +- .../cn/nukkit/level/generator/Generator.java | 21 +- .../level/generator/GeneratorTaskFactory.java | 11 + .../cn/nukkit/level/generator/Nether.java | 327 +- .../cn/nukkit/level/generator/Normal.java | 185 +- .../level/generator/PopChunkManager.java | 20 + .../level/generator/SimpleChunkManager.java | 62 +- .../level/generator/SingleChunkManager.java | 11 + .../java/cn/nukkit/level/generator/Void.java | 57 + .../{nukkit/d/NoiseD.java => Noise.java} | 6 +- .../{nukkit/d/PerlinD.java => Perlin.java} | 16 +- .../{nukkit/d/SimplexD.java => Simplex.java} | 40 +- .../generator/noise/nukkit/f/NoiseF.java | 2 +- .../generator/noise/nukkit/f/PerlinF.java | 2 +- .../generator/noise/nukkit/f/SimplexF.java | 2 +- .../vanilla/d/NoiseGeneratorImprovedD.java | 177 - .../vanilla/d/NoiseGeneratorOctavesD.java | 62 - .../vanilla/d/NoiseGeneratorPerlinD.java | 54 - .../vanilla/d/NoiseGeneratorSimplexD.java | 182 - .../vanilla/f/NoiseGeneratorImprovedF.java | 10 +- .../vanilla/f/NoiseGeneratorOctavesF.java | 6 +- .../vanilla/f/NoiseGeneratorPerlinF.java | 54 - .../vanilla/f/NoiseGeneratorSimplexF.java | 182 - .../generator/object/BasicGenerator.java | 5 +- .../generator/object/ObjectTallGrass.java | 72 +- .../object/mushroom/BigMushroom.java | 16 +- .../level/generator/object/ore/OreType.java | 8 +- .../object/tree/HugeTreesGenerator.java | 27 +- .../generator/object/tree/NewJungleTree.java | 42 +- .../object/tree/ObjectBigSpruceTree.java | 59 +- .../object/tree/ObjectBirchTree.java | 14 +- .../object/tree/ObjectCrimsonTree.java | 19 + .../object/tree/ObjectDarkOakTree.java | 5 +- .../object/tree/ObjectFallenTree.java | 61 + .../object/tree/ObjectJungleBigTree.java | 13 +- .../object/tree/ObjectJungleTree.java | 13 +- .../object/tree/ObjectNetherTree.java | 131 + .../generator/object/tree/ObjectOakTree.java | 14 +- .../object/tree/ObjectSavannaTree.java | 11 +- .../object/tree/ObjectSpruceTree.java | 19 +- .../object/tree/ObjectSwampTree.java | 19 +- .../object/tree/ObjectTallBirchTree.java | 2 +- .../generator/object/tree/ObjectTree.java | 8 +- .../object/tree/ObjectWarpedTree.java | 19 + .../generator/object/tree/TreeGenerator.java | 4 +- .../populator/helper/EnsureBelow.java | 3 +- .../populator/helper/EnsureCover.java | 12 +- .../populator/helper/EnsureGrassBelow.java | 8 +- .../populator/helper/PopulatorHelpers.java | 14 + .../populator/helper/package-info.java | 4 - .../populator/impl/MushroomPopulator.java | 3 +- .../populator/impl/PopulatorBamboo.java | 45 + .../impl/PopulatorBasaltDeltaLava.java | 45 + .../impl/PopulatorBasaltDeltaMagma.java | 45 + .../impl/PopulatorBasaltDeltaPillar.java | 47 + .../populator/impl/PopulatorBedrock.java | 26 +- .../populator/impl/PopulatorCactus.java | 29 +- .../populator/impl/PopulatorCaves.java | 52 +- .../impl/PopulatorCrimsonForestGround.java | 50 + .../impl/PopulatorCrimsonFungus.java | 40 + .../populator/impl/PopulatorDeadBush.java | 3 +- .../populator/impl/PopulatorDoublePlant.java | 14 +- .../populator/impl/PopulatorFallenTree.java | 69 + .../populator/impl/PopulatorFlower.java | 12 +- .../populator/impl/PopulatorForestRock.java | 71 + .../populator/impl/PopulatorGlowStone.java | 25 +- .../populator/impl/PopulatorGrass.java | 6 +- .../populator/impl/PopulatorGroundCover.java | 8 +- .../populator/impl/PopulatorGroundFire.java | 16 +- .../populator/impl/PopulatorKelp.java | 48 + .../populator/impl/PopulatorLava.java | 23 +- .../populator/impl/PopulatorLilyPad.java | 3 +- .../populator/impl/PopulatorMelon.java | 4 +- .../populator/impl/PopulatorMineshaft.java | 25 - .../populator/impl/PopulatorNetherFire.java | 47 + .../populator/impl/PopulatorOre.java | 1 + .../populator/impl/PopulatorPumpkin.java | 39 + .../populator/impl/PopulatorRavines.java | 205 - .../populator/impl/PopulatorSeagrass.java | 36 + .../impl/PopulatorSmallMushroom.java | 3 +- .../impl/PopulatorSoulSandValleyGround.java | 41 + .../populator/impl/PopulatorSpring.java | 110 + .../populator/impl/PopulatorSugarcane.java | 29 +- .../impl/PopulatorSweetBerryBush.java | 25 + .../impl/PopulatorTallSugarcane.java | 1 + .../populator/impl/PopulatorTree.java | 4 +- .../impl/PopulatorTwistingVines.java | 62 + .../impl/PopulatorUnderwaterFloor.java | 77 + .../impl/PopulatorWarpedForestGround.java | 47 + .../populator/impl/PopulatorWarpedFungus.java | 40 + .../populator/impl/PopulatorWeepingVines.java | 60 + .../populator/impl/WaterIcePopulator.java | 9 +- .../impl/tree/DarkOakTreePopulator.java | 6 +- .../impl/tree/JungleBigTreePopulator.java | 13 +- .../impl/tree/JungleFloorPopulator.java | 6 +- .../impl/tree/JungleTreePopulator.java | 6 +- .../impl/tree/SavannaTreePopulator.java | 6 +- .../impl/tree/SpruceBigTreePopulator.java | 12 +- .../impl/tree/SpruceMegaTreePopulator.java | 12 +- .../impl/tree/SwampTreePopulator.java | 6 +- .../populator/tree/DarkOakTreePopulator.java | 64 + .../tree/JungleBigTreePopulator.java | 62 + .../populator/tree/JungleTreePopulator.java | 63 + .../populator/tree/SavannaTreePopulator.java | 63 + .../populator/tree/SwampTreePopulator.java | 63 + .../generator/populator/type/Populator.java | 5 +- .../populator/type/PopulatorCount.java | 1 + .../type/PopulatorOceanFloorSurfaceBlock.java | 20 + .../populator/type/PopulatorSurfaceBlock.java | 3 +- .../level/generator/task/GenerationTask.java | 20 +- .../generator/task/LightPopulationTask.java | 6 +- .../level/generator/task/PopulationTask.java | 42 +- .../level/particle/AngryVillagerParticle.java | 1 + .../particle/BlockForceFieldParticle.java | 1 + .../level/particle/BoneMealParticle.java | 18 +- .../nukkit/level/particle/BubbleParticle.java | 1 + .../level/particle/CriticalParticle.java | 1 + .../level/particle/DestroyBlockParticle.java | 15 +- .../level/particle/ElectricSparkParticle.java | 24 + .../level/particle/EnchantParticle.java | 1 + .../particle/EnchantmentTableParticle.java | 1 + .../level/particle/EntityFlameParticle.java | 1 + .../level/particle/ExplodeParticle.java | 1 + .../nukkit/level/particle/FlameParticle.java | 1 + .../level/particle/FloatingTextParticle.java | 31 +- .../level/particle/GenericParticle.java | 18 +- .../level/particle/HappyVillagerParticle.java | 1 + .../nukkit/level/particle/HeartParticle.java | 1 + .../level/particle/HugeExplodeParticle.java | 1 + .../particle/HugeExplodeSeedParticle.java | 1 + .../particle/InstantEnchantParticle.java | 1 + .../level/particle/InstantSpellParticle.java | 2 - .../level/particle/ItemBreakParticle.java | 1 + .../level/particle/LavaDripParticle.java | 1 + .../nukkit/level/particle/LavaParticle.java | 1 + .../level/particle/MobSpawnParticle.java | 2 +- .../cn/nukkit/level/particle/Particle.java | 42 +- .../nukkit/level/particle/PortalParticle.java | 1 + .../level/particle/PunchBlockParticle.java | 26 +- .../level/particle/RainSplashParticle.java | 1 + .../level/particle/RedstoneParticle.java | 1 + .../nukkit/level/particle/ScrapeParticle.java | 24 + .../nukkit/level/particle/SmokeParticle.java | 1 + .../nukkit/level/particle/SpellParticle.java | 16 +- .../nukkit/level/particle/SplashParticle.java | 1 + .../nukkit/level/particle/SporeParticle.java | 1 + .../level/particle/TerrainParticle.java | 1 + .../level/particle/WaterDripParticle.java | 1 + .../nukkit/level/particle/WaterParticle.java | 1 + .../nukkit/level/particle/WaxOffParticle.java | 24 + .../nukkit/level/particle/WaxOnParticle.java | 24 + .../persistence/ImmutableCompoundTag.java | 241 + .../persistence/PersistentDataContainer.java | 58 + .../level/persistence/PersistentDataType.java | 49 + .../PersistentItemDataContainer.java | 8 + .../impl/DelegatePersistentDataContainer.java | 34 + .../PersistentDataContainerBlockWrapper.java | 58 + .../PersistentDataContainerEntityWrapper.java | 63 + .../PersistentDataContainerItemWrapper.java | 84 + .../cn/nukkit/level/util/BitArrayVersion.java | 17 +- .../cn/nukkit/level/util/PaddedBitArray.java | 6 +- .../level/util/PalettedBlockStorage.java | 86 +- .../cn/nukkit/level/util/Pow2BitArray.java | 6 +- .../nukkit/level/util/SingletonBitArray.java | 39 + src/main/java/cn/nukkit/math/Angle.java | 1 - src/main/java/cn/nukkit/math/BlockFace.java | 37 +- .../java/cn/nukkit/math/BlockVector3.java | 9 +- src/main/java/cn/nukkit/math/MathHelper.java | 5 +- src/main/java/cn/nukkit/math/NukkitMath.java | 3 +- .../java/cn/nukkit/math/NukkitRandom.java | 5 +- .../cn/nukkit/math/SimpleAxisAlignedBB.java | 2 +- src/main/java/cn/nukkit/math/Vector2.java | 14 +- src/main/java/cn/nukkit/math/Vector2f.java | 14 +- src/main/java/cn/nukkit/math/Vector3.java | 15 +- src/main/java/cn/nukkit/math/Vector3f.java | 7 +- src/main/java/cn/nukkit/math/VectorMath.java | 3 +- .../nukkit/metadata/BlockMetadataStore.java | 5 +- .../nukkit/metadata/EntityMetadataStore.java | 2 +- .../nukkit/metadata/LevelMetadataStore.java | 4 +- .../cn/nukkit/metadata/MetadataStore.java | 2 +- .../cn/nukkit/metadata/MetadataValue.java | 3 +- .../java/cn/nukkit/metadata/Metadatable.java | 2 +- .../nukkit/metadata/PlayerMetadataStore.java | 4 +- src/main/java/cn/nukkit/metrics/Metrics.java | 6 +- .../java/cn/nukkit/metrics/NukkitMetrics.java | 28 +- src/main/java/cn/nukkit/nbt/NBTIO.java | 40 +- .../nbt/stream/BufferedRandomAccessFile.java | 467 -- .../nbt/stream/FastByteArrayOutputStream.java | 18 +- .../cn/nukkit/nbt/stream/NBTInputStream.java | 13 +- .../cn/nukkit/nbt/stream/NBTOutputStream.java | 3 +- .../java/cn/nukkit/nbt/stream/PGZIPBlock.java | 18 +- .../nukkit/nbt/stream/PGZIPOutputStream.java | 18 +- .../java/cn/nukkit/nbt/stream/PGZIPState.java | 3 +- .../java/cn/nukkit/nbt/tag/ByteArrayTag.java | 5 +- src/main/java/cn/nukkit/nbt/tag/ByteTag.java | 9 +- .../java/cn/nukkit/nbt/tag/CompoundTag.java | 90 +- .../java/cn/nukkit/nbt/tag/DoubleTag.java | 8 +- src/main/java/cn/nukkit/nbt/tag/EndTag.java | 4 +- src/main/java/cn/nukkit/nbt/tag/FloatTag.java | 8 +- .../java/cn/nukkit/nbt/tag/IntArrayTag.java | 5 +- src/main/java/cn/nukkit/nbt/tag/IntTag.java | 8 +- src/main/java/cn/nukkit/nbt/tag/ListTag.java | 11 +- src/main/java/cn/nukkit/nbt/tag/LongTag.java | 8 +- .../java/cn/nukkit/nbt/tag/NumberTag.java | 3 +- src/main/java/cn/nukkit/nbt/tag/ShortTag.java | 8 +- .../java/cn/nukkit/nbt/tag/StringTag.java | 8 +- src/main/java/cn/nukkit/nbt/tag/Tag.java | 5 +- .../network/AdvancedSourceInterface.java | 2 +- .../cn/nukkit/network/BatchingHelper.java | 37 + .../nukkit/network/CompressBatchedPacket.java | 50 - .../nukkit/network/CompressBatchedTask.java | 50 - .../nukkit/network/CompressionProvider.java | 71 +- src/main/java/cn/nukkit/network/Network.java | 218 +- .../cn/nukkit/network/RakNetInterface.java | 22 +- .../cn/nukkit/network/SourceInterface.java | 11 +- .../network/encryption/EncryptionUtils.java | 2 +- .../protocol/AddBehaviorTreePacket.java | 10 +- .../network/protocol/AddEntityPacket.java | 50 +- .../network/protocol/AddItemEntityPacket.java | 5 +- .../network/protocol/AddPaintingPacket.java | 4 +- .../network/protocol/AddPlayerPacket.java | 28 +- .../protocol/AdventureSettingsPacket.java | 11 +- .../network/protocol/AnimateEntityPacket.java | 3 + .../network/protocol/AnimatePacket.java | 1 - .../network/protocol/AnvilDamagePacket.java | 6 +- .../protocol/AvailableCommandsPacket.java | 9 +- .../AvailableEntityIdentifiersPacket.java | 17 +- .../nukkit/network/protocol/BatchPacket.java | 5 +- .../protocol/BiomeDefinitionListPacket.java | 19 +- .../protocol/BlockEntityDataPacket.java | 3 +- .../network/protocol/BlockEventPacket.java | 5 +- .../protocol/BlockPickRequestPacket.java | 2 +- .../network/protocol/BookEditPacket.java | 2 +- .../network/protocol/BossEventPacket.java | 18 +- .../nukkit/network/protocol/CameraPacket.java | 7 +- .../protocol/ChangeDimensionPacket.java | 2 +- .../protocol/ChunkRadiusUpdatedPacket.java | 5 +- .../protocol/ClientCacheStatusPacket.java | 4 + .../ClientToServerHandshakePacket.java | 8 +- .../ClientboundMapItemDataPacket.java | 13 +- .../network/protocol/CodeBuilderPacket.java | 4 +- .../protocol/CommandBlockUpdatePacket.java | 4 +- .../protocol/CommandRequestPacket.java | 7 +- .../protocol/CompletedUsingItemPacket.java | 2 +- .../protocol/ContainerClosePacket.java | 3 +- .../network/protocol/ContainerOpenPacket.java | 12 +- .../protocol/ContainerSetDataPacket.java | 7 +- .../network/protocol/CraftingDataPacket.java | 51 +- .../network/protocol/CraftingEventPacket.java | 18 +- .../protocol/CreativeContentPacket.java | 12 +- .../nukkit/network/protocol/DataPacket.java | 54 +- .../network/protocol/DeathInfoPacket.java | 1 + .../network/protocol/DebugInfoPacket.java | 4 +- .../network/protocol/DimensionDataPacket.java | 43 + .../network/protocol/DisconnectPacket.java | 6 +- .../network/protocol/EmoteListPacket.java | 4 + .../nukkit/network/protocol/EmotePacket.java | 6 +- .../network/protocol/EntityEventPacket.java | 9 +- .../network/protocol/EntityFallPacket.java | 7 +- .../protocol/EntityPickRequestPacket.java | 4 +- .../nukkit/network/protocol/EventPacket.java | 40 +- .../protocol/GUIDataPickItemPacket.java | 4 +- .../protocol/GameRulesChangedPacket.java | 14 +- .../network/protocol/HurtArmorPacket.java | 3 +- .../InitiateWebSocketConnectionPacket.java | 8 +- .../network/protocol/InteractPacket.java | 14 +- .../protocol/InventoryContentPacket.java | 6 +- .../network/protocol/InventorySlotPacket.java | 7 +- .../protocol/InventoryTransactionPacket.java | 41 +- .../network/protocol/ItemComponentPacket.java | 25 + .../protocol/ItemFrameDropItemPacket.java | 2 +- .../protocol/ItemStackRequestPacket.java | 103 - .../network/protocol/LecternUpdatePacket.java | 2 + .../network/protocol/LevelChunkPacket.java | 7 +- .../protocol/LevelEventGenericPacket.java | 4 + .../network/protocol/LevelEventPacket.java | 11 +- .../protocol/LevelSoundEventPacket.java | 14 +- .../protocol/LevelSoundEventPacketV1.java | 5 +- .../protocol/LevelSoundEventPacketV2.java | 1 + .../nukkit/network/protocol/LoginPacket.java | 179 +- .../protocol/MapCreateLockedCopyPacket.java | 7 +- .../protocol/MapInfoRequestPacket.java | 7 +- .../protocol/MobArmorEquipmentPacket.java | 3 +- .../network/protocol/MobEffectPacket.java | 4 +- .../network/protocol/MobEquipmentPacket.java | 7 +- .../protocol/ModalFormRequestPacket.java | 6 +- .../protocol/ModalFormResponsePacket.java | 6 +- .../protocol/MoveEntityAbsolutePacket.java | 3 +- .../protocol/MoveEntityDeltaPacket.java | 108 +- .../network/protocol/MovePlayerPacket.java | 18 +- .../network/protocol/NPCRequestPacket.java | 13 +- .../NetworkChunkPublisherUpdatePacket.java | 7 +- .../protocol/NetworkSettingsPacket.java | 6 +- .../protocol/NetworkStackLatencyPacket.java | 9 +- .../network/protocol/NpcDialoguePacket.java | 40 + .../OnScreenTextureAnimationPacket.java | 9 +- .../network/protocol/OpenSignPacket.java | 2 +- .../PacketViolationWarningPacket.java | 22 +- .../network/protocol/PlaySoundPacket.java | 4 +- .../network/protocol/PlayStatusPacket.java | 36 +- .../network/protocol/PlayerActionPacket.java | 8 +- .../protocol/PlayerArmorDamagePacket.java | 9 +- .../protocol/PlayerAuthInputPacket.java | 19 +- .../protocol/PlayerEnchantOptionsPacket.java | 33 +- .../network/protocol/PlayerHotbarPacket.java | 5 +- .../network/protocol/PlayerInputPacket.java | 3 +- .../network/protocol/PlayerListPacket.java | 50 +- .../network/protocol/PlayerSkinPacket.java | 7 +- .../PlayerStartItemCooldownPacket.java | 29 + .../nukkit/network/protocol/ProtocolInfo.java | 18 +- .../network/protocol/RemoveEntityPacket.java | 5 +- .../protocol/RequestAbilityPacket.java | 7 +- .../protocol/RequestChunkRadiusPacket.java | 5 +- .../RequestNetworkSettingsPacket.java | 6 +- .../protocol/RequestPermissionsPacket.java | 21 + .../protocol/ResourcePackChunkDataPacket.java | 5 +- .../ResourcePackClientResponsePacket.java | 1 + .../protocol/ResourcePackStackPacket.java | 23 +- .../protocol/ResourcePacksInfoPacket.java | 3 +- .../network/protocol/RespawnPacket.java | 1 - .../network/protocol/RiderJumpPacket.java | 6 +- .../protocol/ScriptCustomEventPacket.java | 6 +- .../protocol/ServerSettingsRequestPacket.java | 8 +- .../ServerSettingsResponsePacket.java | 6 +- .../ServerToClientHandshakePacket.java | 6 +- .../protocol/SetCommandsEnabledPacket.java | 2 +- .../protocol/SetDefaultGameTypePacket.java | 24 + .../network/protocol/SetDifficultyPacket.java | 1 - .../network/protocol/SetEntityDataPacket.java | 5 +- .../network/protocol/SetEntityLinkPacket.java | 15 +- .../protocol/SetEntityMotionPacket.java | 9 +- .../network/protocol/SetHealthPacket.java | 2 +- .../network/protocol/SetLastHurtByPacket.java | 8 +- .../SetLocalPlayerAsInitializedPacket.java | 3 +- .../protocol/SetPlayerGameTypePacket.java | 5 +- .../protocol/SetSpawnPositionPacket.java | 5 +- .../network/protocol/SetTimePacket.java | 6 +- .../network/protocol/SetTitlePacket.java | 13 +- .../protocol/SettingsCommandPacket.java | 25 + .../network/protocol/ShowProfilePacket.java | 6 +- .../network/protocol/SimpleEventPacket.java | 14 +- .../protocol/SpawnExperienceOrbPacket.java | 5 +- .../protocol/SpawnParticleEffectPacket.java | 8 +- .../network/protocol/StartGamePacket.java | 74 +- .../network/protocol/StopSoundPacket.java | 2 +- .../protocol/StructureBlockUpdatePacket.java | 8 +- .../protocol/SubClientLoginPacket.java | 8 +- .../protocol/TakeItemEntityPacket.java | 7 +- .../nukkit/network/protocol/TextPacket.java | 10 +- .../network/protocol/ToastRequestPacket.java | 7 +- .../network/protocol/TransferPacket.java | 10 +- .../protocol/UpdateAbilitiesPacket.java | 7 +- .../UpdateAdventureSettingsPacket.java | 7 +- .../protocol/UpdateAttributesPacket.java | 7 +- .../network/protocol/UpdateBlockPacket.java | 6 +- .../protocol/UpdateEquipmentPacket.java | 8 +- .../protocol/UpdatePlayerGameTypePacket.java | 4 +- .../protocol/UpdateSoftEnumPacket.java | 5 +- .../network/protocol/UpdateTradePacket.java | 9 +- .../protocol/VideoStreamConnectPacket.java | 2 + .../network/protocol/types/AbilityLayer.java | 1 + .../protocol/types/AuthInputAction.java | 1 + .../protocol/types/AuthInteractionModel.java | 1 + .../protocol/types/ClientPlayMode.java | 1 + .../protocol/types/CommandOriginData.java | 2 +- .../protocol/types/DimensionDefinition.java | 12 + .../network/protocol/types/EntityLink.java | 2 +- .../protocol/types/ExperimentData.java | 9 + .../types/NetworkInventoryAction.java | 109 +- .../types/PacketCompressionAlgorithm.java | 1 + .../network/protocol/types/PlayerAbility.java | 1 + .../protocol/types/PlayerBlockActionData.java | 1 + .../cn/nukkit/network/query/QueryHandler.java | 18 +- .../java/cn/nukkit/network/rcon/RCON.java | 15 +- .../cn/nukkit/network/rcon/RCONCommand.java | 1 + .../cn/nukkit/network/rcon/RCONPacket.java | 9 +- .../cn/nukkit/network/rcon/RCONServer.java | 24 +- .../network/session/RakNetPlayerSession.java | 17 +- .../java/cn/nukkit/permission/BanEntry.java | 27 +- .../java/cn/nukkit/permission/BanList.java | 15 +- .../nukkit/permission/DefaultPermissions.java | 149 +- .../cn/nukkit/permission/Permissible.java | 3 +- .../cn/nukkit/permission/PermissibleBase.java | 27 +- .../java/cn/nukkit/permission/Permission.java | 11 +- .../permission/PermissionAttachment.java | 7 +- .../permission/PermissionAttachmentInfo.java | 17 +- .../permission/PermissionRemovedExecutor.java | 2 +- .../cn/nukkit/permission/ServerOperator.java | 3 - .../java/cn/nukkit/plugin/EventExecutor.java | 2 +- .../cn/nukkit/plugin/JavaPluginLoader.java | 10 +- .../cn/nukkit/plugin/LambdaEventExecutor.java | 29 + src/main/java/cn/nukkit/plugin/Library.java | 4 - .../nukkit/plugin/LibraryLoadException.java | 5 +- .../java/cn/nukkit/plugin/LibraryLoader.java | 6 +- .../cn/nukkit/plugin/MethodEventExecutor.java | 2 +- src/main/java/cn/nukkit/plugin/Plugin.java | 150 +- .../java/cn/nukkit/plugin/PluginBase.java | 43 +- .../cn/nukkit/plugin/PluginClassLoader.java | 10 +- .../cn/nukkit/plugin/PluginDescription.java | 84 +- .../cn/nukkit/plugin/PluginLoadOrder.java | 9 +- .../java/cn/nukkit/plugin/PluginLoader.java | 59 +- .../java/cn/nukkit/plugin/PluginLogger.java | 5 +- .../java/cn/nukkit/plugin/PluginManager.java | 101 +- .../cn/nukkit/plugin/RegisteredListener.java | 12 +- .../plugin/service/NKServiceManager.java | 17 +- .../service/RegisteredServiceProvider.java | 13 +- .../nukkit/plugin/service/ServiceManager.java | 3 - .../plugin/service/ServicePriority.java | 4 - src/main/java/cn/nukkit/potion/Effect.java | 66 +- .../java/cn/nukkit/potion/InstantEffect.java | 3 +- src/main/java/cn/nukkit/potion/Potion.java | 198 +- .../resourcepacks/AbstractResourcePack.java | 1 + .../cn/nukkit/resourcepacks/ResourcePack.java | 5 +- .../resourcepacks/ResourcePackManager.java | 5 +- .../resourcepacks/ZippedResourcePack.java | 27 +- .../java/cn/nukkit/scheduler/AsyncPool.java | 5 +- .../java/cn/nukkit/scheduler/AsyncTask.java | 10 +- .../java/cn/nukkit/scheduler/AsyncWorker.java | 8 +- .../scheduler/BlockUpdateScheduler.java | 17 +- .../cn/nukkit/scheduler/FileWriteTask.java | 4 +- .../cn/nukkit/scheduler/NukkitRunnable.java | 4 +- .../java/cn/nukkit/scheduler/PluginTask.java | 28 +- .../cn/nukkit/scheduler/ServerScheduler.java | 109 +- src/main/java/cn/nukkit/scheduler/Task.java | 18 +- .../java/cn/nukkit/scheduler/TaskHandler.java | 16 +- src/main/java/cn/nukkit/timings/JsonUtil.java | 72 - .../java/cn/nukkit/timings/LevelTimings.java | 47 - .../java/cn/nukkit/utils/BannerPattern.java | 31 +- src/main/java/cn/nukkit/utils/Binary.java | 31 +- .../java/cn/nukkit/utils/BinaryStream.java | 137 +- src/main/java/cn/nukkit/utils/BlockColor.java | 30 +- .../java/cn/nukkit/utils/BlockIterator.java | 14 +- .../cn/nukkit/utils/BlockUpdateEntry.java | 7 +- .../java/cn/nukkit/utils/BossBarColor.java | 4 +- .../java/cn/nukkit/utils/ChunkException.java | 5 +- .../java/cn/nukkit/utils/ClientChainData.java | 69 +- src/main/java/cn/nukkit/utils/Config.java | 133 +- .../java/cn/nukkit/utils/ConfigSection.java | 189 +- .../utils/DefaultPlayerDataSerializer.java | 21 +- .../java/cn/nukkit/utils/DummyBossBar.java | 52 +- src/main/java/cn/nukkit/utils/DyeColor.java | 41 + .../java/cn/nukkit/utils/EventException.java | 5 +- src/main/java/cn/nukkit/utils/Faceable.java | 11 +- src/main/java/cn/nukkit/utils/Hash.java | 19 +- .../java/cn/nukkit/utils/HastebinUtility.java | 65 - .../java/cn/nukkit/utils/LevelException.java | 5 +- src/main/java/cn/nukkit/utils/LogLevel.java | 15 +- src/main/java/cn/nukkit/utils/Logger.java | 4 +- .../java/cn/nukkit/utils/LoginChainData.java | 4 + src/main/java/cn/nukkit/utils/MainLogger.java | 21 +- .../java/cn/nukkit/utils/MinecartType.java | 13 +- .../java/cn/nukkit/utils/PersonaPiece.java | 4 + .../cn/nukkit/utils/PersonaPieceTint.java | 4 + .../cn/nukkit/utils/PlayerDataSerializer.java | 5 + .../java/cn/nukkit/utils/PluginException.java | 5 +- src/main/java/cn/nukkit/utils/Rail.java | 22 +- .../java/cn/nukkit/utils/SerializedImage.java | 15 +- .../java/cn/nukkit/utils/ServerException.java | 5 +- .../java/cn/nukkit/utils/ServerKiller.java | 9 +- .../java/cn/nukkit/utils/SimpleConfig.java | 27 +- .../java/cn/nukkit/utils/SkinAnimation.java | 12 +- .../cn/nukkit/utils/SnappyCompression.java | 22 + .../java/cn/nukkit/utils/TerracottaColor.java | 53 +- src/main/java/cn/nukkit/utils/TextFormat.java | 25 +- .../java/cn/nukkit/utils/ThreadCache.java | 3 + .../java/cn/nukkit/utils/ThreadStore.java | 5 +- .../java/cn/nukkit/utils/ThreadedLogger.java | 8 - src/main/java/cn/nukkit/utils/Utils.java | 158 +- src/main/java/cn/nukkit/utils/VarInt.java | 8 +- src/main/java/cn/nukkit/utils/Watchdog.java | 78 +- src/main/java/cn/nukkit/utils/Zlib.java | 53 +- .../java/cn/nukkit/utils/ZlibOriginal.java | 80 - .../java/cn/nukkit/utils/ZlibProvider.java | 8 + .../nukkit/utils/ZlibSingleThreadLowMem.java | 77 - .../java/cn/nukkit/utils/ZlibThreadLocal.java | 81 +- .../utils/bugreport/BugReportGenerator.java | 36 +- .../utils/bugreport/ExceptionHandler.java | 4 - .../functional/ToIntTriFunctionTwoInts.java | 46 + .../ToLongTriFunctionOneIntOneLong.java | 46 + .../cn/nukkit/utils/material/BlockType.java | 4 + .../cn/nukkit/utils/material/ItemType.java | 4 + .../nukkit/utils/material/MaterialType.java | 7 + .../utils/material/TypesGeneratorHelper.java | 123 + .../tags/LazilyInitializedMaterialTag.java | 48 + .../utils/material/tags/MaterialTag.java | 18 + .../utils/material/tags/MaterialTags.java | 111 + .../material/tags/SimpleMaterialTag.java | 27 + .../aikar/timings/FullServerTickTiming.java | 108 - src/main/java/co/aikar/timings/Timing.java | 169 - .../java/co/aikar/timings/TimingData.java | 91 - .../co/aikar/timings/TimingIdentifier.java | 82 - src/main/java/co/aikar/timings/Timings.java | 256 - .../java/co/aikar/timings/TimingsExport.java | 272 - .../java/co/aikar/timings/TimingsHistory.java | 221 - .../co/aikar/timings/TimingsHistoryEntry.java | 49 - .../java/co/aikar/timings/TimingsManager.java | 138 - src/main/resources/biome_id_map.json | 88 +- src/main/resources/creative_items.json | 2 +- src/main/resources/entity_identifiers.dat | Bin 7886 -> 7762 bytes src/main/resources/item_mappings.json | 2 +- src/main/resources/item_tags.json | 748 ++ src/main/resources/legacy_item_ids.json | 1006 +-- src/main/resources/leveldb_palette.nbt | Bin 0 -> 105946 bytes src/main/resources/log4j2.xml | 10 + src/main/resources/report_template.md | 40 - src/main/resources/runtime_block_states.dat | Bin 40547 -> 52833 bytes .../resources/runtime_block_states_594.dat | Bin 0 -> 52862 bytes src/main/resources/smithing.json | 1 + .../cn/nukkit/test/ClientChainDataTest.java | 65 - src/test/java/cn/nukkit/test/VarIntTest.java | 2 +- src/test/java/cn/nukkit/test/ZlibTest.java | 1 - src/test/resources/cn/nukkit/test/chain.dat | Bin 32353 -> 0 bytes 2087 files changed, 66645 insertions(+), 25263 deletions(-) delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 src/main/java/cn/nukkit/api/API.java create mode 100644 src/main/java/cn/nukkit/block/BlockAcaciaSignStanding.java create mode 100644 src/main/java/cn/nukkit/block/BlockAcaciaWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockAllow.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethyst.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethystBud.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethystBudLarge.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethystBudMedium.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethystBudSmall.java create mode 100644 src/main/java/cn/nukkit/block/BlockAmethystCluster.java create mode 100644 src/main/java/cn/nukkit/block/BlockAncientDebris.java create mode 100644 src/main/java/cn/nukkit/block/BlockAzalea.java create mode 100644 src/main/java/cn/nukkit/block/BlockAzaleaFlowering.java create mode 100644 src/main/java/cn/nukkit/block/BlockAzaleaLeaves.java create mode 100644 src/main/java/cn/nukkit/block/BlockAzaleaLeavesFlowered.java create mode 100644 src/main/java/cn/nukkit/block/BlockBamboo.java create mode 100644 src/main/java/cn/nukkit/block/BlockBambooSapling.java create mode 100644 src/main/java/cn/nukkit/block/BlockBarrel.java create mode 100644 src/main/java/cn/nukkit/block/BlockBarrier.java create mode 100644 src/main/java/cn/nukkit/block/BlockBasalt.java create mode 100644 src/main/java/cn/nukkit/block/BlockBasaltSmooth.java create mode 100644 src/main/java/cn/nukkit/block/BlockBeeNest.java create mode 100644 src/main/java/cn/nukkit/block/BlockBeehive.java create mode 100644 src/main/java/cn/nukkit/block/BlockBell.java create mode 100644 src/main/java/cn/nukkit/block/BlockBirchSignStanding.java create mode 100644 src/main/java/cn/nukkit/block/BlockBirchWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlackstoneGilded.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlackstonePolishedChiseled.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlackstoneWall.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlastFurnace.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java create mode 100644 src/main/java/cn/nukkit/block/BlockBlueIce.java create mode 100644 src/main/java/cn/nukkit/block/BlockBorder.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksBlackstonePolishedCracked.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksDeepslateCracked.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksNetherChiseled.java create mode 100644 src/main/java/cn/nukkit/block/BlockBricksNetherCracked.java create mode 100644 src/main/java/cn/nukkit/block/BlockBubbleColumn.java create mode 100644 src/main/java/cn/nukkit/block/BlockBuddingAmethyst.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonAcacia.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonBirch.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonDarkOak.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonJungle.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonPolishedBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonSpruce.java create mode 100644 src/main/java/cn/nukkit/block/BlockButtonWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockCalcite.java create mode 100644 src/main/java/cn/nukkit/block/BlockCampfire.java create mode 100644 src/main/java/cn/nukkit/block/BlockCampfireSoul.java create mode 100644 src/main/java/cn/nukkit/block/BlockCartographyTable.java create mode 100644 src/main/java/cn/nukkit/block/BlockCauldronLava.java create mode 100644 src/main/java/cn/nukkit/block/BlockCaveVines.java create mode 100644 src/main/java/cn/nukkit/block/BlockCaveVinesBerriesBody.java create mode 100644 src/main/java/cn/nukkit/block/BlockCaveVinesBerriesHead.java create mode 100644 src/main/java/cn/nukkit/block/BlockChain.java create mode 100644 src/main/java/cn/nukkit/block/BlockCommandBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockCommandBlockChain.java create mode 100644 src/main/java/cn/nukkit/block/BlockCommandBlockRepeating.java create mode 100644 src/main/java/cn/nukkit/block/BlockComposter.java create mode 100644 src/main/java/cn/nukkit/block/BlockConduit.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopper.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperBase.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCut.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutExposed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutExposedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutOxidized.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutOxidizedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutWeathered.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperCutWeatheredWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperExposed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperExposedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperOxidized.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperOxidizedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperWeathered.java create mode 100644 src/main/java/cn/nukkit/block/BlockCopperWeatheredWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoral.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralFan.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralFanDead.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralFanHang.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralFanHang2.java create mode 100644 src/main/java/cn/nukkit/block/BlockCoralFanHang3.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonDoor.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonFungus.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonNylium.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonPlanks.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonRoots.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonStairs.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonStem.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonTrapdoor.java create mode 100644 src/main/java/cn/nukkit/block/BlockCrimsonWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockDarkOakSignStanding.java create mode 100644 src/main/java/cn/nukkit/block/BlockDarkOakWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockDeepslateChiseled.java create mode 100644 src/main/java/cn/nukkit/block/BlockDeepslateCobbled.java create mode 100644 src/main/java/cn/nukkit/block/BlockDeepslatePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockDeny.java create mode 100644 src/main/java/cn/nukkit/block/BlockDirtRooted.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleMudBrickSlab.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabBase.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabBrickBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabBrickDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperBase.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCut.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposed.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidized.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidizedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeathered.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeatheredWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslateCobbled.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslatePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabStone3.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabStone4.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabTileDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockDoubleSlabWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockDriedKelpBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockDripleafBig.java create mode 100644 src/main/java/cn/nukkit/block/BlockDripleafSmall.java create mode 100644 src/main/java/cn/nukkit/block/BlockDripstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockDropper.java create mode 100644 src/main/java/cn/nukkit/block/BlockFallableMeta.java create mode 100644 src/main/java/cn/nukkit/block/BlockFenceCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockFenceGateCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockFenceGateWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockFenceWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockFletchingTable.java create mode 100644 src/main/java/cn/nukkit/block/BlockFungus.java create mode 100644 src/main/java/cn/nukkit/block/BlockGlassTinted.java create mode 100644 src/main/java/cn/nukkit/block/BlockGlowLichen.java create mode 100644 src/main/java/cn/nukkit/block/BlockGrindstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockHoneyBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockHoneycombBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockHyphaeCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockHyphaeStrippedCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockHyphaeStrippedWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockHyphaeWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockIceFrosted.java create mode 100644 src/main/java/cn/nukkit/block/BlockInfestedDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockInfoUpdate.java create mode 100644 src/main/java/cn/nukkit/block/BlockInfoUpdate2.java create mode 100644 src/main/java/cn/nukkit/block/BlockItemFrameGlow.java create mode 100644 src/main/java/cn/nukkit/block/BlockJigsaw.java create mode 100644 src/main/java/cn/nukkit/block/BlockJungleSignStanding.java create mode 100644 src/main/java/cn/nukkit/block/BlockJungleWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockKelp.java create mode 100644 src/main/java/cn/nukkit/block/BlockLantern.java create mode 100644 src/main/java/cn/nukkit/block/BlockLayer.java create mode 100644 src/main/java/cn/nukkit/block/BlockLectern.java create mode 100644 src/main/java/cn/nukkit/block/BlockLightBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockLightningRod.java create mode 100644 src/main/java/cn/nukkit/block/BlockLodestone.java create mode 100644 src/main/java/cn/nukkit/block/BlockLoom.java create mode 100644 src/main/java/cn/nukkit/block/BlockMoss.java create mode 100644 src/main/java/cn/nukkit/block/BlockMossCarpet.java create mode 100644 src/main/java/cn/nukkit/block/BlockMud.java create mode 100644 src/main/java/cn/nukkit/block/BlockMudBrick.java create mode 100644 src/main/java/cn/nukkit/block/BlockMudBrickSlab.java create mode 100644 src/main/java/cn/nukkit/block/BlockMudBrickStairs.java create mode 100644 src/main/java/cn/nukkit/block/BlockMudBrickWall.java create mode 100644 src/main/java/cn/nukkit/block/BlockNetherReactor.java create mode 100644 src/main/java/cn/nukkit/block/BlockNetherSprouts.java create mode 100644 src/main/java/cn/nukkit/block/BlockNetheriteBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockNylium.java create mode 100644 src/main/java/cn/nukkit/block/BlockObsidianCrying.java create mode 100644 src/main/java/cn/nukkit/block/BlockOre.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreCoalDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreCopper.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreCopperDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreDiamondDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreEmeraldDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreGoldDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreGoldNether.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreIronDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreLapisDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslateGlowing.java create mode 100644 src/main/java/cn/nukkit/block/BlockPackedMud.java create mode 100644 src/main/java/cn/nukkit/block/BlockPistonExtension.java create mode 100644 src/main/java/cn/nukkit/block/BlockPistonHeadSticky.java create mode 100644 src/main/java/cn/nukkit/block/BlockPointedDripstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockPolishedBasalt.java create mode 100644 src/main/java/cn/nukkit/block/BlockPolishedBlackstoneBrickWall.java create mode 100644 src/main/java/cn/nukkit/block/BlockPolishedBlackstoneWall.java create mode 100644 src/main/java/cn/nukkit/block/BlockPowderSnow.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateAcacia.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateBirch.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateDarkOak.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateJungle.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlatePolishedBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateSpruce.java create mode 100644 src/main/java/cn/nukkit/block/BlockPressurePlateWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockPumpkinCarved.java create mode 100644 src/main/java/cn/nukkit/block/BlockQuartzBricks.java create mode 100644 src/main/java/cn/nukkit/block/BlockRawCopper.java create mode 100644 src/main/java/cn/nukkit/block/BlockRawGold.java create mode 100644 src/main/java/cn/nukkit/block/BlockRawIron.java create mode 100644 src/main/java/cn/nukkit/block/BlockRawOreVariant.java create mode 100644 src/main/java/cn/nukkit/block/BlockRespawnAnchor.java create mode 100644 src/main/java/cn/nukkit/block/BlockRoots.java create mode 100644 src/main/java/cn/nukkit/block/BlockRootsHanging.java create mode 100644 src/main/java/cn/nukkit/block/BlockScaffolding.java create mode 100644 src/main/java/cn/nukkit/block/BlockSeaPickle.java create mode 100644 src/main/java/cn/nukkit/block/BlockSeagrass.java create mode 100644 src/main/java/cn/nukkit/block/BlockShroomlight.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabBrickBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabBrickDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperBase.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCut.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutExposed.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutExposedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidized.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidizedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutWeathered.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCopperCutWeatheredWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabCrimson.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabDeepslateCobbled.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabDeepslatePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabStone3.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabStone4.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabTileDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockSlabWarped.java create mode 100644 src/main/java/cn/nukkit/block/BlockSmithingTable.java create mode 100644 src/main/java/cn/nukkit/block/BlockSmoker.java create mode 100644 src/main/java/cn/nukkit/block/BlockSmokerLit.java create mode 100644 src/main/java/cn/nukkit/block/BlockSmoothStone.java create mode 100644 src/main/java/cn/nukkit/block/BlockSoulFire.java create mode 100644 src/main/java/cn/nukkit/block/BlockSoulLantern.java create mode 100644 src/main/java/cn/nukkit/block/BlockSoulSoil.java create mode 100644 src/main/java/cn/nukkit/block/BlockSoulTorch.java create mode 100644 src/main/java/cn/nukkit/block/BlockSporeBlossom.java create mode 100644 src/main/java/cn/nukkit/block/BlockSpruceSignStanding.java create mode 100644 src/main/java/cn/nukkit/block/BlockSpruceWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsAndesite.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsAndesitePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsBlackstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsBrickBlackstonePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsBrickDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperBase.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCut.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutExposed.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutExposedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidized.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidizedWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutWeathered.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsCopperCutWeatheredWaxed.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsDarkPrismarine.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsDeepslateCobbled.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsDeepslatePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsDiorite.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsDioritePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsEndBrick.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsGranite.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsGranitePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsMossyCobblestone.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsMossyStoneBrick.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsPrismarine.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsPrismarineBrick.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsRedNetherBrick.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsSmoothQuartz.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsSmoothRedSandstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsSmoothSandstone.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsStone.java create mode 100644 src/main/java/cn/nukkit/block/BlockStairsTileDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockStem.java create mode 100644 src/main/java/cn/nukkit/block/BlockStemStripped.java create mode 100644 src/main/java/cn/nukkit/block/BlockStonecutterBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockStrippedCrimsonStem.java create mode 100644 src/main/java/cn/nukkit/block/BlockStrippedWarpedStem.java create mode 100644 src/main/java/cn/nukkit/block/BlockStructureBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockSweetBerryBush.java create mode 100644 src/main/java/cn/nukkit/block/BlockTarget.java create mode 100644 src/main/java/cn/nukkit/block/BlockTilesDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockTilesDeepslateCracked.java create mode 100644 src/main/java/cn/nukkit/block/BlockTrapdoorAcacia.java create mode 100644 src/main/java/cn/nukkit/block/BlockTrapdoorBirch.java create mode 100644 src/main/java/cn/nukkit/block/BlockTrapdoorDarkOak.java create mode 100644 src/main/java/cn/nukkit/block/BlockTrapdoorJungle.java create mode 100644 src/main/java/cn/nukkit/block/BlockTrapdoorSpruce.java create mode 100644 src/main/java/cn/nukkit/block/BlockTuff.java create mode 100644 src/main/java/cn/nukkit/block/BlockTurtleEgg.java create mode 100644 src/main/java/cn/nukkit/block/BlockTypes.java create mode 100644 src/main/java/cn/nukkit/block/BlockVinesNether.java create mode 100644 src/main/java/cn/nukkit/block/BlockVinesTwisting.java create mode 100644 src/main/java/cn/nukkit/block/BlockWallBrickDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockWallDeepslateCobbled.java create mode 100644 src/main/java/cn/nukkit/block/BlockWallDeepslatePolished.java create mode 100644 src/main/java/cn/nukkit/block/BlockWallTileDeepslate.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedDoor.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedFungus.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedNylium.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedPlanks.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedRoots.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedStairs.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedStem.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedTrapdoor.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedWallSign.java create mode 100644 src/main/java/cn/nukkit/block/BlockWarpedWartBlock.java create mode 100644 src/main/java/cn/nukkit/block/BlockWeepingVines.java create mode 100644 src/main/java/cn/nukkit/block/BlockWitherRose.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodBark.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStripped.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedAcacia.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedBirch.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedDarkOak.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedJungle.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedOak.java create mode 100644 src/main/java/cn/nukkit/block/BlockWoodStrippedSpruce.java create mode 100644 src/main/java/cn/nukkit/block/Blocks.java create mode 100644 src/main/java/cn/nukkit/block/Oxidizable.java create mode 100644 src/main/java/cn/nukkit/block/Waxable.java create mode 100644 src/main/java/cn/nukkit/block/properties/BlockNotImplemented.java create mode 100644 src/main/java/cn/nukkit/block/properties/BlockPropertiesHelper.java create mode 100644 src/main/java/cn/nukkit/block/properties/DripleafTilt.java create mode 100644 src/main/java/cn/nukkit/block/properties/DripstoneThickness.java create mode 100644 src/main/java/cn/nukkit/block/properties/OxidizationLevel.java create mode 100644 src/main/java/cn/nukkit/block/properties/VanillaProperties.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityBarrel.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityBell.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityBlastFurnace.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityCampfire.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntityLectern.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntitySmoker.java create mode 100644 src/main/java/cn/nukkit/blockentity/BlockEntitySpawner.java create mode 100644 src/main/java/cn/nukkit/blockentity/PersistentDataContainerBlockEntity.java delete mode 100644 src/main/java/cn/nukkit/command/data/args/CommandArg.java delete mode 100644 src/main/java/cn/nukkit/command/data/args/CommandArgBlockVector.java delete mode 100644 src/main/java/cn/nukkit/command/data/args/CommandArgRules.java create mode 100644 src/main/java/cn/nukkit/command/defaults/ConvertCommand.java delete mode 100644 src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java create mode 100644 src/main/java/cn/nukkit/command/defaults/PlaySoundCommand.java create mode 100644 src/main/java/cn/nukkit/command/defaults/SummonCommand.java delete mode 100644 src/main/java/cn/nukkit/command/defaults/TimingsCommand.java create mode 100644 src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java create mode 100644 src/main/java/cn/nukkit/customblock/CustomBlockManager.java create mode 100644 src/main/java/cn/nukkit/customblock/CustomBlockState.java create mode 100644 src/main/java/cn/nukkit/customblock/GsonNBTMapper.java create mode 100644 src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java create mode 100644 src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java create mode 100644 src/main/java/cn/nukkit/customblock/container/BlockContainer.java create mode 100644 src/main/java/cn/nukkit/customblock/container/BlockContainerFactory.java create mode 100644 src/main/java/cn/nukkit/customblock/container/BlockStorageContainer.java create mode 100644 src/main/java/cn/nukkit/customblock/container/CustomBlock.java create mode 100644 src/main/java/cn/nukkit/customblock/container/CustomBlockMeta.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/BlockProperties.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/BlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/BlockPropertyUtils.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/EnumBlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/RegisteredBlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/exception/BlockPropertyNotFoundException.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyException.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyMetaException.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyPersistenceValueException.java create mode 100644 src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyValueException.java create mode 100644 src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java create mode 100644 src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/ChestBoatDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java delete mode 100644 src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/MinecartDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/ShearsDispenseBehaviour.java create mode 100644 src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/dispenser/UndyedShulkerBoxDispenseBehavior.java create mode 100644 src/main/java/cn/nukkit/entity/BaseEntity.java create mode 100644 src/main/java/cn/nukkit/entity/EntityBoss.java create mode 100644 src/main/java/cn/nukkit/entity/EntityControllable.java create mode 100644 src/main/java/cn/nukkit/entity/EntityFlying.java create mode 100644 src/main/java/cn/nukkit/entity/EntityJumping.java create mode 100644 src/main/java/cn/nukkit/entity/EntitySwimming.java create mode 100644 src/main/java/cn/nukkit/entity/EntityTameable.java create mode 100644 src/main/java/cn/nukkit/entity/EntityWalking.java create mode 100644 src/main/java/cn/nukkit/entity/custom/CustomEntity.java create mode 100644 src/main/java/cn/nukkit/entity/custom/EntityDefinition.java create mode 100644 src/main/java/cn/nukkit/entity/custom/EntityManager.java create mode 100644 src/main/java/cn/nukkit/entity/item/EntityAreaEffectCloud.java create mode 100644 src/main/java/cn/nukkit/entity/item/EntityArmorStand.java create mode 100644 src/main/java/cn/nukkit/entity/item/EntityChestBoat.java create mode 100644 src/main/java/cn/nukkit/entity/item/EntityPotionLingering.java create mode 100644 src/main/java/cn/nukkit/entity/mob/EntityFlyingMob.java create mode 100644 src/main/java/cn/nukkit/entity/mob/EntityJumpingMob.java create mode 100644 src/main/java/cn/nukkit/entity/mob/EntitySwimmingMob.java create mode 100644 src/main/java/cn/nukkit/entity/mob/EntityTameableMob.java create mode 100644 src/main/java/cn/nukkit/entity/mob/EntityWalkingMob.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityCamel.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityFish.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityFlyingAnimal.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityHorseBase.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityIronGolem.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityJumpingAnimal.java delete mode 100644 src/main/java/cn/nukkit/entity/passive/EntityTameable.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityTameableAnimal.java create mode 100644 src/main/java/cn/nukkit/entity/passive/EntityWalkingAnimal.java create mode 100644 src/main/java/cn/nukkit/entity/projectile/EntityEnderEye.java create mode 100644 src/main/java/cn/nukkit/event/block/BellRingEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/BlockExplodeEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/BlockHarvestEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/BlockPistonEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/CampfireSmeltEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/ComposterEmptyEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/ComposterFillEvent.java create mode 100644 src/main/java/cn/nukkit/event/block/WaterFrostEvent.java create mode 100644 src/main/java/cn/nukkit/event/entity/EndermanBlockPickUpEvent.java create mode 100644 src/main/java/cn/nukkit/event/entity/EntityDamageBlockedEvent.java create mode 100644 src/main/java/cn/nukkit/event/inventory/LoomItemEvent.java create mode 100644 src/main/java/cn/nukkit/event/inventory/SmithItemEvent.java create mode 100644 src/main/java/cn/nukkit/event/level/NetherPortalSpawnEvent.java create mode 100644 src/main/java/cn/nukkit/event/player/CraftingTableOpenEvent.java create mode 100644 src/main/java/cn/nukkit/event/player/PlayerInitializedEvent.java create mode 100644 src/main/java/cn/nukkit/event/player/PlayerToggleCrawlEvent.java create mode 100644 src/main/java/cn/nukkit/event/player/PlayerToggleSpinAttackEvent.java create mode 100644 src/main/java/cn/nukkit/inventory/BarrelInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/BlastFurnaceInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/CampfireInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/CampfireRecipe.java create mode 100644 src/main/java/cn/nukkit/inventory/ChestBoatInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/DispenserInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/DropperInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/EntityArmorInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/EntityEquipmentInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/LoomInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/SmithingInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/SmithingRecipe.java create mode 100644 src/main/java/cn/nukkit/inventory/SmokerInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/TradeInventory.java create mode 100644 src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java create mode 100644 src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java create mode 100644 src/main/java/cn/nukkit/inventory/transaction/SmithingTransaction.java create mode 100644 src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java create mode 100644 src/main/java/cn/nukkit/inventory/transaction/action/SmithingItemAction.java create mode 100644 src/main/java/cn/nukkit/item/ItemAmethystShard.java create mode 100644 src/main/java/cn/nukkit/item/ItemCactus.java create mode 100644 src/main/java/cn/nukkit/item/ItemCampfire.java create mode 100644 src/main/java/cn/nukkit/item/ItemCampfireSoul.java create mode 100644 src/main/java/cn/nukkit/item/ItemChain.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatAcacia.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatBirch.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatDarkOak.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatJungle.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatMangrove.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatOak.java create mode 100644 src/main/java/cn/nukkit/item/ItemChestBoatSpruce.java create mode 100644 src/main/java/cn/nukkit/item/ItemCopperRaw.java create mode 100644 src/main/java/cn/nukkit/item/ItemDiscFragment5.java create mode 100644 src/main/java/cn/nukkit/item/ItemDoorCrimson.java create mode 100644 src/main/java/cn/nukkit/item/ItemDoorWarped.java create mode 100644 src/main/java/cn/nukkit/item/ItemEchoShard.java create mode 100644 src/main/java/cn/nukkit/item/ItemGlowBerries.java create mode 100644 src/main/java/cn/nukkit/item/ItemGoatHorn.java create mode 100644 src/main/java/cn/nukkit/item/ItemGoldRaw.java create mode 100644 src/main/java/cn/nukkit/item/ItemIngotCopper.java create mode 100644 src/main/java/cn/nukkit/item/ItemIronRaw.java create mode 100644 src/main/java/cn/nukkit/item/ItemItemFrameGlow.java create mode 100644 src/main/java/cn/nukkit/item/ItemKelp.java create mode 100644 src/main/java/cn/nukkit/item/ItemLadder.java create mode 100644 src/main/java/cn/nukkit/item/ItemLodestoneCompass.java create mode 100644 src/main/java/cn/nukkit/item/ItemNetherSprouts.java create mode 100644 src/main/java/cn/nukkit/item/ItemRail.java create mode 100644 src/main/java/cn/nukkit/item/ItemRecoveryCompass.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignAcacia.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignBirch.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignCrimson.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignDarkOak.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignJungle.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignSpruce.java create mode 100644 src/main/java/cn/nukkit/item/ItemSignWarped.java create mode 100644 src/main/java/cn/nukkit/item/ItemTypes.java create mode 100644 src/main/java/cn/nukkit/item/food/FoodEffectiveInBow.java create mode 100644 src/main/java/cn/nukkit/level/AsyncChunkData.java create mode 100644 src/main/java/cn/nukkit/level/AsyncChunkThread.java create mode 100644 src/main/java/cn/nukkit/level/BlockPalette.java delete mode 100644 src/main/java/cn/nukkit/level/LevelProviderConverter.java create mode 100644 src/main/java/cn/nukkit/level/biome/impl/EndBiome.java create mode 100644 src/main/java/cn/nukkit/level/biome/impl/nether/BasaltDeltasBiome.java create mode 100644 src/main/java/cn/nukkit/level/biome/impl/nether/CrimsonForestBiome.java create mode 100644 src/main/java/cn/nukkit/level/biome/impl/nether/SoulSandValleyBiome.java create mode 100644 src/main/java/cn/nukkit/level/biome/impl/nether/WarpedForestBiome.java delete mode 100644 src/main/java/cn/nukkit/level/format/anvil/palette/BitArray4096.java delete mode 100644 src/main/java/cn/nukkit/level/format/anvil/palette/BlockDataPalette.java delete mode 100644 src/main/java/cn/nukkit/level/format/anvil/palette/BytePalette.java delete mode 100644 src/main/java/cn/nukkit/level/format/anvil/palette/CharPalette.java create mode 100644 src/main/java/cn/nukkit/level/format/generic/Anvil2LevelDBConverter.java delete mode 100644 src/main/java/cn/nukkit/level/format/generic/ChunkConverter.java create mode 100644 src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkData.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/Chunk.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/LegacyStateMapper.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/LevelDBKey.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/BaseKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/EntitiesKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/ExtraDataKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/FlagsKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/TerrainKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/TicksKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/TilesKey.java delete mode 100644 src/main/java/cn/nukkit/level/format/leveldb/key/VersionKey.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/BlockEntitySerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkDataLoader.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV1.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV7.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV8.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV9.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializerV3.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializers.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/Data2dSerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/Data3dSerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/serializer/EntitySerializer.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/structure/ChunkBuilder.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunk.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunkSection.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterChunker.java create mode 100644 src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterVanilla.java delete mode 100644 src/main/java/cn/nukkit/level/format/mcregion/Chunk.java delete mode 100644 src/main/java/cn/nukkit/level/format/mcregion/McRegion.java delete mode 100644 src/main/java/cn/nukkit/level/format/mcregion/RegionLoader.java create mode 100644 src/main/java/cn/nukkit/level/generator/End.java create mode 100644 src/main/java/cn/nukkit/level/generator/GeneratorTaskFactory.java create mode 100644 src/main/java/cn/nukkit/level/generator/Void.java rename src/main/java/cn/nukkit/level/generator/noise/{nukkit/d/NoiseD.java => Noise.java} (97%) rename src/main/java/cn/nukkit/level/generator/noise/{nukkit/d/PerlinD.java => Perlin.java} (83%) rename src/main/java/cn/nukkit/level/generator/noise/{nukkit/d/SimplexD.java => Simplex.java} (80%) delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorImprovedD.java delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorOctavesD.java delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorPerlinD.java delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorSimplexD.java delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorPerlinF.java delete mode 100644 src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorSimplexF.java create mode 100644 src/main/java/cn/nukkit/level/generator/object/tree/ObjectCrimsonTree.java create mode 100644 src/main/java/cn/nukkit/level/generator/object/tree/ObjectFallenTree.java create mode 100644 src/main/java/cn/nukkit/level/generator/object/tree/ObjectNetherTree.java create mode 100644 src/main/java/cn/nukkit/level/generator/object/tree/ObjectWarpedTree.java delete mode 100644 src/main/java/cn/nukkit/level/generator/populator/helper/package-info.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBamboo.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaLava.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaMagma.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaPillar.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonForestGround.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonFungus.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFallenTree.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorForestRock.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorKelp.java delete mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMineshaft.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorNetherFire.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorPumpkin.java delete mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorRavines.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSeagrass.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSoulSandValleyGround.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSpring.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSweetBerryBush.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTwistingVines.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorUnderwaterFloor.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedForestGround.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedFungus.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWeepingVines.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/tree/DarkOakTreePopulator.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/tree/JungleBigTreePopulator.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/tree/JungleTreePopulator.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/tree/SavannaTreePopulator.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/tree/SwampTreePopulator.java create mode 100644 src/main/java/cn/nukkit/level/generator/populator/type/PopulatorOceanFloorSurfaceBlock.java create mode 100644 src/main/java/cn/nukkit/level/particle/ElectricSparkParticle.java create mode 100644 src/main/java/cn/nukkit/level/particle/ScrapeParticle.java create mode 100644 src/main/java/cn/nukkit/level/particle/WaxOffParticle.java create mode 100644 src/main/java/cn/nukkit/level/particle/WaxOnParticle.java create mode 100644 src/main/java/cn/nukkit/level/persistence/ImmutableCompoundTag.java create mode 100644 src/main/java/cn/nukkit/level/persistence/PersistentDataContainer.java create mode 100644 src/main/java/cn/nukkit/level/persistence/PersistentDataType.java create mode 100644 src/main/java/cn/nukkit/level/persistence/PersistentItemDataContainer.java create mode 100644 src/main/java/cn/nukkit/level/persistence/impl/DelegatePersistentDataContainer.java create mode 100644 src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerBlockWrapper.java create mode 100644 src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerEntityWrapper.java create mode 100644 src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerItemWrapper.java create mode 100644 src/main/java/cn/nukkit/level/util/SingletonBitArray.java delete mode 100644 src/main/java/cn/nukkit/nbt/stream/BufferedRandomAccessFile.java create mode 100644 src/main/java/cn/nukkit/network/BatchingHelper.java delete mode 100644 src/main/java/cn/nukkit/network/CompressBatchedPacket.java delete mode 100644 src/main/java/cn/nukkit/network/CompressBatchedTask.java create mode 100644 src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/ItemComponentPacket.java delete mode 100644 src/main/java/cn/nukkit/network/protocol/ItemStackRequestPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/NpcDialoguePacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/PlayerStartItemCooldownPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/RequestPermissionsPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/SetDefaultGameTypePacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/SettingsCommandPacket.java create mode 100644 src/main/java/cn/nukkit/network/protocol/types/DimensionDefinition.java create mode 100644 src/main/java/cn/nukkit/network/protocol/types/ExperimentData.java create mode 100644 src/main/java/cn/nukkit/plugin/LambdaEventExecutor.java delete mode 100644 src/main/java/cn/nukkit/timings/JsonUtil.java delete mode 100644 src/main/java/cn/nukkit/timings/LevelTimings.java delete mode 100644 src/main/java/cn/nukkit/utils/HastebinUtility.java create mode 100644 src/main/java/cn/nukkit/utils/SnappyCompression.java delete mode 100644 src/main/java/cn/nukkit/utils/ThreadedLogger.java delete mode 100644 src/main/java/cn/nukkit/utils/ZlibOriginal.java delete mode 100644 src/main/java/cn/nukkit/utils/ZlibSingleThreadLowMem.java create mode 100644 src/main/java/cn/nukkit/utils/functional/ToIntTriFunctionTwoInts.java create mode 100644 src/main/java/cn/nukkit/utils/functional/ToLongTriFunctionOneIntOneLong.java create mode 100644 src/main/java/cn/nukkit/utils/material/BlockType.java create mode 100644 src/main/java/cn/nukkit/utils/material/ItemType.java create mode 100644 src/main/java/cn/nukkit/utils/material/MaterialType.java create mode 100644 src/main/java/cn/nukkit/utils/material/TypesGeneratorHelper.java create mode 100644 src/main/java/cn/nukkit/utils/material/tags/LazilyInitializedMaterialTag.java create mode 100644 src/main/java/cn/nukkit/utils/material/tags/MaterialTag.java create mode 100644 src/main/java/cn/nukkit/utils/material/tags/MaterialTags.java create mode 100644 src/main/java/cn/nukkit/utils/material/tags/SimpleMaterialTag.java delete mode 100644 src/main/java/co/aikar/timings/FullServerTickTiming.java delete mode 100644 src/main/java/co/aikar/timings/Timing.java delete mode 100644 src/main/java/co/aikar/timings/TimingData.java delete mode 100644 src/main/java/co/aikar/timings/TimingIdentifier.java delete mode 100644 src/main/java/co/aikar/timings/Timings.java delete mode 100644 src/main/java/co/aikar/timings/TimingsExport.java delete mode 100644 src/main/java/co/aikar/timings/TimingsHistory.java delete mode 100644 src/main/java/co/aikar/timings/TimingsHistoryEntry.java delete mode 100644 src/main/java/co/aikar/timings/TimingsManager.java create mode 100644 src/main/resources/item_tags.json create mode 100644 src/main/resources/leveldb_palette.nbt delete mode 100644 src/main/resources/report_template.md create mode 100644 src/main/resources/runtime_block_states_594.dat create mode 100644 src/main/resources/smithing.json delete mode 100644 src/test/java/cn/nukkit/test/ClientChainDataTest.java delete mode 100644 src/test/resources/cn/nukkit/test/chain.dat diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index cbd9470d3ad..00000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,107 +0,0 @@ -How to submit a bug report ---- - -Before creating an issue, make sure: - 1. Your title and content is not confusing or content-less. - 2. All text is written in proper English. - -If it's a bug or problem: - 1. This bug can be reproduced. - 2. This bug can be found in the latest build. - 3. Dumps, backtraces or files are provided. - 4. It's you yourself who first found this bug. - -If it's advice or a feature request: - 1. This feature does not exist in the latest build. - 2. This feature is logical and clear-cut. - 3. It's you yourself who first came up with the idea. - -Nukkit will create a bug report for EVERY exception and error detected, and there are some columns you need to fill out in the report. If multiple exceptions are triggered, you should combine the stacktrace into one report and then submit the report. - -In the report, you can see if the error is caused by Nukkit or a plugin. However, when "PLUGIN ERROR" is "false" and there are plugins running, it does not necessarily indicate that the error is caused by Nukkit. - -To submit bugs and problems, please upload the automatically generated report. Make sure you have filled in all blanks in the template. Please provide **as much information as you could**, or our developers might got stuck or confused when looking into your issue. - -To submit feature requests and suggestions, please explicitly describe the feature you want or your suggestion. - -**Note that the Issues section on GitHub is not for contents that are not related to the two categories listed above. Irrelevant issues will be closed. Please visit our forums for other kinds of discussions.** - -Example ---- - -### Issue Description - -It seems that the player you are manipulating does not seem to be moving from other people, and it seems that you are not moving from others. - -I do not know because I have not logged in to anything other than my server, but it works normally with Wi-Fi multi. - -### OS and Versions - -* Nukkit Version: https://github.com/Nukkit/Nukkit/pull/1517 - -* Java Version: -``` -java version "9" -Java(TM) SE Runtime Environment (build 9+175) -Java HotSpot(TM) 64-Bit Server VM (build 9+175, mixed mode) -``` - -* Host Configuration: - - -| Item | Value | -|:----:|:-----:| -| Host OS | Microsoft Windows [10.0.10240] | -| Memory(RAM) | 4 GB | -| Storage Size | 1 TB | -| Storage Type | SSD | -| CPU Type | Intel Xeon X5650 | -| CPU Core Count | 12 cores 24 threads | -| Upstream Bandwidth | 100 Mbps | - -* Client Configuration: - -| Item | Value | -|:----:|:-----:| -| Client Edition | Android | -| Client Version | 1.0.4 | - -``` -### Issue Description - -It seems that the player you are manipulating does not seem to be moving from other people, and it seems that you are not moving from others. - -I do not know because I have not logged in to anything other than my server, but it works normally with Wi-Fi multi. - -### OS and Versions - -* Nukkit Version: https://github.com/Nukkit/Nukkit/pull/1517 - -* Java Version: - -java version "9" -Java(TM) SE Runtime Environment (build 9+175) -Java HotSpot(TM) 64-Bit Server VM (build 9+175, mixed mode) - - -* Host Configuration: - - -| Item | Value | -|:----:|:-----:| -| Host OS | Microsoft Windows [10.0.10240] | -| Memory(RAM) | 4 GB | -| Storage Size | 1 TB | -| Storage Type | SSD | -| CPU Type | Intel Xeon X5650 | -| CPU Core Count | 12 cores 24 threads | -| Upstream Bandwidth | 100 Mbps | - -* Client Configuration: - -| Item | Value | -|:----:|:-----:| -| Client Edition | Android | -| Client Version | 1.0.4 | - -``` diff --git a/README.md b/README.md index a87814192a7..620b263e8c4 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,24 @@ Introduction ------------- -Nukkit is nuclear-powered server software for Minecraft: Pocket Edition. +Nukkit is nuclear-powered server software for Minecraft Bedrock Edition. It has a few key advantages over other server software: * Written in Java, Nukkit is faster and more stable. * Having a friendly structure, it's easy to contribute to Nukkit's development and rewrite plugins from other platforms into Nukkit plugins. -Nukkit is **under improvement** yet, we welcome contributions. +Nukkit is under improvement yet, we welcome contributions. Links -------------------- -* __[News](https://nukkitx.com)__ -* __[Forums](https://nukkitx.com/forums)__ +* __[Forums](https://cloudburstmc.org/forums/)__ * __[Discord](https://discord.gg/5PzMkyK)__ -* __[Download](https://ci.nukkitx.com/job/NukkitX/job/Nukkit/job/master)__ -* __[Plugins](https://nukkitx.com/resources/categories/nukkit-plugins.1)__ -* __[Wiki](https://nukkitx.com/wiki/nukkit)__ +* __[Wiki](https://cloudburstmc.org/wiki/nukkit)__ +* __[Download Nukkit](https://ci.opencollab.dev/job/NukkitX/job/Nukkit/job/master/)__ +* __[Download Plugins](https://cloudburstmc.org/resources/categories/nukkit-plugins.1/)__ -Contributing -------------- -Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) guide before submitting any issue. Issues with insufficient information or in the wrong format will be closed and will not be reviewed. - -Build JAR file +Compile Nukkit ------------- - `git clone https://github.com/CloudburstMC/Nukkit` - `cd Nukkit` @@ -38,6 +33,8 @@ Build JAR file The compiled JAR can be found in the `target/` directory. +Note: You don't need to compile Nukkit yourself if you don't intend to modify the code. You can find precompiled JARs on Jenkins. + Running ------------- Simply run `java -jar nukkit-1.0-SNAPSHOT.jar`. @@ -114,3 +111,8 @@ Testing after deployment: Completely remove the chart: `helm uninstall nukkit` + +Pterodactyl Panel +------------- + +[Download the official egg](https://raw.githubusercontent.com/parkervcp/eggs/master/game_eggs/minecraft/bedrock/nukkit/egg-nukkit.json) diff --git a/build.gradle.kts b/build.gradle.kts index 0135d643731..cb869c23ed2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,27 +12,47 @@ plugins { group = "cn.nukkit" version = "1.0-SNAPSHOT" -description = "Nuclear powered server software for Minecraft: Bedrock Edition" +description = "Nuclear powered server software for Minecraft Bedrock Edition" repositories { mavenLocal() mavenCentral() maven("https://repo.opencollab.dev/maven-releases") maven("https://repo.opencollab.dev/maven-snapshots") + maven("https://repo.mznt.eu/snapshots") //TODO } dependencies { api(libs.network) api(libs.natives) api(libs.fastutil) + api(libs.fastutil1) + api(libs.fastutil2) + api(libs.fastutil3) api(libs.guava) api(libs.gson) api(libs.snakeyaml) api(libs.leveldb) + api(libs.leveldbjni) { + exclude(group = "com.google.guava", module = "guava") + exclude(group = "io.netty", module = "netty-buffer") + exclude(group = "org.iq80.snappy", module = "snappy") + exclude(group = "org.iq80.leveldb", module = "leveldb") + } + api(libs.snappy) + api(libs.expiringmap) api(libs.jwt) api(libs.bundles.terminal) api(libs.bundles.log4j) api(libs.jopt.simple) + api(libs.blockstateupdater) + api(libs.lmbda) { + exclude(group = "org.checkerframework", module = "checker-qual") + } + api(libs.noise) { + exclude(group = "net.daporkchop.lib", module = "common") + exclude(group = "net.daporkchop.lib", module = "math") + } compileOnly(libs.lombok) annotationProcessor(libs.lombok) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bd557a11fc9..6f0eda4c861 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,12 +7,21 @@ jline = "3.22.0" network = { group = "com.nukkitx.network", name = "raknet", version = "1.6.28-SNAPSHOT" } natives = { group = "com.nukkitx", name = "natives", version = "1.0.3" } fastutil = { group = "com.nukkitx", name = "fastutil-lite", version = "8.1.1" } +fastutil1 = { group = "com.nukkitx.fastutil", name = "fastutil-int-short-maps", version = "8.5.3" } +fastutil2 = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version = "8.5.3" } +fastutil3 = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version = "8.5.3" } guava = { group = "com.google.guava", name = "guava", version = "30.1.1-jre" } gson = { group = "com.google.code.gson", name = "gson", version = "2.10.1" } snakeyaml = { group = "org.yaml", name = "snakeyaml", version = "1.33" } leveldb = { group = "org.iq80.leveldb", name = "leveldb", version = "0.11-SNAPSHOT" } +leveldbjni = { group = "net.daporkchop", name = "leveldb-mcpe-jni", version = "0.0.10-SNAPSHOT" } +snappy = { group = "org.xerial.snappy", name = "snappy-java", version = "1.1.10.5" } +expiringmap = { group = "net.jodah", name = "expiringmap", version = "0.5.11" } jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version = "9.13" } jopt-simple = { group = "net.sf.jopt-simple", name = "jopt-simple", version = "5.0.4" } +blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.10-ONBT-SNAPSHOT" } +lmbda = { group = "org.lanternpowered", name = "lmbda", version = "2.0.0" } +noise = { group = "net.daporkchop.lib", name = "noise", version = "0.5.6-SNAPSHOT" } lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.26" } # Logging dependencies diff --git a/src/main/java/cn/nukkit/Achievement.java b/src/main/java/cn/nukkit/Achievement.java index 632170db48b..d86490a0055 100644 --- a/src/main/java/cn/nukkit/Achievement.java +++ b/src/main/java/cn/nukkit/Achievement.java @@ -5,23 +5,53 @@ import java.util.HashMap; /** - * Created by CreeperFace on 9. 11. 2016. + * Achievement list and functions + * + * @author CreeperFace */ public class Achievement { + /** + * All known achievements. + * + * Based on ... + */ public static final HashMap achievements = new HashMap() { { - put("mineWood", new Achievement("Getting Wood")); + put("openInventory", new Achievement("Taking Inventory")); + put("mineWood", new Achievement("Getting Wood", "openInventory")); put("buildWorkBench", new Achievement("Benchmarking", "mineWood")); put("buildPickaxe", new Achievement("Time to Mine!", "buildWorkBench")); put("buildFurnace", new Achievement("Hot Topic", "buildPickaxe")); - put("acquireIron", new Achievement("Acquire hardware", "buildFurnace")); + put("acquireIron", new Achievement("Acquire Hardware", "buildFurnace")); put("buildHoe", new Achievement("Time to Farm!", "buildWorkBench")); put("makeBread", new Achievement("Bake Bread", "buildHoe")); put("bakeCake", new Achievement("The Lie", "buildHoe")); put("buildBetterPickaxe", new Achievement("Getting an Upgrade", "buildPickaxe")); + put("cookFish", new Achievement("Delicious Fish", "buildFurnace")); + put("onARail", new Achievement("On A Rail", "acquireIron")); put("buildSword", new Achievement("Time to Strike!", "buildWorkBench")); + put("killEnemy", new Achievement("Monster Hunter", "buildSword")); + put("killCow", new Achievement("Cow Tipper", "buildSword")); + put("flyPig", new Achievement("When Pigs Fly", "killCow")); + put("snipeSkeleton", new Achievement("Sniper Duel", "killEnemy")); put("diamonds", new Achievement("DIAMONDS!", "acquireIron")); + put("portal", new Achievement("We Need to Go Deeper", "diamonds")); + put("ghast", new Achievement("Return to Sender", "portal")); + put("blazeRod", new Achievement("Into Fire", "portal")); + put("potion", new Achievement("Local Brewery", "blazeRod")); + put("theEnd", new Achievement("The End?", "blazeRod")); + put("theEnd2", new Achievement("The End.", "theEnd")); + put("enchantments", new Achievement("Enchanter", "diamonds")); + put("overkill", new Achievement("Overkill", "enchantments")); + put("bookcase", new Achievement("Librarian", "enchantments")); + put("exploreAllBiomes", new Achievement("Adventuring Time", "theEnd")); //TODO + put("spawnWither", new Achievement("The Beginning?", "theEnd")); + put("killWither", new Achievement("The Beginning.", "spawnWither")); + put("fullBeacon", new Achievement("Beaconator", "killWither")); + put("breedCow", new Achievement("Repopulation", "killCow")); + put("diamondsToYou", new Achievement("Diamonds to you!", "diamonds")); + put("overpowered", new Achievement("Overpowered", "buildBetterPickaxe")); } }; @@ -29,9 +59,10 @@ public static boolean broadcast(Player player, String achievementId) { if (!achievements.containsKey(achievementId)) { return false; } - String translation = Server.getInstance().getLanguage().translateString("chat.type.achievement", player.getDisplayName(), TextFormat.GREEN + achievements.get(achievementId).getMessage() + TextFormat.RESET); - if (Server.getInstance().getPropertyBoolean("announce-player-achievements", true)) { + String translation = TextFormat.WHITE + Server.getInstance().getLanguage().translateString("chat.type.achievement", player.getDisplayName(), TextFormat.GREEN + "[" + achievements.get(achievementId).message + "]", null); + + if (Server.getInstance().announceAchievements) { Server.getInstance().broadcastMessage(translation); } else { player.sendMessage(translation); @@ -61,12 +92,12 @@ public String getMessage() { } public void broadcast(Player player) { - String translation = Server.getInstance().getLanguage().translateString("chat.type.achievement", player.getDisplayName(), TextFormat.GREEN + this.getMessage(), null); + String translation = TextFormat.WHITE + Server.getInstance().getLanguage().translateString("chat.type.achievement", player.getDisplayName(), TextFormat.GREEN + "[" + this.message + "]", null); - if (Server.getInstance().getPropertyBoolean("announce-player-achievements", true)) { + if (Server.getInstance().announceAchievements) { Server.getInstance().broadcastMessage(translation); } else { player.sendMessage(translation); } } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/AdventureSettings.java b/src/main/java/cn/nukkit/AdventureSettings.java index 695cc36e5d9..11970b6ade4 100644 --- a/src/main/java/cn/nukkit/AdventureSettings.java +++ b/src/main/java/cn/nukkit/AdventureSettings.java @@ -1,5 +1,6 @@ package cn.nukkit; +import cn.nukkit.network.protocol.AdventureSettingsPacket; import cn.nukkit.network.protocol.UpdateAbilitiesPacket; import cn.nukkit.network.protocol.UpdateAdventureSettingsPacket; import cn.nukkit.network.protocol.types.AbilityLayer; @@ -9,8 +10,10 @@ import java.util.Map; /** + * Adventure settings + * + * @author MagicDroidX * Nukkit Project - * Author: MagicDroidX */ public class AdventureSettings implements Cloneable { @@ -20,7 +23,7 @@ public class AdventureSettings implements Cloneable { public static final int PERMISSION_AUTOMATION = 3; public static final int PERMISSION_ADMIN = 4; - private Map values = new EnumMap<>(Type.class); + private final Map values = new EnumMap<>(Type.class); private Player player; @@ -38,21 +41,45 @@ public AdventureSettings clone(Player newPlayer) { } } + /** + * Set an adventure setting value + * + * @param type adventure setting + * @param value new value + * @return AdventureSettings + */ public AdventureSettings set(Type type, boolean value) { this.values.put(type, value); return this; } + /** + * Get an adventure setting value + * + * @param type adventure setting + * @return value + */ public boolean get(Type type) { Boolean value = this.values.get(type); return value == null ? type.getDefaultValue() : value; } + /** + * Send adventure settings values to the player + */ public void update() { + this.update(true); + } + + /** + * Send adventure settings values to the player + * @param reset reset in air ticks + */ + void update(boolean reset) { UpdateAbilitiesPacket packet = new UpdateAbilitiesPacket(); packet.setEntityId(player.getId()); packet.setCommandPermission(player.isOp() ? UpdateAbilitiesPacket.CommandPermission.OPERATOR : UpdateAbilitiesPacket.CommandPermission.NORMAL); - packet.setPlayerPermission(player.isOp() && !player.isSpectator() ? UpdateAbilitiesPacket.PlayerPermission.OPERATOR : UpdateAbilitiesPacket.PlayerPermission.MEMBER); + packet.setPlayerPermission(player.isOp() && !player.isSpectator() ? UpdateAbilitiesPacket.PlayerPermission.OPERATOR : UpdateAbilitiesPacket.PlayerPermission.MEMBER); // Spectator: fix operators being able to break blocks on spectator mode AbilityLayer layer = new AbilityLayer(); layer.setLayerType(AbilityLayer.Type.BASE); @@ -80,17 +107,25 @@ public void update() { layer.setFlySpeed(Player.DEFAULT_FLY_SPEED); packet.getAbilityLayers().add(layer); - if (this.get(Type.NO_CLIP)) { - AbilityLayer layer2 = new AbilityLayer(); - layer2.setLayerType(AbilityLayer.Type.SPECTATOR); + if (player.isSpectator()) { + AbilityLayer spectator = new AbilityLayer(); + spectator.setLayerType(AbilityLayer.Type.SPECTATOR); - layer2.getAbilitiesSet().addAll(PlayerAbility.VALUES); - layer2.getAbilitiesSet().remove(PlayerAbility.FLY_SPEED); - layer2.getAbilitiesSet().remove(PlayerAbility.WALK_SPEED); + spectator.getAbilitiesSet().addAll(PlayerAbility.VALUES); + spectator.getAbilitiesSet().remove(PlayerAbility.FLY_SPEED); + spectator.getAbilitiesSet().remove(PlayerAbility.WALK_SPEED); + + for (Type type : Type.values()) { + if (type.isAbility() && this.get(type)) { + spectator.getAbilityValues().add(type.getAbility()); + } + } - layer2.getAbilityValues().add(PlayerAbility.FLYING); - layer2.getAbilityValues().add(PlayerAbility.NO_CLIP); - packet.getAbilityLayers().add(layer2); + if (player.isOp()) { + layer.getAbilityValues().add(PlayerAbility.OPERATOR_COMMANDS); + } + + packet.getAbilityLayers().add(spectator); } UpdateAdventureSettingsPacket adventurePacket = new UpdateAdventureSettingsPacket(); @@ -102,54 +137,84 @@ public void update() { player.dataPacket(packet); player.dataPacket(adventurePacket); - player.resetInAirTicks(); + + if (reset) { + player.resetInAirTicks(); + } } + /** + * List of adventure settings + */ public enum Type { - WORLD_IMMUTABLE(false), - NO_PVM(false), - NO_MVP(PlayerAbility.INVULNERABLE, false), - SHOW_NAME_TAGS(false), - AUTO_JUMP(true), - ALLOW_FLIGHT(PlayerAbility.MAY_FLY, false), - NO_CLIP(PlayerAbility.NO_CLIP, false), - WORLD_BUILDER(PlayerAbility.WORLD_BUILDER, false), - FLYING(PlayerAbility.FLYING, false), - MUTED(PlayerAbility.MUTED, false), - MINE(PlayerAbility.MINE, true), - DOORS_AND_SWITCHED(PlayerAbility.DOORS_AND_SWITCHES, true), - OPEN_CONTAINERS(PlayerAbility.OPEN_CONTAINERS, true), - ATTACK_PLAYERS(PlayerAbility.ATTACK_PLAYERS, true), - ATTACK_MOBS(PlayerAbility.ATTACK_MOBS, true), - OPERATOR(PlayerAbility.OPERATOR_COMMANDS, false), - TELEPORT(PlayerAbility.TELEPORT, false), - BUILD(PlayerAbility.BUILD, true), - PRIVILEGED_BUILDER(PlayerAbility.PRIVILEGED_BUILDER, false), - + WORLD_IMMUTABLE(AdventureSettingsPacket.WORLD_IMMUTABLE, null, false), + NO_PVM(AdventureSettingsPacket.NO_PVM, null, false), + NO_MVP(AdventureSettingsPacket.NO_MVP, PlayerAbility.INVULNERABLE, false), + SHOW_NAME_TAGS(AdventureSettingsPacket.SHOW_NAME_TAGS, null, false), + AUTO_JUMP(AdventureSettingsPacket.AUTO_JUMP, null, true), + ALLOW_FLIGHT(AdventureSettingsPacket.ALLOW_FLIGHT, PlayerAbility.MAY_FLY, false), + NO_CLIP(AdventureSettingsPacket.NO_CLIP, PlayerAbility.NO_CLIP, false), + WORLD_BUILDER(AdventureSettingsPacket.WORLD_BUILDER, PlayerAbility.WORLD_BUILDER, false), + FLYING(AdventureSettingsPacket.FLYING, PlayerAbility.FLYING, false), + MUTED(AdventureSettingsPacket.MUTED, PlayerAbility.MUTED, false), + MINE(AdventureSettingsPacket.MINE, PlayerAbility.MINE, true), + DOORS_AND_SWITCHED(AdventureSettingsPacket.DOORS_AND_SWITCHES, PlayerAbility.DOORS_AND_SWITCHES, true), + OPEN_CONTAINERS(AdventureSettingsPacket.OPEN_CONTAINERS, PlayerAbility.OPEN_CONTAINERS, true), + ATTACK_PLAYERS(AdventureSettingsPacket.ATTACK_PLAYERS, PlayerAbility.ATTACK_PLAYERS, true), + ATTACK_MOBS(AdventureSettingsPacket.ATTACK_MOBS, PlayerAbility.ATTACK_MOBS, true), + OPERATOR(AdventureSettingsPacket.OPERATOR, PlayerAbility.OPERATOR_COMMANDS, false), + TELEPORT(AdventureSettingsPacket.TELEPORT, PlayerAbility.TELEPORT, false), + BUILD(AdventureSettingsPacket.BUILD, PlayerAbility.BUILD, true), + PRIVILEGED_BUILDER(0, PlayerAbility.PRIVILEGED_BUILDER, false), + + // For backwards compatibility + @Deprecated + BUILD_AND_MINE(0, null, true), @Deprecated - DEFAULT_LEVEL_PERMISSIONS(null, false); + DEFAULT_LEVEL_PERMISSIONS(AdventureSettingsPacket.DEFAULT_LEVEL_PERMISSIONS, null, false); + private final int id; private final PlayerAbility ability; private final boolean defaultValue; - Type(boolean defaultValue) { + Type(int id, PlayerAbility ability, boolean defaultValue) { + this.id = id; + this.ability = ability; this.defaultValue = defaultValue; - this.ability = null; } - Type(PlayerAbility ability, boolean defaultValue) { - this.ability = ability; - this.defaultValue = defaultValue; + /** + * Legacy: Get adventure setting ID if available + * + * @return adventure setting ID + */ + public int getId() { + return this.id; } + /** + * Get default value + * + * @return default value + */ public boolean getDefaultValue() { return this.defaultValue; } + /** + * Get player ability type + * + * @return player ability type + */ public PlayerAbility getAbility() { return this.ability; } + /** + * Check whether adventure setting is a valid player ability + * + * @return is a valid player ability + */ public boolean isAbility() { return this.ability != null; } diff --git a/src/main/java/cn/nukkit/IPlayer.java b/src/main/java/cn/nukkit/IPlayer.java index d940e26ab74..76651c6d000 100644 --- a/src/main/java/cn/nukkit/IPlayer.java +++ b/src/main/java/cn/nukkit/IPlayer.java @@ -6,77 +6,63 @@ import java.util.UUID; /** - * 用来描述一个玩家和获得这个玩家相应信息的接口。
* An interface to describe a player and get its information. - * - *

这个玩家可以在线,也可以是不在线。
- * This player can be online or offline.

+ * + * This player can be online or offline. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.Player * @see cn.nukkit.OfflinePlayer - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface IPlayer extends ServerOperator, Metadatable { /** - * 返回这个玩家是否在线。
* Returns if this player is online. * * @return 这个玩家是否在线。
If this player is online. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isOnline(); /** - * 返回这个玩家的名称。
* Returns the name of this player. - * - *

如果是在线的玩家,这个函数只会返回登录名字。如果要返回显示的名字,参见{@link cn.nukkit.Player#getDisplayName}
+ * + * 如果是在线的玩家,这个函数只会返回登录名字。如果要返回显示的名字,参见{@link cn.nukkit.Player#getDisplayName}
* Notice that this will only return its login name. If you need its display name, turn to - * {@link cn.nukkit.Player#getDisplayName}

+ * {@link cn.nukkit.Player#getDisplayName} * * @return 这个玩家的名称。
The name of this player. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ String getName(); - + UUID getUniqueId(); /** - * 返回这个玩家是否被封禁(ban)。
* Returns if this player is banned. * * @return 这个玩家的名称。
The name of this player. * @see #setBanned - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isBanned(); /** - * 设置这个玩家是否被封禁(ban)。
* Sets this player to be banned or to be pardoned. * * @param value 如果为{@code true},封禁这个玩家。如果为{@code false},解封这个玩家。
* {@code true} for ban and {@code false} for pardon. * @see #isBanned - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void setBanned(boolean value); /** - * 返回这个玩家是否已加入白名单。
* Returns if this player is pardoned by whitelist. * * @return 这个玩家是否已加入白名单。
If this player is pardoned by whitelist. * @see cn.nukkit.Server#isWhitelisted - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isWhitelisted(); /** - * 把这个玩家加入白名单,或者取消这个玩家的白名单。
* Adds this player to the white list, or removes it from the whitelist. * * @param value 如果为{@code true},把玩家加入白名单。如果为{@code false},取消这个玩家的白名单。
@@ -84,59 +70,45 @@ public interface IPlayer extends ServerOperator, Metadatable { * @see #isWhitelisted * @see cn.nukkit.Server#addWhitelist * @see cn.nukkit.Server#removeWhitelist - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void setWhitelisted(boolean value); /** - * 得到这个接口的{@code Player}对象。
* Returns a {@code Player} object for this interface. * * @return 这个接口的 {@code Player}对象。
a {@code Player} object for this interface. * @see cn.nukkit.Server#getPlayerExact - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Player getPlayer(); /** - * 返回玩家所在的服务器。
* Returns the server carrying this player. * * @return 玩家所在的服务器。
the server carrying this player. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Server getServer(); /** - * 得到这个玩家第一次游戏的时间。
* Returns the time this player first played in this server. * * @return Unix时间(以秒为单位。
Unix time in seconds. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Long getFirstPlayed(); /** - * 得到这个玩家上次加入游戏的时间。
* Returns the time this player last joined in this server. * * @return Unix时间(以秒为单位。
Unix time in seconds. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Long getLastPlayed(); /** - * 返回这个玩家以前是否来过服务器。
* Returns if this player has played in this server before. - * - *

如果想得到这个玩家是不是第一次玩,可以使用:
+ * * If you want to know if this player is the first time playing in this server, you can use:
- *

- *
if(!player.hasPlayerBefore()) {...}
+ *
if (!player.hasPlayerBefore()) {...}
* * @return 这个玩家以前是不是玩过游戏。
If this player has played in this server before. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean hasPlayedBefore(); - } diff --git a/src/main/java/cn/nukkit/InterruptibleThread.java b/src/main/java/cn/nukkit/InterruptibleThread.java index c36cdf2f743..c2352328991 100644 --- a/src/main/java/cn/nukkit/InterruptibleThread.java +++ b/src/main/java/cn/nukkit/InterruptibleThread.java @@ -1,17 +1,14 @@ package cn.nukkit; /** - * 描述一个可以被中断的线程的接口。
* An interface to describe a thread that can be interrupted. - * - *

在Nukkit服务器停止时,Nukkit会找到所有实现了{@code InterruptibleThread}的线程,并逐一中断。
+ * * When a Nukkit server is stopping, Nukkit finds all threads implements {@code InterruptibleThread}, - * and interrupt them one by one.

+ * and interrupt them one by one. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.scheduler.AsyncWorker - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface InterruptibleThread { } diff --git a/src/main/java/cn/nukkit/Nukkit.java b/src/main/java/cn/nukkit/Nukkit.java index bf50d9187c7..8485c56426b 100644 --- a/src/main/java/cn/nukkit/Nukkit.java +++ b/src/main/java/cn/nukkit/Nukkit.java @@ -1,6 +1,5 @@ package cn.nukkit; -import cn.nukkit.network.protocol.ProtocolInfo; import cn.nukkit.utils.ServerKiller; import com.google.common.base.Preconditions; import io.netty.util.ResourceLeakDetector; @@ -13,7 +12,6 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; -import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import java.io.IOException; @@ -30,56 +28,52 @@ */ /** - * Nukkit启动类,包含{@code main}函数。
- * The launcher class of Nukkit, including the {@code main} function. + * The launcher class of Nukkit, including the {@code main} function * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ @Log4j2 public class Nukkit { public final static Properties GIT_INFO = getGitInfo(); public final static String VERSION = getVersion(); - public final static String API_VERSION = "1.0.14"; - public final static String CODENAME = ""; - @Deprecated - public final static String MINECRAFT_VERSION = ProtocolInfo.MINECRAFT_VERSION; - @Deprecated - public final static String MINECRAFT_VERSION_NETWORK = ProtocolInfo.MINECRAFT_VERSION_NETWORK; - - public final static String PATH = System.getProperty("user.dir") + "/"; - public final static String DATA_PATH = System.getProperty("user.dir") + "/"; + public final static String API_VERSION = "1.0.20"; + public final static String PATH = System.getProperty("user.dir") + '/'; + public final static String DATA_PATH = System.getProperty("user.dir") + '/'; public final static String PLUGIN_PATH = DATA_PATH + "plugins"; - public static final long START_TIME = System.currentTimeMillis(); - public static boolean ANSI = true; - public static boolean TITLE = false; - public static boolean shortTitle = requiresShortTitle(); + /** + * Server start time + */ + public final static long START_TIME = System.currentTimeMillis(); + /** + * Console title enabled + */ + public static boolean TITLE = true; + /** + * Debug logging level + */ public static int DEBUG = 1; public static void main(String[] args) { - // Force IPv4 since Nukkit is not compatible with IPv6 System.setProperty("java.net.preferIPv4Stack" , "true"); System.setProperty("log4j.skipJansi", "false"); - System.getProperties().putIfAbsent("io.netty.allocator.type", "unpooled"); // Disable memory pooling unless specified - // Force Mapped ByteBuffers for LevelDB till fixed. - System.setProperty("leveldb.mmap", "true"); + // Disable memory pooling unless specified + System.getProperties().putIfAbsent("io.netty.allocator.type", "unpooled"); - // Netty logger for debug info - InternalLoggerFactory.setDefaultFactory(Log4J2LoggerFactory.INSTANCE); - ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); + // Force Mapped ByteBuffers for LevelDB till fixed + System.setProperty("leveldb.mmap", "true"); // Define args OptionParser parser = new OptionParser(); parser.allowsUnrecognizedOptions(); OptionSpec helpSpec = parser.accepts("help", "Shows this page").forHelp(); - OptionSpec ansiSpec = parser.accepts("disable-ansi", "Disables console coloring"); OptionSpec titleSpec = parser.accepts("enable-title", "Enables title at the top of the window"); OptionSpec vSpec = parser.accepts("v", "Set verbosity of logging").withRequiredArg().ofType(String.class); OptionSpec verbositySpec = parser.accepts("verbosity", "Set verbosity of logging").withRequiredArg().ofType(String.class); OptionSpec languageSpec = parser.accepts("language", "Set a predefined language").withOptionalArg().ofType(String.class); + OptionSpec nettyDebugSpec = parser.accepts("debug", "Enables debug stuff"); // Parse arguments OptionSet options = parser.parse(args); @@ -88,13 +82,17 @@ public static void main(String[] args) { try { // Display help page parser.printHelpOn(System.out); - } catch (IOException e) { - // ignore + } catch (IOException ignored) { } return; } - ANSI = !options.has(ansiSpec); + // Netty logger for debug info + if (options.has(nettyDebugSpec)) { + InternalLoggerFactory.setDefaultFactory(Log4J2LoggerFactory.INSTANCE); + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); + } + TITLE = options.has(titleSpec); String verbosity = options.valueOf(vSpec); @@ -106,8 +104,7 @@ public static void main(String[] args) { try { Level level = Level.valueOf(verbosity); setLogLevel(level); - } catch (Exception e) { - // ignore + } catch (Exception ignored) { } } @@ -115,7 +112,7 @@ public static void main(String[] args) { try { if (TITLE) { - System.out.print((char) 0x1b + "]0;Nukkit is starting up..." + (char) 0x07); + System.out.print((char) 0x1b + "]0;Nukkit " + getVersion() + (char) 0x07); } new Server(PATH, DATA_PATH, PLUGIN_PATH, language); } catch (Throwable t) { @@ -125,7 +122,7 @@ public static void main(String[] args) { if (TITLE) { System.out.print((char) 0x1b + "]0;Stopping Server..." + (char) 0x07); } - log.info("Stopping other threads"); + log.info("Stopping other threads..."); for (Thread thread : java.lang.Thread.getAllStackTraces().keySet()) { if (!(thread instanceof InterruptibleThread)) { @@ -146,21 +143,17 @@ public static void main(String[] args) { System.exit(0); } - private static boolean requiresShortTitle() { - //Shorter title for windows 8/2012 - String osName = System.getProperty("os.name").toLowerCase(); - return osName.contains("windows") &&(osName.contains("windows 8") || osName.contains("2012")); - } - private static Properties getGitInfo() { InputStream gitFileStream = Nukkit.class.getClassLoader().getResourceAsStream("git.properties"); if (gitFileStream == null) { + log.debug("Unable to find git.properties"); return null; } Properties properties = new Properties(); try { properties.load(gitFileStream); } catch (IOException e) { + log.debug("Unable to load git.properties", e); return null; } return properties; @@ -170,7 +163,7 @@ private static String getVersion() { StringBuilder version = new StringBuilder(); version.append("git-"); String commitId; - if (GIT_INFO == null || (commitId = GIT_INFO.getProperty("git.commit.id.abbrev")) == null) { + if (GIT_INFO == null || (commitId = GIT_INFO.getProperty("git.commit.id.abbrev")) == null || commitId.isEmpty()) { return version.append("null").toString(); } return version.append(commitId).toString(); @@ -179,16 +172,12 @@ private static String getVersion() { public static void setLogLevel(Level level) { Preconditions.checkNotNull(level, "level"); LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - Configuration log4jConfig = ctx.getConfiguration(); - LoggerConfig loggerConfig = log4jConfig.getLoggerConfig(org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); + LoggerConfig loggerConfig = ctx.getConfiguration().getLoggerConfig(org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); loggerConfig.setLevel(level); ctx.updateLoggers(); } public static Level getLogLevel() { - LoggerContext ctx = (LoggerContext) LogManager.getContext(false); - Configuration log4jConfig = ctx.getConfiguration(); - LoggerConfig loggerConfig = log4jConfig.getLoggerConfig(org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME); - return loggerConfig.getLevel(); + return ((LoggerContext) LogManager.getContext(false)).getConfiguration().getLoggerConfig(org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME).getLevel(); } } diff --git a/src/main/java/cn/nukkit/OfflinePlayer.java b/src/main/java/cn/nukkit/OfflinePlayer.java index 66c8c9d39cb..18c45cf292c 100644 --- a/src/main/java/cn/nukkit/OfflinePlayer.java +++ b/src/main/java/cn/nukkit/OfflinePlayer.java @@ -8,27 +8,24 @@ import java.util.UUID; /** - * 描述一个不在线的玩家的类。
- * Describes an offline player. + * Describes an offline player * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.Player - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public class OfflinePlayer implements IPlayer { + private final Server server; private final CompoundTag namedTag; /** - * 初始化这个{@code OfflinePlayer}对象。
* Initializes the object {@code OfflinePlayer}. * * @param server 这个玩家所在服务器的{@code Server}对象。
* The server this player is in, as a {@code Server} object. * @param uuid 这个玩家的UUID。
* UUID of this player. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public OfflinePlayer(Server server, UUID uuid) { this(server, uuid, null); @@ -49,6 +46,7 @@ public OfflinePlayer(Server server, UUID uuid, String name) { } else { throw new IllegalArgumentException("Name and UUID cannot both be null"); } + if (nbt == null) { nbt = new CompoundTag(); } @@ -57,7 +55,9 @@ public OfflinePlayer(Server server, UUID uuid, String name) { if (uuid != null) { this.namedTag.putLong("UUIDMost", uuid.getMostSignificantBits()); this.namedTag.putLong("UUIDLeast", uuid.getLeastSignificantBits()); - } else { + } + + if (name != null && (uuid == null || !this.namedTag.contains("NameTag"))) { this.namedTag.putString("NameTag", name); } } @@ -173,5 +173,4 @@ public boolean hasMetadata(String metadataKey) { public void removeMetadata(String metadataKey, Plugin owningPlugin) { this.server.getPlayerMetadata().removeMetadata(this, metadataKey, owningPlugin); } - } diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 10a16693b81..38860c36293 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -4,21 +4,22 @@ import cn.nukkit.block.*; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityItemFrame; +import cn.nukkit.blockentity.BlockEntityLectern; import cn.nukkit.blockentity.BlockEntitySpawnable; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.command.data.CommandDataVersions; +import cn.nukkit.customblock.CustomBlockManager; import cn.nukkit.entity.*; import cn.nukkit.entity.data.*; import cn.nukkit.entity.item.*; import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.entity.projectile.EntityThrownTrident; -import cn.nukkit.event.entity.EntityDamageByBlockEvent; -import cn.nukkit.event.entity.EntityDamageByEntityEvent; -import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.event.block.WaterFrostEvent; +import cn.nukkit.event.entity.*; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.EntityDamageEvent.DamageModifier; -import cn.nukkit.event.entity.ProjectileLaunchEvent; import cn.nukkit.event.inventory.InventoryCloseEvent; import cn.nukkit.event.inventory.InventoryPickupArrowEvent; import cn.nukkit.event.inventory.InventoryPickupItemEvent; @@ -33,10 +34,7 @@ import cn.nukkit.form.window.FormWindow; import cn.nukkit.form.window.FormWindowCustom; import cn.nukkit.inventory.*; -import cn.nukkit.inventory.transaction.CraftingTransaction; -import cn.nukkit.inventory.transaction.EnchantTransaction; -import cn.nukkit.inventory.transaction.InventoryTransaction; -import cn.nukkit.inventory.transaction.RepairItemTransaction; +import cn.nukkit.inventory.transaction.*; import cn.nukkit.inventory.transaction.action.InventoryAction; import cn.nukkit.inventory.transaction.data.ReleaseItemData; import cn.nukkit.inventory.transaction.data.UseItemData; @@ -47,13 +45,14 @@ import cn.nukkit.lang.TranslationContainer; import cn.nukkit.level.*; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.level.particle.ItemBreakParticle; import cn.nukkit.level.particle.PunchBlockParticle; import cn.nukkit.math.*; import cn.nukkit.metadata.MetadataValue; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.*; import cn.nukkit.network.CompressionProvider; -import cn.nukkit.network.Network; import cn.nukkit.network.SourceInterface; import cn.nukkit.network.encryption.PrepareEncryptionTask; import cn.nukkit.network.protocol.*; @@ -68,33 +67,39 @@ import cn.nukkit.resourcepacks.ResourcePack; import cn.nukkit.scheduler.AsyncTask; import cn.nukkit.utils.*; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; import com.google.common.base.Strings; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; +import com.google.common.collect.Sets; import io.netty.util.internal.PlatformDependent; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectMap; -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongIterator; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectIterator; +import lombok.Getter; +import lombok.Setter; import lombok.extern.log4j.Log4j2; +import javax.annotation.Nullable; +import java.awt.*; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.nio.ByteOrder; +import java.util.List; import java.util.*; +import java.util.Queue; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; +import java.util.stream.Stream; /** + * The Player class + * * @author MagicDroidX & Box * Nukkit Project */ @@ -107,17 +112,16 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde public static final int SPECTATOR = 3; public static final int VIEW = SPECTATOR; - public static final int SURVIVAL_SLOTS = 36; - public static final int CREATIVE_SLOTS = 112; - public static final int CRAFTING_SMALL = 0; public static final int CRAFTING_BIG = 1; public static final int CRAFTING_ANVIL = 2; public static final int CRAFTING_ENCHANT = 3; public static final int CRAFTING_BEACON = 4; + public static final int CRAFTING_SMITHING = 1003; + public static final int CRAFTING_LOOM = 1004; public static final float DEFAULT_SPEED = 0.1f; - public static final float MAXIMUM_SPEED = 0.5f; + public static final float MAXIMUM_SPEED = 6f; // TODO: Decrease when block collisions are fixed public static final float DEFAULT_FLY_SPEED = 0.05f; public static final int PERMISSION_CUSTOM = 3; @@ -128,41 +132,31 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde public static final int ANVIL_WINDOW_ID = 2; public static final int ENCHANT_WINDOW_ID = 3; public static final int BEACON_WINDOW_ID = 4; + public static final int LOOM_WINDOW_ID = 2; + public static final int SMITHING_WINDOW_ID = 6; - protected static final int RESOURCE_PACK_CHUNK_SIZE = 8 * 1024; // 8KB + protected static final int RESOURCE_PACK_CHUNK_SIZE = 8192; // 8KB protected final SourceInterface interfaz; protected final NetworkPlayerSession networkSession; public boolean playedBefore; - public boolean spawned = false; - public boolean loggedIn = false; - public boolean locallyInitialized = false; - private boolean loginVerified = false; - private int unverifiedPackets; + public boolean spawned; + public boolean loggedIn; + private boolean loginVerified; private boolean loginPacketReceived; - private boolean awaitingEncryptionHandshake; public int gamemode; - public long lastBreak; - private BlockVector3 lastBreakPosition = new BlockVector3(); - - protected int windowCnt = 4; + protected long randomClientId; + private String unverifiedUsername = ""; protected final BiMap windows = HashBiMap.create(); - protected final BiMap windowIndex = windows.inverse(); protected final Set permanentWindows = new IntOpenHashSet(); + @Getter private boolean inventoryOpen; + protected int windowCnt = 4; protected int closingWindowId = Integer.MIN_VALUE; - protected int messageCounter = 2; - - private String clientSecret; - - public Vector3 speed = null; - - private final Queue clientMovements = PlatformDependent.newMpscQueue(4); - public final HashSet achievements = new HashSet<>(); public int craftingType = CRAFTING_SMALL; @@ -172,14 +166,16 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected CraftingTransaction craftingTransaction; protected EnchantTransaction enchantTransaction; protected RepairItemTransaction repairItemTransaction; - - public long creationTime = 0; - - protected long randomClientId; - - protected Vector3 forceMovement = null; - - protected Vector3 teleportPosition = null; + protected LoomTransaction loomTransaction; + protected SmithingTransaction smithingTransaction; + + public Vector3 speed; + protected Vector3 forceMovement; + protected Vector3 teleportPosition; + protected Vector3 newPosition; + protected Vector3 sleeping; + private Vector3 lastRightClickPos; + private final Queue clientMovements = PlatformDependent.newMpscQueue(4); protected boolean connected = true; protected final InetSocketAddress socketAddress; @@ -189,45 +185,39 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected String iusername; protected String displayName; - protected int startAction = -1; - - protected Vector3 sleeping = null; - protected Long clientID = null; - - private int loaderId; - + private boolean hasSpawnChunks; + private final int loaderId; + private int chunksSent = 0; + protected int nextChunkOrderRun = 1; + protected int chunkRadius; + protected int viewDistance; public final Map usedChunks = new Long2ObjectOpenHashMap<>(); - - protected int chunkLoadCount = 0; protected final Long2ObjectLinkedOpenHashMap loadQueue = new Long2ObjectLinkedOpenHashMap<>(); - protected int nextChunkOrderRun = 1; protected final Map hiddenPlayers = new HashMap<>(); - protected Vector3 newPosition = null; - - protected int chunkRadius; - protected int viewDistance; - protected final int chunksPerTick; - protected final int spawnThreshold; - - protected Position spawnPosition = null; + protected Position spawnPosition; protected int inAirTicks = 0; - protected int startAirTicks = 5; + protected int startAirTicks = 10; protected AdventureSettings adventureSettings; - protected boolean checkMovement = true; + private PermissibleBase perm; - private PermissibleBase perm = null; + /** + * Option not to update shield blocking status. + */ + @Getter + @Setter + private boolean canTickShield = true; private int exp = 0; private int expLevel = 0; - protected PlayerFood foodData = null; + protected PlayerFood foodData; - private Entity killer = null; + private Entity killer; private final AtomicReference locale = new AtomicReference<>(null); @@ -237,17 +227,12 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected boolean enableClientCommand = true; - private BlockEnderChest viewingEnderChest = null; - - protected int lastEnderPearl = 20; - protected int lastChorusFruitTeleport = 20; + private BlockEnderChest viewingEnderChest; private LoginChainData loginChainData; - public Block breakingBlock = null; - private PlayerBlockActionData lastBlockAction; - public int pickedXPOrb = 0; + private boolean canPickupXP = true; protected int formWindowCount = 0; protected Map formWindows = new Int2ObjectOpenHashMap<>(); @@ -255,50 +240,137 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde protected Map dummyBossBars = new Long2ObjectLinkedOpenHashMap<>(); - private AsyncTask preLoginEventTask = null; - protected boolean shouldLogin = false; + private AsyncTask preLoginEventTask; + protected boolean shouldLogin; - public EntityFishingHook fishing = null; - - public long lastSkinChange; - - protected double lastRightClickTime = 0.0; - protected Vector3 lastRightClickPos = null; + private static Stream pkIDs; + protected int startAction = -1; + private int lastEmote; + protected int lastEnderPearl = 20; + protected int lastChorusFruitTeleport = 20; + protected int lastFireworkBoost = 20; + public long lastSkinChange = -1; + private double lastRightClickTime = 0.0; + public long lastBreak = -1; // When last block break was started + private BlockVector3 lastBreakPosition = new BlockVector3(); + public Block breakingBlock; // Block player is breaking currently + public BlockFace breakingBlockFace; // Block face player is breaking currently + private PlayerBlockActionData lastBlockAction; + public EntityFishingHook fishing; + @Getter + private boolean formOpen; + public boolean locallyInitialized; + private boolean foodEnabled = true; + protected boolean checkMovement = true; private int timeSinceRest; + private boolean inSoulSand; + private boolean dimensionChangeInProgress; + private boolean awaitingDimensionAck; + private boolean awaitingEncryptionHandshake; + private int riderJumpTick; + private int riptideTicks; + private int blockingDelay; + + private boolean needSendData; + private boolean needSendAdventureSettings; + private boolean needSendFoodLevel; + private boolean needSendInventory; + private boolean needSendHeldItem; + private boolean needSendRotation; + private boolean dimensionFix560; + + /** + * Save last crossbow load tick (used to prevent loading a crossbow launching it immediately afterward) + */ + private int crossbowLoadTick; + /** + * Packets that can be received before the player has logged in + */ + private static final Set PRE_LOGIN_PACKETS = Sets.newHashSet(ProtocolInfo.BATCH_PACKET, ProtocolInfo.LOGIN_PACKET, ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET, ProtocolInfo.REQUEST_CHUNK_RADIUS_PACKET, ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET, ProtocolInfo.RESOURCE_PACK_CHUNK_REQUEST_PACKET, ProtocolInfo.RESOURCE_PACK_CLIENT_RESPONSE_PACKET, ProtocolInfo.CLIENT_CACHE_STATUS_PACKET, ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET, ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET); + /** + * Default kick message for flying + */ + private static final String MSG_FLYING_NOT_ENABLED = "Flying is not enabled on this server"; + /** + * Get action start tick + * @return action start tick, -1 = no action in progress + */ public int getStartActionTick() { return startAction; } + /** + * Set action start tick to current tick + */ public void startAction() { this.startAction = this.server.getTick(); } + /** + * Reset action start tick + */ public void stopAction() { this.startAction = -1; } + /** + * Get last tick an ender pearl was used + * @return last ender pearl used tick + */ public int getLastEnderPearlThrowingTick() { return lastEnderPearl; } + /** + * Set last ender pearl throw tick to current tick + */ public void onThrowEnderPearl() { this.lastEnderPearl = this.server.getTick(); } + /** + * Get last chorus fruit teleport tick + * @return last chorus fruit teleport tick + */ public int getLastChorusFruitTeleport() { return lastChorusFruitTeleport; } + /** + * Set last chorus fruit teleport tick to current tick + */ public void onChorusFruitTeleport() { this.lastChorusFruitTeleport = this.server.getTick(); } + /** + * Get last tick firework boost for elytra was used + * @return last firework boost tick + */ + public int getLastFireworkBoostTick() { + return lastFireworkBoost; + } + + /** + * Set last firework boost tick to current tick + */ + public void onFireworkBoost() { + this.lastFireworkBoost = this.server.getTick(); + } + + /** + * Get ender chest the player is viewing + * @return the ender chest player is viewing or null if player is not viewing an ender chest + */ public BlockEnderChest getViewingEnderChest() { return viewingEnderChest; } + /** + * Add player to ender chest viewers + */ public void setViewingEnderChest(BlockEnderChest chest) { if (chest == null && this.viewingEnderChest != null) { this.viewingEnderChest.getViewers().remove(this); @@ -308,12 +380,12 @@ public void setViewingEnderChest(BlockEnderChest chest) { this.viewingEnderChest = chest; } + /** + * Get player quit message + * @return quit message + */ public TranslationContainer getLeaveMessage() { - return new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.left", this.getDisplayName()); - } - - public String getClientSecret() { - return clientSecret; + return new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.left", this.displayName); } /** @@ -321,37 +393,36 @@ public String getClientSecret() { * Please use getUniqueId() instead (IP + clientId + name combo, in the future it'll change to real UUID for online auth) * @return random client id */ - @Deprecated public Long getClientId() { return randomClientId; } @Override public boolean isBanned() { - return this.server.getNameBans().isBanned(this.getName()); + return this.server.getNameBans().isBanned(this.username); } @Override public void setBanned(boolean value) { if (value) { - this.server.getNameBans().addBan(this.getName(), null, null, null); - this.kick(PlayerKickEvent.Reason.NAME_BANNED, "Banned by admin"); + this.server.getNameBans().addBan(this.username, null, null, null); + this.kick(PlayerKickEvent.Reason.NAME_BANNED, "You are banned!"); } else { - this.server.getNameBans().remove(this.getName()); + this.server.getNameBans().remove(this.username); } } @Override public boolean isWhitelisted() { - return this.server.isWhitelisted(this.getName().toLowerCase()); + return this.server.isWhitelisted(this.iusername); } @Override public void setWhitelisted(boolean value) { if (value) { - this.server.addWhitelist(this.getName().toLowerCase()); + this.server.addWhitelist(this.iusername); } else { - this.server.removeWhitelist(this.getName().toLowerCase()); + this.server.removeWhitelist(this.iusername); } } @@ -375,87 +446,141 @@ public boolean hasPlayedBefore() { return this.playedBefore; } + /** + * Get current adventure settings + * @return adventure settings + */ public AdventureSettings getAdventureSettings() { return adventureSettings; } + /** + * Set and send adventure settings + * @param adventureSettings new adventure settings + */ public void setAdventureSettings(AdventureSettings adventureSettings) { this.adventureSettings = adventureSettings.clone(this); this.adventureSettings.update(); } + /** + * Reset in air ticks + */ public void resetInAirTicks() { + if (this.inAirTicks != 0) { + this.startAirTicks = 10; + } this.inAirTicks = 0; } - @Deprecated + /** + * Set allow flight adventure setting + * @param value allow flight enabled + */ public void setAllowFlight(boolean value) { - this.getAdventureSettings().set(Type.ALLOW_FLIGHT, value); - this.getAdventureSettings().update(); + this.adventureSettings.set(Type.ALLOW_FLIGHT, value); + this.adventureSettings.update(); } - @Deprecated + /** + * Check wether allow flight adventure setting is enabled + * @return allow flight enabled + */ public boolean getAllowFlight() { - return this.getAdventureSettings().get(Type.ALLOW_FLIGHT); + return this.adventureSettings.get(Type.ALLOW_FLIGHT); } + /** + * Set can modify world adventure setting(s) + * @param value can modify world + */ public void setAllowModifyWorld(boolean value) { - this.getAdventureSettings().set(Type.WORLD_IMMUTABLE, !value); - this.getAdventureSettings().set(Type.MINE, value); - this.getAdventureSettings().set(Type.BUILD, value); - this.getAdventureSettings().update(); + this.adventureSettings.set(Type.WORLD_IMMUTABLE, !value); + this.adventureSettings.set(Type.MINE, value); + this.adventureSettings.set(Type.BUILD, value); + this.adventureSettings.update(); } + /** + * Set can interact adventure setting(s) + * @param value can interact + */ public void setAllowInteract(boolean value) { setAllowInteract(value, value); } + /** + * Set can interact adventure setting(s) + * @param value can interact + * @param containers can open containers + */ public void setAllowInteract(boolean value, boolean containers) { - this.getAdventureSettings().set(Type.WORLD_IMMUTABLE, !value); - this.getAdventureSettings().set(Type.DOORS_AND_SWITCHED, value); - this.getAdventureSettings().set(Type.OPEN_CONTAINERS, containers); - this.getAdventureSettings().update(); + this.adventureSettings.set(Type.WORLD_IMMUTABLE, !value); + this.adventureSettings.set(Type.DOORS_AND_SWITCHED, value); + this.adventureSettings.set(Type.OPEN_CONTAINERS, containers); + this.adventureSettings.update(); } - @Deprecated + /** + * Set auto jump adventure setting + * @param value auto jump enabled + */ public void setAutoJump(boolean value) { - this.getAdventureSettings().set(Type.AUTO_JUMP, value); - this.getAdventureSettings().update(); + this.adventureSettings.set(Type.AUTO_JUMP, value); + this.adventureSettings.update(); } - @Deprecated + /** + * Check wether auto jump adventure setting is enabled + * @return auto jump enabled + */ public boolean hasAutoJump() { - return this.getAdventureSettings().get(Type.AUTO_JUMP); + return this.adventureSettings.get(Type.AUTO_JUMP); } @Override public void spawnTo(Player player) { - if (this.spawned && player.spawned && this.isAlive() && player.getLevel() == this.level && player.canSee(this) && !this.isSpectator()) { + if (this.spawned && player.spawned && this.isAlive() && player.isAlive() && player.getLevel() == this.level && player.canSee(this) && !this.isSpectator()) { super.spawnTo(player); } } - @Override - public Server getServer() { - return this.server; - } - + /** + * Check whether player can use text formatting + * @return player can use text formatting + */ public boolean getRemoveFormat() { return removeFormat; } + /** + * Make player unable to use text formatting (color codes etc.) + */ public void setRemoveFormat() { this.setRemoveFormat(true); } + /** + * Set whether player can use text formatting (color codes etc.) + * @param remove remove formatting from received texts + */ public void setRemoveFormat(boolean remove) { this.removeFormat = remove; } + /** + * Check whether player can see another player (not hidden) + * @param player target player + * @return player can see the target player + */ public boolean canSee(Player player) { return !this.hiddenPlayers.containsKey(player.getUniqueId()); } + /** + * Hide this player from target player + * @param player target player + */ public void hidePlayer(Player player) { if (this == player) { return; @@ -464,6 +589,10 @@ public void hidePlayer(Player player) { player.despawnFrom(this); } + /** + * Allow target player to see this player + * @param player target player + */ public void showPlayer(Player player) { if (this == player) { return; @@ -479,14 +608,26 @@ public boolean canCollideWith(Entity entity) { return false; } + /** + * Check whether player can pick up xp orbs + * @return can pick up xp orbs + */ + public boolean canPickupXP() { + return this.canPickupXP; + } + + /** + * Set whether player can pick up xp orbs + * @param canPickupXP can pick up xp orbs + */ + public void setCanPickupXP(boolean canPickupXP) { + this.canPickupXP = canPickupXP; + } + @Override public void resetFallDistance() { super.resetFallDistance(); - if (this.inAirTicks != 0) { - this.startAirTicks = 5; - } - this.inAirTicks = 0; - this.highestPosition = this.y; + this.resetInAirTicks(); } @Override @@ -496,7 +637,7 @@ public boolean isOnline() { @Override public boolean isOp() { - return this.server.isOp(this.getName()); + return this.server.isOp(this.username); } @Override @@ -506,13 +647,13 @@ public void setOp(boolean value) { } if (value) { - this.server.addOp(this.getName()); + this.server.addOp(this.username); } else { - this.server.removeOp(this.getName()); + this.server.removeOp(this.username); } this.recalculatePermissions(); - this.getAdventureSettings().update(); + this.adventureSettings.update(); this.sendCommandData(); } @@ -575,13 +716,21 @@ public void recalculatePermissions() { this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this); } - if (this.isEnableClientCommand() && spawned) this.sendCommandData(); + if (this.enableClientCommand && spawned) this.sendCommandData(); } + /** + * Are commands enabled for this player on the client side + * @return commands enabled + */ public boolean isEnableClientCommand() { return this.enableClientCommand; } + /** + * Set commands enabled client side. This does not necessarily prevent commands from being used. + * @param enable can use commands + */ public void setEnableClientCommand(boolean enable) { this.enableClientCommand = enable; SetCommandsEnabledPacket pk = new SetCommandsEnabledPacket(); @@ -590,26 +739,26 @@ public void setEnableClientCommand(boolean enable) { if (enable) this.sendCommandData(); } + /** + * Send updated command data (AvailableCommandsPacket) + */ public void sendCommandData() { - if (!spawned) { - return; - } AvailableCommandsPacket pk = new AvailableCommandsPacket(); Map data = new HashMap<>(); - int count = 0; + for (Command command : this.server.getCommandMap().getCommands().values()) { - if ("help".equals(command.getName())) { - continue; // The client will add this - } - if (!command.testPermissionSilent(this) || !command.isRegistered()) { - continue; + if (command.isRegistered()) { + if ("help".equals(command.getName())) { + continue; // The client will add this + } + CommandDataVersions commandData = command.generateCustomCommandData(this); + if (commandData != null) { // No permission + data.put(command.getName(), commandData); + } } - ++count; - CommandDataVersions data0 = command.generateCustomCommandData(this); - data.put(command.getName(), data0); } - if (count > 0) { - //TODO: structure checking + + if (!data.isEmpty()) { pk.commands = data; this.dataPacket(pk); } @@ -626,25 +775,13 @@ public Player(SourceInterface interfaz, Long clientID, InetSocketAddress socketA this.networkSession = interfaz.getSession(socketAddress); this.perm = new PermissibleBase(this); this.server = Server.getInstance(); - this.lastBreak = -1; this.socketAddress = socketAddress; - this.clientID = clientID; this.loaderId = Level.generateChunkLoaderId(this); - this.chunksPerTick = this.server.getConfig("chunk-sending.per-tick", 4); - this.spawnThreshold = this.server.getConfig("chunk-sending.spawn-threshold", 56); - this.spawnPosition = null; this.gamemode = this.server.getGamemode(); this.setLevel(this.server.getDefaultLevel()); this.viewDistance = this.server.getViewDistance(); this.chunkRadius = viewDistance; - //this.newPosition = new Vector3(0, 0, 0); this.boundingBox = new SimpleAxisAlignedBB(0, 0, 0, 0, 0, 0); - this.lastSkinChange = -1; - - this.uuid = null; - this.rawUUID = null; - - this.creationTime = System.currentTimeMillis(); } @Override @@ -658,26 +795,51 @@ public boolean isPlayer() { return true; } + /** + * Remove achievement from player if the player has it + * @param achievementId achievement id + */ public void removeAchievement(String achievementId) { achievements.remove(achievementId); } + /** + * Check whether player has an achievement + * @param achievementId achievement id + * @return has achievement + */ public boolean hasAchievement(String achievementId) { return achievements.contains(achievementId); } + /** + * Check whether player is still connected + * @return connected + */ public boolean isConnected() { return connected; } + /** + * Get player's display name. Default value is player's username. + * @return display name + */ public String getDisplayName() { return this.displayName; } + /** + * Set player's display name + * @param displayName display name + */ public void setDisplayName(String displayName) { + if (displayName == null) { + displayName = ""; + server.getLogger().debug("Warning: setDisplayName: argument is null", new Throwable()); + } this.displayName = displayName; if (this.spawned) { - this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getDisplayName(), this.getSkin(), this.getLoginChainData().getXUID()); + this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.displayName, this.getSkin(), this.loginChainData.getXUID()); } } @@ -685,30 +847,63 @@ public void setDisplayName(String displayName) { public void setSkin(Skin skin) { super.setSkin(skin); if (this.spawned) { - this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getDisplayName(), skin, this.getLoginChainData().getXUID()); + this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.displayName, skin, this.loginChainData.getXUID()); } } + /** + * Get player's host address + * @return host address + */ public String getAddress() { return this.socketAddress.getAddress().getHostAddress(); } + /** + * Get the port of player's connection + * @return port + */ public int getPort() { return this.socketAddress.getPort(); } + /** + * Get player's socket address + * @return socket address + */ public InetSocketAddress getSocketAddress() { return this.socketAddress; } + /** + * Get most recent position of received movements + * @return next position or current position if no next position has been received + */ public Position getNextPosition() { return this.newPosition != null ? new Position(this.newPosition.x, this.newPosition.y, this.newPosition.z, this.level) : this.getPosition(); } + private static final Vector3 ZERO_VECTOR3 = new Vector3(0, 0, 0); + + public Vector3 getPositionOffset() { + if (this.newPosition == null) { + return ZERO_VECTOR3; + } + return this.newPosition.subtract(this); + } + + /** + * Check whether player is sleeping + * @return is sleeping + */ public boolean isSleeping() { return this.sleeping != null; } + /** + * Get in air ticks + * @return in air ticks + */ public int getInAirTicks() { return this.inAirTicks; } @@ -716,33 +911,58 @@ public int getInAirTicks() { /** * Returns whether the player is currently using an item (right-click and hold). * - * @return bool + * @return whether the player is currently using an item */ public boolean isUsingItem() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_ACTION) && this.startAction > -1; + return this.startAction > -1 && this.getDataFlag(DATA_FLAGS, DATA_FLAG_ACTION); } + /** + * Set using item flag + * @param value is using item + */ public void setUsingItem(boolean value) { this.startAction = value ? this.server.getTick() : -1; this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, value); } + /** + * Get interaction button text + * @return button text + */ public String getButtonText() { return this.buttonText; } + /** + * Set interaction button text + * @param text button text + */ public void setButtonText(String text) { - if (this.buttonText.equals(text)) { - return; + if (text == null) { + text = ""; + server.getLogger().debug("Warning: setButtonText: argument is null", new Throwable()); + } + if (!text.equals(buttonText)) { + this.buttonText = text; + this.setDataPropertyAndSendOnlyToSelf(new StringEntityData(Entity.DATA_INTERACTIVE_TAG, this.buttonText)); } - this.buttonText = text; - this.setDataProperty(new StringEntityData(Entity.DATA_INTERACTIVE_TAG, this.buttonText)); } + /** + * Unload a chunk on current level + * @param x chunk x + * @param z chunk z + */ public void unloadChunk(int x, int z) { this.unloadChunk(x, z, null); } + /** + * Unload a chunk on given level + * @param x chunk x + * @param z chunk z + */ public void unloadChunk(int x, int z, Level level) { level = level == null ? this.level : level; long index = Level.chunkHash(x, z); @@ -759,24 +979,69 @@ public void unloadChunk(int x, int z, Level level) { this.loadQueue.remove(index); } + /** + * Unload all loaded chunks + * @param online player is online; send entity despawn packets + */ + private void unloadChunks(boolean online) { + for (long index : this.usedChunks.keySet()) { + int chunkX = Level.getHashX(index); + int chunkZ = Level.getHashZ(index); + this.level.unregisterChunkLoader(this, chunkX, chunkZ); + + for (Entity entity : level.getChunkEntities(chunkX, chunkZ).values()) { + if (entity != this) { + if (online) { + entity.despawnFrom(this); + } else { + entity.hasSpawned.remove(loaderId); + } + } + } + } + + this.usedChunks.clear(); + this.loadQueue.clear(); + } + + /** + * Get player's spawn position + * @return player's spawn position or server's default (safe) spawn position if not set + */ public Position getSpawn() { - if (this.spawnPosition != null && this.spawnPosition.getLevel() != null) { - return this.spawnPosition; + if (this.spawnPosition != null && this.spawnPosition.getLevel() != null && this.spawnPosition.getLevel().getProvider() != null) { + return this.spawnPosition.add(0.5, 0, 0.5); } else { return this.server.getDefaultLevel().getSafeSpawn(); } } + /** + * Get player's spawn position + * @return player's spawn position or null if not set + */ + @Nullable + public Position getSpawnPosition() { + return this.spawnPosition; + } + + /** + * Send a chunk packet + * @param x chunk x + * @param z xhunk z + * @param packet chunk packet + */ public void sendChunk(int x, int z, DataPacket packet) { if (!this.connected) { return; } - this.usedChunks.put(Level.chunkHash(x, z), Boolean.TRUE); - this.chunkLoadCount++; + this.usedChunks.put(Level.chunkHash(x, z), true); this.dataPacket(packet); + this.chunksSent++; + if (this.spawned) { for (Entity entity : this.level.getChunkEntities(x, z).values()) { if (this != entity && !entity.closed && entity.isAlive()) { @@ -784,30 +1049,33 @@ public void sendChunk(int x, int z, DataPacket packet) { } } } - } - public void sendChunk(int x, int z, int subChunkCount, byte[] payload) { - if (!this.connected) { - return; + // Hack: Fix dimension screen issues + if (this.dimensionFix560) { + this.dimensionFix560 = false; + PlayerActionPacket pap = new PlayerActionPacket(); + pap.action = PlayerActionPacket.ACTION_DIMENSION_CHANGE_ACK; + pap.entityId = this.getId(); + this.dataPacket(pap); } + } - this.usedChunks.put(Level.chunkHash(x, z), true); - this.chunkLoadCount++; - - LevelChunkPacket pk = new LevelChunkPacket(); - pk.chunkX = x; - pk.chunkZ = z; - pk.subChunkCount = subChunkCount; - pk.data = payload; - - this.dataPacket(pk); - - if (this.spawned) { - for (Entity entity : this.level.getChunkEntities(x, z).values()) { - if (this != entity && !entity.closed && entity.isAlive()) { - entity.spawnTo(this); - } - } + /** + * Send a chunk packet + * @param x chunk x + * @param z xhunk z + * @param subChunkCount sub chunk count + * @param payload packet payload + */ + public void sendChunk(int x, int z, int subChunkCount, byte[] payload, int dimension) { + if (this.connected) { + LevelChunkPacket pk = new LevelChunkPacket(); + pk.chunkX = x; + pk.chunkZ = z; + pk.dimension = dimension; + pk.subChunkCount = subChunkCount; + pk.data = payload; + this.sendChunk(x, z, pk); } } @@ -816,35 +1084,38 @@ protected void sendNextChunk() { return; } - Timings.playerChunkSendTimer.startTiming(); - if (!loadQueue.isEmpty()) { int count = 0; ObjectIterator> iter = loadQueue.long2ObjectEntrySet().fastIterator(); while (iter.hasNext()) { - Long2ObjectMap.Entry entry = iter.next(); - long index = entry.getLongKey(); - - if (count >= this.chunksPerTick) { + if (count >= server.chunksPerTick) { break; } + + Long2ObjectMap.Entry entry = iter.next(); + long index = entry.getLongKey(); int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); ++count; - this.usedChunks.put(index, false); - this.level.registerChunkLoader(this, chunkX, chunkZ, false); + try { + this.usedChunks.put(index, false); + this.level.registerChunkLoader(this, chunkX, chunkZ, false); - if (!this.level.populateChunk(chunkX, chunkZ)) { - if (this.spawned && this.teleportPosition == null) { - continue; - } else { - break; + if (!this.level.populateChunk(chunkX, chunkZ)) { + if (this.spawned && this.teleportPosition == null) { + continue; + } else { + break; + } } - } - iter.remove(); + iter.remove(); + } catch (Exception ex) { + server.getLogger().logException(ex); + return; + } PlayerChunkRequestEvent ev = new PlayerChunkRequestEvent(this, chunkX, chunkZ); this.server.getPluginManager().callEvent(ev); @@ -853,51 +1124,68 @@ protected void sendNextChunk() { } } } - if (this.chunkLoadCount >= this.spawnThreshold && !this.spawned && this.teleportPosition == null) { - this.doFirstSpawn(); + + if (!this.hasSpawnChunks && this.chunksSent >= server.spawnThreshold) { + this.hasSpawnChunks = true; + + this.sendPlayStatus(PlayStatusPacket.PLAYER_SPAWN); } - Timings.playerChunkSendTimer.stopTiming(); } protected void doFirstSpawn() { - this.spawned = true; - - this.inventory.sendContents(this); - this.inventory.sendArmorContents(this); - this.offhandInventory.sendContents(this); - this.setEnableClientCommand(true); + if (this.spawned) { + return; + } - SetTimePacket setTimePacket = new SetTimePacket(); - setTimePacket.time = this.level.getTime(); - this.dataPacket(setTimePacket); + this.noDamageTicks = 60; + this.setAirTicks(400); - Position pos = this.level.getSafeSpawn(this); + if (this.hasPermission(Server.BROADCAST_CHANNEL_USERS)) { + this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_USERS, this); + } - PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this, pos, true); + if (this.hasPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE)) { + this.server.getPluginManager().subscribeToPermission(Server.BROADCAST_CHANNEL_ADMINISTRATIVE, this); + } + boolean dead = this.getHealth() < 1; + Position spawn = dead ? this.getSpawn() : this; + PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this, spawn.level.getSafeSpawn(spawn), true); this.server.getPluginManager().callEvent(respawnEvent); - pos = respawnEvent.getRespawnPosition(); + this.spawned = true; + + if (dead) { + if (this.server.isHardcore()) { + this.setBanned(true); + return; + } + + this.teleport(respawnEvent.getRespawnPosition(), null); + + // TODO: should probably respawn() here + this.setHealth(this.getMaxHealth()); + this.foodData.setLevel(20, 20); + this.sendData(this); + } else { + this.setPosition(respawnEvent.getRespawnPosition()); + this.sendPosition(respawnEvent.getRespawnPosition(), yaw, pitch, MovePlayerPacket.MODE_TELEPORT); + this.forceMovement = this.teleportPosition = respawnEvent.getRespawnPosition(); - this.sendPlayStatus(PlayStatusPacket.PLAYER_SPAWN); + this.getLevel().sendTime(this); + this.getLevel().sendWeather(this); + } PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(this, - new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.joined", new String[]{ - this.getDisplayName() - }) + new TranslationContainer(TextFormat.YELLOW + "%multiplayer.player.joined", new String[]{this.displayName}) ); this.server.getPluginManager().callEvent(playerJoinEvent); - if (playerJoinEvent.getJoinMessage().toString().trim().length() > 0) { + if (!playerJoinEvent.getJoinMessage().toString().trim().isEmpty()) { this.server.broadcastMessage(playerJoinEvent.getJoinMessage()); } - this.noDamageTicks = 60; - - this.getServer().sendRecipeList(this); - - for (long index : this.usedChunks.keySet()) { int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); @@ -908,37 +1196,18 @@ protected void doFirstSpawn() { } } - int experience = this.getExperience(); - if (experience != 0) { - this.sendExperience(experience); - } - - int level = this.getExperienceLevel(); - if (level != 0) { - this.sendExperienceLevel(this.getExperienceLevel()); - } - - this.teleport(pos, null); // Prevent PlayerTeleportEvent during player spawn + // Prevent PlayerTeleportEvent during player spawn + //this.teleport(pos, null); if (!this.isSpectator()) { this.spawnToAll(); } - //todo Updater - - //Weather - this.getLevel().sendWeather(this); - - //FoodLevel - PlayerFood food = this.getFoodData(); - if (food.getLevel() != food.getMaxLevel()) { - food.sendFoodLevel(); - } - - if (this.getHealth() < 1) { - this.respawn(); + if (!this.locallyInitialized) { + // Not really needed anymore but it's here for plugin compatibility + this.server.getPluginManager().callEvent(new PlayerLocallyInitializedEvent(this)); + this.locallyInitialized = true; } - } protected boolean orderChunks() { @@ -946,21 +1215,17 @@ protected boolean orderChunks() { return false; } - Timings.playerChunkOrderTimer.startTiming(); - this.nextChunkOrderRun = 200; loadQueue.clear(); Long2ObjectOpenHashMap lastChunk = new Long2ObjectOpenHashMap<>(this.usedChunks); - int centerX = (int) this.x >> 4; - int centerZ = (int) this.z >> 4; + int centerX = this.getChunkX(); + int centerZ = this.getChunkZ(); - int radius = spawned ? this.chunkRadius : (int) Math.ceil(Math.sqrt(spawnThreshold)); + int radius = spawned ? this.chunkRadius : server.c_s_spawnThreshold; int radiusSqr = radius * radius; - - long index; for (int x = 0; x <= radius; x++) { int xx = x * x; @@ -969,43 +1234,43 @@ protected boolean orderChunks() { if (distanceSqr > radiusSqr) continue; /* Top right quadrant */ - if(this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ + z)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ + z)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Top left quadrant */ - if(this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ + z)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ + z)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Bottom right quadrant */ - if(this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ - z - 1)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX + x, centerZ - z - 1)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Bottom left quadrant */ - if(this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ - z - 1)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX - x - 1, centerZ - z - 1)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); - if(x != z){ + if (x != z) { /* Top right quadrant mirror */ - if(this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ + x)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ + x)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Top left quadrant mirror */ - if(this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ + x)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ + x)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Bottom right quadrant mirror */ - if(this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ - x - 1)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX + z, centerZ - x - 1)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); /* Bottom left quadrant mirror */ - if(this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ - x - 1)) != Boolean.TRUE) { + if (this.usedChunks.get(index = Level.chunkHash(centerX - z - 1, centerZ - x - 1)) != Boolean.TRUE) { this.loadQueue.put(index, Boolean.TRUE); } lastChunk.remove(index); @@ -1022,83 +1287,99 @@ protected boolean orderChunks() { if (!loadQueue.isEmpty()) { NetworkChunkPublisherUpdatePacket packet = new NetworkChunkPublisherUpdatePacket(); packet.position = this.asBlockVector3(); - packet.radius = viewDistance << 4; + packet.radius = this.chunkRadius << 4; this.dataPacket(packet); } - Timings.playerChunkOrderTimer.stopTiming(); return true; } - @Deprecated + /** + * This method no longer has special function. Calls dataPacket(). + * @param packet data packet + * @return return value of dataPacket() + */ public boolean batchDataPacket(DataPacket packet) { return this.dataPacket(packet); } /** - * 0 is true - * -1 is false - * other is identifer - * @param packet packet to send - * @return packet successfully sent + * Send a data packet + * @param packet data packet + * @return sent */ public boolean dataPacket(DataPacket packet) { if (!this.connected) { return false; } - try (Timing ignored = Timings.getSendDataPacketTiming(packet)) { - DataPacketSendEvent ev = new DataPacketSendEvent(this, packet); - this.server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return false; - } + DataPacket dataPacket = packet.clone(); - if (log.isTraceEnabled() && !server.isIgnoredPacket(packet.getClass())) { - log.trace("Outbound {}: {}", this.getName(), packet); - } + DataPacketSendEvent ev = new DataPacketSendEvent(this, dataPacket); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + + if (Nukkit.DEBUG > 2 /*&& !server.isIgnoredPacket(packet.getClass())*/) { + log.trace("Outbound {}: {}", this.getName(), dataPacket); + } - this.networkSession.sendPacket(packet); + if (dataPacket instanceof BatchPacket) { + this.networkSession.sendPacket(dataPacket); + } else { + // Make sure packets always go to BatchingHelper so they are not reordered + this.server.batchPackets(new Player[]{this}, new DataPacket[]{dataPacket}); } return true; } - @Deprecated public int dataPacket(DataPacket packet, boolean needACK) { - return dataPacket(packet) ? 1 : 0; + return this.dataPacket(packet) ? 1 : 0; } - /** - * 0 is true - * -1 is false - * other is identifer - * @param packet packet to send - * @return packet successfully sent - */ - @Deprecated public boolean directDataPacket(DataPacket packet) { return this.dataPacket(packet); } - @Deprecated public int directDataPacket(DataPacket packet, boolean needACK) { - return this.dataPacket(packet) ? 1 : 0; + return this.directDataPacket(packet) ? 1 : 0; } public void forceDataPacket(DataPacket packet, Runnable callback) { + DataPacketSendEvent ev = new DataPacketSendEvent(this, packet); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } + + if (Nukkit.DEBUG > 2 /*&& !server.isIgnoredPacket(packet.getClass())*/) { + log.trace("Outbound {}: {}", this.getName(), packet); + } + this.networkSession.sendImmediatePacket(packet, (callback == null ? () -> {} : callback)); } + /** + * Get network latency + * @return network latency in milliseconds + */ public int getPing() { return this.interfaz.getNetworkLatency(this); } + /** + * Attempt to sleep at position + * @param pos position + * @return successfully set sleeping + */ public boolean sleepOn(Vector3 pos) { if (!this.isOnline()) { return false; } - for (Entity p : this.level.getNearbyEntities(this.boundingBox.grow(2, 1, 2), this)) { + Entity[] e = this.level.getNearbyEntities(this.boundingBox.grow(2, 1, 2), this); + for (Entity p : e) { if (p instanceof Player) { if (((Player) p).sleeping != null && pos.distance(((Player) p).sleeping) <= 0.1) { return false; @@ -1118,15 +1399,21 @@ public boolean sleepOn(Vector3 pos) { this.setDataProperty(new IntPositionEntityData(DATA_PLAYER_BED_POSITION, (int) pos.x, (int) pos.y, (int) pos.z)); this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, true); - this.setSpawn(pos); + if (!pos.equals(this.getSpawnPosition())) { + this.setSpawn(pos); + this.sendMessage("§7%tile.bed.respawnSet", true); + } this.level.sleepTicks = 60; - this.timeSinceRest = 0; return true; } + /** + * Set player's spawn position + * @param pos spawn position + */ public void setSpawn(Vector3 pos) { Level level; if (!(pos instanceof Position)) { @@ -1134,15 +1421,26 @@ public void setSpawn(Vector3 pos) { } else { level = ((Position) pos).getLevel(); } - this.spawnPosition = new Position(pos.x, pos.y, pos.z, level); + this.spawnPosition = pos instanceof Block ? ((Block) pos).clone().setLevel(level) : new Position(pos.x, pos.y, pos.z, level); + this.sendSpawnPos((int) pos.x, (int) pos.y, (int) pos.z, level.getDimension()); + } + + /** + * Internal: Send player spawn position + */ + private void sendSpawnPos(int x, int y, int z, int dimension) { SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); pk.spawnType = SetSpawnPositionPacket.TYPE_PLAYER_SPAWN; - pk.x = (int) this.spawnPosition.x; - pk.y = (int) this.spawnPosition.y; - pk.z = (int) this.spawnPosition.z; + pk.x = x; + pk.y = y; + pk.z = z; + pk.dimension = dimension; this.dataPacket(pk); } + /** + * Stop sleeping. Does nothing if the player is not sleeping. + */ public void stopSleep() { if (this.sleeping != null) { this.server.getPluginManager().callEvent(new PlayerBedLeaveEvent(this, this.level.getBlock(this.sleeping))); @@ -1151,7 +1449,6 @@ public void stopSleep() { this.setDataProperty(new IntPositionEntityData(DATA_PLAYER_BED_POSITION, 0, 0, 0)); this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, false); - this.level.sleepTicks = 0; AnimatePacket pk = new AnimatePacket(); @@ -1161,8 +1458,21 @@ public void stopSleep() { } } + /** + * Get sleeping position + * @return current sleeping position or null if not sleeping + */ + public Vector3 getSleepingPos() { + return this.sleeping; + } + + /** + * Attempts to award an achievement + * @param achievementId achievement id + * @return new achievement awarded + */ public boolean awardAchievement(String achievementId) { - if (!Server.getInstance().getPropertyBoolean("achievements", true)) { + if (!server.achievementsEnabled) { return false; } @@ -1189,6 +1499,15 @@ public boolean awardAchievement(String achievementId) { return true; } + /** + * Get player's gamemode: + * 0 = survival + * 1 = creative + * 2 = adventure + * 3 = spectator + * + * @return gamemode (number) + */ public int getGamemode() { return gamemode; } @@ -1196,8 +1515,6 @@ public int getGamemode() { /** * 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 */ private static int getClientFriendlyGamemode(int gamemode) { gamemode &= 0x03; @@ -1207,32 +1524,46 @@ private static int getClientFriendlyGamemode(int gamemode) { return gamemode; } + /** + * Set player's gamemode + * @param gamemode new gamemode + * @return gamemode changed + */ public boolean setGamemode(int gamemode) { return this.setGamemode(gamemode, false, null); } + /** + * Set player's gamemode + * @param gamemode new gamemode + * @param clientSide whether change was client initiated + * @return gamemode changed + */ public boolean setGamemode(int gamemode, boolean clientSide) { return this.setGamemode(gamemode, clientSide, null); } + /** + * Set player's gamemode + * @param gamemode new gamemode + * @param clientSide whether change was client initiated + * @param newSettings updated adventure settings for the new gamemode; calculated automatically if null + * @return gamemode changed + */ public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings newSettings) { if (gamemode < 0 || gamemode > 3 || this.gamemode == gamemode) { return false; } if (newSettings == null) { - newSettings = this.getAdventureSettings().clone(this); + newSettings = this.adventureSettings.clone(this); newSettings.set(Type.WORLD_IMMUTABLE, (gamemode & 0x02) > 0); newSettings.set(Type.MINE, (gamemode & 0x02) <= 0); newSettings.set(Type.BUILD, (gamemode & 0x02) <= 0); newSettings.set(Type.NO_PVM, gamemode == SPECTATOR); newSettings.set(Type.ALLOW_FLIGHT, (gamemode & 0x01) > 0); newSettings.set(Type.NO_CLIP, gamemode == SPECTATOR); - if (gamemode == SPECTATOR) { - newSettings.set(Type.FLYING, true); - } else if ((gamemode & 0x1) == 0) { - newSettings.set(Type.FLYING, false); - } + newSettings.set(Type.FLYING, (gamemode & 0x1) == 1); } PlayerGameModeChangeEvent ev; @@ -1265,18 +1596,11 @@ public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings n if (this.isSpectator()) { this.teleport(this, null); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, true); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, false); - /*InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); - inventoryContentPacket.inventoryId = InventoryContentPacket.SPECIAL_CREATIVE; - this.dataPacket(inventoryContentPacket);*/ + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, true, false); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, false); // Sends both } else { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, false); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, true); - /*InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); - inventoryContentPacket.inventoryId = InventoryContentPacket.SPECIAL_CREATIVE; - inventoryContentPacket.slots = Item.getCreativeItems().toArray(new Item[0]); - this.dataPacket(inventoryContentPacket);*/ + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, false, false); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, true); // Sends both } this.resetFallDistance(); @@ -1290,23 +1614,41 @@ public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings n return true; } - @Deprecated + /** + * Send adventure settings + */ public void sendSettings() { - this.getAdventureSettings().update(); + this.adventureSettings.update(); } + /** + * Check player game mode + * @return whether player is in survival mode + */ public boolean isSurvival() { return this.gamemode == SURVIVAL; } + /** + * Check player game mode + * @return whether player is in creative mode + */ public boolean isCreative() { return this.gamemode == CREATIVE; } + /** + * Check player game mode + * @return whether player is in spectator mode + */ public boolean isSpectator() { return this.gamemode == SPECTATOR; } + /** + * Check player game mode + * @return whether player is in adventure mode + */ public boolean isAdventure() { return this.gamemode == ADVENTURE; } @@ -1314,45 +1656,16 @@ public boolean isAdventure() { @Override public Item[] getDrops() { if (!this.isCreative() && !this.isSpectator()) { - return super.getDrops(); - } - - return new Item[0]; - } - - @Override - public boolean fastMove(double dx, double dy, double dz) { - if (dx == 0 && dy == 0 && dz == 0) { - return true; - } - - Timings.entityMoveTimer.startTiming(); - - AxisAlignedBB newBB = this.boundingBox.getOffsetBoundingBox(dx, dy, dz); - - if (this.isSpectator() || server.getAllowFlight() || !this.level.hasCollision(this, newBB.shrink(0, this.getStepHeight(), 0), false)) { - this.boundingBox = newBB; - } - - this.x = (this.boundingBox.getMinX() + this.boundingBox.getMaxX()) / 2; - this.y = this.boundingBox.getMinY() - this.ySize; - this.z = (this.boundingBox.getMinZ() + this.boundingBox.getMaxZ()) / 2; - - this.checkChunks(); - - if (!this.isSpectator()) { - if (!this.onGround || dy != 0) { - AxisAlignedBB bb = this.boundingBox.clone(); - bb.setMinY(bb.getMinY() - 0.75); - - this.onGround = this.level.getCollisionBlocks(bb).length > 0; + if (this.inventory != null) { + List drops = new ArrayList<>(this.inventory.getContents().values()); + drops.addAll(this.offhandInventory.getContents().values()); + //drops.addAll(this.playerUIInventory.getContents().values()); // handled in resetCraftingGridType + return drops.toArray(new Item[0]); } - this.isCollided = this.onGround; - this.updateFallState(this.onGround); + return new Item[0]; } - Timings.entityMoveTimer.stopTiming(); - return true; + return new Item[0]; } @Override @@ -1378,7 +1691,7 @@ protected void checkGroundState(double movX, double movY, double movZ, double dx for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.level.getBlock(this.temporalVector.setComponents(x, y, z)); + Block block = this.level.getBlock(chunk, x, y, z, true); if (!block.canPassThrough() && block.collidesWithBB(realBB)) { onGround = true; @@ -1397,189 +1710,379 @@ protected void checkGroundState(double movX, double movY, double movZ, double dx @Override protected void checkBlockCollision() { if (this.isSpectator()) { - if (this.blocksAround == null) { - this.blocksAround = new ArrayList<>(); - } - if (this.collisionBlocks == null) { - this.collisionBlocks = new ArrayList<>(); - } return; } - boolean portal = false; + boolean netherPortal = false; + boolean endPortal = false; for (Block block : this.getCollisionBlocks()) { if (block.getId() == Block.NETHER_PORTAL) { - portal = true; + netherPortal = true; + continue; + } else if (block.getId() == Block.END_PORTAL) { + endPortal = true; continue; } block.onEntityCollide(this); } - if (portal) { - if (this.isCreative() && this.inPortalTicks < 80) { - this.inPortalTicks = 80; - } else { - this.inPortalTicks++; + if (endPortal) { + inEndPortalTicks++; + } else { + this.inEndPortalTicks = 0; + } + + if (server.endEnabled && inEndPortalTicks == 1) { + EntityPortalEnterEvent ev = new EntityPortalEnterEvent(this, EntityPortalEnterEvent.PortalType.END); + this.getServer().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + int oldDimension = this.getLevel().getDimension(); + if (oldDimension == Level.DIMENSION_THE_END) { + Position spawn; + if ((spawn = this.getSpawn()).getLevel().getDimension() == Level.DIMENSION_OVERWORLD) { + if (this.teleport(spawn, TeleportCause.END_PORTAL)) { + this.awardAchievement("theEnd2"); + } + } else { + if (this.teleport(this.getServer().getDefaultLevel().getSafeSpawn(), TeleportCause.END_PORTAL)) { + this.awardAchievement("theEnd2"); + } + } + } else { + Level end = this.getServer().getLevelByName("the_end"); + if (end != null) { + Position pos = new Position(100.5, 49, 0.5, end); + + FullChunk chunk = end.getChunk(pos.getChunkX(), pos.getChunkZ(), false); + if (chunk == null || !chunk.isGenerated()) { + end.generateChunk(pos.getChunkX(), pos.getChunkZ(), true); + + int x = pos.getFloorX(); + int y = pos.getFloorY(); + int z = pos.getFloorZ(); + for (int xx = x - 2; xx < x + 3; xx++) { + for (int zz = z - 2; zz < z + 3; zz++) { + end.setBlockAt(xx, y - 1, zz, BlockID.OBSIDIAN); + for (int yy = y; yy < y + 4; yy++) { + end.setBlockAt(xx, yy, zz, BlockID.AIR); + } + } + } + } + + if (this.teleport(pos, TeleportCause.END_PORTAL) && oldDimension == Level.DIMENSION_OVERWORLD) { + this.awardAchievement("theEnd"); + } + } + } } + } + + if (netherPortal) { + this.inPortalTicks++; } else { this.inPortalTicks = 0; + this.portalPos = null; + } + + if (this.server.isNetherAllowed()) { + if (this.inPortalTicks == (this.gamemode == CREATIVE ? 1 : 40) && this.portalPos == null) { + Position portalPos = this.level.calculatePortalMirror(this); + if (portalPos == null) { + return; + } + + for (int x = -1; x < 2; x++) { + for (int z = -1; z < 2; z++) { + int chunkX = (portalPos.getChunkX()) + x, chunkZ = (portalPos.getChunkZ()) + z; + FullChunk chunk = portalPos.level.getChunk(chunkX, chunkZ, false); + if (chunk == null || !(chunk.isGenerated() || chunk.isPopulated())) { + portalPos.level.generateChunk(chunkX, chunkZ, true); + } + } + } + this.portalPos = portalPos; + } + + if (this.inPortalTicks == (this.gamemode == CREATIVE ? 1 : 80)) { + EntityPortalEnterEvent ev = new EntityPortalEnterEvent(this, EntityPortalEnterEvent.PortalType.NETHER); + this.getServer().getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + this.portalPos = null; + return; + } + + int oldDimension = this.getLevel().getDimension(); + Position foundPortal = BlockNetherPortal.findNearestPortal(this.portalPos); + if (foundPortal == null) { + BlockNetherPortal.spawnPortal(this.portalPos); + if (this.teleport(this.portalPos.add(1.5, 1, 0.5), TeleportCause.NETHER_PORTAL) && oldDimension == Level.DIMENSION_OVERWORLD) { + this.awardAchievement("portal"); + } + } else { + if (this.teleport(BlockNetherPortal.getSafePortal(foundPortal), TeleportCause.NETHER_PORTAL) && oldDimension == Level.DIMENSION_OVERWORLD) { + this.awardAchievement("portal"); + } + } + this.portalPos = null; + } } } + /** + * Internal: Check nearby entities and try to pick them up + */ protected void checkNearEntities() { - for (Entity entity : this.level.getNearbyEntities(this.boundingBox.grow(1, 0.5, 1), this)) { - entity.scheduleUpdate(); - - if (!entity.isAlive() || !this.isAlive()) { + Entity[] e = this.level.getNearbyEntities(this.boundingBox.grow(1, 0.5, 1), this); + for (Entity entity : e) { + if (!entity.isAlive()) { continue; } + // Update pickup delay + if (entity.updateMode % 3 == 2) { + entity.scheduleUpdate(); + } + this.pickupEntity(entity, true); } } - protected void handleMovement(Vector3 clientPos) { + /** + * Internal: Process player movement + * + * @param newPos client position + */ + protected void handleMovement(Vector3 newPos) { if (!this.isAlive() || !this.spawned || this.teleportPosition != null || this.isSleeping()) { return; } - boolean invalidMotion = false; - double distance = clientPos.distanceSquared(this); - if (distance > 128) { - invalidMotion = true; - } else if (!this.level.isChunkGenerated(clientPos.getChunkX(), clientPos.getChunkZ())) { - invalidMotion = true; - this.nextChunkOrderRun = 0; - } + double distanceSquared = newPos.distanceSquared(this); + if (distanceSquared == 0) { + if (this.lastYaw != this.yaw || this.lastPitch != this.pitch) { + this.lastYaw = this.yaw; + this.lastPitch = this.pitch; + this.needSendRotation = true; + } - if (invalidMotion) { - this.revertClientMotion(this.getLocation()); + if (this.speed == null) speed = new Vector3(0, 0, 0); + else this.speed.setComponents(0, 0, 0); return; } - double diffX = clientPos.getX() - this.x; - double diffY = clientPos.getY() - this.y; - double diffZ = clientPos.getZ() - this.z; + int maxDist = 9; + if (this.riptideTicks > 95 || newPos.y - this.y < 2 || this.isOnLadder()) { // TODO: Remove ladder/vines check when block collisions are fixed + maxDist = 49; + } - // Client likes to clip into few blocks like stairs or slabs - // This should help reduce the server mis-prediction at least a bit - diffY += this.ySize * (1 - 0.4D); + if (distanceSquared > maxDist) { + this.revertClientMotion(this); + server.getLogger().debug(username + ": distanceSquared=" + distanceSquared + " > maxDist=" + maxDist); + return; + } - this.fastMove(diffX, diffY, diffZ); + if (this.chunk == null || !this.chunk.isGenerated()) { + BaseFullChunk chunk = this.level.getChunk(newPos.getChunkX(), newPos.getChunkZ(), false); + if (chunk == null || !chunk.isGenerated()) { + this.nextChunkOrderRun = 0; + this.revertClientMotion(this); + return; + } else { + if (this.chunk != null) { + this.chunk.removeEntity(this); + } + this.chunk = chunk; + } + } - double corrX = this.x - clientPos.getX(); - double corrY = this.y - clientPos.getY(); - double corrZ = this.z - clientPos.getZ(); + double dx = newPos.x - this.x; + double dy = newPos.y - this.y; + double dz = newPos.z - this.z; - double yS = this.getStepHeight() + this.ySize; - if (corrY >= -yS || corrY <= yS) { - corrY = 0; - } + //the client likes to clip into blocks like stairs, but we do full server-side prediction of that without + //help from the client's position changes, so we deduct the expected clip height from the moved distance. + dy += this.ySize * (1 - STEP_CLIP_MULTIPLIER); // FIXME: ySize is always 0 - if (this.checkMovement && (Math.abs(corrX) > 0.5 || Math.abs(corrY) > 0.5 || Math.abs(corrZ) > 0.5) && - this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING)) { - double diff = corrX * corrX + corrZ * corrZ; - if (diff > 0.5) { - PlayerInvalidMoveEvent event = new PlayerInvalidMoveEvent(this, true); - this.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled() && (invalidMotion = event.isRevert())) { - this.server.getLogger().warning(this.getServer().getLanguage().translateString("nukkit.player.invalidMove", this.getName())); + if (this.checkMovement && this.riptideTicks <= 0 && this.riding == null && !this.isGliding() && !this.getAllowFlight()) { + double hSpeed = dx * dx + dz * dz; + if (hSpeed > MAXIMUM_SPEED) { + PlayerInvalidMoveEvent ev; + this.getServer().getPluginManager().callEvent(ev = new PlayerInvalidMoveEvent(this, true)); + if (!ev.isCancelled()) { + server.getLogger().debug(username + ": hSpeed=" + hSpeed + " > MAXIMUM_SPEED=" + MAXIMUM_SPEED); + this.revertClientMotion(this); + return; } } + } - if (!invalidMotion) { - this.x = clientPos.getX(); - this.y = clientPos.getY(); - this.z = clientPos.getZ(); - double radius = this.getWidth() / 2; - this.boundingBox.setBounds(this.x - radius, this.y, this.z - radius, this.x + radius, this.y + this.getHeight(), this.z + radius); - } + // Replacement for this.fastMove(dx, dy, dz) start + if (this.isSpectator() || !this.level.hasCollision(this, this.boundingBox.getOffsetBoundingBox(dx, dy, dz).shrink(0.1, this.getStepHeight(), 0.1), false)) { + this.x = newPos.x; + this.y = newPos.y; + this.z = newPos.z; + + this.boundingBox.setBounds(this.x - 0.3, this.y, this.z - 0.3, this.x + 0.3, this.y + this.getHeight(), this.z + 0.3); } - if (invalidMotion) { - this.revertClientMotion(this.getLocation()); - return; + this.checkChunks(); + + if (!this.isSpectator() && (!this.onGround || dy != 0)) { + AxisAlignedBB bb = this.boundingBox.clone(); + bb.setMinY(bb.getMinY() - 0.75); + + // Hack: fix fall damage from walls while falling + if (Math.abs(dy) > 0.01) { + bb.setMinX(bb.getMinX() + 0.1); + bb.setMaxX(bb.getMaxX() - 0.1); + bb.setMinZ(bb.getMinZ() + 0.1); + bb.setMaxZ(bb.getMaxZ() - 0.1); + } + + this.onGround = this.level.hasCollisionBlocks(this, bb); } - Location source = new Location(this.lastX, this.lastY, this.lastZ, this.lastYaw, this.lastPitch, this.level); - Location target = this.getLocation(); - double delta = Math.pow(this.lastX - target.getX(), 2) + Math.pow(this.lastY - target.getY(), 2) + Math.pow(this.lastZ - target.getZ(), 2); - double deltaAngle = Math.abs(this.lastYaw - target.getYaw()) + Math.abs(this.lastPitch - target.getPitch()); + this.isCollided = this.onGround; + this.updateFallState(this.onGround); + // Replacement for this.fastMove(dx, dy, dz) end + + Location from = new Location( + this.lastX, + this.lastY, + this.lastZ, + this.lastYaw, + this.lastPitch, + this.level); + Location to = this.getLocation(); + + if (!this.firstMove) { + PlayerMoveEvent moveEvent = new PlayerMoveEvent(this, from, to); + this.server.getPluginManager().callEvent(moveEvent); + + if (moveEvent.isCancelled()) { + this.teleport(from, null); + return; + } + + this.lastX = to.x; + this.lastY = to.y; + this.lastZ = to.z; - if (delta > 0.0005 || deltaAngle > 1) { - boolean isFirst = this.firstMove; - this.firstMove = false; + this.lastYaw = to.yaw; + this.lastPitch = to.pitch; - this.lastX = target.x; - this.lastY = target.y; - this.lastZ = target.z; - this.lastYaw = target.yaw; - this.lastPitch = target.pitch; + this.blocksAround = null; + this.collisionBlocks = null; - if (!isFirst) { - List blocksAround = null; - if (this.blocksAround != null) { - blocksAround = new ObjectArrayList<>(this.blocksAround); - } - List collidingBlocks = null; - if (this.collisionBlocks != null) { - collidingBlocks = new ObjectArrayList<>(this.collisionBlocks); - } + if (!to.equals(moveEvent.getTo())) { // If plugins modify the destination + this.teleport(moveEvent.getTo(), null); + } else { + this.addMovement(this.x, this.y, this.z, this.yaw, this.pitch, this.yaw); + } + } else { + this.lastX = to.x; + this.lastY = to.y; + this.lastZ = to.z; - PlayerMoveEvent event = new PlayerMoveEvent(this, source, target); - this.blocksAround = null; - this.collisionBlocks = null; - this.server.getPluginManager().callEvent(event); + this.lastYaw = to.yaw; + this.lastPitch = to.pitch; + } - if (!(invalidMotion = event.isCancelled())) { - if (!target.equals(event.getTo())) { - this.teleport(event.getTo(), null); - } else { - this.addMovement(this.x, this.y, this.z, this.yaw, this.pitch, this.yaw); + this.firstMove = false; + + if (this.speed == null) speed = new Vector3(from.x - to.x, from.y - to.y, from.z - to.z); + else this.speed.setComponents(from.x - to.x, from.y - to.y, from.z - to.z); + + if (this.isFoodEnabled() && this.getServer().getDifficulty() > 0) { + if (distanceSquared >= 0.05) { + double jump = 0; + double swimming = this.isInsideOfWater() ? 0.01 * distanceSquared : 0; + double dd = distanceSquared; + if (swimming != 0) dd = 0; + if (this.isSprinting()) { + if (this.inAirTicks == 3 && swimming == 0) { + jump = 0.2; } + this.foodData.updateFoodExpLevel(0.1 * dd + jump + swimming); } else { - this.blocksAround = blocksAround; - this.collisionBlocks = collidingBlocks; + if (this.inAirTicks == 3 && swimming == 0) { + jump = 0.05; + } + this.foodData.updateFoodExpLevel(jump + swimming); } } - - if (this.speed == null) { - this.speed = new Vector3(source.x - target.x, source.y - target.y, source.z - target.z); - } else { - this.speed.setComponents(source.x - target.x, source.y - target.y, source.z - target.z); - } - } else { - if (this.speed == null) speed = new Vector3(0, 0, 0); - else this.speed.setComponents(0, 0, 0); } - if ((this.isFoodEnabled() || this.getServer().getDifficulty() == 0) && distance >= 0.05) { - double jump = 0; - double swimming = this.isInsideOfWater() ? 0.015 * distance : 0; - double distance2 = distance; - if (swimming != 0) distance2 = 0; - if (this.isSprinting()) { - if (this.inAirTicks == 3 && swimming == 0) { - jump = 0.2; + if (this.riding == null && this.inventory != null) { + Item boots = this.inventory.getBootsFast(); + + Enchantment frostWalker = boots.getEnchantment(Enchantment.ID_FROST_WALKER); + if (frostWalker != null && frostWalker.getLevel() > 0 && !this.isSpectator() && this.y > this.level.getMinBlockY() && this.y <= this.level.getMaxBlockY()) { + int radius = 2 + frostWalker.getLevel(); + for (int coordX = this.getFloorX() - radius; coordX < this.getFloorX() + radius + 1; coordX++) { + for (int coordZ = this.getFloorZ() - radius; coordZ < this.getFloorZ() + radius + 1; coordZ++) { + Block block = level.getBlock(this.chunk, coordX, this.getFloorY() - 1, coordZ, true); + if ((block.getId() == Block.STILL_WATER || block.getId() == Block.WATER && block.getDamage() == 0) && block.up().getId() == Block.AIR) { + WaterFrostEvent waterFrostEvent = new WaterFrostEvent(block); + server.getPluginManager().callEvent(waterFrostEvent); + if (!waterFrostEvent.isCancelled()) { + level.setBlockAt((int) block.x, (int) block.y, (int) block.z, Block.ICE_FROSTED, 0); + level.scheduleUpdate(level.getBlock(this.chunk, block.getFloorX(), block.getFloorY(), block.getFloorZ(), true), Utils.random.nextInt(20, 40)); + } + } + } } - this.getFoodData().updateFoodExpLevel(0.1 * distance2 + jump + swimming); - } else { - if (this.inAirTicks == 3 && swimming == 0) { - jump = 0.05; + } + + Enchantment soulSpeedEnchantment = boots.getEnchantment(Enchantment.ID_SOUL_SPEED); + if (soulSpeedEnchantment != null && soulSpeedEnchantment.getLevel() > 0) { + int down = this.getLevel().getBlockIdAt(chunk, getFloorX(), getFloorY() - 1, getFloorZ()); + if (this.inSoulSand && down != BlockID.SOUL_SAND) { + this.inSoulSand = false; + this.setMovementSpeed(DEFAULT_SPEED, true); + } else if (!this.inSoulSand && down == BlockID.SOUL_SAND) { + this.inSoulSand = true; + float soulSpeed = (soulSpeedEnchantment.getLevel() * 0.105f) + 1.3f; + this.setMovementSpeed(DEFAULT_SPEED * soulSpeed, true); } - this.getFoodData().updateFoodExpLevel(jump + swimming); } } this.forceMovement = null; - if (distance != 0 && this.nextChunkOrderRun > 20) { + if (distanceSquared != 0 && this.nextChunkOrderRun > 20) { this.nextChunkOrderRun = 20; } + this.needSendRotation = false; // Sent with movement + this.resetClientMovement(); } + @Override + public void recalculateBoundingBox(boolean send) { + double height = isSwimming() || isGliding() || isCrawling() ? 0.6 : isSneaking() ? 1.5 : 1.8; + this.boundingBox.setBounds( + this.x - 0.3, + this.y + this.ySize, + z - 0.3, + x + 0.3, + y + height + this.ySize, + z + 0.3 + ); + + if (send) { + FloatEntityData bbH = new FloatEntityData(DATA_BOUNDING_BOX_HEIGHT, (float) height); + FloatEntityData bbW = new FloatEntityData(DATA_BOUNDING_BOX_WIDTH, this.getWidth()); + this.dataProperties.put(bbH); + this.dataProperties.put(bbW); + sendData(this.hasSpawned.values().toArray(new Player[0]), new EntityMetadata().put(bbH).put(bbW)); + } + } + protected void resetClientMovement() { this.newPosition = null; } @@ -1590,11 +2093,10 @@ protected void revertClientMotion(Location originalPos) { this.lastZ = originalPos.getZ(); this.lastYaw = originalPos.getYaw(); this.lastPitch = originalPos.getPitch(); - Vector3 syncPos = originalPos.add(0, 0.00001, 0); + this.needSendRotation = false; this.sendPosition(syncPos, originalPos.getYaw(), originalPos.getPitch(), MovePlayerPacket.MODE_RESET); this.forceMovement = syncPos; - if (this.speed == null) { this.speed = new Vector3(0, 0, 0); } else { @@ -1602,31 +2104,25 @@ protected void revertClientMotion(Location originalPos) { } } - @Override - public double getStepHeight() { - return 0.6f; - } - @Override public void addMovement(double x, double y, double z, double yaw, double pitch, double headYaw) { - this.sendPosition(new Vector3(x, y, z), yaw, pitch, MovePlayerPacket.MODE_NORMAL, this.getViewers().values().toArray(new Player[0])); + this.sendPositionToViewers(x, y, z, yaw, pitch, headYaw); } @Override public boolean setMotion(Vector3 motion) { if (super.setMotion(motion)) { - if (this.chunk != null) { - this.addMotion(this.motionX, this.motionY, this.motionZ); //Send to others + if (this.chunk != null && this.spawned) { + this.addMotion(this.motionX, this.motionY, this.motionZ); // Send to others SetEntityMotionPacket pk = new SetEntityMotionPacket(); pk.eid = this.id; pk.motionX = (float) motion.x; pk.motionY = (float) motion.y; pk.motionZ = (float) motion.z; - this.dataPacket(pk); //Send to self + this.dataPacket(pk); } if (this.motionY > 0) { - //todo: check this this.startAirTicks = (int) ((-(Math.log(this.getGravity() / (this.getGravity() + this.getDrag() * this.motionY))) / this.getDrag()) * 2 + 5); } @@ -1636,20 +2132,46 @@ public boolean setMotion(Vector3 motion) { return false; } - public void sendAttributes() { - UpdateAttributesPacket pk = new UpdateAttributesPacket(); - pk.entityId = this.getId(); - pk.entries = new Attribute[]{ - Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0), - Attribute.getAttribute(Attribute.MAX_HUNGER).setValue(this.getFoodData().getLevel()), - Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(this.getMovementSpeed()), - Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(this.getExperienceLevel()), - Attribute.getAttribute(Attribute.EXPERIENCE).setValue(((float) this.getExperience()) / calculateRequireExperience(this.getExperienceLevel())) - }; - this.dataPacket(pk); - } - - @Override + /** + * Set player's server side motion. Does not send updated motion to client. + * @param motion new motion vector + */ + public void setMotionLocally(Vector3 motion) { + if (!this.justCreated) { + EntityMotionEvent ev = new EntityMotionEvent(this, motion); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } + } + + this.motionX = motion.x; + this.motionY = motion.y; + this.motionZ = motion.z; + + if (this.motionY > 0) { + this.startAirTicks = (int) ((-(Math.log(this.getGravity() / (this.getGravity() + this.getDrag() * this.motionY))) / this.getDrag()) * 2 + 5); + } + } + + /** + * Send all default attributes + */ + public void sendAttributes() { + int healthMax = this.getMaxHealth(); + UpdateAttributesPacket pk = new UpdateAttributesPacket(); + pk.entityId = this.getId(); + pk.entries = new Attribute[]{ + Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(healthMax).setValue(health > 0 ? (health < healthMax ? health : healthMax) : 0), + Attribute.getAttribute(Attribute.MAX_HUNGER).setValue(this.foodData.getLevel()).setDefaultValue(this.foodData.getMaxLevel()), + Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(this.getMovementSpeed()).setDefaultValue(this.getMovementSpeed()), + Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(this.expLevel), + Attribute.getAttribute(Attribute.EXPERIENCE).setValue(((float) this.exp) / calculateRequireExperience(this.expLevel)) + }; + this.dataPacket(pk); + } + + @Override public boolean onUpdate(int currentTick) { if (!this.loggedIn) { return false; @@ -1661,21 +2183,23 @@ public boolean onUpdate(int currentTick) { return true; } - this.messageCounter = 2; - this.lastUpdate = currentTick; - if (this.fishing != null && this.server.getTick() % 20 == 0) { - if (this.distance(fishing) > 33) { + if (this.riptideTicks > 0) { + this.riptideTicks -= tickDiff; + } + + if (this.fishing != null && this.age % 10 == 0) { + if (this.distanceSquared(fishing) > 1089) { // 33 blocks this.stopFishing(false); } } if (!this.isAlive() && this.spawned) { - ++this.deadTicks; - if (this.deadTicks >= 10) { - this.despawnFromAll(); - } + //++this.deadTicks; + //if (this.deadTicks >= 10) { + this.despawnFromAll(); // HACK: fix "dead" players + //} return true; } @@ -1684,96 +2208,152 @@ public boolean onUpdate(int currentTick) { this.handleMovement(this.clientMovements.poll()); } - if (!this.isSpectator()) { + if (this.needSendRotation) { + this.addMovement(this.x, this.y, this.z, this.yaw, this.pitch, this.yaw); + this.needSendRotation = false; + } + + this.motionX = this.motionY = this.motionZ = 0; // HACK: fix player knockback being messed up + + if (!this.isSpectator() && this.isAlive()) { this.checkNearEntities(); } this.entityBaseTick(tickDiff); if (this.getServer().getDifficulty() == 0 && this.level.getGameRules().getBoolean(GameRule.NATURAL_REGENERATION)) { - if (this.getHealth() < this.getMaxHealth() && this.ticksLived % 20 == 0) { + if (this.getHealth() < this.getRealMaxHealth() && this.age % 20 == 0) { this.heal(1); } - PlayerFood foodData = this.getFoodData(); - - if (foodData.getLevel() < 20 && this.ticksLived % 10 == 0) { - foodData.addFoodLevel(1, 0); + if (this.foodData.getLevel() < 20 && this.age % 10 == 0) { + this.foodData.addFoodLevel(1, 0); } } - if (this.isOnFire() && this.lastUpdate % 10 == 0) { + if (this.isOnFire() && this.age % 10 == 0) { if (this.isCreative() && !this.isInsideOfFire()) { this.extinguish(); - } else if (this.getLevel().isRaining()) { - if (this.getLevel().canBlockSeeSky(this)) { - this.extinguish(); - } + } else if (this.getLevel().isRaining() && this.canSeeSky()) { + this.extinguish(); } } if (!this.isSpectator() && this.speed != null) { if (this.onGround) { - if (this.inAirTicks != 0) { - this.startAirTicks = 5; + + // 1.20.10 doesn't stop it automatically + if (this.isGliding()) { + this.setGliding(false); } - this.inAirTicks = 0; - this.highestPosition = this.y; + + this.resetFallDistance(); } else { - if (this.checkMovement && !this.isGliding() && !server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && this.inAirTicks > 20 && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING)) { + if (this.checkMovement && this.riptideTicks < 1 && !this.isGliding() && !server.getAllowFlight() && this.inAirTicks > 20 && !this.getAllowFlight() && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING) && this.speed != null && !ZERO_VECTOR3.equals(this.speed)) { double expectedVelocity = (-this.getGravity()) / ((double) this.getDrag()) - ((-this.getGravity()) / ((double) this.getDrag())) * Math.exp(-((double) this.getDrag()) * ((double) (this.inAirTicks - this.startAirTicks))); - double diff = (this.speed.y - expectedVelocity) * (this.speed.y - expectedVelocity); - - int block = this.getLevelBlock().getId(); - boolean ignore = block == Block.LADDER || block == Block.VINES || block == Block.COBWEB; - - if (!this.hasEffect(Effect.JUMP) && diff > 0.6 && expectedVelocity < this.speed.y && !ignore) { - if (this.inAirTicks < 150) { - this.setMotion(new Vector3(0, expectedVelocity, 0)); - } else if (this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, "Flying is not enabled on this server")) { + double diff = Math.abs(Math.abs(expectedVelocity) - Math.abs(this.speed.y)); + + if (diff > 1 && expectedVelocity < 0) { + if (this.inAirTicks < 200) { + PlayerInvalidMoveEvent ev = new PlayerInvalidMoveEvent(this, true); + this.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + this.startAirTicks = this.inAirTicks - 10; + this.setMotion(new Vector3(0, expectedVelocity, 0)); + } + } else if (this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, MSG_FLYING_NOT_ENABLED, true)) { return false; } } - if (ignore) { - this.resetFallDistance(); - } } if (this.y > highestPosition) { this.highestPosition = this.y; } - if (this.isGliding()) this.resetFallDistance(); - - ++this.inAirTicks; - + if (this.isSwimming() || this.isOnLadder() || (this.isGliding() && this.getPitch() <= 40 && Math.abs(this.speed.y) < 0.5)) { + this.resetFallDistance(); + } else if (this.isGliding()) { + this.resetInAirTicks(); + } else { + ++this.inAirTicks; + } } - if (this.isSurvival() || this.isAdventure()) { - if (this.getFoodData() != null) this.getFoodData().update(tickDiff); + if (this.foodData != null) { + this.foodData.update(tickDiff); } } + } - if (!this.isSleeping()) { - this.timeSinceRest++; + if (this.age % 20 == 0) { + if (this.isGliding()) { + PlayerInventory inv = this.getInventory(); + if (inv != null) { + Item elytra = inv.getChestplate(); + if (elytra == null || elytra.getId() != ItemID.ELYTRA) { + this.setGliding(false); + } else if ((this.gamemode & 0x01) == 0 && this.age % (20 * (elytra.getEnchantmentLevel(Enchantment.ID_DURABILITY) + 1)) == 0) { + elytra.setDamage(elytra.getDamage() + 1); + if (elytra.getDamage() >= elytra.getMaxDurability()) { + this.setGliding(false); + } + inv.setChestplate(elytra); + } + } } } + if (this.age % 5 == 0 && this.isBreakingBlock() && !this.isCreative()) { + //this.level.addLevelSoundEvent(this.breakingBlock, LevelSoundEventPacket.SOUND_HIT, blockRuntimeId); + this.level.addParticle(new PunchBlockParticle(this.breakingBlock, this.breakingBlock, this.breakingBlockFace)); + } + this.checkTeleportPosition(); - if (this.spawned && this.dummyBossBars.size() > 0 && currentTick % 100 == 0) { + if (this.spawned && !this.dummyBossBars.isEmpty() && currentTick % 100 == 0) { this.dummyBossBars.values().forEach(DummyBossBar::updateBossEntityPosition); } + this.tickShield(tickDiff); + + if (!this.isSleeping()) { + this.timeSinceRest += tickDiff; + } + return true; } + /** + * Update shield blocking status + */ + private void tickShield(int tickDiff) { + if (!this.canTickShield) { + return; + } + if (this.blockingDelay > 0) { + this.blockingDelay -= tickDiff; + } + boolean shieldInHand = this.getInventory().getItemInHandFast().getId() == ItemID.SHIELD; + boolean shieldInOffhand = this.getOffhandInventory().getItemFast(0).getId() == ItemID.SHIELD; + if (this.isBlocking()) { + if (!this.isSneaking() || (!shieldInHand && !shieldInOffhand)) { + this.setBlocking(false); + } + } else if (this.blockingDelay <= 0 && this.isSneaking() && (shieldInHand || shieldInOffhand)) { + this.setBlocking(true); + } + } + + /** + * Update interaction button text + */ public void checkInteractNearby() { int interactDistance = isCreative() ? 5 : 3; if (canInteract(this, interactDistance)) { - if (getEntityPlayerLookingAt(interactDistance) != null) { - EntityInteractable onInteract = getEntityPlayerLookingAt(interactDistance); - String buttonText = onInteract.getInteractButtonText(this); + EntityInteractable e = getEntityPlayerLookingAt(interactDistance); + if (e != null) { + String buttonText = e.getInteractButtonText(this); if (buttonText == null) { buttonText = ""; } @@ -1793,8 +2373,6 @@ public void checkInteractNearby() { * @return Entity|null either NULL if no entity is found or an instance of the entity */ public EntityInteractable getEntityPlayerLookingAt(int maxDistance) { - timing.startTiming(); - EntityInteractable entity = null; // just a fix because player MAY not be fully initialized @@ -1814,17 +2392,13 @@ public EntityInteractable getEntityPlayerLookingAt(int maxDistance) { } } } - } catch (Exception ex) { - // nothing to log here! - } + } catch (Exception ignored) {} } - timing.stopTiming(); - return entity; } - private EntityInteractable getEntityAtPosition(Entity[] nearbyEntities, int x, int y, int z) { + private static EntityInteractable getEntityAtPosition(Entity[] nearbyEntities, int x, int y, int z) { for (Entity nearestEntity : nearbyEntities) { if (nearestEntity.getFloorX() == x && nearestEntity.getFloorY() == y && nearestEntity.getFloorZ() == z && nearestEntity instanceof EntityInteractable @@ -1835,6 +2409,9 @@ private EntityInteractable getEntityAtPosition(Entity[] nearbyEntities, int x, i return null; } + /** + * Internal: Process chunk sending + */ public void checkNetwork() { if (!this.isOnline()) { return; @@ -1849,68 +2426,122 @@ public void checkNetwork() { } } + /** + * Check whether target is too far away to be interacted with + * @param pos target position + * @param maxDistance maximum distance + * @return can interact + */ public boolean canInteract(Vector3 pos, double maxDistance) { return this.canInteract(pos, maxDistance, 6.0); } + /** + * Check whether target is too far away to be interacted with + * @param pos target position + * @param maxDistance maximum distance + * @param maxDiff maximum diff + * @return can interact + */ public boolean canInteract(Vector3 pos, double maxDistance, double maxDiff) { if (this.distanceSquared(pos) > maxDistance * maxDistance) { return false; } Vector2 dV = this.getDirectionPlane(); - double dot = dV.dot(new Vector2(this.x, this.z)); - double dot1 = dV.dot(new Vector2(pos.x, pos.z)); - return (dot1 - dot) >= -maxDiff; + return (dV.dot(new Vector2(pos.x, pos.z)) - dV.dot(new Vector2(this.x, this.z))) >= -maxDiff; + } + + private boolean canInteractEntity(Vector3 pos, double maxDistanceSquared) { + if (this.distanceSquared(pos) > maxDistanceSquared) { + return false; + } + + Vector2 dV = this.getDirectionPlane(); + return (dV.dot(new Vector2(pos.x, pos.z)) - dV.dot(new Vector2(this.x, this.z))) >= -0.87; + } + + protected void processPreLogin() { + this.loginVerified = true; + final Player playerInstance = this; + + this.preLoginEventTask = new AsyncTask() { + private PlayerAsyncPreLoginEvent event; + + @Override + public void onRun() { + this.event = new PlayerAsyncPreLoginEvent(username, uuid, loginChainData, skin, playerInstance.getAddress(), playerInstance.getPort()); + server.getPluginManager().callEvent(this.event); + } + + @Override + public void onCompletion(Server server) { + if (!playerInstance.connected) { + return; + } + + if (this.event.getLoginResult() == LoginResult.KICK) { + playerInstance.close(this.event.getKickMessage(), this.event.getKickMessage()); + } else if (playerInstance.shouldLogin) { + try { + playerInstance.setSkin(this.event.getSkin()); + playerInstance.completeLoginSequence(); + for (Consumer action : this.event.getScheduledActions()) { + action.accept(server); + } + } catch (Exception ex) { + server.getLogger().logException(ex); + playerInstance.close("", "Internal Server Error"); + } + } + } + }; + + this.server.getScheduler().scheduleAsyncTask(this.preLoginEventTask); + + try { + this.processLogin(); + } catch (Exception ex) { + this.server.getLogger().logException(ex); + this.close("", "Internal Server Error"); + } } protected void processLogin() { - if (!this.server.isWhitelisted((this.getName()).toLowerCase())) { + String lowerName = this.iusername; + if (!this.server.isWhitelisted(lowerName)) { this.kick(PlayerKickEvent.Reason.NOT_WHITELISTED, "Server is white-listed"); - return; } else if (this.isBanned()) { - String reason = this.server.getNameBans().getEntires().get(this.getName().toLowerCase()).getReason(); - this.kick(PlayerKickEvent.Reason.NAME_BANNED, !reason.isEmpty() ? "You are banned. Reason: " + reason : "You are banned"); + String reason = this.server.getNameBans().getEntires().get(lowerName).getReason(); + this.kick(PlayerKickEvent.Reason.NAME_BANNED, "You are banned!" + (reason.isEmpty() ? "" : (" Reason: " + reason))); return; } else if (this.server.getIPBans().isBanned(this.getAddress())) { - String reason = this.server.getIPBans().getEntires().get(this.getAddress()).getReason(); - this.kick(PlayerKickEvent.Reason.IP_BANNED, !reason.isEmpty() ? "You are banned. Reason: " + reason : "You are banned"); + this.kick(PlayerKickEvent.Reason.IP_BANNED, "Your IP is 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); - } - - Player oldPlayer = null; - for (Player p : new ArrayList<>(this.server.getOnlinePlayers().values())) { - if (p != this && p.getName() != null && p.getName().equalsIgnoreCase(this.getName()) || - this.getUniqueId().equals(p.getUniqueId())) { - oldPlayer = p; - break; + for (Player p : new ArrayList<>(this.server.playerList.values())) { + if (p != this && p.username != null) { + if (p.username.equalsIgnoreCase(this.username) || this.getUniqueId().equals(p.getUniqueId())) { + p.close("", "disconnectionScreen.loggedinOtherLocation"); + break; + } } } - CompoundTag nbt; - if (oldPlayer != null) { - oldPlayer.saveNBT(); - nbt = oldPlayer.namedTag; - oldPlayer.close("", "disconnectionScreen.loggedinOtherLocation"); - } else { - File legacyDataFile = new File(server.getDataPath() + "players/" + this.username.toLowerCase() + ".dat"); - File dataFile = new File(server.getDataPath() + "players/" + this.uuid.toString() + ".dat"); - if (legacyDataFile.exists() && !dataFile.exists()) { - nbt = this.server.getOfflinePlayerData(this.username, false); - if (!legacyDataFile.delete()) { - log.warn("Could not delete legacy player data for {}", this.username); - } - } else { - nbt = this.server.getOfflinePlayerData(this.uuid, true); + CompoundTag nbt; + File legacyDataFile = new File(server.getDataPath() + "players/" + lowerName + ".dat"); + File dataFile = new File(server.getDataPath() + "players/" + this.uuid.toString() + ".dat"); + + boolean dataFound = dataFile.exists(); + if (!dataFound && legacyDataFile.exists()) { + nbt = this.server.getOfflinePlayerData(lowerName, false); + if (!legacyDataFile.delete()) { + this.server.getLogger().warning("Could not delete legacy player data for " + this.username); } + } else { + nbt = this.server.getOfflinePlayerData(this.uuid, !dataFound); } if (nbt == null) { @@ -1918,47 +2549,60 @@ protected void processLogin() { return; } - if (loginChainData.isXboxAuthed() && server.getPropertyBoolean("xbox-auth") || !server.getPropertyBoolean("xbox-auth")) { + if (loginChainData.isXboxAuthed() || !server.xboxAuth) { server.updateName(this.uuid, this.username); } this.playedBefore = (nbt.getLong("lastPlayed") - nbt.getLong("firstPlayed")) > 1; - nbt.putString("NameTag", this.username); - int exp = nbt.getInt("EXP"); - int expLevel = nbt.getInt("expLevel"); - this.setExperience(exp, expLevel); + this.setExperience(nbt.getInt("EXP"), nbt.getInt("expLevel")); - this.gamemode = nbt.getInt("playerGameType") & 0x03; if (this.server.getForceGamemode()) { this.gamemode = this.server.getGamemode(); nbt.putInt("playerGameType", this.gamemode); + } else { + this.gamemode = nbt.getInt("playerGameType") & 0x03; } this.adventureSettings = new AdventureSettings(this) .set(Type.WORLD_IMMUTABLE, isAdventure() || isSpectator()) .set(Type.MINE, !isAdventure() && !isSpectator()) .set(Type.BUILD, !isAdventure() && !isSpectator()) - .set(Type.NO_PVM, this.isSpectator()) + .set(Type.NO_PVM, isSpectator()) .set(Type.AUTO_JUMP, true) .set(Type.ALLOW_FLIGHT, isCreative() || isSpectator()) .set(Type.NO_CLIP, isSpectator()) .set(Type.FLYING, isSpectator()); Level level; - if ((level = this.server.getLevelByName(nbt.getString("Level"))) == null) { + if ((level = this.server.getLevelByName(nbt.getString("Level"))) == null || nbt.getShort("Health") < 1) { this.setLevel(this.server.getDefaultLevel()); nbt.putString("Level", this.level.getName()); + Position sp = this.level.getSpawnLocation(); nbt.getList("Pos", DoubleTag.class) - .add(new DoubleTag("0", this.level.getSpawnLocation().x)) - .add(new DoubleTag("1", this.level.getSpawnLocation().y)) - .add(new DoubleTag("2", this.level.getSpawnLocation().z)); + .add(new DoubleTag("0", sp.x)) + .add(new DoubleTag("1", sp.y)) + .add(new DoubleTag("2", sp.z)); } else { this.setLevel(level); } + if (nbt.contains("SpawnLevel")) { + Level spawnLevel = server.getLevelByName(nbt.getString("SpawnLevel")); + if (spawnLevel != null) { + this.spawnPosition = new Position( + nbt.getInt("SpawnX"), + nbt.getInt("SpawnY"), + nbt.getInt("SpawnZ"), + spawnLevel + ); + } + } + + this.timeSinceRest = nbt.getInt("TimeSinceRest"); + for (Tag achievement : nbt.getCompound("Achievements").getAllTags()) { if (!(achievement instanceof ByteTag)) { continue; @@ -1980,21 +2624,20 @@ protected void processLogin() { } this.sendPlayStatus(PlayStatusPacket.LOGIN_SUCCESS); - this.server.onPlayerLogin(this); ListTag posList = nbt.getList("Pos", DoubleTag.class); - super.init(this.level.getChunk((int) posList.get(0).data >> 4, (int) posList.get(2).data >> 4, true), nbt); + super.init(this.level.getChunk(NukkitMath.floorDouble(posList.get(0).data) >> 4, NukkitMath.floorDouble(posList.get(2).data) >> 4, true), nbt); if (!this.namedTag.contains("foodLevel")) { this.namedTag.putInt("foodLevel", 20); } - int foodLevel = this.namedTag.getInt("foodLevel"); + if (!this.namedTag.contains("foodSaturationLevel")) { this.namedTag.putFloat("foodSaturationLevel", 20); } - float foodSaturationLevel = this.namedTag.getFloat("foodSaturationLevel"); - this.foodData = new PlayerFood(this, foodLevel, foodSaturationLevel); + + this.foodData = new PlayerFood(this, this.namedTag.getInt("foodLevel"), this.namedTag.getFloat("foodSaturationLevel")); if (this.isSpectator()) { this.keepMovement = true; @@ -2003,11 +2646,6 @@ protected void processLogin() { this.forceMovement = this.teleportPosition = this.getPosition(); - if (!this.namedTag.contains("TimeSinceRest")) { - this.namedTag.putInt("TimeSinceRest", 0); - } - this.timeSinceRest = this.namedTag.getInt("TimeSinceRest"); - ResourcePacksInfoPacket infoPacket = new ResourcePacksInfoPacket(); infoPacket.resourcePackEntries = this.server.getResourcePackManager().getResourceStack(); infoPacket.mustAccept = this.server.getForceResources(); @@ -2015,6 +2653,11 @@ protected void processLogin() { } protected void completeLoginSequence() { + if (this.loggedIn) { + this.server.getLogger().warning("Tried to call completeLoginSequence but player is already logged in: " + this.username); + return; + } + PlayerLoginEvent ev; this.server.getPluginManager().callEvent(ev = new PlayerLoginEvent(this, "Plugin reason")); if (ev.isCancelled()) { @@ -2022,15 +2665,10 @@ protected void completeLoginSequence() { return; } - Level level = this.server.getLevelByName(this.namedTag.getString("SpawnLevel")); - if(level != null){ - this.spawnPosition = new Position(this.namedTag.getInt("SpawnX"), this.namedTag.getInt("SpawnY"), this.namedTag.getInt("SpawnZ"), level); - }else{ - this.spawnPosition = this.level.getSafeSpawn(); + if (this.isClosed() || !this.isConnected()) { + return; // Player was probably disconnected by a plugin } - spawnPosition = this.getSpawn(); - StartGamePacket startGamePacket = new StartGamePacket(); startGamePacket.entityUniqueId = this.id; startGamePacket.entityRuntimeId = this.id; @@ -2040,48 +2678,36 @@ protected void completeLoginSequence() { startGamePacket.z = (float) this.z; startGamePacket.yaw = (float) this.yaw; startGamePacket.pitch = (float) this.pitch; - startGamePacket.seed = -1; - startGamePacket.dimension = /*(byte) (this.level.getDimension() & 0xff)*/0; + startGamePacket.dimension = (byte) (this.level.getDimension() & 0xff); startGamePacket.worldGamemode = getClientFriendlyGamemode(this.gamemode); startGamePacket.difficulty = this.server.getDifficulty(); - startGamePacket.spawnX = spawnPosition.getFloorX(); - startGamePacket.spawnY = spawnPosition.getFloorY(); - startGamePacket.spawnZ = spawnPosition.getFloorZ(); - startGamePacket.hasAchievementsDisabled = true; - startGamePacket.dayCycleStopTime = -1; - startGamePacket.rainLevel = 0; - startGamePacket.lightningLevel = 0; - startGamePacket.commandsEnabled = this.isEnableClientCommand(); - startGamePacket.gameRules = getLevel().getGameRules(); - startGamePacket.levelId = ""; + if (this.level.getProvider() == null || this.level.getProvider().getSpawn() == null) { + startGamePacket.spawnX = (int) this.x; + startGamePacket.spawnY = (int) this.y; + startGamePacket.spawnZ = (int) this.z; + } else { + Vector3 spawn = this.level.getProvider().getSpawn(); + startGamePacket.spawnX = (int) spawn.x; + startGamePacket.spawnY = (int) spawn.y; + startGamePacket.spawnZ = (int) spawn.z; + } + startGamePacket.commandsEnabled = this.enableClientCommand; + startGamePacket.gameRules = this.getLevel().getGameRules(); startGamePacket.worldName = this.getServer().getNetwork().getName(); - startGamePacket.generator = 1; //0 old, 1 infinite, 2 flat - startGamePacket.isMovementServerAuthoritative = true; - this.dataPacket(startGamePacket); - - this.dataPacket(new BiomeDefinitionListPacket()); - this.dataPacket(new AvailableEntityIdentifiersPacket()); - this.inventory.sendCreativeContents(); - this.getAdventureSettings().update(); - - this.sendAttributes(); - - this.sendPotionEffects(this); - - if (this.isSpectator()) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, true); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, false); + if (this.getLevel().isRaining()) { + startGamePacket.rainLevel = this.getLevel().getRainTime(); + if (this.getLevel().isThundering()) { + startGamePacket.lightningLevel = this.getLevel().getThunderTime(); + } } - this.sendData(this); - this.loggedIn = true; + if (!CustomBlockManager.get().getBlockDefinitions().isEmpty()) { + startGamePacket.experiments.add(new ExperimentData("data_driven_items", true)); + } - this.level.sendTime(this); + this.forceDataPacket(startGamePacket, null); - this.sendAttributes(); - this.setNameTagVisible(true); - this.setNameTagAlwaysVisible(true); - this.setCanClimb(true); + this.loggedIn = true; this.server.getLogger().info(this.getServer().getLanguage().translateString("nukkit.player.logIn", TextFormat.AQUA + this.username + TextFormat.WHITE, @@ -2089,116 +2715,191 @@ protected void completeLoginSequence() { String.valueOf(this.getPort()), String.valueOf(this.id), this.level.getName(), - String.valueOf(NukkitMath.round(this.x, 4)), - String.valueOf(NukkitMath.round(this.y, 4)), - String.valueOf(NukkitMath.round(this.z, 4)))); + String.valueOf(this.getFloorX()), + String.valueOf(this.getFloorY()), + String.valueOf(this.getFloorZ()))); + + this.setDataFlag(DATA_FLAGS, DATA_FLAG_CAN_CLIMB, true, false); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_CAN_SHOW_NAMETAG, true, false); + this.setDataProperty(new ByteEntityData(DATA_ALWAYS_SHOW_NAMETAG, 1), false); + + if (this.isSpectator()) { + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SILENT, true, false); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, false, false); + } + + this.dataPacket(new BiomeDefinitionListPacket()); + this.dataPacket(new AvailableEntityIdentifiersPacket()); + + this.sendSpawnPos((int) this.x, (int) this.y, (int) this.z, this.level.getDimension()); + this.getLevel().sendTime(this); + + SetDifficultyPacket difficultyPacket = new SetDifficultyPacket(); + difficultyPacket.difficulty = this.server.getDifficulty(); + this.dataPacket(difficultyPacket); + + SetCommandsEnabledPacket commandsPacket = new SetCommandsEnabledPacket(); + commandsPacket.enabled = this.isEnableClientCommand(); + this.dataPacket(commandsPacket); + + this.adventureSettings.update(); + + GameRulesChangedPacket gameRulesPK = new GameRulesChangedPacket(); + gameRulesPK.gameRulesMap = level.getGameRules().getGameRules(); + this.dataPacket(gameRulesPK); + + this.server.sendFullPlayerListData(this); + this.sendAttributes(); + + this.inventory.sendCreativeContents(); + this.sendAllInventories(); + this.inventory.sendHeldItem(this); + this.server.sendRecipeList(this); + + if (this.isEnableClientCommand()) { + this.sendCommandData(); + } + + this.sendPotionEffects(this); + this.sendData(this); if (this.isOp() || this.hasPermission("nukkit.textcolor")) { this.setRemoveFormat(false); } - this.server.addOnlinePlayer(this); this.server.onPlayerCompleteLoginSequence(this); } + /** + * Handling received data packets + * @param packet packet + */ public void handleDataPacket(DataPacket packet) { if (!connected) { return; } byte pid = packet.pid(); - if (!loginVerified && pid != ProtocolInfo.LOGIN_PACKET && pid != ProtocolInfo.BATCH_PACKET && pid != ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET && pid != ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET) { + if (!loginVerified && pid != ProtocolInfo.LOGIN_PACKET && pid != ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET && pid != ProtocolInfo.BATCH_PACKET && pid != ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET) { server.getLogger().warning("Ignoring " + packet.getClass().getSimpleName() + " from " + getAddress() + " due to player not verified yet"); - if (unverifiedPackets++ > 100) { - this.close("", "Too many failed login attempts"); - } return; } - try (Timing ignored = Timings.getReceiveDataPacketTiming(packet)) { - DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet); - this.server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return; - } + if (!loggedIn && !PRE_LOGIN_PACKETS.contains(pid)) { + server.getLogger().warning("Ignoring " + packet.getClass().getSimpleName() + " from " + username + " due to player not logged in yet"); + return; + } - if (log.isTraceEnabled() && !server.isIgnoredPacket(packet.getClass())) { - log.trace("Inbound {}: {}", this.getName(), packet); - } + DataPacketReceiveEvent ev = new DataPacketReceiveEvent(this, packet); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } - BlockFace face; + if (Nukkit.DEBUG > 2 /*&& !server.isIgnoredPacket(packet.getClass())*/) { + log.trace("Inbound {}: {}", this.getName(), packet); + } - packetswitch: - switch (pid) { - case ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET: - if (this.loginPacketReceived) { - this.getServer().getLogger().debug(username + ": got a RequestNetworkSettingsPacket but player is already logged in"); - return; - } + switch (pid) { + case ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET: + if (this.loginPacketReceived) { + this.getServer().getLogger().debug(username + ": got a RequestNetworkSettingsPacket but player is already logged in"); + return; + } else if (this.getNetworkSession().getCompression() != CompressionProvider.NONE) { + this.getServer().getLogger().debug(username + ": got a RequestNetworkSettingsPacket but network settings are already updated"); + return; + } - int protocolVersion = ((RequestNetworkSettingsPacket) packet).protocolVersion; + RequestNetworkSettingsPacket networkSettingsRequest = (RequestNetworkSettingsPacket) packet; - if (!ProtocolInfo.SUPPORTED_PROTOCOLS.contains(protocolVersion)) { - String message; - if (protocolVersion < ProtocolInfo.CURRENT_PROTOCOL) { - message = "disconnectionScreen.outdatedClient"; - } else { - message = "disconnectionScreen.outdatedServer"; - } - this.close("", message, true); - break; + if (!ProtocolInfo.SUPPORTED_PROTOCOLS.contains(networkSettingsRequest.protocolVersion)) { + String message; + if (networkSettingsRequest.protocolVersion < ProtocolInfo.CURRENT_PROTOCOL) { + message = "disconnectionScreen.outdatedClient"; + } else { + message = "disconnectionScreen.outdatedServer"; } + this.close("", message, true); + this.server.getLogger().debug(getAddress() + " disconnected with unsupported protocol " + networkSettingsRequest.protocolVersion); + return; + } - NetworkSettingsPacket settingsPacket = new NetworkSettingsPacket(); - settingsPacket.compressionAlgorithm = PacketCompressionAlgorithm.ZLIB; - settingsPacket.compressionThreshold = 1; // compress everything - this.forceDataPacket(settingsPacket, () -> { - this.networkSession.setCompression(CompressionProvider.ZLIB); - }); - break; - case ProtocolInfo.LOGIN_PACKET: - if (this.loginPacketReceived) { - this.close("", "Invalid login packet"); - return; - } + NetworkSettingsPacket settingsPacket = new NetworkSettingsPacket(); + settingsPacket.compressionAlgorithm = server.useSnappy ? PacketCompressionAlgorithm.SNAPPY : PacketCompressionAlgorithm.ZLIB; + settingsPacket.compressionThreshold = server.networkCompressionThreshold; + this.forceDataPacket(settingsPacket, () -> this.networkSession.setCompression(server.useSnappy ? CompressionProvider.SNAPPY : CompressionProvider.ZLIB_RAW)); + return; + case ProtocolInfo.LOGIN_PACKET: + if (this.loginPacketReceived) { + this.close("", "Invalid login packet"); + return; + } - this.loginPacketReceived = true; + this.loginPacketReceived = true; - LoginPacket loginPacket = (LoginPacket) packet; - this.username = TextFormat.clean(loginPacket.username); - this.displayName = this.username; - this.iusername = this.username.toLowerCase(); + LoginPacket loginPacket = (LoginPacket) packet; + this.unverifiedUsername = TextFormat.clean(loginPacket.username); - this.setDataProperty(new StringEntityData(DATA_NAMETAG, this.username), false); + if (!ProtocolInfo.SUPPORTED_PROTOCOLS.contains(loginPacket.getProtocol())) { + String message; + if (loginPacket.getProtocol() < ProtocolInfo.CURRENT_PROTOCOL) { + message = "disconnectionScreen.outdatedClient"; + } else { + message = "disconnectionScreen.outdatedServer"; + } + this.close("", message, true); + this.server.getLogger().debug(getAddress() + " disconnected with unsupported protocol " + loginPacket.getProtocol()); + return; + } - this.loginChainData = ClientChainData.read(loginPacket); + if (loginPacket.skin == null) { + this.close("", "disconnectionScreen.invalidSkin"); + return; + } - if (!loginChainData.isXboxAuthed() && server.getPropertyBoolean("xbox-auth")) { - this.close("", "disconnectionScreen.notAuthenticated"); - break; - } + if (this.server.getOnlinePlayersCount() >= this.server.getMaxPlayers() && this.kick(PlayerKickEvent.Reason.SERVER_FULL, "disconnectionScreen.serverFull", false)) { + return; + } - if (this.server.getOnlinePlayers().size() >= this.server.getMaxPlayers() && this.kick(PlayerKickEvent.Reason.SERVER_FULL, "disconnectionScreen.serverFull", false)) { - break; - } + try { + // TODO: Why do we read this separately? + this.loginChainData = ClientChainData.read(loginPacket); + } catch (ClientChainData.TooBigSkinException ex) { + this.close("", "disconnectionScreen.invalidSkin"); + return; + } - this.randomClientId = loginPacket.clientId; + server.getLogger().debug("Name: " + this.unverifiedUsername + " Protocol: " + loginPacket.getProtocol() + " Version: " + loginChainData.getGameVersion()); - this.uuid = loginPacket.clientUUID; - this.rawUUID = Binary.writeUUID(this.uuid); + if (!loginChainData.isXboxAuthed() && server.xboxAuth) { + this.close("", "disconnectionScreen.notAuthenticated"); + return; + } - boolean valid = true; - int len = loginPacket.username.length(); - if (len > 16 || len < 3 || loginPacket.username.trim().isEmpty()) { - valid = false; - } + // Do not set username before the user is authenticated + this.username = this.unverifiedUsername; + this.unverifiedUsername = null; + this.displayName = this.username; + this.iusername = this.username.toLowerCase(); + this.setDataProperty(new StringEntityData(DATA_NAMETAG, this.username), false); + + this.randomClientId = loginPacket.clientId; + this.uuid = loginPacket.clientUUID; + this.rawUUID = Binary.writeUUID(this.uuid); + + boolean valid = true; + int len = loginPacket.username.length(); + if (len > 16 || len < 3 || loginPacket.username.trim().isEmpty()) { + valid = false; + } - for (int i = 0; i < len && valid; i++) { + if (valid) { + for (int i = 0; i < len; i++) { char c = loginPacket.username.charAt(i); if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || - c == '_' || c == ' ' + c == '_' || (c == ' ' && i != 0 && i != len - 1) ) { continue; } @@ -2206,922 +2907,1192 @@ public void handleDataPacket(DataPacket packet) { valid = false; break; } + } - if (!valid || Objects.equals(this.iusername, "rcon") || Objects.equals(this.iusername, "console")) { - this.close("", "disconnectionScreen.invalidName"); + if (!valid || Objects.equals(this.iusername, "rcon") || Objects.equals(this.iusername, "console")) { + this.close("", "disconnectionScreen.invalidName"); + return; + } - break; - } + Skin skin = loginPacket.skin; + if (!skin.isValid()) { + this.close("", "disconnectionScreen.invalidSkin"); + return; + } + this.setSkin(skin); - if (!loginPacket.skin.isValid()) { - this.close("", "disconnectionScreen.invalidSkin"); - break; - } else { - this.setSkin(loginPacket.skin); - } + PlayerPreLoginEvent playerPreLoginEvent; + this.server.getPluginManager().callEvent(playerPreLoginEvent = new PlayerPreLoginEvent(this, "Plugin reason")); + if (playerPreLoginEvent.isCancelled()) { + this.close("", playerPreLoginEvent.getKickMessage()); + return; + } - PlayerPreLoginEvent playerPreLoginEvent; - this.server.getPluginManager().callEvent(playerPreLoginEvent = new PlayerPreLoginEvent(this, "Plugin reason")); - if (playerPreLoginEvent.isCancelled()) { - this.close("", playerPreLoginEvent.getKickMessage()); + if (server.encryptionEnabled) { + this.getServer().getScheduler().scheduleAsyncTask(new PrepareEncryptionTask(this) { - break; - } + @Override + public void onCompletion(Server server) { + if (!Player.this.connected) { + return; + } - if (server.encryptionEnabled) { - this.getServer().getScheduler().scheduleAsyncTask(new PrepareEncryptionTask(this) { - - @Override - public void onCompletion(Server server) { - if (!Player.this.connected) { - return; - } - - if (this.getHandshakeJwt() == null || this.getEncryptionKey() == null || this.getEncryptionCipher() == null || this.getDecryptionCipher() == null) { - Player.this.close("Failed to enable encryption"); - return; - } - - ServerToClientHandshakePacket handshakePacket = new ServerToClientHandshakePacket(); - handshakePacket.jwt = this.getHandshakeJwt(); - Player.this.forceDataPacket(handshakePacket, () -> { - Player.this.awaitingEncryptionHandshake = true; - Player.this.networkSession.setEncryption(this.getEncryptionKey(), this.getEncryptionCipher(), this.getDecryptionCipher()); - }); + if (this.getHandshakeJwt() == null || this.getEncryptionKey() == null || this.getEncryptionCipher() == null || this.getDecryptionCipher() == null) { + Player.this.close("Failed to enable encryption"); + return; } - }); - } else { - this.processPreLogin(); - } - break; - case ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET: - if (!this.awaitingEncryptionHandshake) { - this.close("Invalid encryption handshake"); - return; - } - this.awaitingEncryptionHandshake = false; + ServerToClientHandshakePacket handshakePacket = new ServerToClientHandshakePacket(); + handshakePacket.jwt = this.getHandshakeJwt(); + Player.this.forceDataPacket(handshakePacket, () -> { + Player.this.awaitingEncryptionHandshake = true; + Player.this.networkSession.setEncryption(this.getEncryptionKey(), this.getEncryptionCipher(), this.getDecryptionCipher()); + }); + } + }); + } else { this.processPreLogin(); + } + return; + case ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET: + if (!this.awaitingEncryptionHandshake) { + this.close("Invalid encryption handshake"); return; - case ProtocolInfo.RESOURCE_PACK_CLIENT_RESPONSE_PACKET: - ResourcePackClientResponsePacket responsePacket = (ResourcePackClientResponsePacket) packet; - switch (responsePacket.responseStatus) { - case ResourcePackClientResponsePacket.STATUS_REFUSED: - this.close("", "disconnectionScreen.noReason"); - break; - case ResourcePackClientResponsePacket.STATUS_SEND_PACKS: - for (ResourcePackClientResponsePacket.Entry entry : responsePacket.packEntries) { - ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(entry.uuid); - if (resourcePack == null) { - this.close("", "disconnectionScreen.resourcePack"); - break; - } - - ResourcePackDataInfoPacket dataInfoPacket = new ResourcePackDataInfoPacket(); - dataInfoPacket.packId = resourcePack.getPackId(); - dataInfoPacket.maxChunkSize = RESOURCE_PACK_CHUNK_SIZE; - dataInfoPacket.chunkCount = MathHelper.ceil(resourcePack.getPackSize() / (float) RESOURCE_PACK_CHUNK_SIZE); - dataInfoPacket.compressedPackSize = resourcePack.getPackSize(); - dataInfoPacket.sha256 = resourcePack.getSha256(); - this.dataPacket(dataInfoPacket); - } - break; - case ResourcePackClientResponsePacket.STATUS_HAVE_ALL_PACKS: - ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); - stackPacket.mustAccept = this.server.getForceResources(); - stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack(); - this.dataPacket(stackPacket); - break; - case ResourcePackClientResponsePacket.STATUS_COMPLETED: - this.shouldLogin = true; + } - if (this.preLoginEventTask.isFinished()) { - this.preLoginEventTask.onCompletion(server); + this.awaitingEncryptionHandshake = false; + this.processPreLogin(); + return; + case ProtocolInfo.RESOURCE_PACK_CLIENT_RESPONSE_PACKET: + if (this.spawned) { + this.getServer().getLogger().debug(username + ": ResourcePackClientResponsePacket after player spawned"); + return; + } + ResourcePackClientResponsePacket responsePacket = (ResourcePackClientResponsePacket) packet; + switch (responsePacket.responseStatus) { + case ResourcePackClientResponsePacket.STATUS_REFUSED: + this.close("", "disconnectionScreen.noReason"); + return; + case ResourcePackClientResponsePacket.STATUS_SEND_PACKS: + for (ResourcePackClientResponsePacket.Entry entry : responsePacket.packEntries) { + ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(entry.uuid); + if (resourcePack == null) { + this.close("", "disconnectionScreen.resourcePack"); + return; } - break; - } - break; - case ProtocolInfo.RESOURCE_PACK_CHUNK_REQUEST_PACKET: - ResourcePackChunkRequestPacket requestPacket = (ResourcePackChunkRequestPacket) packet; - ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(requestPacket.packId); - if (resourcePack == null) { - this.close("", "disconnectionScreen.resourcePack"); - break; - } - - ResourcePackChunkDataPacket dataPacket = new ResourcePackChunkDataPacket(); - dataPacket.packId = resourcePack.getPackId(); - dataPacket.chunkIndex = requestPacket.chunkIndex; - dataPacket.data = resourcePack.getPackChunk(RESOURCE_PACK_CHUNK_SIZE * requestPacket.chunkIndex, RESOURCE_PACK_CHUNK_SIZE); - dataPacket.progress = (long) RESOURCE_PACK_CHUNK_SIZE * requestPacket.chunkIndex; - this.dataPacket(dataPacket); - break; - case ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET: - if (this.locallyInitialized) { - break; - } - this.locallyInitialized = true; - PlayerLocallyInitializedEvent locallyInitializedEvent = new PlayerLocallyInitializedEvent(this); - this.server.getPluginManager().callEvent(locallyInitializedEvent); - break; - case ProtocolInfo.PLAYER_SKIN_PACKET: - PlayerSkinPacket skinPacket = (PlayerSkinPacket) packet; - Skin skin = skinPacket.skin; - - if (!skin.isValid()) { - this.getServer().getLogger().debug(username + ": PlayerSkinPacket with invalid skin"); - break; - } - PlayerChangeSkinEvent playerChangeSkinEvent = new PlayerChangeSkinEvent(this, skin); - playerChangeSkinEvent.setCancelled(TimeUnit.SECONDS.toMillis(this.server.getPlayerSkinChangeCooldown()) > System.currentTimeMillis() - this.lastSkinChange); - this.server.getPluginManager().callEvent(playerChangeSkinEvent); - if (!playerChangeSkinEvent.isCancelled()) { - this.lastSkinChange = System.currentTimeMillis(); - this.setSkin(skin); - } - - break; - case ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET: - log.warn("Violation warning from {}: {}", this.getName(), packet.toString()); - break; - case ProtocolInfo.EMOTE_PACKET: - if (!this.spawned) { + ResourcePackDataInfoPacket dataInfoPacket = new ResourcePackDataInfoPacket(); + dataInfoPacket.packId = resourcePack.getPackId(); + dataInfoPacket.maxChunkSize = RESOURCE_PACK_CHUNK_SIZE; + dataInfoPacket.chunkCount = MathHelper.ceil(resourcePack.getPackSize() / (float) RESOURCE_PACK_CHUNK_SIZE); + dataInfoPacket.compressedPackSize = resourcePack.getPackSize(); + dataInfoPacket.sha256 = resourcePack.getSha256(); + this.dataPacket(dataInfoPacket); + } return; - } - EmotePacket emotePacket = (EmotePacket) packet; - if (emotePacket.runtimeId != this.id) { - server.getLogger().warning(this.username + " sent EmotePacket with invalid entity id: " + emotePacket.runtimeId + " != " + this.id); + case ResourcePackClientResponsePacket.STATUS_HAVE_ALL_PACKS: + ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); + stackPacket.mustAccept = this.server.getForceResources() && !this.server.forceResourcesAllowOwnPacks; // Option not to disable client's own packs + stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack(); + if (!CustomBlockManager.get().getBlockDefinitions().isEmpty()) { + stackPacket.experiments.add(new ExperimentData("data_driven_items", true)); + } + this.dataPacket(stackPacket); return; - } - for (Player viewer : this.getViewers().values()) { - viewer.dataPacket(emotePacket); - } - return; - case ProtocolInfo.PLAYER_INPUT_PACKET: - if (!this.isAlive() || !this.spawned) { - break; - } - PlayerInputPacket ipk = (PlayerInputPacket) packet; - if (riding instanceof EntityMinecartAbstract) { - ((EntityMinecartAbstract) riding).setCurrentSpeed(ipk.motionY); - } - break; - case ProtocolInfo.MOVE_PLAYER_PACKET: - if (this.teleportPosition != null) { - break; - } + case ResourcePackClientResponsePacket.STATUS_COMPLETED: + this.shouldLogin = true; - MovePlayerPacket movePlayerPacket = (MovePlayerPacket) packet; - Vector3 newPos = new Vector3(movePlayerPacket.x, movePlayerPacket.y - this.getEyeHeight(), movePlayerPacket.z); - - double dis = newPos.distanceSquared(this); - if (dis == 0 && movePlayerPacket.yaw % 360 == this.yaw && movePlayerPacket.pitch % 360 == this.pitch) { - break; - } + if (this.preLoginEventTask.isFinished()) { + this.preLoginEventTask.onCompletion(server); + } + return; + } + return; + case ProtocolInfo.RESOURCE_PACK_CHUNK_REQUEST_PACKET: + ResourcePackChunkRequestPacket requestPacket = (ResourcePackChunkRequestPacket) packet; + ResourcePack resourcePack = this.server.getResourcePackManager().getPackById(requestPacket.packId); + if (resourcePack == null) { + this.close("", "disconnectionScreen.resourcePack"); + return; + } - if (dis > 100) { - this.sendPosition(this, movePlayerPacket.yaw, movePlayerPacket.pitch, MovePlayerPacket.MODE_RESET); - break; - } + ResourcePackChunkDataPacket dataPacket = new ResourcePackChunkDataPacket(); + dataPacket.packId = resourcePack.getPackId(); + dataPacket.chunkIndex = requestPacket.chunkIndex; + dataPacket.data = resourcePack.getPackChunk(RESOURCE_PACK_CHUNK_SIZE * requestPacket.chunkIndex, RESOURCE_PACK_CHUNK_SIZE); + dataPacket.progress = (long) RESOURCE_PACK_CHUNK_SIZE * requestPacket.chunkIndex; + this.dataPacket(dataPacket); + return; + case ProtocolInfo.PLAYER_SKIN_PACKET: + PlayerSkinPacket skinPacket = (PlayerSkinPacket) packet; + skin = skinPacket.skin; - boolean revert = false; - if (!this.isAlive() || !this.spawned) { - revert = true; - this.forceMovement = new Vector3(this.x, this.y, this.z); - } + if (!skin.isValid()) { + this.close("", "disconnectionScreen.invalidSkin"); + return; + } - if (this.forceMovement != null && (newPos.distanceSquared(this.forceMovement) > 0.1 || revert)) { - this.sendPosition(this.forceMovement, movePlayerPacket.yaw, movePlayerPacket.pitch, MovePlayerPacket.MODE_RESET); - } else { + PlayerChangeSkinEvent playerChangeSkinEvent = new PlayerChangeSkinEvent(this, skin); + playerChangeSkinEvent.setCancelled(TimeUnit.SECONDS.toMillis(this.server.getPlayerSkinChangeCooldown()) > System.currentTimeMillis() - this.lastSkinChange); + this.server.getPluginManager().callEvent(playerChangeSkinEvent); + if (!playerChangeSkinEvent.isCancelled()) { + this.lastSkinChange = System.currentTimeMillis(); + this.setSkin(skin); + } + return; + case ProtocolInfo.PLAYER_AUTH_INPUT_PACKET: + if (!this.spawned) { + return; + } - movePlayerPacket.yaw %= 360; - movePlayerPacket.pitch %= 360; + PlayerAuthInputPacket authPacket = (PlayerAuthInputPacket) packet; + if (!authPacket.getBlockActionData().isEmpty()) { + for (PlayerBlockActionData action : authPacket.getBlockActionData().values()) { + BlockVector3 blockPos = action.getPosition(); + BlockFace blockFace = BlockFace.fromIndex(action.getFacing()); + if (this.lastBlockAction != null && this.lastBlockAction.getAction() == PlayerActionType.PREDICT_DESTROY_BLOCK && + action.getAction() == PlayerActionType.CONTINUE_DESTROY_BLOCK) { + this.onBlockBreakStart(blockPos, blockFace); + } - if (movePlayerPacket.yaw < 0) { - movePlayerPacket.yaw += 360; + BlockVector3 lastBreakPos = this.lastBlockAction == null ? null : this.lastBlockAction.getPosition(); + if (lastBreakPos != null && (lastBreakPos.getX() != blockPos.getX() || + lastBreakPos.getY() != blockPos.getY() || lastBreakPos.getZ() != blockPos.getZ())) { + this.onBlockBreakAbort(lastBreakPos, BlockFace.DOWN); + this.onBlockBreakStart(blockPos, blockFace); } - this.setRotation(movePlayerPacket.yaw, movePlayerPacket.pitch); - this.newPosition = newPos; - this.forceMovement = null; + switch (action.getAction()) { + case START_DESTROY_BLOCK: + this.onBlockBreakStart(blockPos, blockFace); + break; + case ABORT_DESTROY_BLOCK: + //case STOP_DESTROY_BLOCK: + this.onBlockBreakAbort(blockPos, blockFace); + break; + case CONTINUE_DESTROY_BLOCK: + // When player moves cursor to another block + break; + case PREDICT_DESTROY_BLOCK: + //this.onBlockBreakAbort(blockPos, blockFace); + this.onBlockBreakComplete(blockPos, blockFace); + break; + } + this.lastBlockAction = action; } - break; - case ProtocolInfo.PLAYER_AUTH_INPUT_PACKET: - PlayerAuthInputPacket authPacket = (PlayerAuthInputPacket) packet; - - if (!authPacket.getBlockActionData().isEmpty()) { - for (PlayerBlockActionData action : authPacket.getBlockActionData().values()) { - BlockVector3 blockPos = action.getPosition(); - BlockFace blockFace = BlockFace.fromIndex(action.getFacing()); - if (this.lastBlockAction != null && this.lastBlockAction.getAction() == PlayerActionType.PREDICT_DESTROY_BLOCK && - action.getAction() == PlayerActionType.CONTINUE_DESTROY_BLOCK) { - this.onBlockBreakStart(blockPos.asVector3(), blockFace); - } + } - BlockVector3 lastBreakPos = this.lastBlockAction == null ? null : this.lastBlockAction.getPosition(); - if (lastBreakPos != null && (lastBreakPos.getX() != blockPos.getX() || - lastBreakPos.getY() != blockPos.getY() || lastBreakPos.getZ() != blockPos.getZ())) { - this.onBlockBreakAbort(lastBreakPos.asVector3(), BlockFace.DOWN); - this.onBlockBreakStart(blockPos.asVector3(), blockFace); - } + if (this.teleportPosition != null) { + return; + } - switch (action.getAction()) { - case START_DESTROY_BLOCK: - this.onBlockBreakStart(blockPos.asVector3(), blockFace); - break; - case ABORT_DESTROY_BLOCK: - case STOP_DESTROY_BLOCK: - this.onBlockBreakAbort(blockPos.asVector3(), blockFace); - break; - case CONTINUE_DESTROY_BLOCK: - this.onBlockBreakContinue(blockPos.asVector3(), blockFace); - break; - case PREDICT_DESTROY_BLOCK: - this.onBlockBreakAbort(blockPos.asVector3(), blockFace); - this.onBlockBreakComplete(blockPos, blockFace); - break; - } - this.lastBlockAction = action; - } + if (this.riding instanceof EntityControllable && riding.isControlling(this)) { + boolean jumping = authPacket.getInputData().contains(AuthInputAction.JUMPING); + if (jumping && this.riderJumpTick <= 0) { + this.riderJumpTick = server.getTick(); + } else if (!jumping && this.riderJumpTick > 0) { + ((EntityControllable) riding).onJump(this, server.getTick() - this.riderJumpTick); + this.riderJumpTick = 0; } - - if (this.teleportPosition != null) { - return; + double inputX = authPacket.getMotion().getX(); + double inputY = authPacket.getMotion().getY(); + if (inputX >= -1.001 && inputX <= 1.001 && inputY >= -1.001 && inputY <= 1.001) { + ((EntityControllable) riding).onPlayerInput(this, inputX, inputY); } - - if (this.riding instanceof EntityMinecartAbstract) { - double inputY = authPacket.getMotion().getY(); - if (inputY >= -1.001 && inputY <= 1.001) { - ((EntityMinecartAbstract) riding).setCurrentSpeed(inputY); - } - } else if (this.riding instanceof EntityBoat && authPacket.getInputData().contains(AuthInputAction.IN_CLIENT_PREDICTED_IN_VEHICLE)) { - if (this.riding.getId() == authPacket.getPredictedVehicle() && this.riding.isControlling(this)) { - if (this.temporalVector.setComponents(authPacket.getPosition().getX(), authPacket.getPosition().getY(), authPacket.getPosition().getZ()).distanceSquared(this.riding) < 100) { - ((EntityBoat) this.riding).onInput(authPacket.getPosition().getX(), authPacket.getPosition().getY(), authPacket.getPosition().getZ(), authPacket.getHeadYaw()); - } + } else if (this.riding instanceof EntityBoat && authPacket.getInputData().contains(AuthInputAction.IN_CLIENT_PREDICTED_IN_VEHICLE)) { + if (this.riding.getId() == authPacket.getPredictedVehicle() && this.riding.isControlling(this)) { + if (this.temporalVector.setComponents(authPacket.getPosition().getX(), authPacket.getPosition().getY(), authPacket.getPosition().getZ()).distanceSquared(this.riding) < 9) { + ((EntityBoat) this.riding).onInput(authPacket.getPosition().getX(), authPacket.getPosition().getY(), authPacket.getPosition().getZ(), authPacket.getHeadYaw()); } } + } - if (!this.isSpectator() && authPacket.getInputData().contains(AuthInputAction.MISSED_SWING)) { - level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_NODAMAGE, -1, "minecraft:player", false, false); - } + if (!this.isSpectator() && authPacket.getInputData().contains(AuthInputAction.MISSED_SWING)) { + level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_NODAMAGE, -1, "minecraft:player", false, false); + } - if (authPacket.getInputData().contains(AuthInputAction.START_SPRINTING)) { - PlayerToggleSprintEvent event = new PlayerToggleSprintEvent(this, true); - this.server.getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.sendData(this); - } else { - this.setSprinting(true); - } + if (authPacket.getInputData().contains(AuthInputAction.START_SPRINTING)) { + PlayerToggleSprintEvent playerToggleSprintEvent = new PlayerToggleSprintEvent(this, true); + if ((this.foodData.getLevel() <= 6 && !this.getAdventureSettings().get(Type.FLYING)) || this.hasEffect(Effect.BLINDNESS)) { + playerToggleSprintEvent.setCancelled(true); } - - if (authPacket.getInputData().contains(AuthInputAction.STOP_SPRINTING)) { - PlayerToggleSprintEvent event = new PlayerToggleSprintEvent(this, false); - this.server.getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.sendData(this); - } else { - this.setSprinting(false); - } + this.server.getPluginManager().callEvent(playerToggleSprintEvent); + if (playerToggleSprintEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSprinting(true, false); } + this.setUsingItem(false); + } - if (authPacket.getInputData().contains(AuthInputAction.START_SNEAKING)) { - PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this, true); - this.server.getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.sendData(this); - } else { - this.setSneaking(true); - } + if (authPacket.getInputData().contains(AuthInputAction.STOP_SPRINTING)) { + PlayerToggleSprintEvent playerToggleSprintEvent = new PlayerToggleSprintEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleSprintEvent); + if (playerToggleSprintEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSprinting(false, false); } + } - if (authPacket.getInputData().contains(AuthInputAction.STOP_SNEAKING)) { - PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this, false); - this.server.getPluginManager().callEvent(event); - if (event.isCancelled()) { - this.sendData(this); - } else { - this.setSneaking(false); - } + if (authPacket.getInputData().contains(AuthInputAction.START_SNEAKING)) { + PlayerToggleSneakEvent playerToggleSneakEvent = new PlayerToggleSneakEvent(this, true); + this.server.getPluginManager().callEvent(playerToggleSneakEvent); + if (playerToggleSneakEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSneaking(true); } + } - if (authPacket.getInputData().contains(AuthInputAction.START_JUMPING)) { - PlayerJumpEvent playerJumpEvent = new PlayerJumpEvent(this); - this.server.getPluginManager().callEvent(playerJumpEvent); + if (authPacket.getInputData().contains(AuthInputAction.STOP_SNEAKING)) { + PlayerToggleSneakEvent playerToggleSneakEvent = new PlayerToggleSneakEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleSneakEvent); + if (playerToggleSneakEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSneaking(false); } + } - if (authPacket.getInputData().contains(AuthInputAction.START_GLIDING)) { - PlayerToggleGlideEvent playerToggleGlideEvent = new PlayerToggleGlideEvent(this, true); - this.server.getPluginManager().callEvent(playerToggleGlideEvent); - if (playerToggleGlideEvent.isCancelled()) { - this.sendData(this); - } else { - this.setGliding(true); - } + if (authPacket.getInputData().contains(AuthInputAction.START_CRAWLING)) { + PlayerToggleCrawlEvent playerToggleCrawlEvent = new PlayerToggleCrawlEvent(this, true); + this.server.getPluginManager().callEvent(playerToggleCrawlEvent); + if (playerToggleCrawlEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setCrawling(true); } + } - if (authPacket.getInputData().contains(AuthInputAction.STOP_GLIDING)) { - PlayerToggleGlideEvent playerToggleGlideEvent = new PlayerToggleGlideEvent(this, false); - this.server.getPluginManager().callEvent(playerToggleGlideEvent); - if (playerToggleGlideEvent.isCancelled()) { - this.sendData(this); - } else { - this.setGliding(false); - } + if (authPacket.getInputData().contains(AuthInputAction.STOP_CRAWLING)) { + PlayerToggleCrawlEvent playerToggleCrawlEvent = new PlayerToggleCrawlEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleCrawlEvent); + if (playerToggleCrawlEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setCrawling(false); } + } - if (authPacket.getInputData().contains(AuthInputAction.START_SWIMMING)) { - PlayerToggleSwimEvent ptse = new PlayerToggleSwimEvent(this, true); - this.server.getPluginManager().callEvent(ptse); - if (ptse.isCancelled()) { - this.sendData(this); - } else { - this.setSwimming(true); - } - } + if (authPacket.getInputData().contains(AuthInputAction.START_JUMPING)) { + this.server.getPluginManager().callEvent(new PlayerJumpEvent(this)); + } - if (authPacket.getInputData().contains(AuthInputAction.STOP_SWIMMING)) { - PlayerToggleSwimEvent ptse = new PlayerToggleSwimEvent(this, false); - this.server.getPluginManager().callEvent(ptse); - if (ptse.isCancelled()) { - this.sendData(this); - } else { - this.setSwimming(false); - } + if (authPacket.getInputData().contains(AuthInputAction.START_GLIDING)) { + boolean withoutElytra = false; + Item chestplate = this.getInventory().getChestplateFast(); + if (chestplate == null || chestplate.getId() != ItemID.ELYTRA || chestplate.getDamage() >= chestplate.getMaxDurability()) { + withoutElytra = true; } - - if (authPacket.getInputData().contains(AuthInputAction.START_FLYING)) { - if (!server.getAllowFlight() && !this.getAdventureSettings().get(Type.ALLOW_FLIGHT)) { - this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, "Flying is not enabled on this server"); - break; - } - PlayerToggleFlightEvent playerToggleFlightEvent = new PlayerToggleFlightEvent(this, true); - if (this.isSpectator()) { - playerToggleFlightEvent.setCancelled(); - } - this.getServer().getPluginManager().callEvent(playerToggleFlightEvent); - if (playerToggleFlightEvent.isCancelled()) { - this.getAdventureSettings().update(); - } else { - this.getAdventureSettings().set(AdventureSettings.Type.FLYING, playerToggleFlightEvent.isFlying()); - } + if (withoutElytra && !server.getAllowFlight()) { + this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, MSG_FLYING_NOT_ENABLED, true); + return; } - - if (authPacket.getInputData().contains(AuthInputAction.STOP_FLYING)) { - PlayerToggleFlightEvent playerToggleFlightEvent = new PlayerToggleFlightEvent(this, false); - if (this.isSpectator()) { - playerToggleFlightEvent.setCancelled(); - } - this.getServer().getPluginManager().callEvent(playerToggleFlightEvent); - if (playerToggleFlightEvent.isCancelled()) { - this.getAdventureSettings().update(); - } else { - this.getAdventureSettings().set(AdventureSettings.Type.FLYING, playerToggleFlightEvent.isFlying()); - } + PlayerToggleGlideEvent playerToggleGlideEvent = new PlayerToggleGlideEvent(this, true); + if (withoutElytra) { + playerToggleGlideEvent.setCancelled(true); } + this.server.getPluginManager().callEvent(playerToggleGlideEvent); + if (playerToggleGlideEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setGliding(true); + } + } - Vector3 clientPosition = authPacket.getPosition().subtract(0, this.getEyeHeight(), 0).asVector3(); - - double distSqrt = clientPosition.distanceSquared(this); - if (distSqrt == 0.0 && authPacket.getYaw() % 360 == this.yaw && authPacket.getPitch() % 360 == this.pitch) { - break; + if (authPacket.getInputData().contains(AuthInputAction.STOP_GLIDING)) { + PlayerToggleGlideEvent playerToggleGlideEvent = new PlayerToggleGlideEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleGlideEvent); + if (playerToggleGlideEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setGliding(false); } + } - if (distSqrt > 100) { - this.sendPosition(this, authPacket.getYaw(), authPacket.getPitch(), MovePlayerPacket.MODE_RESET); - break; + if (authPacket.getInputData().contains(AuthInputAction.START_SWIMMING)) { + PlayerToggleSwimEvent ptse = new PlayerToggleSwimEvent(this, true); + if (!this.isInsideOfWater()) { + ptse.setCancelled(true); } + this.server.getPluginManager().callEvent(ptse); + if (ptse.isCancelled()) { + this.needSendData = true; + } else { + this.setSwimming(true); + } + this.setUsingItem(false); + } - boolean revertMotion = false; - if (!this.isAlive() || !this.spawned) { - revertMotion = true; - this.forceMovement = new Vector3(this.x, this.y, this.z); + if (authPacket.getInputData().contains(AuthInputAction.STOP_SWIMMING)) { + PlayerToggleSwimEvent ptse = new PlayerToggleSwimEvent(this, false); + this.server.getPluginManager().callEvent(ptse); + if (ptse.isCancelled()) { + this.needSendData = true; + } else { + this.setSwimming(false); } + } - if (this.forceMovement != null && (clientPosition.distanceSquared(this.forceMovement) > 0.1 || revertMotion)) { - this.sendPosition(this.forceMovement, authPacket.getYaw(), authPacket.getPitch(), MovePlayerPacket.MODE_RESET); + if (authPacket.getInputData().contains(AuthInputAction.START_FLYING)) { + if (!server.getAllowFlight() && !this.adventureSettings.get(Type.ALLOW_FLIGHT)) { + this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, MSG_FLYING_NOT_ENABLED, true); + break; + } + PlayerToggleFlightEvent playerToggleFlightEvent = new PlayerToggleFlightEvent(this, true); + server.getPluginManager().callEvent(playerToggleFlightEvent); + if (playerToggleFlightEvent.isCancelled()) { + this.needSendAdventureSettings = true; } else { - float yaw = authPacket.getYaw() % 360; - float pitch = authPacket.getPitch() % 360; - if (yaw < 0) { - yaw += 360; - } + this.adventureSettings.set(AdventureSettings.Type.FLYING, playerToggleFlightEvent.isFlying()); + } + } - this.setRotation(yaw, pitch); - this.newPosition = clientPosition; - this.clientMovements.offer(clientPosition); - this.forceMovement = null; + if (authPacket.getInputData().contains(AuthInputAction.STOP_FLYING)) { + PlayerToggleFlightEvent playerToggleFlightEvent = new PlayerToggleFlightEvent(this, false); + if (this.isSpectator()) { + playerToggleFlightEvent.setCancelled(true); } - break; - case ProtocolInfo.MOB_EQUIPMENT_PACKET: - if (!this.spawned || !this.isAlive()) { - break; + server.getPluginManager().callEvent(playerToggleFlightEvent); + if (playerToggleFlightEvent.isCancelled()) { + this.needSendAdventureSettings = true; + } else { + this.adventureSettings.set(AdventureSettings.Type.FLYING, playerToggleFlightEvent.isFlying()); } + } - MobEquipmentPacket mobEquipmentPacket = (MobEquipmentPacket) packet; + Vector3 clientPosition = authPacket.getPosition().subtract(0, this.getBaseOffset(), 0).asVector3(); - Inventory inv = this.getWindowById(mobEquipmentPacket.windowId); + double distSqrt = clientPosition.distanceSquared(this); + if (distSqrt > 100) { // Notice: This is the distance to player's position on server side. There are likely still unhandled previous movements when next move packet is received. + this.sendPosition(this, authPacket.getYaw(), authPacket.getPitch(), MovePlayerPacket.MODE_RESET); + server.getLogger().debug(username + ": move " + distSqrt + " > 100"); + return; + } - if (inv == null) { - this.server.getLogger().debug(this.getName() + " has no open container with window ID " + mobEquipmentPacket.windowId); - return; + boolean revertMotion = false; + if (!this.isAlive() || !this.spawned) { + revertMotion = true; + this.forceMovement = this; + } + + if (this.forceMovement != null && (revertMotion || clientPosition.distanceSquared(this.forceMovement) > 0.1)) { + this.sendPosition(this.forceMovement, authPacket.getYaw(), authPacket.getPitch(), MovePlayerPacket.MODE_RESET); + } else { + float yaw = authPacket.getYaw() % 360; + float pitch = authPacket.getPitch() % 360; + if (yaw < 0) { + yaw += 360; } - Item item = inv.getItem(mobEquipmentPacket.hotbarSlot); + this.setRotation(yaw, pitch); + this.newPosition = clientPosition; + this.clientMovements.offer(clientPosition); + this.forceMovement = null; + } + return; + case ProtocolInfo.MOB_EQUIPMENT_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - if (!item.equals(mobEquipmentPacket.item)) { - this.server.getLogger().debug(this.getName() + " tried to equip " + mobEquipmentPacket.item + " but have " + item + " in target slot"); - inv.sendContents(this); - return; - } + MobEquipmentPacket mobEquipmentPacket = (MobEquipmentPacket) packet; - if (inv instanceof PlayerInventory) { - ((PlayerInventory) inv).equipItem(mobEquipmentPacket.hotbarSlot); - } + Inventory inv = this.getWindowById(mobEquipmentPacket.windowId); - this.setDataFlag(Player.DATA_FLAGS, Player.DATA_FLAG_ACTION, false); + if (inv == null) { + this.server.getLogger().debug("Player " + this.getName() + " has no open container with window ID " + mobEquipmentPacket.windowId); + return; + } - break; - case ProtocolInfo.PLAYER_ACTION_PACKET: - PlayerActionPacket playerActionPacket = (PlayerActionPacket) packet; - if (!this.spawned || !this.isAlive() && playerActionPacket.action != PlayerActionPacket.ACTION_RESPAWN) { - break; - } + /*Item item = inv.getItem(mobEquipmentPacket.hotbarSlot); - switch (playerActionPacket.action) { - case PlayerActionPacket.ACTION_STOP_SLEEPING: - this.stopSleep(); - break; - case PlayerActionPacket.ACTION_RESPAWN: - if (!this.spawned || this.isAlive() || !this.isOnline()) { - break; - } - this.respawn(); - break; - case PlayerActionPacket.ACTION_DIMENSION_CHANGE_ACK: - this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_RESET); - break; + if (!item.equals(mobEquipmentPacket.item)) { + if (Nukkit.DEBUG > 1) { + this.server.getLogger().debug("Tried to equip " + mobEquipmentPacket.item + " but have " + item + " in target slot"); } + inv.sendSlot(mobEquipmentPacket.hotbarSlot, this); + return; + }*/ - this.setUsingItem(false); - break; - case ProtocolInfo.MOB_ARMOR_EQUIPMENT_PACKET: - break; - case ProtocolInfo.MODAL_FORM_RESPONSE_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } + if (inv instanceof PlayerInventory) { + ((PlayerInventory) inv).equipItem(mobEquipmentPacket.hotbarSlot); + } - ModalFormResponsePacket modalFormPacket = (ModalFormResponsePacket) packet; + this.setUsingItem(false); + return; + case ProtocolInfo.PLAYER_ACTION_PACKET: + PlayerActionPacket playerActionPacket = (PlayerActionPacket) packet; + if (!this.spawned || (!this.isAlive() && playerActionPacket.action != PlayerActionPacket.ACTION_RESPAWN)) { + return; + } - if (formWindows.containsKey(modalFormPacket.formId)) { - FormWindow window = formWindows.remove(modalFormPacket.formId); - window.setResponse(modalFormPacket.data.trim()); + playerActionPacket.entityId = this.id; - for (FormResponseHandler handler : window.getHandlers()) { - handler.handle(this, modalFormPacket.formId); + stopItemHold: + switch (playerActionPacket.action) { + case PlayerActionPacket.ACTION_START_BREAK: + break stopItemHold; + case PlayerActionPacket.ACTION_ABORT_BREAK: + //case PlayerActionPacket.ACTION_STOP_BREAK: // This could be used instead of inventory transaction when the breaking is done? + return; + case PlayerActionPacket.ACTION_STOP_SLEEPING: + this.stopSleep(); + return; + case PlayerActionPacket.ACTION_RESPAWN: + if (!this.spawned || this.isAlive() || !this.isOnline()) { + return; + } + this.respawn(); + break stopItemHold; + case PlayerActionPacket.ACTION_JUMP: + return; + case PlayerActionPacket.ACTION_START_SPRINT: + break stopItemHold; + case PlayerActionPacket.ACTION_STOP_SPRINT: + return; + case PlayerActionPacket.ACTION_START_SNEAK: + return; + case PlayerActionPacket.ACTION_STOP_SNEAK: + return; + case PlayerActionPacket.ACTION_DIMENSION_CHANGE_ACK: + if (this.awaitingDimensionAck) { + this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_RESET); + this.dummyBossBars.values().forEach(DummyBossBar::reshow); + this.awaitingDimensionAck = false; + } else { + this.getServer().getLogger().debug(username + ": got a dimension change ack but no dimension change is in progress"); + } + return; + case PlayerActionPacket.ACTION_START_GLIDE: + return; + case PlayerActionPacket.ACTION_STOP_GLIDE: + return; + case PlayerActionPacket.ACTION_CONTINUE_BREAK: + // When player moves cursor to another block + return; + case PlayerActionPacket.ACTION_START_SWIMMING: + break stopItemHold; + case PlayerActionPacket.ACTION_STOP_SWIMMING: + return; + case PlayerActionPacket.ACTION_START_SPIN_ATTACK: + if (this.inventory == null) { + this.getServer().getLogger().debug(username + ": got ACTION_START_SPIN_ATTACK but inventory was null"); + break stopItemHold; } - PlayerFormRespondedEvent event = new PlayerFormRespondedEvent(this, modalFormPacket.formId, window); - getServer().getPluginManager().callEvent(event); - } else if (serverSettings.containsKey(modalFormPacket.formId)) { - FormWindow window = serverSettings.get(modalFormPacket.formId); - window.setResponse(modalFormPacket.data.trim()); + PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, true); - for (FormResponseHandler handler : window.getHandlers()) { - handler.handle(this, modalFormPacket.formId); + int riptideLevel = 0; + Item hand; + if ((hand = this.inventory.getItemInHandFast()).getId() != ItemID.TRIDENT) { + playerToggleSpinAttackEvent.setCancelled(true); + this.getServer().getLogger().debug(username + ": got ACTION_START_SPIN_ATTACK but hand item is not a trident"); + } else { + Enchantment riptide = hand.getEnchantment(Enchantment.ID_TRIDENT_RIPTIDE); + if (riptide == null) { + playerToggleSpinAttackEvent.setCancelled(true); + } else { + riptideLevel = riptide.getLevel(); + if (riptideLevel < 1) { + playerToggleSpinAttackEvent.setCancelled(true); + } else { + boolean inWater = false; + for (Block block : this.getCollisionBlocks()) { + if (block instanceof BlockWater) { + inWater = true; + break; + } + } + if (!(inWater || (this.getLevel().isRaining() && this.canSeeSky()))) { + playerToggleSpinAttackEvent.setCancelled(true); + } + } + } } - PlayerSettingsRespondedEvent event = new PlayerSettingsRespondedEvent(this, modalFormPacket.formId, window); - getServer().getPluginManager().callEvent(event); + this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); - //Set back new settings if not been cancelled - if (!event.isCancelled() && window instanceof FormWindowCustom) - ((FormWindowCustom) window).setElementsFromResponse(); - } + if (playerToggleSpinAttackEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSpinAttack(true); + this.resetFallDistance(); - break; + this.riptideTicks = 50 + (riptideLevel << 5); - case ProtocolInfo.INTERACT_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } + int riptideSound; + if (riptideLevel >= 3) { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_3; + } else if (riptideLevel == 2) { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_2; + } else { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_1; + } + this.level.addLevelSoundEvent(this, riptideSound); + } + break stopItemHold; + case PlayerActionPacket.ACTION_STOP_SPIN_ATTACK: + playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); + if (playerToggleSpinAttackEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSpinAttack(false); + } + return; + } - InteractPacket interactPacket = (InteractPacket) packet; + this.setUsingItem(false); + return; + case ProtocolInfo.MODAL_FORM_RESPONSE_PACKET: + this.formOpen = false; - if (interactPacket.target == 0 && interactPacket.action == InteractPacket.ACTION_MOUSEOVER) { - this.setButtonText(""); - break; - } + if (!this.spawned || !this.isAlive()) { + return; + } - Entity targetEntity = interactPacket.target == this.getId() ? this : this.level.getEntity(interactPacket.target); + ModalFormResponsePacket modalFormPacket = (ModalFormResponsePacket) packet; - if (targetEntity == null || !this.isAlive() || !targetEntity.isAlive()) { - break; - } + if (formWindows.containsKey(modalFormPacket.formId)) { + FormWindow window = formWindows.remove(modalFormPacket.formId); + window.setResponse(modalFormPacket.data.trim()); - if (targetEntity instanceof EntityItem || targetEntity instanceof EntityArrow || targetEntity instanceof EntityXPOrb) { - this.kick(PlayerKickEvent.Reason.INVALID_PVE, "Attempting to interact with an invalid entity"); - this.server.getLogger().warning(this.getServer().getLanguage().translateString("nukkit.player.invalidEntity", this.getName())); - break; + for (FormResponseHandler handler : window.getHandlers()) { + handler.handle(this, modalFormPacket.formId); } - switch (interactPacket.action) { - case InteractPacket.ACTION_MOUSEOVER: - String buttonText = ""; - if (targetEntity instanceof EntityInteractable) { - buttonText = ((EntityInteractable) targetEntity).getInteractButtonText(this); - if (buttonText == null) { - buttonText = ""; - } - } - this.setButtonText(buttonText); + PlayerFormRespondedEvent event = new PlayerFormRespondedEvent(this, modalFormPacket.formId, window); + getServer().getPluginManager().callEvent(event); + } else if (serverSettings.containsKey(modalFormPacket.formId)) { + FormWindow window = serverSettings.get(modalFormPacket.formId); + window.setResponse(modalFormPacket.data.trim()); - this.getServer().getPluginManager().callEvent(new PlayerMouseOverEntityEvent(this, targetEntity)); - break; - case InteractPacket.ACTION_VEHICLE_EXIT: - if (!(targetEntity instanceof EntityRideable) || this.riding != targetEntity) { - break; - } + for (FormResponseHandler handler : window.getHandlers()) { + handler.handle(this, modalFormPacket.formId); + } - ((EntityRideable) riding).dismountEntity(this); - break; - case InteractPacket.ACTION_OPEN_INVENTORY: - if (targetEntity != this) { - break; - } - if (!this.inventoryOpen && this.inventory.open(this)) { + PlayerSettingsRespondedEvent event = new PlayerSettingsRespondedEvent(this, modalFormPacket.formId, window); + getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled() && window instanceof FormWindowCustom) + ((FormWindowCustom) window).setElementsFromResponse(); + } + + return; + case ProtocolInfo.INTERACT_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } + + InteractPacket interactPacket = (InteractPacket) packet; + + if (interactPacket.target == 0 && interactPacket.action == InteractPacket.ACTION_MOUSEOVER) { + this.setButtonText(""); + return; + } + + Entity targetEntity = interactPacket.target == this.getId() ? this : this.level.getEntity(interactPacket.target); + + if (targetEntity == null || !this.isAlive() || !targetEntity.isAlive()) { + if (targetEntity != null || interactPacket.action != InteractPacket.ACTION_OPEN_INVENTORY) { + return; + } + } + + if (targetEntity instanceof EntityItem || targetEntity instanceof EntityArrow || targetEntity instanceof EntityXPOrb) { + this.kick(PlayerKickEvent.Reason.INVALID_PVE); + return; + } + + switch (interactPacket.action) { + case InteractPacket.ACTION_OPEN_INVENTORY: + if (!this.inventoryOpen) { + if (this.riding instanceof EntityChestBoat && this.riding == targetEntity) { + this.addWindow(((InventoryHolder) targetEntity).getInventory()); + } else if (this.inventory.open(this)) { this.inventoryOpen = true; + this.awardAchievement("openInventory"); } - break; - } - break; - case ProtocolInfo.BLOCK_PICK_REQUEST_PACKET: - BlockPickRequestPacket pickRequestPacket = (BlockPickRequestPacket) packet; - Block block = this.level.getBlock(pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z, false); - if (block.distanceSquared(this) > 1000) { - this.getServer().getLogger().debug(username + ": Block pick request for a block too far away"); + } return; - } - item = block.toItem(); - - if (pickRequestPacket.addUserData) { - BlockEntity blockEntity = this.getLevel().getBlockEntity(new Vector3(pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z)); - if (blockEntity != null) { - CompoundTag nbt = blockEntity.getCleanedNBT(); - if (nbt != null) { - item.setCustomBlockData(nbt); - item.setLore("+(DATA)"); + case InteractPacket.ACTION_MOUSEOVER: + String buttonText = ""; + if (targetEntity instanceof EntityInteractable) { + buttonText = ((EntityInteractable) targetEntity).getInteractButtonText(this); + if (buttonText == null) { + buttonText = ""; } } - } + this.setButtonText(buttonText); + this.getServer().getPluginManager().callEvent(new PlayerMouseOverEntityEvent(this, targetEntity)); + return; + case InteractPacket.ACTION_VEHICLE_EXIT: + if (!(targetEntity instanceof EntityRideable) || this.riding != targetEntity) { + return; + } - PlayerBlockPickEvent pickEvent = new PlayerBlockPickEvent(this, block, item); - if (this.isSpectator()) { - log.debug("Got block-pick request from " + this.getName() + " when in spectator mode"); - pickEvent.setCancelled(); + this.riderJumpTick = 0; + ((EntityRideable) riding).dismountEntity(this); + return; + } + return; + case ProtocolInfo.BLOCK_PICK_REQUEST_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } + + if (this.inventory == null) { + this.getServer().getLogger().debug(username + ": got block pick request but inventory was null"); + return; + } + + BlockPickRequestPacket pickRequestPacket = (BlockPickRequestPacket) packet; + Block block = this.level.getBlock(chunk, pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z, false); + if (block.distanceSquared(this) > 1000) { + this.getServer().getLogger().debug(username + ": block pick request for a block too far away"); + return; + } + Item item = block.toItem(); + if (pickRequestPacket.addUserData) { + BlockEntity blockEntity = this.getLevel().getBlockEntityIfLoaded(this.chunk, this.temporalVector.setComponents(pickRequestPacket.x, pickRequestPacket.y, pickRequestPacket.z)); + if (blockEntity != null) { + CompoundTag nbt = blockEntity.getCleanedNBT(); + if (nbt != null) { + item.setCustomBlockData(nbt); + item.setLore("+(DATA)"); + } } + } - this.server.getPluginManager().callEvent(pickEvent); + PlayerBlockPickEvent pickEvent = new PlayerBlockPickEvent(this, block, item); + if (this.isSpectator()) { + pickEvent.setCancelled(); + } - if (!pickEvent.isCancelled()) { - boolean itemExists = false; - int itemSlot = -1; - for (int slot = 0; slot < this.inventory.getSize(); slot++) { - if (this.inventory.getItem(slot).equals(pickEvent.getItem())) { - if (slot < this.inventory.getHotbarSize()) { - this.inventory.setHeldItemSlot(slot); - } else { - itemSlot = slot; - } - itemExists = true; - break; + this.server.getPluginManager().callEvent(pickEvent); + + if (!pickEvent.isCancelled()) { + boolean itemExists = false; + int itemSlot = -1; + for (int slot = 0; slot < this.inventory.getSize(); slot++) { + if (this.inventory.getItem(slot).equals(pickEvent.getItem())) { + if (slot < this.inventory.getHotbarSize()) { + this.inventory.setHeldItemSlot(slot); + } else { + itemSlot = slot; } + itemExists = true; + break; } + } - for (int slot = 0; slot < this.inventory.getHotbarSize(); slot++) { - if (this.inventory.getItem(slot).isNull()) { - if (!itemExists && this.isCreative()) { - this.inventory.setHeldItemSlot(slot); - this.inventory.setItemInHand(pickEvent.getItem()); - break packetswitch; - } else if (itemSlot > -1) { - this.inventory.setHeldItemSlot(slot); - this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); - this.inventory.clear(itemSlot, true); - break packetswitch; - } + for (int slot = 0; slot < this.inventory.getHotbarSize(); slot++) { + if (this.inventory.getItem(slot).isNull()) { + if (!itemExists && this.isCreative()) { + this.inventory.setHeldItemSlot(slot); + this.inventory.setItemInHand(pickEvent.getItem()); + return; + } else if (itemSlot > -1) { + this.inventory.setHeldItemSlot(slot); + this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); + this.inventory.clear(itemSlot, true); + return; } } + } - if (!itemExists && this.isCreative()) { - Item itemInHand = this.inventory.getItemInHand(); - this.inventory.setItemInHand(pickEvent.getItem()); - if (!this.inventory.isFull()) { - for (int slot = 0; slot < this.inventory.getSize(); slot++) { - if (this.inventory.getItem(slot).isNull()) { - this.inventory.setItem(slot, itemInHand); - break; - } + if (!itemExists && this.isCreative()) { + Item itemInHand = this.inventory.getItemInHand(); + this.inventory.setItemInHand(pickEvent.getItem()); + if (!this.inventory.isFull()) { + for (int slot = 0; slot < this.inventory.getSize(); slot++) { + if (this.inventory.getItem(slot).isNull()) { + this.inventory.setItem(slot, itemInHand); + return; } } - } else if (itemSlot > -1) { - Item itemInHand = this.inventory.getItemInHand(); - this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); - this.inventory.setItem(itemSlot, itemInHand); } + } else if (itemSlot > -1) { + Item itemInHand = this.inventory.getItemInHand(); + this.inventory.setItemInHand(this.inventory.getItem(itemSlot)); + this.inventory.setItem(itemSlot, itemInHand); } - break; - case ProtocolInfo.ANIMATE_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } + } + return; + case ProtocolInfo.ANIMATE_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - AnimatePacket animatePacket = (AnimatePacket) packet; - PlayerAnimationEvent animationEvent = new PlayerAnimationEvent(this, animatePacket.action); + AnimatePacket animatePacket = (AnimatePacket) packet; - // prevent client send illegal packet to server and broadcast to other client and make other client crash - if(animatePacket.action == null // illegal action id - || animatePacket.action == AnimatePacket.Action.WAKE_UP // these actions are only for server to client - || animatePacket.action == AnimatePacket.Action.CRITICAL_HIT - || animatePacket.action == AnimatePacket.Action.MAGIC_CRITICAL_HIT) { - break; // maybe we should cancel the event here? but if client send too many packets, server will lag - } + if (animatePacket.action == null // Illegal action ID + || animatePacket.action == AnimatePacket.Action.WAKE_UP // These actions are server to client only + || animatePacket.action == AnimatePacket.Action.CRITICAL_HIT + || animatePacket.action == AnimatePacket.Action.MAGIC_CRITICAL_HIT) { + return; + } - this.server.getPluginManager().callEvent(animationEvent); - if (animationEvent.isCancelled()) { - break; - } + PlayerAnimationEvent animationEvent = new PlayerAnimationEvent(this, animatePacket.action); + this.server.getPluginManager().callEvent(animationEvent); + if (animationEvent.isCancelled()) { + return; + } - AnimatePacket.Action animation = animationEvent.getAnimationType(); + AnimatePacket.Action animation = animationEvent.getAnimationType(); - switch (animation) { - case ROW_RIGHT: - case ROW_LEFT: - if (this.riding instanceof EntityBoat) { - ((EntityBoat) this.riding).onPaddle(animation, ((AnimatePacket) packet).rowingTime); - } - break; - } + switch (animation) { + case ROW_RIGHT: + case ROW_LEFT: + if (this.riding instanceof EntityBoat) { + ((EntityBoat) this.riding).onPaddle(animation, animatePacket.rowingTime); + } + break; + } - animatePacket.eid = this.getId(); - animatePacket.action = animationEvent.getAnimationType(); - Server.broadcastPacket(this.getViewers().values(), animatePacket); - break; - case ProtocolInfo.SET_HEALTH_PACKET: - //use UpdateAttributePacket instead - break; + animatePacket = new AnimatePacket(); + animatePacket.eid = this.getId(); + animatePacket.action = animationEvent.getAnimationType(); + Server.broadcastPacket(this.getViewers().values(), animatePacket); + return; + case ProtocolInfo.ENTITY_EVENT_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - case ProtocolInfo.ENTITY_EVENT_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } - EntityEventPacket entityEventPacket = (EntityEventPacket) packet; - if (entityEventPacket.event != EntityEventPacket.ENCHANT) - this.craftingType = CRAFTING_SMALL; - //this.resetCraftingGridType(); + EntityEventPacket entityEventPacket = (EntityEventPacket) packet; + if (entityEventPacket.event != EntityEventPacket.ENCHANT) { + this.craftingType = CRAFTING_SMALL; + } - if (entityEventPacket.event == EntityEventPacket.EATING_ITEM) { + switch (entityEventPacket.event) { + case EntityEventPacket.EATING_ITEM: if (entityEventPacket.data == 0 || entityEventPacket.eid != this.id) { - break; + this.getServer().getLogger().debug(username + ": entity event eid mismatch"); + return; } entityEventPacket.eid = this.id; entityEventPacket.isEncoded = false; - this.dataPacket(entityEventPacket); Server.broadcastPacket(this.getViewers().values(), entityEventPacket); - } else if (entityEventPacket.event == EntityEventPacket.ENCHANT) { + return; + case EntityEventPacket.ENCHANT: if (entityEventPacket.eid != this.id) { - break; + this.getServer().getLogger().debug(username + ": entity event eid mismatch"); + return; } Inventory inventory = this.getWindowById(ANVIL_WINDOW_ID); if (inventory instanceof AnvilInventory) { ((AnvilInventory) inventory).setCost(-entityEventPacket.data); } - } - break; - case ProtocolInfo.COMMAND_REQUEST_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } - this.craftingType = CRAFTING_SMALL; - CommandRequestPacket commandRequestPacket = (CommandRequestPacket) packet; - PlayerCommandPreprocessEvent playerCommandPreprocessEvent = new PlayerCommandPreprocessEvent(this, commandRequestPacket.command); - this.server.getPluginManager().callEvent(playerCommandPreprocessEvent); - if (playerCommandPreprocessEvent.isCancelled()) { - break; - } + return; + } + return; + case ProtocolInfo.COMMAND_REQUEST_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - Timings.playerCommandTimer.startTiming(); - this.server.dispatchCommand(playerCommandPreprocessEvent.getPlayer(), playerCommandPreprocessEvent.getMessage().substring(1)); - Timings.playerCommandTimer.stopTiming(); - break; - case ProtocolInfo.TEXT_PACKET: - if (!this.spawned || !this.isAlive()) { - break; - } + this.resetCraftingGridType(); + + CommandRequestPacket commandRequestPacket = (CommandRequestPacket) packet; + PlayerCommandPreprocessEvent playerCommandPreprocessEvent = new PlayerCommandPreprocessEvent(this, commandRequestPacket.command + ' '); + this.server.getPluginManager().callEvent(playerCommandPreprocessEvent); + if (playerCommandPreprocessEvent.isCancelled()) { + return; + } - TextPacket textPacket = (TextPacket) packet; + this.server.dispatchCommand(playerCommandPreprocessEvent.getPlayer(), playerCommandPreprocessEvent.getMessage().substring(1)); + return; + case ProtocolInfo.TEXT_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - if (textPacket.type == TextPacket.TYPE_CHAT) { - String chatMessage = textPacket.message; - int breakLine = chatMessage.indexOf('\n'); - // Chat messages shouldn't contain break lines so ignore text afterwards - if (breakLine != -1) { - chatMessage = chatMessage.substring(0, breakLine); - } - this.chat(chatMessage); - } - break; - case ProtocolInfo.CONTAINER_CLOSE_PACKET: - ContainerClosePacket containerClosePacket = (ContainerClosePacket) packet; - if (!this.spawned || containerClosePacket.windowId == ContainerIds.INVENTORY && !inventoryOpen) { - break; - } + TextPacket textPacket = (TextPacket) packet; - if (this.windowIndex.containsKey(containerClosePacket.windowId)) { - this.server.getPluginManager().callEvent(new InventoryCloseEvent(this.windowIndex.get(containerClosePacket.windowId), this)); - if (containerClosePacket.windowId == ContainerIds.INVENTORY) this.inventoryOpen = false; - this.closingWindowId = containerClosePacket.windowId; - this.removeWindow(this.windowIndex.get(containerClosePacket.windowId), true); - this.closingWindowId = Integer.MIN_VALUE; - } - if (containerClosePacket.windowId == -1) { - this.craftingType = CRAFTING_SMALL; - this.resetCraftingGridType(); - this.addWindow(this.craftingGrid, ContainerIds.NONE); - ContainerClosePacket pk = new ContainerClosePacket(); - pk.wasServerInitiated = false; - pk.windowId = -1; - this.dataPacket(pk); - } - break; - case ProtocolInfo.CRAFTING_EVENT_PACKET: - break; - case ProtocolInfo.BLOCK_ENTITY_DATA_PACKET: - if (!this.spawned || !this.isAlive()) { - break; + if (textPacket.type == TextPacket.TYPE_CHAT && textPacket.message.length() < 512) { + String chatMessage = textPacket.message; + int breakLine = chatMessage.indexOf('\n'); + // Chat messages shouldn't contain break lines so ignore text afterwards + if (breakLine != -1) { + chatMessage = chatMessage.substring(0, breakLine); } + this.chat(chatMessage); + } + return; + case ProtocolInfo.CONTAINER_CLOSE_PACKET: + ContainerClosePacket containerClosePacket = (ContainerClosePacket) packet; + if (!this.spawned || (containerClosePacket.windowId == ContainerIds.INVENTORY && !inventoryOpen)) { + return; + } - BlockEntityDataPacket blockEntityDataPacket = (BlockEntityDataPacket) packet; - this.craftingType = CRAFTING_SMALL; + if (this.windowIndex.containsKey(containerClosePacket.windowId)) { + this.server.getPluginManager().callEvent(new InventoryCloseEvent(this.windowIndex.get(containerClosePacket.windowId), this)); + if (containerClosePacket.windowId == ContainerIds.INVENTORY) this.inventoryOpen = false; + this.closingWindowId = containerClosePacket.windowId; + this.removeWindow(this.windowIndex.get(containerClosePacket.windowId), true); + this.closingWindowId = Integer.MIN_VALUE; + } + + if (containerClosePacket.windowId == -1) { this.resetCraftingGridType(); + this.addWindow(this.craftingGrid, ContainerIds.NONE); + ContainerClosePacket pk = new ContainerClosePacket(); + pk.windowId = -1; + pk.wasServerInitiated = false; + this.dataPacket(pk); + } + return; + case ProtocolInfo.BLOCK_ENTITY_DATA_PACKET: + if (!this.spawned || !this.isAlive()) { + return; + } - Vector3 pos = new Vector3(blockEntityDataPacket.x, blockEntityDataPacket.y, blockEntityDataPacket.z); - if (pos.distanceSquared(this) > 10000) { - break; + BlockEntityDataPacket blockEntityDataPacket = (BlockEntityDataPacket) packet; + this.resetCraftingGridType(); + + Vector3 pos = this.temporalVector.setComponents(blockEntityDataPacket.x, blockEntityDataPacket.y, blockEntityDataPacket.z); + if (pos.distanceSquared(this) > 10000) { + if (Nukkit.DEBUG > 1) { + server.getLogger().debug(username + ": BlockEntityDataPacket target too far " + pos); } + return; + } - BlockEntity t = this.level.getBlockEntity(pos); - if (t instanceof BlockEntitySpawnable) { - CompoundTag nbt; - try { - nbt = NBTIO.read(blockEntityDataPacket.namedTag, ByteOrder.LITTLE_ENDIAN, true); - } catch (IOException e) { - throw new RuntimeException(e); - } + BlockEntity t = this.level.getBlockEntity(pos); + if (t instanceof BlockEntitySpawnable) { + CompoundTag nbt; + try { + nbt = NBTIO.read(blockEntityDataPacket.namedTag, ByteOrder.LITTLE_ENDIAN, true); + } catch (IOException e) { + throw new RuntimeException(e); + } - if (!((BlockEntitySpawnable) t).updateCompoundTag(nbt, this)) { - ((BlockEntitySpawnable) t).spawnTo(this); - } + if (!((BlockEntitySpawnable) t).updateCompoundTag(nbt, this)) { + ((BlockEntitySpawnable) t).spawnTo(this); } - break; - case ProtocolInfo.REQUEST_CHUNK_RADIUS_PACKET: - RequestChunkRadiusPacket requestChunkRadiusPacket = (RequestChunkRadiusPacket) packet; - ChunkRadiusUpdatedPacket chunkRadiusUpdatePacket = new ChunkRadiusUpdatedPacket(); - this.chunkRadius = Math.max(3, Math.min(requestChunkRadiusPacket.radius, this.viewDistance)); - chunkRadiusUpdatePacket.radius = this.chunkRadius; - this.dataPacket(chunkRadiusUpdatePacket); - break; - case ProtocolInfo.SET_PLAYER_GAME_TYPE_PACKET: - SetPlayerGameTypePacket setPlayerGameTypePacket = (SetPlayerGameTypePacket) packet; - if (setPlayerGameTypePacket.gamemode != this.gamemode) { - if (!this.hasPermission("nukkit.command.gamemode")) { - SetPlayerGameTypePacket setPlayerGameTypePacket1 = new SetPlayerGameTypePacket(); - setPlayerGameTypePacket1.gamemode = this.gamemode & 0x01; - this.dataPacket(setPlayerGameTypePacket1); - this.getAdventureSettings().update(); - break; + } + return; + case ProtocolInfo.REQUEST_CHUNK_RADIUS_PACKET: + RequestChunkRadiusPacket requestChunkRadiusPacket = (RequestChunkRadiusPacket) packet; + ChunkRadiusUpdatedPacket chunkRadiusUpdatePacket = new ChunkRadiusUpdatedPacket(); + this.chunkRadius = Math.max(3, Math.min(requestChunkRadiusPacket.radius, this.viewDistance)); + chunkRadiusUpdatePacket.radius = this.chunkRadius; + this.dataPacket(chunkRadiusUpdatePacket); + return; + case ProtocolInfo.SET_PLAYER_GAME_TYPE_PACKET: + if (!this.spawned) { + return; + } + + SetPlayerGameTypePacket setPlayerGameTypePacket = (SetPlayerGameTypePacket) packet; + if (setPlayerGameTypePacket.gamemode != this.gamemode) { + if (!this.hasPermission("nukkit.command.gamemode")) { + if (!this.isOp()) { + this.kick(PlayerKickEvent.Reason.INVALID_PACKET, "Invalid SetPlayerGameTypePacket", true); } - this.setGamemode(setPlayerGameTypePacket.gamemode, true); - Command.broadcastCommandMessage(this, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(this.gamemode))); + return; } - break; - case ProtocolInfo.ITEM_FRAME_DROP_ITEM_PACKET: - ItemFrameDropItemPacket itemFrameDropItemPacket = (ItemFrameDropItemPacket) packet; - Vector3 vector3 = this.temporalVector.setComponents(itemFrameDropItemPacket.x, itemFrameDropItemPacket.y, itemFrameDropItemPacket.z); - if (vector3.distanceSquared(this) < 1000) { - BlockEntity itemFrame = this.level.getBlockEntity(vector3); - if (itemFrame instanceof BlockEntityItemFrame) { - ((BlockEntityItemFrame) itemFrame).dropItem(this); - } + this.setGamemode(setPlayerGameTypePacket.gamemode, true); + Command.broadcastCommandMessage(this, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(this.gamemode))); + } + return; + case ProtocolInfo.ITEM_FRAME_DROP_ITEM_PACKET: + if (!this.spawned) { + return; + } + + ItemFrameDropItemPacket itemFrameDropItemPacket = (ItemFrameDropItemPacket) packet; + Vector3 frame = this.temporalVector.setComponents(itemFrameDropItemPacket.x, itemFrameDropItemPacket.y, itemFrameDropItemPacket.z); + if (frame.distanceSquared(this) < 1000) { + BlockEntity itemFrame = this.level.getBlockEntityIfLoaded(this.chunk, frame); + if (itemFrame instanceof BlockEntityItemFrame) { + ((BlockEntityItemFrame) itemFrame).dropItem(this); } - break; - case ProtocolInfo.MAP_INFO_REQUEST_PACKET: - MapInfoRequestPacket pk = (MapInfoRequestPacket) packet; - Item mapItem = null; + } + return; + case ProtocolInfo.MAP_INFO_REQUEST_PACKET: + if (this.inventory == null) { + this.getServer().getLogger().debug(username + ": got map info request but inventory was null"); + return; + } + + MapInfoRequestPacket pk = (MapInfoRequestPacket) packet; + + Item mapItem = null; + + for (Item item1 : this.offhandInventory.getContents().values()) { + if (item1 instanceof ItemMap && ((ItemMap) item1).getMapId() == pk.mapId) { + mapItem = item1; + } + } - for (Item item1 : this.offhandInventory.getContents().values()) { + if (mapItem == null) { + for (Item item1 : this.inventory.getContents().values()) { if (item1 instanceof ItemMap && ((ItemMap) item1).getMapId() == pk.mapId) { mapItem = item1; } } + } + + if (mapItem == null) { + for (BlockEntity be : this.level.getBlockEntities().values()) { + if (be instanceof BlockEntityItemFrame) { + BlockEntityItemFrame itemFrame1 = (BlockEntityItemFrame) be; - if (mapItem == null) { - for (Item item1 : this.inventory.getContents().values()) { - if (item1 instanceof ItemMap && ((ItemMap) item1).getMapId() == pk.mapId) { - mapItem = item1; + if (itemFrame1.getItem() instanceof ItemMap && ((ItemMap) itemFrame1.getItem()).getMapId() == pk.mapId) { + ((ItemMap) itemFrame1.getItem()).sendImage(this); + return; } } } + } else { + PlayerMapInfoRequestEvent event = new PlayerMapInfoRequestEvent(this, mapItem); + getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + ItemMap map = (ItemMap) mapItem; + if (map.trySendImage(this)) { + return; + } + try { + BufferedImage image = new BufferedImage(128, 128, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics = image.createGraphics(); + + int worldX = (Math.floorDiv(this.getFloorX(), 128)) << 7; + int worldZ = (Math.floorDiv(this.getFloorZ(), 128)) << 7; + for (int x = 0; x < 128; x++) { + int avgY = 0; + for (int y = -1; y < 128; y++) { + if (this.getLevel().getDimension() == Level.DIMENSION_NETHER) { + if (y == -1) { + continue; + } + graphics.setColor(colorizeMapColor(new SplittableRandom((((long) (worldZ + y) & 0x3ffffff) << 26) + ((long) (worldX + x) & 0x3ffffff)).nextBoolean() ? BlockColor.STONE_BLOCK_COLOR : BlockColor.DIRT_BLOCK_COLOR, 1)); + } else { + if (y == -1) { // Hack: Make sure we have average world height for the first row + avgY = this.getLevel().getHighestBlockAt(worldX + x, worldZ, false); + continue; + } - if (mapItem == null) { - for (BlockEntity be : this.level.getBlockEntities().values()) { - if (be instanceof BlockEntityItemFrame) { - BlockEntityItemFrame itemFrame1 = (BlockEntityItemFrame) be; + int worldY = this.getLevel().getHighestBlockAt(worldX + x, worldZ + y, false); + double avgYDifference = (worldY - avgY) * 4 / 5 + ((x + y & 1) - 0.5) * 0.4; // 4d / 5d would provide more detail but is that better? + int colorDepth = 1; + if (avgYDifference > 0.6) { + colorDepth = 2; + } + if (avgYDifference < -0.6) { + colorDepth = 0; + } + avgY = worldY; + graphics.setColor(colorizeMapColor(this.getLevel().getMapColorAt(worldX + x, worldY, worldZ + y), colorDepth)); + } - if (itemFrame1.getItem() instanceof ItemMap && ((ItemMap) itemFrame1.getItem()).getMapId() == pk.mapId) { - ((ItemMap) itemFrame1.getItem()).sendImage(this); - break; + graphics.fillRect(x, y, x + 1, y + 1); } } + + map.setImage(image); + map.sendImage(this); + } catch (Exception ex) { + this.getServer().getLogger().debug(username + ": there was an error while generating map image", ex); } } + } + + return; + case ProtocolInfo.INVENTORY_TRANSACTION_PACKET: + if (this.isSpectator()) { + this.needSendInventory = true; + return; + } - if (mapItem != null) { - PlayerMapInfoRequestEvent event; - getServer().getPluginManager().callEvent(event = new PlayerMapInfoRequestEvent(this, mapItem)); + InventoryTransactionPacket transactionPacket = (InventoryTransactionPacket) packet; - if (!event.isCancelled()) { - ((ItemMap) mapItem).sendImage(this); - } - } + if ((transactionPacket.transactionType == InventoryTransactionPacket.TYPE_MISMATCH || + (transactionPacket.transactionType == InventoryTransactionPacket.TYPE_NORMAL && this.isCreative() && Arrays.stream(transactionPacket.actions).anyMatch(action -> action.sourceType == NetworkInventoryAction.SOURCE_TODO))) + && (inv = getWindowById(SMITHING_WINDOW_ID)) instanceof SmithingInventory) { - break; - case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V1: - case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2: - case ProtocolInfo.LEVEL_SOUND_EVENT_PACKET: - if (!this.isSpectator()) { - this.level.addChunkPacket(this.getChunkX(), this.getChunkZ(), packet); - } - break; - case ProtocolInfo.INVENTORY_TRANSACTION_PACKET: - if (this.isSpectator()) { - this.sendAllInventories(); - break; - } + SmithingInventory smithingInventory = (SmithingInventory) inv; + if (!smithingInventory.getResult().isNull()) { + InventoryTransactionPacket fixedPacket = new InventoryTransactionPacket(); + fixedPacket.isRepairItemPart = true; + fixedPacket.actions = new NetworkInventoryAction[6]; + + Item fromIngredient = smithingInventory.getIngredient().clone(); + Item toIngredient = fromIngredient.decrement(1); + + Item fromEquipment = smithingInventory.getEquipment().clone(); + Item toEquipment = fromEquipment.decrement(1); + + Item fromResult = Item.get(Item.AIR); + Item toResult = smithingInventory.getResult().clone(); + + NetworkInventoryAction action = new NetworkInventoryAction(); + action.windowId = ContainerIds.UI; + action.inventorySlot = SmithingInventory.SMITHING_INGREDIENT_UI_SLOT; + action.oldItem = fromIngredient.clone(); + action.newItem = toIngredient.clone(); + fixedPacket.actions[0] = action; + + action = new NetworkInventoryAction(); + action.windowId = ContainerIds.UI; + action.inventorySlot = SmithingInventory.SMITHING_EQUIPMENT_UI_SLOT; + action.oldItem = fromEquipment.clone(); + action.newItem = toEquipment.clone(); + fixedPacket.actions[1] = action; - InventoryTransactionPacket transactionPacket = (InventoryTransactionPacket) packet; + if (this.getLoginChainData().getUIProfile() == 0) { + // We can't know whether shift click was used so we must make sure we won't overwrite item in cursor inventory + Item[] drops = this.inventory.addItem(this.playerUIInventory.getItemFast(0)); // Cloned in addItem + this.playerUIInventory.getCursorInventory().clear(0); - List actions = new ArrayList<>(); - for (NetworkInventoryAction networkInventoryAction : transactionPacket.actions) { - InventoryAction a = networkInventoryAction.createInventoryAction(this); + for (Item drop : drops) { + this.level.dropItem(this, drop); + } - if (a == null) { - this.getServer().getLogger().debug("Unmatched inventory action from " + this.getName() + ": " + networkInventoryAction); - this.sendAllInventories(); - break packetswitch; + action = new NetworkInventoryAction(); + action.windowId = ContainerIds.UI; + action.inventorySlot = 0; // cursor + action.oldItem = Item.get(Item.AIR); + action.newItem = toResult.clone(); + fixedPacket.actions[2] = action; + } else { + int emptyPlayerSlot = -1; + for (int slot = 0; slot < inventory.getSize(); slot++) { + if (inventory.getItemFast(slot).isNull()) { + emptyPlayerSlot = slot; + break; + } + } + if (emptyPlayerSlot == -1) { + this.needSendInventory = true; + return; + } else { + action = new NetworkInventoryAction(); + action.windowId = ContainerIds.INVENTORY; + action.inventorySlot = emptyPlayerSlot; + action.oldItem = Item.get(Item.AIR); + action.newItem = toResult.clone(); + fixedPacket.actions[2] = action; + } } - actions.add(a); + action = new NetworkInventoryAction(); + action.sourceType = NetworkInventoryAction.SOURCE_TODO; + action.windowId = NetworkInventoryAction.SOURCE_TYPE_ANVIL_RESULT; + action.inventorySlot = 2; // result + action.oldItem = toResult.clone(); + action.newItem = fromResult.clone(); + fixedPacket.actions[3] = action; + + action = new NetworkInventoryAction(); + action.sourceType = NetworkInventoryAction.SOURCE_TODO; + action.windowId = NetworkInventoryAction.SOURCE_TYPE_ANVIL_INPUT; + action.inventorySlot = 0; // equipment + action.oldItem = toEquipment.clone(); + action.newItem = fromEquipment.clone(); + fixedPacket.actions[4] = action; + + action = new NetworkInventoryAction(); + action.sourceType = NetworkInventoryAction.SOURCE_TODO; + action.windowId = NetworkInventoryAction.SOURCE_TYPE_ANVIL_MATERIAL; + action.inventorySlot = 1; // material + action.oldItem = toIngredient.clone(); + action.newItem = fromIngredient.clone(); + fixedPacket.actions[5] = action; + + transactionPacket = fixedPacket; + } + } + + List actions = new ArrayList<>(); + for (NetworkInventoryAction networkInventoryAction : transactionPacket.actions) { + InventoryAction a = networkInventoryAction.createInventoryAction(this); + + if (a == null) { + this.getServer().getLogger().debug("Unmatched inventory action from " + this.username + ": " + networkInventoryAction); + this.needSendInventory = true; + return; } - if (transactionPacket.isCraftingPart) { - if (this.craftingTransaction == null) { - this.craftingTransaction = new CraftingTransaction(this, actions); + actions.add(a); + } + + if (transactionPacket.isCraftingPart) { + if (LoomTransaction.checkForItemPart(actions)) { + if (this.loomTransaction == null) { + this.loomTransaction = new LoomTransaction(this, actions); } else { for (InventoryAction action : actions) { - this.craftingTransaction.addAction(action); + this.loomTransaction.addAction(action); + } + } + if (this.loomTransaction.canExecute()) { + if (this.loomTransaction.execute()) { + level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_LOOM_USE); } } + this.loomTransaction = null; + return; + } - if (this.craftingTransaction.getPrimaryOutput() != null && this.craftingTransaction.canExecute()) { - //we get the actions for this in several packets, so we can't execute it until we get the result + if (this.craftingTransaction == null) { + this.craftingTransaction = new CraftingTransaction(this, actions); + } else { + for (InventoryAction action : actions) { + this.craftingTransaction.addAction(action); + } + } + if (this.craftingTransaction.getPrimaryOutput() != null && this.craftingTransaction.canExecute()) { + try { this.craftingTransaction.execute(); - this.craftingTransaction = null; + } catch (Exception e) { + this.server.getLogger().debug(username + ": executing crafting transaction failed"); } - - return; - } else if (transactionPacket.isEnchantingPart) { - if (this.enchantTransaction == null) { - this.enchantTransaction = new EnchantTransaction(this, actions); + this.craftingTransaction = null; + } + return; + } else if (transactionPacket.isEnchantingPart) { + if (this.enchantTransaction == null) { + this.enchantTransaction = new EnchantTransaction(this, actions); + } else { + for (InventoryAction action : actions) { + this.enchantTransaction.addAction(action); + } + } + if (this.enchantTransaction.canExecute()) { + this.enchantTransaction.execute(); + this.enchantTransaction = null; + } + return; + } else if (transactionPacket.isRepairItemPart) { + if (SmithingTransaction.checkForItemPart(actions)) { + if (this.smithingTransaction == null) { + this.smithingTransaction = new SmithingTransaction(this, actions); } else { for (InventoryAction action : actions) { - this.enchantTransaction.addAction(action); + this.smithingTransaction.addAction(action); } } - if (this.enchantTransaction.canExecute()) { - this.enchantTransaction.execute(); - this.enchantTransaction = null; + if (this.smithingTransaction.canExecute()) { + if (this.smithingTransaction.execute()) { + Collection players = level.getChunkPlayers(getChunkX(), getChunkZ()).values(); + players.remove(this); + if (!players.isEmpty()) { + level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_SMITHING_TABLE_USE); + } + } + this.smithingTransaction = null; } - return; - } else if (transactionPacket.isRepairItemPart) { + } else { if (this.repairItemTransaction == null) { this.repairItemTransaction = new RepairItemTransaction(this, actions); } else { @@ -3133,493 +4104,740 @@ public void onCompletion(Server server) { this.repairItemTransaction.execute(); this.repairItemTransaction = null; } - return; - } else if (this.craftingTransaction != null) { - if (craftingTransaction.checkForCraftingPart(actions)) { - for (InventoryAction action : actions) { - craftingTransaction.addAction(action); - } + } + return; + } else if (this.craftingTransaction != null) { + if (craftingTransaction.checkForCraftingPart(actions)) { + if (craftingType == CRAFTING_LOOM) { + craftingTransaction = null; return; - } else { - this.server.getLogger().debug("Got unexpected normal inventory action with incomplete crafting transaction from " + this.getName() + ", refusing to execute crafting"); - this.removeAllWindows(false); - this.sendAllInventories(); - this.craftingTransaction = null; } - } else if (this.enchantTransaction != null) { - if (enchantTransaction.checkForEnchantPart(actions)) { - for (InventoryAction action : actions) { - enchantTransaction.addAction(action); - } - return; - } else { - this.server.getLogger().debug("Got unexpected normal inventory action with incomplete enchanting transaction from " + this.getName() + ", refusing to execute enchant " + transactionPacket.toString()); - this.removeAllWindows(false); - this.sendAllInventories(); - this.enchantTransaction = null; + for (InventoryAction action : actions) { + craftingTransaction.addAction(action); } - } else if (this.repairItemTransaction != null) { - if (RepairItemTransaction.checkForRepairItemPart(actions)) { - for (InventoryAction action : actions) { - this.repairItemTransaction.addAction(action); - } - return; - } else { - this.server.getLogger().debug("Got unexpected normal inventory action with incomplete repair item transaction from " + this.getName() + ", refusing to execute repair item " + transactionPacket.toString()); - this.removeAllWindows(false); - this.sendAllInventories(); - this.repairItemTransaction = null; + return; + } else { + this.server.getLogger().debug("Got unexpected normal inventory action with incomplete crafting transaction from " + this.username + ", refusing to execute crafting"); + this.removeAllWindows(false); + this.needSendInventory = true; + this.craftingTransaction = null; + } + } else if (this.enchantTransaction != null) { + if (enchantTransaction.checkForEnchantPart(actions)) { + for (InventoryAction action : actions) { + enchantTransaction.addAction(action); + } + return; + } else { + this.server.getLogger().debug("Got unexpected normal inventory action with incomplete enchanting transaction from " + this.username + ", refusing to execute enchant " + transactionPacket.toString()); + this.removeAllWindows(false); + this.enchantTransaction = null; + this.needSendInventory = true; + } + } else if (this.repairItemTransaction != null) { + if (RepairItemTransaction.checkForRepairItemPart(actions)) { + for (InventoryAction action : actions) { + this.repairItemTransaction.addAction(action); } + return; + } else { + this.server.getLogger().debug("Got unexpected normal inventory action with incomplete repair item transaction from " + this.username + ", refusing to execute repair item " + transactionPacket.toString()); + this.removeAllWindows(false); + this.repairItemTransaction = null; + this.needSendInventory = true; } + } else if (this.smithingTransaction != null) { + if (SmithingTransaction.checkForItemPart(actions)) { + for (InventoryAction action : actions) { + this.smithingTransaction.addAction(action); + } + return; + } else { + this.server.getLogger().debug("Got unexpected normal inventory action with incomplete repair item transaction from " + this.username + ", refusing to execute smithing " + transactionPacket.toString()); + this.removeAllWindows(false); + this.smithingTransaction = null; + this.needSendInventory = true; + } + } - switch (transactionPacket.transactionType) { - case InventoryTransactionPacket.TYPE_NORMAL: - InventoryTransaction transaction = new InventoryTransaction(this, actions); + switch (transactionPacket.transactionType) { + case InventoryTransactionPacket.TYPE_NORMAL: + InventoryTransaction transaction = new InventoryTransaction(this, actions); - if (!transaction.execute()) { - this.server.getLogger().debug("Failed to execute inventory transaction from " + this.getName() + " with actions: " + Arrays.toString(transactionPacket.actions)); - break packetswitch; //oops! - } + if (!transaction.execute()) { + this.server.getLogger().debug("Failed to execute inventory transaction from " + this.username + " with actions: " + Arrays.toString(transactionPacket.actions)); + return; + } - //TODO: fix achievement for getting iron from furnace + return; + case InventoryTransactionPacket.TYPE_MISMATCH: + if (transactionPacket.actions.length > 0) { + this.server.getLogger().debug("Expected 0 actions for mismatch, got " + transactionPacket.actions.length + ", " + Arrays.toString(transactionPacket.actions)); + } + this.needSendInventory = true; + return; + case InventoryTransactionPacket.TYPE_USE_ITEM: + UseItemData useItemData = (UseItemData) transactionPacket.transactionData; + BlockVector3 blockVector = useItemData.blockPos; + BlockFace face = useItemData.face; + int type = useItemData.actionType; - break packetswitch; - case InventoryTransactionPacket.TYPE_MISMATCH: - if (transactionPacket.actions.length > 0) { - this.server.getLogger().debug("Expected 0 actions for mismatch, got " + transactionPacket.actions.length + ", " + Arrays.toString(transactionPacket.actions)); - } - this.sendAllInventories(); + this.setShieldBlockingDelay(5); - break packetswitch; - case InventoryTransactionPacket.TYPE_USE_ITEM: - UseItemData useItemData = (UseItemData) transactionPacket.transactionData; + boolean itemSent = false; // Fix inventory desync but only send the slot once - BlockVector3 blockVector = useItemData.blockPos; - face = useItemData.face; + if (inventory.getHeldItemIndex() != useItemData.hotbarSlot) { + inventory.equipItem(useItemData.hotbarSlot); - int type = useItemData.actionType; - switch (type) { - case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_BLOCK: - // Remove if client bug is ever fixed - boolean spamBug = (lastRightClickPos != null && System.currentTimeMillis() - lastRightClickTime < 100.0 && blockVector.distanceSquared(lastRightClickPos) < 0.00001); - lastRightClickPos = blockVector.asVector3(); - lastRightClickTime = System.currentTimeMillis(); - if (spamBug) { - return; - } + itemSent = true; // Assume that the item is still correct even if the selected slot is not + } - this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); + switch (type) { + case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_BLOCK: + // Hack: Fix client spamming right clicks + if ((lastRightClickPos != null && this.getInventory().getItemInHandFast().getBlockId() == BlockID.AIR && System.currentTimeMillis() - lastRightClickTime < 200.0 && blockVector.distanceSquared(lastRightClickPos) < 0.00001)) { + return; + } - if (this.canInteract(blockVector.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7)) { - if (this.isCreative()) { - Item i = inventory.getItemInHand(); - if (this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this) != null) { - break packetswitch; - } - } else if (inventory.getItemInHand().equals(useItemData.itemInHand)) { - Item i = inventory.getItemInHand(); - Item oldItem = i.clone(); - //TODO: Implement adventure mode checks - if ((i = this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this)) != null) { - if (!i.equals(oldItem) || i.getCount() != oldItem.getCount()) { - if (oldItem.getId() == i.getId() || i.getId() == 0) { - inventory.setItemInHand(i); - } else { - server.getLogger().debug("Tried to set item " + i.getId() + " but " + this.username + " had item " + oldItem.getId() + " in their hand slot"); - } - inventory.sendHeldItem(this.getViewers().values()); + lastRightClickPos = blockVector.asVector3(); + lastRightClickTime = System.currentTimeMillis(); + + this.breakingBlock = null; + + this.setUsingItem(false); + + // We don't seem to verify useItemData.clickPos so don't use it for anything important + if (this.canInteract(blockVector.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7)) { + Item i = inventory.getItemInHand(); + if (this.isCreative()) { + if (this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this) != null) { + return; + } + } else { + Item oldItem = i.clone(); // This must be cloned + + if ((i = this.level.useItemOn(blockVector.asVector3(), i, face, useItemData.clickPos.x, useItemData.clickPos.y, useItemData.clickPos.z, this)) != null) { + if (i.getCount() != oldItem.getCount() || i.getDamage() != oldItem.getDamage() || !i.equals(oldItem)) { // Quick checks first + if (oldItem.getId() == i.getId() || i.getId() == 0) { + inventory.setItemInHand(i); + + itemSent = true; + } else if (Nukkit.DEBUG > 1) { + server.getLogger().debug("Tried to set item " + i.getId() + " but " + this.username + " had item " + oldItem.getId() + " in their hand slot"); } - break packetswitch; } + + if (!itemSent && !oldItem.equals(useItemData.itemInHand)) { + this.needSendHeldItem = true; + } + return; } } + } - inventory.sendHeldItem(this); + this.needSendHeldItem = true; - if (blockVector.distanceSquared(this) > 10000) { - break packetswitch; - } + if (blockVector.distanceSquared(this) > 10000) { + return; + } - Block target = this.level.getBlock(blockVector.asVector3()); - block = target.getSide(face); + Block target = this.level.getBlock(blockVector.asVector3()); + block = target.getSide(face); - this.level.sendBlocks(new Player[]{this}, new Block[]{target, block}, UpdateBlockPacket.FLAG_ALL_PRIORITY); - break packetswitch; - case InventoryTransactionPacket.USE_ITEM_ACTION_BREAK_BLOCK: - if (!this.spawned || !this.isAlive()) { - break packetswitch; - } + this.level.sendBlocks(this, new Block[]{target, block}, UpdateBlockPacket.FLAG_ALL_PRIORITY); - System.out.println("USE_ITEM_ACTION_BREAK_BLOCK"); + if (target instanceof BlockDoor) { + BlockDoor door = (BlockDoor) target; - this.resetCraftingGridType(); + Block part; - Item i = this.getInventory().getItemInHand(); + if ((door.getDamage() & 0x08) > 0) { + part = target.down(); - Item oldItem = i.clone(); + if (part.getId() == target.getId()) { + target = part; - if (this.canInteract(blockVector.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7) && (i = this.level.useBreakOn(blockVector.asVector3(), face, i, this, true)) != null) { - if (this.isSurvival()) { - this.getFoodData().updateFoodExpLevel(0.005); - if (!i.equals(oldItem) || i.getCount() != oldItem.getCount()) { - if (oldItem.getId() == i.getId() || i.getId() == 0) { - inventory.setItemInHand(i); - } else { - server.getLogger().debug("Tried to set item " + i.getId() + " but " + this.username + " had item " + oldItem.getId() + " in their hand slot"); - } - inventory.sendHeldItem(this.getViewers().values()); - } + this.level.sendBlocks(this, new Block[]{target}, UpdateBlockPacket.FLAG_ALL_PRIORITY); } - break packetswitch; } + } + return; + case InventoryTransactionPacket.USE_ITEM_ACTION_BREAK_BLOCK: + return; + case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_AIR: + if (!this.spawned || !this.isAlive()) { + return; + } - inventory.sendContents(this); - inventory.sendHeldItem(this); - - if (blockVector.distanceSquared(this) < 10000) { - target = this.level.getBlock(blockVector.asVector3()); - this.level.sendBlocks(new Player[]{this}, new Block[]{target}, UpdateBlockPacket.FLAG_ALL_PRIORITY); + if (inventory.getHeldItemIndex() != useItemData.hotbarSlot) { + this.inventory.equipItem(useItemData.hotbarSlot); - BlockEntity blockEntity = this.level.getBlockEntity(blockVector.asVector3()); - if (blockEntity instanceof BlockEntitySpawnable) { - ((BlockEntitySpawnable) blockEntity).spawnTo(this); - } - } + this.crossbowLoadTick = 0; + } - break packetswitch; - case InventoryTransactionPacket.USE_ITEM_ACTION_CLICK_AIR: - Vector3 directionVector = this.getDirectionVector(); + item = this.inventory.getItemInHand(); - if (this.isCreative()) { - item = this.inventory.getItemInHand(); - } else if (!this.inventory.getItemInHand().equals(useItemData.itemInHand)) { - this.inventory.sendHeldItem(this); - break packetswitch; - } else { - item = this.inventory.getItemInHand(); - } + this.breakingBlock = null; - PlayerInteractEvent interactEvent = new PlayerInteractEvent(this, item, directionVector, face, Action.RIGHT_CLICK_AIR); + Vector3 directionVector = this.getDirectionVector(); + PlayerInteractEvent interactEvent = new PlayerInteractEvent(this, item, directionVector, face, Action.RIGHT_CLICK_AIR); + this.server.getPluginManager().callEvent(interactEvent); - this.server.getPluginManager().callEvent(interactEvent); + if (interactEvent.isCancelled()) { + this.needSendHeldItem = true; + return; + } - if (interactEvent.isCancelled()) { - this.inventory.sendHeldItem(this); - break packetswitch; + if (item instanceof ItemCrossbow) { + ItemCrossbow crossbow = ((ItemCrossbow) item); + if (crossbow.isLoaded()) { + if (this.crossbowLoadTick + 5 < this.server.getTick()) { + crossbow.launchArrow(this); + } + } else { + if (this.isUsingItem()) { + // Used item + int ticksUsed = this.server.getTick() - this.startAction; + this.crossbowLoadTick = this.server.getTick(); + this.setUsingItem(false); + item.onUse(this, ticksUsed); // Load crossbow + } else { + this.setUsingItem(true); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_CROSSBOW_LOADING_START); + } } + return; + } - if (item.onClickAir(this, directionVector)) { - if (!this.isCreative()) { - if (item.getId() == 0 || this.inventory.getItemInHand().getId() == item.getId()) { - this.inventory.setItemInHand(item); - } else { - server.getLogger().debug("Tried to set item " + item.getId() + " but " + this.username + " had item " + this.inventory.getItemInHand().getId() + " in their hand slot"); + int oldCount = item.getCount(); + int oldDamage = item.getDamage(); + if (item.onClickAir(this, directionVector)) { + if (this.isSurvival() || this.isAdventure()) { + // Don't set the item if not changed + // Update this to use equals() if NBT is ever modified in onClickAir + if (item.getId() == 0 || ((item.getCount() != oldCount || item.getDamage() != oldDamage) && this.inventory.getItemInHandFast().getId() == item.getId())) { + if (item instanceof ItemFishingRod && item.getDamage() >= item.getMaxDurability()) { + this.level.addSound(this, Sound.RANDOM_BREAK); + this.level.addParticle(new ItemBreakParticle(this, item)); + item = Item.get(Item.AIR); } - } - if (!this.isUsingItem()) { - this.setUsingItem(true); - break packetswitch; + this.inventory.setItemInHand(item); } + } + if (this.isUsingItem()) { // Used item int ticksUsed = this.server.getTick() - this.startAction; this.setUsingItem(false); - if (!item.onUse(this, ticksUsed)) { - this.inventory.sendContents(this); + this.needSendHeldItem = true; } + } else { + this.setUsingItem(true); } + } - break packetswitch; - default: - //unknown - break; - } - break; - case InventoryTransactionPacket.TYPE_USE_ITEM_ON_ENTITY: - UseItemOnEntityData useItemOnEntityData = (UseItemOnEntityData) transactionPacket.transactionData; - - Entity target = this.level.getEntity(useItemOnEntityData.entityRuntimeId); - if (target == null) { return; - } + } + return; + case InventoryTransactionPacket.TYPE_USE_ITEM_ON_ENTITY: + UseItemOnEntityData useItemOnEntityData = (UseItemOnEntityData) transactionPacket.transactionData; - type = useItemOnEntityData.actionType; + Entity target = this.level.getEntity(useItemOnEntityData.entityRuntimeId); + if (target == null) { + return; + } - if (!useItemOnEntityData.itemInHand.equalsExact(this.inventory.getItemInHand())) { - this.inventory.sendHeldItem(this); - } + type = useItemOnEntityData.actionType; - item = this.inventory.getItemInHand(); + if (inventory.getHeldItemIndex() != useItemOnEntityData.hotbarSlot) { + inventory.equipItem(useItemOnEntityData.hotbarSlot); + } - switch (type) { - case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_INTERACT: - PlayerInteractEntityEvent playerInteractEntityEvent = new PlayerInteractEntityEvent(this, target, item, useItemOnEntityData.clickPos); - if (this.isSpectator()) playerInteractEntityEvent.setCancelled(); - getServer().getPluginManager().callEvent(playerInteractEntityEvent); + item = this.inventory.getItemInHand(); - if (playerInteractEntityEvent.isCancelled()) { - break; - } - if (target.onInteract(this, item, useItemOnEntityData.clickPos) && this.isSurvival()) { - if (item.isTool()) { - if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { - item = new ItemBlock(Block.get(BlockID.AIR)); - } - } else { - if (item.count > 1) { - item.count--; - } else { - item = new ItemBlock(Block.get(BlockID.AIR)); - } - } + switch (type) { + case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_INTERACT: + if (this.distanceSquared(target) > 1000) { + this.getServer().getLogger().debug(username + ": target entity is too far away"); + return; + } - if (item.getId() == 0 || this.inventory.getItemInHand().getId() == item.getId()) { - this.inventory.setItemInHand(item); - } else { - server.getLogger().debug("Tried to set item " + item.getId() + " but " + this.username + " had item " + this.inventory.getItemInHand().getId() + " in their hand slot"); + this.breakingBlock = null; + + this.setUsingItem(false); + + PlayerInteractEntityEvent playerInteractEntityEvent = new PlayerInteractEntityEvent(this, target, item, useItemOnEntityData.clickPos); + if (this.isSpectator()) playerInteractEntityEvent.setCancelled(); + getServer().getPluginManager().callEvent(playerInteractEntityEvent); + + if (playerInteractEntityEvent.isCancelled()) { + return; + } + + if (target.onInteract(this, item, useItemOnEntityData.clickPos) && (this.isSurvival() || this.isAdventure())) { + if (item.isTool()) { + if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { + level.addSound(this, Sound.RANDOM_BREAK); + level.addParticle(new ItemBreakParticle(this, item)); + item = Item.get(Item.AIR); } - } - break; - case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_ATTACK: - if (!this.canInteract(target, isCreative() ? 8 : 5)) { - break; - } else if (target instanceof Player) { - if ((((Player) target).getGamemode() & 0x01) > 0) { - break; - } else if (!this.server.getPropertyBoolean("pvp")) { - break; + } else { + if (item.count > 1) { + item.count--; + } else { + item = Item.get(Item.AIR); } } - Enchantment[] enchantments = item.getEnchantments(); + if (item.getId() == 0 || this.inventory.getItemInHandFast().getId() == item.getId()) { + this.inventory.setItemInHand(item); + } else if (Nukkit.DEBUG > 1) { + server.getLogger().debug("Tried to set item " + item.getId() + " but " + this.username + " had item " + this.inventory.getItemInHandFast().getId() + " in their hand slot"); + } + } + return; + case InventoryTransactionPacket.USE_ITEM_ON_ENTITY_ACTION_ATTACK: + if (target.getId() == this.getId()) { + this.kick(PlayerKickEvent.Reason.INVALID_PVP, "Tried to attack invalid player"); + return; + } - float itemDamage = item.getAttackDamage(); - for (Enchantment enchantment : enchantments) { - itemDamage += enchantment.getDamageBonus(target); + if (!this.canInteractEntity(target, isCreative() ? 64 : 25)) { // 8 : 5 + return; + } else if (target instanceof Player) { + if ((((Player) target).gamemode & 0x01) > 0) { + return; + } else if (!this.server.pvpEnabled) { + return; } + } - Map damage = new EnumMap<>(DamageModifier.class); - damage.put(DamageModifier.BASE, itemDamage); + this.breakingBlock = null; - float knockBack = 0.3f; - Enchantment knockBackEnchantment = item.getEnchantment(Enchantment.ID_KNOCKBACK); - if (knockBackEnchantment != null) { - knockBack += knockBackEnchantment.getLevel() * 0.1f; - } + this.setUsingItem(false); + + this.setShieldBlockingDelay(5); + + if (server.attackStopSprint) { + this.setSprinting(false); + } + + Enchantment[] enchantments = item.getEnchantments(); - EntityDamageByEntityEvent entityDamageByEntityEvent = new EntityDamageByEntityEvent(this, target, DamageCause.ENTITY_ATTACK, damage, knockBack, enchantments); - if (this.isSpectator()) entityDamageByEntityEvent.setCancelled(); - if ((target instanceof Player) && !this.level.getGameRules().getBoolean(GameRule.PVP)) { - entityDamageByEntityEvent.setCancelled(); + float itemDamage = item.getAttackDamage(); + for (Enchantment enchantment : enchantments) { + itemDamage += enchantment.getDamageBonus(target); + } + + Map damage = new EnumMap<>(DamageModifier.class); + damage.put(DamageModifier.BASE, itemDamage); + + float knockBack = 0.3f; + Enchantment knockBackEnchantment = item.getEnchantment(Enchantment.ID_KNOCKBACK); + if (knockBackEnchantment != null) { + knockBack += knockBackEnchantment.getLevel() * 0.1f; + } + + EntityDamageByEntityEvent entityDamageByEntityEvent = new EntityDamageByEntityEvent(this, target, DamageCause.ENTITY_ATTACK, damage, knockBack, enchantments); + entityDamageByEntityEvent.setWeapon(item); + + if (this.isSpectator()) { + entityDamageByEntityEvent.setCancelled(); + } + if ((target instanceof Player) && !this.level.getGameRules().getBoolean(GameRule.PVP)) { + entityDamageByEntityEvent.setCancelled(); + } + + if (!target.attack(entityDamageByEntityEvent)) { + if (item.isTool() && !this.isCreative()) { + this.needSendHeldItem = true; } + return; + } - if (!target.attack(entityDamageByEntityEvent)) { - if (item.isTool() && this.isSurvival()) { - this.inventory.sendContents(this); + for (Enchantment enchantment : item.getEnchantments()) { + enchantment.doPostAttack(this, target); + } + + if (item.isTool() && !this.isCreative()) { + if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { + level.addSound(this, Sound.RANDOM_BREAK); + level.addParticle(new ItemBreakParticle(this, item)); + this.inventory.clear(this.inventory.getHeldItemIndex(), true); + } else { + if (item.getId() == 0 || this.inventory.getItemInHandFast().getId() == item.getId()) { + this.inventory.setItemInHand(item); + } else if (Nukkit.DEBUG > 1) { + server.getLogger().debug("Tried to set item " + item.getId() + " but " + this.username + " had item " + this.inventory.getItemInHandFast().getId() + " in their hand slot"); } - break; } + } + return; + } - for (Enchantment enchantment : item.getEnchantments()) { - enchantment.doPostAttack(this, target); - } + return; + case InventoryTransactionPacket.TYPE_RELEASE_ITEM: + if (this.isSpectator()) { + this.needSendInventory = true; + return; + } + ReleaseItemData releaseItemData = (ReleaseItemData) transactionPacket.transactionData; - if (item.isTool() && (this.isSurvival() || this.isAdventure())) { - if (item.useOn(target) && item.getDamage() >= item.getMaxDurability()) { - this.inventory.setItemInHand(Item.get(0)); - } else { - if (item.getId() == 0 || this.inventory.getItemInHand().getId() == item.getId()) { - this.inventory.setItemInHand(item); - } else { - server.getLogger().debug("Tried to set item " + item.getId() + " but " + this.username + " had item " + this.inventory.getItemInHand().getId() + " in their hand slot"); - } + try { + type = releaseItemData.actionType; + switch (type) { + case InventoryTransactionPacket.RELEASE_ITEM_ACTION_RELEASE: + if (this.isUsingItem()) { + int ticksUsed = this.server.getTick() - this.startAction; + if (!this.inventory.getItemInHand().onRelease(this, ticksUsed)) { + this.needSendHeldItem = true; } + this.setUsingItem(false); + } else { + this.needSendHeldItem = true; } return; + case InventoryTransactionPacket.RELEASE_ITEM_ACTION_CONSUME: + return; default: - break; //unknown + this.getServer().getLogger().debug(username + ": unknown release item action type: " + type); } + } finally { + this.setUsingItem(false); + } + return; + default: + this.needSendHeldItem = true; + } + return; + case ProtocolInfo.PLAYER_HOTBAR_PACKET: + if (this.inventory == null) { + this.getServer().getLogger().debug(username + ": got PlayerHotbarPacket but inventory was null"); + return; + } - break; - case InventoryTransactionPacket.TYPE_RELEASE_ITEM: - if (this.isSpectator()) { - this.sendAllInventories(); - break packetswitch; - } - ReleaseItemData releaseItemData = (ReleaseItemData) transactionPacket.transactionData; + PlayerHotbarPacket hotbarPacket = (PlayerHotbarPacket) packet; - try { - type = releaseItemData.actionType; - switch (type) { - case InventoryTransactionPacket.RELEASE_ITEM_ACTION_RELEASE: - if (this.isUsingItem()) { - item = this.inventory.getItemInHand(); + if (hotbarPacket.windowId != ContainerIds.INVENTORY) { + return; + } - int ticksUsed = this.server.getTick() - this.startAction; - if (!item.onRelease(this, ticksUsed)) { - this.inventory.sendContents(this); - } + this.inventory.equipItem(hotbarPacket.selectedHotbarSlot); + this.setUsingItem(false); + return; + case ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET: + PlayerServerSettingsRequestEvent settingsRequestEvent = new PlayerServerSettingsRequestEvent(this, new HashMap<>(this.serverSettings)); + this.getServer().getPluginManager().callEvent(settingsRequestEvent); + + if (!settingsRequestEvent.isCancelled()) { + settingsRequestEvent.getSettings().forEach((id, window) -> { + ServerSettingsResponsePacket re = new ServerSettingsResponsePacket(); + re.formId = id; + re.data = window.getJSONData(); + this.dataPacket(re); + }); + } + return; + case ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET: + if (this.locallyInitialized) { + return; + } + this.doFirstSpawn(); + return; + case ProtocolInfo.RESPAWN_PACKET: + if (this.isAlive()) { + return; + } - this.setUsingItem(false); - } else { - this.inventory.sendContents(this); - } - return; - case InventoryTransactionPacket.RELEASE_ITEM_ACTION_CONSUME: - log.debug("Unexpected release item action consume from {}", this::getName); - return; - default: - break; - } - } finally { - this.setUsingItem(false); - } - break; - default: - this.inventory.sendContents(this); - break; - } - break; - case ProtocolInfo.PLAYER_HOTBAR_PACKET: - PlayerHotbarPacket hotbarPacket = (PlayerHotbarPacket) packet; + RespawnPacket respawnPacket = (RespawnPacket) packet; + if (respawnPacket.respawnState == RespawnPacket.STATE_CLIENT_READY_TO_SPAWN) { + RespawnPacket respawn1 = new RespawnPacket(); + respawn1.x = (float) this.getX(); + respawn1.y = (float) this.getY(); + respawn1.z = (float) this.getZ(); + respawn1.respawnState = RespawnPacket.STATE_READY_TO_SPAWN; + this.dataPacket(respawn1); + } + return; + case ProtocolInfo.BOOK_EDIT_PACKET: + if (!this.spawned) { + return; + } - if (hotbarPacket.windowId != ContainerIds.INVENTORY) { - return; //In PE this should never happen - } + if (this.inventory == null) { + this.getServer().getLogger().debug(username + ": got BookEditPacket but inventory was null"); + return; + } - this.inventory.equipItem(hotbarPacket.selectedHotbarSlot); - break; - case ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET: - PlayerServerSettingsRequestEvent settingsRequestEvent = new PlayerServerSettingsRequestEvent(this, new HashMap<>(this.serverSettings)); - this.getServer().getPluginManager().callEvent(settingsRequestEvent); - - if (!settingsRequestEvent.isCancelled()) { - settingsRequestEvent.getSettings().forEach((id, window) -> { - ServerSettingsResponsePacket re = new ServerSettingsResponsePacket(); - re.formId = id; - re.data = window.getJSONData(); - this.dataPacket(re); - }); - } - break; - case ProtocolInfo.RESPAWN_PACKET: - if (this.isAlive()) { + BookEditPacket bookEditPacket = (BookEditPacket) packet; + Item oldBook = this.inventory.getItem(bookEditPacket.inventorySlot); + if (oldBook.getId() != Item.BOOK_AND_QUILL) { + this.getServer().getLogger().debug(username + ": BookEditPacket for invalid item: expected Book & Quill (386), got " + oldBook.getId()); + return; + } + + if (bookEditPacket.text != null && bookEditPacket.text.length() > 256) { + this.getServer().getLogger().debug(username + ": BookEditPacket with too long text"); + return; + } + + Item newBook = oldBook.clone(); + boolean success; + switch (bookEditPacket.action) { + case REPLACE_PAGE: + success = ((ItemBookAndQuill) newBook).setPageText(bookEditPacket.pageNumber, bookEditPacket.text); + break; + case ADD_PAGE: + success = ((ItemBookAndQuill) newBook).insertPage(bookEditPacket.pageNumber, bookEditPacket.text); + break; + case DELETE_PAGE: + success = ((ItemBookAndQuill) newBook).deletePage(bookEditPacket.pageNumber); + break; + case SWAP_PAGES: + success = ((ItemBookAndQuill) newBook).swapPages(bookEditPacket.pageNumber, bookEditPacket.secondaryPageNumber); break; + case SIGN_BOOK: + newBook = Item.get(Item.WRITTEN_BOOK, 0, 1, oldBook.getCompoundTag()); + if (bookEditPacket.title == null || bookEditPacket.author == null || bookEditPacket.xuid == null || bookEditPacket.title.length() > 64 || bookEditPacket.author.length() > 64 || bookEditPacket.xuid.length() > 64) { + this.getServer().getLogger().debug(username + ": invalid BookEditPacket action SIGN_BOOK: title/author/xuid is too long"); + return; + } + success = ((ItemBookWritten) newBook).signBook(bookEditPacket.title, bookEditPacket.author, bookEditPacket.xuid, ItemBookWritten.GENERATION_ORIGINAL); + break; + default: + this.getServer().getLogger().debug(username + ": BookEditPacket unknown action: " + bookEditPacket.action); + return; + } + + if (success) { + PlayerEditBookEvent editBookEvent = new PlayerEditBookEvent(this, oldBook, newBook, bookEditPacket.action); + this.server.getPluginManager().callEvent(editBookEvent); + if (!editBookEvent.isCancelled()) { + this.inventory.setItem(bookEditPacket.inventorySlot, editBookEvent.getNewBook()); + } + } + return; + case ProtocolInfo.FILTER_TEXT_PACKET: + if (!this.spawned) { + return; + } + + FilterTextPacket filterTextPacket = (FilterTextPacket) packet; + if (filterTextPacket.text == null || filterTextPacket.text.length() > 64) { + this.getServer().getLogger().debug(username + ": FilterTextPacket with too long text"); + return; + } + FilterTextPacket textResponsePacket = new FilterTextPacket(); + textResponsePacket.text = filterTextPacket.text; + textResponsePacket.fromServer = true; + this.dataPacket(textResponsePacket); + return; + case ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET: + PacketViolationWarningPacket PVWpk = (PacketViolationWarningPacket) packet; + if (pkIDs == null) { + pkIDs = Arrays.stream(ProtocolInfo.class.getDeclaredFields()).filter(field -> field.getType() == Byte.TYPE); + } + Optional PVWpkName = pkIDs + .filter(field -> { + try { + return field.getByte(null) == ((PacketViolationWarningPacket) packet).packetId; + } catch (IllegalAccessException e) { + return false; + } + }).map(Field::getName).findFirst(); + this.getServer().getLogger().warning("PacketViolationWarningPacket" + PVWpkName.map(name -> " for " + name).orElse(" UNKNOWN") + " from " + this.username + ": " + PVWpk.toString()); + return; + case ProtocolInfo.EMOTE_PACKET: + if (!this.spawned || server.getTick() - this.lastEmote < 20 || this.isSpectator()) { + return; + } + this.lastEmote = server.getTick(); + EmotePacket emotePacket = (EmotePacket) packet; + if (emotePacket.runtimeId != this.id) { + this.getServer().getLogger().debug(username + ": EmotePacket eid mismatch"); + return; + } else if (emotePacket.emoteID == null || emotePacket.emoteID.isEmpty() || emotePacket.emoteID.length() > 100) { + this.getServer().getLogger().debug(username + " EmotePacket invalid emote id: " + emotePacket.emoteID); + return; + } + for (Player player : this.getViewers().values()) { + player.dataPacket(emotePacket); + } + return; + case ProtocolInfo.LECTERN_UPDATE_PACKET: + if (!this.spawned) { + return; + } + + LecternUpdatePacket lecternUpdatePacket = (LecternUpdatePacket) packet; + Vector3 lecternPos = lecternUpdatePacket.blockPosition.asVector3(); + if (lecternPos.distanceSquared(this) > 4096) { + return; + } + if (lecternUpdatePacket.dropBook) { + Block blockLectern = this.getLevel().getBlock(chunk, lecternPos.getFloorX(), lecternPos.getFloorY(), lecternPos.getFloorZ(), false); + if (blockLectern instanceof BlockLectern) { + PlayerInteractEvent interactEvent = new PlayerInteractEvent(this, this.getInventory().getItemInHand(), blockLectern, BlockFace.UP, Action.RIGHT_CLICK_BLOCK); + server.getPluginManager().callEvent(interactEvent); + if (!interactEvent.isCancelled()) { + ((BlockLectern) blockLectern).dropBook(); + } } - RespawnPacket respawnPacket = (RespawnPacket) packet; - if (respawnPacket.respawnState == RespawnPacket.STATE_CLIENT_READY_TO_SPAWN) { - RespawnPacket respawn1 = new RespawnPacket(); - respawn1.x = (float) this.getX(); - respawn1.y = (float) this.getY(); - respawn1.z = (float) this.getZ(); - respawn1.respawnState = RespawnPacket.STATE_READY_TO_SPAWN; - this.dataPacket(respawn1); + } else { + BlockEntity blockEntityLectern = this.level.getBlockEntityIfLoaded(this.chunk, lecternPos); + if (blockEntityLectern instanceof BlockEntityLectern) { + BlockEntityLectern lectern = (BlockEntityLectern) blockEntityLectern; + if (lectern.getRawPage() != lecternUpdatePacket.page) { + lectern.setRawPage(lecternUpdatePacket.page); + lectern.spawnToAll(); + } } - break; - case ProtocolInfo.BOOK_EDIT_PACKET: - BookEditPacket bookEditPacket = (BookEditPacket) packet; - Item oldBook = this.inventory.getItem(bookEditPacket.inventorySlot); - if (oldBook.getId() != Item.BOOK_AND_QUILL) { - return; + } + return; + case ProtocolInfo.SET_DIFFICULTY_PACKET: + if (!this.spawned) { + return; + } + + if (!this.hasPermission("nukkit.command.difficulty")) { + if (!this.isOp()) { + this.kick(PlayerKickEvent.Reason.INVALID_PACKET, "Invalid SetDifficultyPacket", true); } + return; + } + server.setDifficulty(((SetDifficultyPacket) packet).difficulty); + Command.broadcastCommandMessage(this, new TranslationContainer("commands.difficulty.success", String.valueOf(server.getDifficulty()))); + + SetDifficultyPacket difficultyPacket = new SetDifficultyPacket(); + difficultyPacket.difficulty = server.getDifficulty(); + Server.broadcastPacket(server.getOnlinePlayers().values(), difficultyPacket); + return; + case ProtocolInfo.REQUEST_PERMISSIONS_PACKET: + if (!this.spawned) { + return; + } + + if (!this.isOp()) { + this.kick(PlayerKickEvent.Reason.INVALID_PACKET, "Invalid RequestPermissionsPacket", true); + return; + } + this.sendMessage(TextFormat.RED + "Unimplemented feature: REQUEST_PERMISSIONS_PACKET"); // TODO + return; + case ProtocolInfo.SET_DEFAULT_GAME_TYPE_PACKET: + if (!this.spawned) { + return; + } - if (bookEditPacket.text != null && bookEditPacket.text.length() > 256) { - this.getServer().getLogger().debug(username + ": BookEditPacket with too long text"); - return; + if (!this.hasPermission("nukkit.command.defaultgamemode")) { + if (!this.isOp()) { + this.kick(PlayerKickEvent.Reason.INVALID_PACKET, "Invalid SetDefaultGameTypePacket", true); } + return; + } + int gamemode = ((SetDefaultGameTypePacket) packet).gamemode & 0b11; + server.gamemode = gamemode; + server.setPropertyInt("gamemode", gamemode); + Command.broadcastCommandMessage(this, new TranslationContainer("commands.defaultgamemode.success", new String[]{Server.getGamemodeString(server.getDefaultGamemode())})); + + SetDefaultGameTypePacket gameTypePacket = new SetDefaultGameTypePacket(); + gameTypePacket.gamemode = server.getDefaultGamemode(); + Server.broadcastPacket(server.getOnlinePlayers().values(), gameTypePacket); + return; + case ProtocolInfo.SETTINGS_COMMAND_PACKET: + if (!this.spawned) { + return; + } - Item newBook = oldBook.clone(); - boolean success; - switch (bookEditPacket.action) { - case REPLACE_PAGE: - success = ((ItemBookAndQuill) newBook).setPageText(bookEditPacket.pageNumber, bookEditPacket.text); - break; - case ADD_PAGE: - success = ((ItemBookAndQuill) newBook).insertPage(bookEditPacket.pageNumber, bookEditPacket.text); - break; - case DELETE_PAGE: - success = ((ItemBookAndQuill) newBook).deletePage(bookEditPacket.pageNumber); - break; - case SWAP_PAGES: - success = ((ItemBookAndQuill) newBook).swapPages(bookEditPacket.pageNumber, bookEditPacket.secondaryPageNumber); - break; - case SIGN_BOOK: - if (bookEditPacket.title == null || bookEditPacket.author == null || bookEditPacket.xuid == null || bookEditPacket.title.length() > 64 || bookEditPacket.author.length() > 64 || bookEditPacket.xuid.length() > 64) { - this.getServer().getLogger().debug(username + ": Invalid BookEditPacket action SIGN_BOOK: title/author/xuid is too long"); - return; - } - newBook = Item.get(Item.WRITTEN_BOOK, 0, 1, oldBook.getCompoundTag()); - success = ((ItemBookWritten) newBook).signBook(bookEditPacket.title, bookEditPacket.author, bookEditPacket.xuid, ItemBookWritten.GENERATION_ORIGINAL); - break; - default: - return; + if (!this.hasPermission("nukkit.command.gamerule")) { + if (!this.isOp()) { + this.kick(PlayerKickEvent.Reason.INVALID_PACKET, "Invalid SettingsCommandPacket", true); } + return; + } + String command = ((SettingsCommandPacket) packet).command; + if (command.startsWith("/gamerule")) { + server.dispatchCommand(this, command.substring(1)); + } else { + this.getServer().getLogger().debug(username + ": SettingsCommandPacket unsupported command: " + command); + } + return; + } + } - if (success) { - PlayerEditBookEvent editBookEvent = new PlayerEditBookEvent(this, oldBook, newBook, bookEditPacket.action); - this.server.getPluginManager().callEvent(editBookEvent); - if (!editBookEvent.isCancelled()) { - this.inventory.setItem(bookEditPacket.inventorySlot, editBookEvent.getNewBook()); - } - } - break; - case ProtocolInfo.FILTER_TEXT_PACKET: - FilterTextPacket filterTextPacket = (FilterTextPacket) packet; - if (filterTextPacket.text == null || filterTextPacket.text.length() > 64) { - this.getServer().getLogger().debug(username + ": FilterTextPacket with too long text"); - return; - } - FilterTextPacket textResponsePacket = new FilterTextPacket(); - textResponsePacket.text = filterTextPacket.text; - textResponsePacket.fromServer = true; - this.dataPacket(textResponsePacket); - break; - case ProtocolInfo.SET_DIFFICULTY_PACKET: - if (!this.spawned || !this.hasPermission("nukkit.command.difficulty")) { - return; - } - server.setDifficulty(((SetDifficultyPacket) packet).difficulty); - SetDifficultyPacket difficultyPacket = new SetDifficultyPacket(); - difficultyPacket.difficulty = server.getDifficulty(); - Server.broadcastPacket(server.getOnlinePlayers().values(), difficultyPacket); - Command.broadcastCommandMessage(this, new TranslationContainer("commands.difficulty.success", String.valueOf(server.getDifficulty()))); - break; - default: - break; - } + private void setShieldBlockingDelay(int delay) { + if (this.isBlocking()) { + this.setBlocking(false); + this.blockingDelay = delay; + } + } + + @Override + protected void onBlock(Entity damager, EntityDamageBlockedEvent event, EntityDamageEvent source) { + super.onBlock(damager, event, source); + + if (source.getWeapon() != null && source.getWeapon().isAxe()) { + this.setShieldBlockingDelay(100); + this.startItemCooldown(100, "shield"); } } - private void onBlockBreakContinue(Vector3 pos, BlockFace face) { + public void startItemCooldown(int cooldownDuration, String itemCategory) { + PlayerStartItemCooldownPacket pk = new PlayerStartItemCooldownPacket(); + pk.itemCategory = itemCategory; + pk.cooldownDuration = cooldownDuration; + this.dataPacket(pk); + } + + private void onBlockBreakAbort(BlockVector3 blockPos, BlockFace face) { if (this.isBreakingBlock()) { - Block block = this.level.getBlock(pos, false); - this.level.addParticle(new PunchBlockParticle(pos, block, face)); + LevelEventPacket pk = new LevelEventPacket(); + pk.evid = LevelEventPacket.EVENT_BLOCK_STOP_BREAK; + pk.x = (float) breakingBlock.x; + pk.y = (float) breakingBlock.y; + pk.z = (float) breakingBlock.z; + pk.data = 0; + this.getLevel().addChunkPacket((int) breakingBlock.x >> 4, (int) breakingBlock.z >> 4, pk); } + this.breakingBlock = null; } - private void onBlockBreakStart(Vector3 pos, BlockFace face) { - BlockVector3 blockPos = pos.asBlockVector3(); + private void onBlockBreakStart(BlockVector3 blockPos, BlockFace face) { + if (this.isSpectator()) { + return; + } + + boolean posEquals = lastBreakPosition.equals(blockPos); + this.lastBreakPosition = blockPos; long currentBreak = System.currentTimeMillis(); // HACK: Client spams multiple left clicks so we need to skip them. - if ((this.lastBreakPosition.equals(blockPos) && (currentBreak - this.lastBreak) < 10) || pos.distanceSquared(this) > 100) { + if (posEquals && (currentBreak - this.lastBreak) < 10) { + return; + } else if (blockPos.distanceSquared(this) > 100) { + this.breakingBlock = null; return; } - Block target = this.level.getBlock(pos); - PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(this, this.inventory.getItemInHand(), target, face, - target.getId() == 0 ? Action.LEFT_CLICK_AIR : Action.LEFT_CLICK_BLOCK); + // Reset current block break + this.breakingBlock = null; + + this.setUsingItem(false); + + Block target = this.level.getBlock(chunk, blockPos.x, blockPos.y, blockPos.z, false); + PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(this, this.inventory.getItemInHand(), target, face, target.getId() == 0 ? Action.LEFT_CLICK_AIR : Action.LEFT_CLICK_BLOCK); this.getServer().getPluginManager().callEvent(playerInteractEvent); if (playerInteractEvent.isCancelled()) { - this.inventory.sendHeldItem(this); + this.needSendHeldItem = true; return; } switch (target.getId()) { + case Block.AIR: + return; case Block.NOTEBLOCK: ((BlockNoteblock) target).emitSound(); - return; + break; // note blocks can be broken case Block.DRAGON_EGG: if (!this.isCreative()) { ((BlockDragonEgg) target).teleport(); @@ -3627,90 +4845,72 @@ private void onBlockBreakStart(Vector3 pos, BlockFace face) { } break; case Block.ITEM_FRAME_BLOCK: - BlockEntity itemFrame = this.level.getBlockEntity(pos); + BlockEntity itemFrame = this.level.getBlockEntityIfLoaded(this.chunk, this.temporalVector.setComponents(blockPos.x, blockPos.y, blockPos.z)); if (itemFrame instanceof BlockEntityItemFrame && ((BlockEntityItemFrame) itemFrame).dropItem(this)) { return; } break; + case Block.LECTERN: // 1.20.70+ + if (target instanceof BlockLectern) { + ((BlockLectern) target).dropBook(); + } + break; } - Block block = target.getSide(face); - if (block.getId() == Block.FIRE) { + int bid = this.level.getBlockIdAt(blockPos.x + face.getXOffset(), blockPos.y + face.getYOffset(), blockPos.z + face.getZOffset()); + if (bid == Block.FIRE || bid == Block.SOUL_FIRE) { + Vector3 block = this.temporalVector.setComponents(blockPos.x + face.getXOffset(), blockPos.y + face.getYOffset(), blockPos.z + face.getZOffset()); this.level.setBlock(block, Block.get(BlockID.AIR), true); this.level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_EXTINGUISH_FIRE); return; } if (!this.isCreative()) { - double breakTime = Math.ceil(target.getBreakTime(this.inventory.getItemInHand(), this) * 20); - if (breakTime > 0) { + double breakTime = target.getBreakTime(this.inventory.getItemInHandFast(), this); + int breakTimeTicks = (int) (breakTime * 20 + 0.5); + if (breakTimeTicks > 0) { LevelEventPacket pk = new LevelEventPacket(); pk.evid = LevelEventPacket.EVENT_BLOCK_START_BREAK; - pk.x = (float) pos.x; - pk.y = (float) pos.y; - pk.z = (float) pos.z; - pk.data = (int) (65535 / breakTime); - this.getLevel().addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); + pk.x = (float) blockPos.x; + pk.y = (float) blockPos.y; + pk.z = (float) blockPos.z; + pk.data = 65535 / breakTimeTicks; + this.getLevel().addChunkPacket(blockPos.x >> 4, blockPos.z >> 4, pk); } } this.breakingBlock = target; + this.breakingBlockFace = face; this.lastBreak = currentBreak; - this.lastBreakPosition = blockPos; - } - - private void onBlockBreakAbort(Vector3 pos, BlockFace face) { - if (pos.distanceSquared(this) < 100) { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = LevelEventPacket.EVENT_BLOCK_STOP_BREAK; - pk.x = (float) pos.x; - pk.y = (float) pos.y; - pk.z = (float) pos.z; - pk.data = 0; - this.getLevel().addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); - } - this.breakingBlock = null; } - private void onBlockBreakComplete(BlockVector3 blockPos, BlockFace face) { + private void onBlockBreakComplete(BlockVector3 blockPos, BlockFace face) { // From InventoryTransactionPacket.USE_ITEM_ACTION_BREAK_BLOCK if (!this.spawned || !this.isAlive()) { return; } - this.resetCraftingGridType(); - - Item handItem = this.getInventory().getItemInHand(); - Item clone = handItem.clone(); - - boolean canInteract = this.canInteract(blockPos.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7); - if (canInteract) { - handItem = this.level.useBreakOn(blockPos.asVector3(), face, handItem, this, true); - if (handItem == null) { - this.level.sendBlocks(new Player[]{this}, new Block[]{this.level.getBlock(blockPos.asVector3())}, UpdateBlockPacket.FLAG_ALL_PRIORITY); - } else if (this.isSurvival()) { - this.getFoodData().updateFoodExpLevel(0.005); - if (handItem.equals(clone) && handItem.getCount() == clone.getCount()) { - return; - } - - if (clone.getId() == handItem.getId() || handItem.getId() == 0) { - inventory.setItemInHand(handItem); - } else { - server.getLogger().debug("Tried to set item " + handItem.getId() + " but " + this.username + " had item " + clone.getId() + " in their hand slot"); + Item i = this.getInventory().getItemInHand(); + Item oldItem = i.clone(); + if (this.canInteract(blockPos.add(0.5, 0.5, 0.5), this.isCreative() ? 13 : 7) && (i = this.level.useBreakOn(blockPos.asVector3(), face, i, this, true)) != null) { + if (this.isSurvival() || this.isAdventure()) { + this.foodData.updateFoodExpLevel(0.005); + if (i.getCount() != oldItem.getCount() || i.getDamage() != oldItem.getDamage() || !i.equals(oldItem)) { + if (i.getId() == 0 || oldItem.getId() == i.getId()) { + inventory.setItemInHand(i); + + // setItem can only send armor to others, I wonder why this isn't needed at other places though + inventory.sendHeldItem(this.getViewers().values()); + } else if (Nukkit.DEBUG > 1) { + server.getLogger().debug("Tried to set item " + i.getId() + " but " + this.username + " had item " + oldItem.getId() + " in their hand slot"); + } } - inventory.sendHeldItem(this.getViewers().values()); } return; } - - inventory.sendContents(this); - inventory.sendHeldItem(this); - - if (blockPos.distanceSquared(this) < 100) { - Block target = this.level.getBlock(blockPos.asVector3()); - this.level.sendBlocks(new Player[]{this}, new Block[]{target}, UpdateBlockPacket.FLAG_ALL_PRIORITY); - - BlockEntity blockEntity = this.level.getBlockEntity(blockPos.asVector3()); + this.needSendHeldItem = true; + if (blockPos.distanceSquared(this) < 10000) { + this.level.sendBlocks(this, new Block[]{this.level.getBlock(blockPos.asVector3(), false)}, UpdateBlockPacket.FLAG_ALL_PRIORITY); + BlockEntity blockEntity = this.level.getBlockEntityIfLoaded(this.chunk, blockPos.asVector3()); if (blockEntity instanceof BlockEntitySpawnable) { ((BlockEntitySpawnable) blockEntity).spawnTo(this); } @@ -3718,29 +4918,51 @@ private void onBlockBreakComplete(BlockVector3 blockPos, BlockFace face) { } /** - * Sends a chat message as this player. If the message begins with a / (forward-slash) it will be treated - * as a command. + * Adjust map color to height map + * + * @param color block color + * @param colorLevel color level + * @return adjusted Color + */ + private static Color colorizeMapColor(BlockColor color, int colorLevel) { + int colorDepth; + + if (colorLevel == 2) { + colorDepth = 255; + } else if (colorLevel == 1) { + colorDepth = 220; + } else if (colorLevel == 0) { + colorDepth = 180; + } else { + throw new IllegalArgumentException("Invalid colorLevel: " + colorLevel); + } + + int r = color.getRed() * colorDepth / 255; + int g = color.getGreen() * colorDepth / 255; + int b = color.getBlue() * colorDepth / 255; + + return new Color(r, g, b); + } + + /** + * Sends a chat message as this player + * * @param message message to send * @return successful */ public boolean chat(String message) { - if (!this.spawned || !this.isAlive()) { - return false; - } - this.resetCraftingGridType(); - this.craftingType = CRAFTING_SMALL; if (this.removeFormat) { message = TextFormat.clean(message, true); } for (String msg : message.split("\n")) { - if (!msg.trim().isEmpty() && msg.length() <= 512 && this.messageCounter-- > 0) { + if (!msg.trim().isEmpty() && msg.length() < 512) { PlayerChatEvent chatEvent = new PlayerChatEvent(this, msg); this.server.getPluginManager().callEvent(chatEvent); if (!chatEvent.isCancelled()) { - this.server.broadcastMessage(this.getServer().getLanguage().translateString(chatEvent.getFormat(), new String[]{chatEvent.getPlayer().getDisplayName(), chatEvent.getMessage()}), chatEvent.getRecipients()); + this.server.broadcastMessage(this.getServer().getLanguage().translateString(chatEvent.getFormat(), new String[]{chatEvent.getPlayer().displayName, chatEvent.getMessage()}), chatEvent.getRecipients()); } } } @@ -3772,6 +4994,13 @@ public boolean kick(PlayerKickEvent.Reason reason, boolean isAdmin) { return this.kick(reason, reason.toString(), isAdmin); } + /** + * Kick the player + * @param reason reason + * @param reasonString reason string + * @param isAdmin display "kicked" or only reason string + * @return PlayerKickEvent not cancelled + */ public boolean kick(PlayerKickEvent.Reason reason, String reasonString, boolean isAdmin) { PlayerKickEvent ev; this.server.getPluginManager().callEvent(ev = new PlayerKickEvent(this, reason, reasonString, this.getLeaveMessage())); @@ -3779,7 +5008,7 @@ public boolean kick(PlayerKickEvent.Reason reason, String reasonString, boolean String message; if (isAdmin) { if (!this.isBanned()) { - message = "Kicked by admin." + (!reasonString.isEmpty() ? " Reason: " + reasonString : ""); + message = "Kicked!" + (!reasonString.isEmpty() ? " Reason: " + reasonString : ""); } else { message = reasonString; } @@ -3799,7 +5028,12 @@ public boolean kick(PlayerKickEvent.Reason reason, String reasonString, boolean return false; } + /** + * Set view distance + * @param distance view distance + */ public void setViewDistance(int distance) { + this.viewDistance = distance; this.chunkRadius = distance; ChunkRadiusUpdatedPacket pk = new ChunkRadiusUpdatedPacket(); @@ -3808,15 +5042,37 @@ public void setViewDistance(int distance) { this.dataPacket(pk); } + /** + * Get view distance (client may have updated this within the limits) + * @return view distance + */ public int getViewDistance() { return this.chunkRadius; } + /** + * Get maximum view distance. Use getViewDistance() to get the view distance possibly updated by client. + * @return view distance + */ + public int getMaximumViewDistance() { + return this.viewDistance; + } + @Override public void sendMessage(String message) { + this.sendMessage(message, false); + } + + /** + * Send a message + * @param message message + * @param isLocalized message has a translation + */ + public void sendMessage(String message, boolean isLocalized) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_RAW; pk.message = this.server.getLanguage().translateString(message); + pk.isLocalized = isLocalized; this.dataPacket(pk); } @@ -3826,7 +5082,7 @@ public void sendMessage(TextContainer message) { this.sendTranslation(message.getText(), ((TranslationContainer) message).getParameters()); return; } - this.sendMessage(message.getText()); + this.sendMessage(message.getText(), false); } public void sendTranslation(String message) { @@ -3835,17 +5091,16 @@ public void sendTranslation(String message) { public void sendTranslation(String message, String[] parameters) { TextPacket pk = new TextPacket(); - if (!this.server.isLanguageForced()) { + if (this.server.isLanguageForced()) { + pk.type = TextPacket.TYPE_RAW; + pk.message = this.server.getLanguage().translateString(message, parameters); + } else { pk.type = TextPacket.TYPE_TRANSLATION; pk.message = this.server.getLanguage().translateString(message, parameters, "nukkit."); for (int i = 0; i < parameters.length; i++) { parameters[i] = this.server.getLanguage().translateString(parameters[i], parameters, "nukkit."); - } pk.parameters = parameters; - } else { - pk.type = TextPacket.TYPE_RAW; - pk.message = this.server.getLanguage().translateString(message, parameters); } this.dataPacket(pk); } @@ -3863,16 +5118,16 @@ public void sendChat(String source, String message) { } public void sendPopup(String message) { - this.sendPopup(message, ""); - } - - public void sendPopup(String message, String subtitle) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_POPUP; pk.message = message; this.dataPacket(pk); } + public void sendPopup(String message, String subtitle) { + this.sendPopup(message); + } + public void sendTip(String message) { TextPacket pk = new TextPacket(); pk.type = TextPacket.TYPE_TIP; @@ -3880,6 +5135,9 @@ public void sendTip(String message) { this.dataPacket(pk); } + /** + * Remove currently playing title + */ public void clearTitle() { SetTitlePacket pk = new SetTitlePacket(); pk.type = SetTitlePacket.TYPE_CLEAR; @@ -3911,7 +5169,6 @@ public void setTitleAnimationTimes(int fadein, int duration, int fadeout) { this.dataPacket(pk); } - private void setTitle(String text) { SetTitlePacket packet = new SetTitlePacket(); packet.text = text; @@ -3932,7 +5189,7 @@ public void sendTitle(String title, String subtitle, int fadeIn, int stay, int f if (!Strings.isNullOrEmpty(subtitle)) { this.setSubtitle(subtitle); } - // title won't send if an empty string is used. + // Title won't send if an empty string is used this.setTitle(Strings.isNullOrEmpty(title) ? " " : title); } @@ -3950,6 +5207,11 @@ public void sendActionBar(String title, int fadein, int duration, int fadeout) { this.dataPacket(pk); } + /** + * Send toast notification for 1.19+ client + * @param title toast title + * @param content toast text + */ public void sendToast(String title, String content) { ToastRequestPacket pk = new ToastRequestPacket(); pk.title = title; @@ -3982,27 +5244,39 @@ public void close(TextContainer message, String reason) { this.close(message, reason, true); } + /** + * Close and disconnect the player + * @param message message + * @param reason reason + * @param notify send disconnection screen + */ public void close(TextContainer message, String reason, boolean notify) { if (this.connected && !this.closed) { - if (notify && reason.length() > 0) { + if (notify && !reason.isEmpty()) { DisconnectPacket pk = new DisconnectPacket(); pk.message = reason; this.forceDataPacket(pk, null); } this.connected = false; + + // Do all inventory changes before the last save + this.resetCraftingGridType(); + this.removeAllWindows(true); + + if (this.fishing != null) { + this.stopFishing(false); + } + PlayerQuitEvent ev = null; - if (this.getName() != null && this.getName().length() > 0) { + if (this.username != null && !this.username.isEmpty()) { this.server.getPluginManager().callEvent(ev = new PlayerQuitEvent(this, message, true, reason)); if (this.loggedIn && ev.getAutoSave()) { this.save(); } - if (this.fishing != null) { - this.stopFishing(false); - } } - for (Player player : new ArrayList<>(this.server.getOnlinePlayers().values())) { + for (Player player : new ArrayList<>(this.server.playerList.values())) { if (!player.canSee(this)) { player.showPlayer(this); } @@ -4010,31 +5284,18 @@ public void close(TextContainer message, String reason, boolean notify) { this.hiddenPlayers.clear(); - this.removeAllWindows(true); - - for (long index : new ArrayList<>(this.usedChunks.keySet())) { - int chunkX = Level.getHashX(index); - int chunkZ = Level.getHashZ(index); - this.level.unregisterChunkLoader(this, chunkX, chunkZ); - this.usedChunks.remove(index); - - for (Entity entity : level.getChunkEntities(chunkX, chunkZ).values()) { - if (entity != this) { - entity.getViewers().remove(getLoaderId()); - } - } - } + this.unloadChunks(false); super.close(); this.interfaz.close(this, notify ? reason : ""); + this.server.removeOnlinePlayer(this); + if (this.loggedIn) { - this.server.removeOnlinePlayer(this); + this.loggedIn = false; } - this.loggedIn = false; - if (ev != null && !Objects.equals(this.username, "") && this.spawned && !Objects.equals(ev.getQuitMessage().toString(), "")) { this.server.broadcastMessage(ev.getQuitMessage()); } @@ -4042,15 +5303,13 @@ public void close(TextContainer message, String reason, boolean notify) { this.server.getPluginManager().unsubscribeFromPermission(Server.BROADCAST_CHANNEL_USERS, this); this.spawned = false; this.server.getLogger().info(this.getServer().getLanguage().translateString("nukkit.player.logOut", - TextFormat.AQUA + (this.getName() == null ? "" : this.getName()) + TextFormat.WHITE, + TextFormat.AQUA + (this.username == null ? this.unverifiedUsername : this.username) + TextFormat.WHITE, this.getAddress(), String.valueOf(this.getPort()), this.getServer().getLanguage().translateString(reason))); + this.windows.clear(); - this.usedChunks.clear(); - this.loadQueue.clear(); this.hasSpawned.clear(); - this.spawnPosition = null; if (this.riding instanceof EntityRideable) { this.riding.passengers.remove(this); @@ -4064,33 +5323,50 @@ public void close(TextContainer message, String reason, boolean notify) { this.perm = null; } - if (this.inventory != null) { - this.inventory = null; - } - + this.inventory = null; this.chunk = null; this.server.removePlayer(this); + + if (this.loggedIn) { + this.server.getLogger().warning("Player is still logged in: " + (this.username == null ? this.unverifiedUsername : this.username)); + this.interfaz.close(this, notify ? reason : ""); + this.server.removeOnlinePlayer(this); + this.loggedIn = false; + } + + this.clientMovements.clear(); } + /** + * Save player data to disk + */ public void save() { this.save(false); } + /** + * Save player data to disk + * @param async save asynchronously + */ public void save(boolean async) { if (this.closed) { throw new IllegalStateException("Tried to save closed player"); } + if (!this.server.shouldSavePlayerData) { + return; + } + super.saveNBT(); if (this.level != null) { this.namedTag.putString("Level", this.level.getFolderName()); if (this.spawnPosition != null && this.spawnPosition.getLevel() != null) { this.namedTag.putString("SpawnLevel", this.spawnPosition.getLevel().getFolderName()); - this.namedTag.putInt("SpawnX", (int) this.spawnPosition.x); - this.namedTag.putInt("SpawnY", (int) this.spawnPosition.y); - this.namedTag.putInt("SpawnZ", (int) this.spawnPosition.z); + this.namedTag.putInt("SpawnX", this.spawnPosition.getFloorX()); + this.namedTag.putInt("SpawnY", this.spawnPosition.getFloorY()); + this.namedTag.putInt("SpawnZ", this.spawnPosition.getFloorZ()); } CompoundTag achievements = new CompoundTag(); @@ -4105,11 +5381,11 @@ public void save(boolean async) { this.namedTag.putString("lastIP", this.getAddress()); - this.namedTag.putInt("EXP", this.getExperience()); - this.namedTag.putInt("expLevel", this.getExperienceLevel()); + this.namedTag.putInt("EXP", this.exp); + this.namedTag.putInt("expLevel", this.expLevel); - this.namedTag.putInt("foodLevel", this.getFoodData().getLevel()); - this.namedTag.putFloat("foodSaturationLevel", this.getFoodData().getFoodSaturationLevel()); + this.namedTag.putInt("foodLevel", this.foodData.getLevel()); + this.namedTag.putFloat("foodSaturationLevel", this.foodData.getFoodSaturationLevel()); this.namedTag.putInt("TimeSinceRest", this.timeSinceRest); @@ -4119,6 +5395,10 @@ public void save(boolean async) { } } + /** + * Get player's username + * @return username + */ public String getName() { return this.username; } @@ -4135,24 +5415,27 @@ public void kill() { EntityDamageEvent cause = this.getLastDamageCause(); if (showMessages) { - params.add(this.getDisplayName()); + params.add(this.displayName); switch (cause == null ? DamageCause.CUSTOM : cause.getCause()) { case ENTITY_ATTACK: + case THORNS: if (cause instanceof EntityDamageByEntityEvent) { Entity e = ((EntityDamageByEntityEvent) cause).getDamager(); killer = e; if (e instanceof Player) { message = "death.attack.player"; - params.add(((Player) e).getDisplayName()); + params.add(((Player) e).displayName); break; } else if (e instanceof EntityLiving) { message = "death.attack.mob"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; } else { - params.add("Unknown"); + message = "death.attack.generic"; } + } else { + message = "death.attack.generic"; } break; case PROJECTILE: @@ -4161,14 +5444,16 @@ public void kill() { killer = e; if (e instanceof Player) { message = "death.attack.arrow"; - params.add(((Player) e).getDisplayName()); + params.add(((Player) e).displayName); } else if (e instanceof EntityLiving) { message = "death.attack.arrow"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; } else { - params.add("Unknown"); + message = "death.attack.generic"; } + } else { + message = "death.attack.generic"; } break; case VOID: @@ -4187,14 +5472,13 @@ public void kill() { break; case LAVA: - Block block = this.level.getBlock(new Vector3(this.x, this.y - 1, this.z)); - if (block.getId() == Block.MAGMA) { - message = "death.attack.lava.magma"; - break; - } message = "death.attack.lava"; break; + case MAGMA: + message = "death.attack.magma"; + break; + case FIRE: message = "death.attack.onFire"; break; @@ -4214,7 +5498,13 @@ public void kill() { message = "death.attack.cactus"; } else if (id == Block.ANVIL) { message = "death.attack.anvil"; + } else if (id == Block.SWEET_BERRY_BUSH) { + message = "death.attack.sweetBerry"; + } else { + message = "death.attack.generic"; } + } else { + message = "death.attack.generic"; } break; @@ -4225,11 +5515,14 @@ public void kill() { killer = e; if (e instanceof Player) { message = "death.attack.explosion.player"; - params.add(((Player) e).getDisplayName()); + params.add(((Player) e).displayName); } else if (e instanceof EntityLiving) { message = "death.attack.explosion.player"; params.add(!Objects.equals(e.getNameTag(), "") ? e.getNameTag() : e.getName()); break; + } else if (e instanceof EntityFirework && cause.getEntity() instanceof Player) { + params.add(((Player) cause.getEntity()).displayName); + message = "death.attack.fireworks"; } else { message = "death.attack.explosion"; } @@ -4252,10 +5545,11 @@ public void kill() { } } - PlayerDeathEvent ev = new PlayerDeathEvent(this, this.getDrops(), new TranslationContainer(message, params.toArray(new String[0])), this.expLevel); - ev.setKeepExperience(this.level.gameRules.getBoolean(GameRule.KEEP_INVENTORY)); - ev.setKeepInventory(ev.getKeepExperience()); + this.resetCraftingGridType(); // This must be called before getDrops() for UI inventories to be handled properly + PlayerDeathEvent ev = new PlayerDeathEvent(this, this.getDrops(), new TranslationContainer(message, params.toArray(new String[0])), this.expLevel); + ev.setKeepInventory(this.level.gameRules.getBoolean(GameRule.KEEP_INVENTORY)); + ev.setKeepExperience(ev.getKeepInventory()); // Same as above this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { @@ -4264,9 +5558,10 @@ public void kill() { } this.health = 0; - this.extinguish(); this.scheduleUpdate(); + //this.resetCraftingGridType(); + if (!ev.getKeepInventory() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { for (Item item : ev.getDrops()) { if (!item.hasEnchantment(Enchantment.ID_VANISHING_CURSE)) { @@ -4277,9 +5572,9 @@ public void kill() { if (this.inventory != null) { this.inventory.clearAll(); } - if (this.offhandInventory != null) { - this.offhandInventory.clearAll(); - } + + // Offhand inventory is already cleared in inventory.clearAll() + // UI inventories are handled in resetCraftingGridType() } if (!ev.getKeepExperience() && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { @@ -4291,28 +5586,29 @@ public void kill() { this.setExperience(0, 0); } - this.timeSinceRest = 0; - - if (showMessages && !ev.getDeathMessage().toString().isEmpty()) { - this.server.broadcast(ev.getDeathMessage(), Server.BROADCAST_CHANNEL_USERS); + if (level.getGameRules().getBoolean(GameRule.DO_IMMEDIATE_RESPAWN)) { + this.respawn(); + } else { + if (showMessages && !ev.getDeathMessage().toString().isEmpty()) { + this.server.broadcast(ev.getDeathMessage(), Server.BROADCAST_CHANNEL_USERS); - DeathInfoPacket pk = new DeathInfoPacket(); - if (ev.getDeathMessage() instanceof TranslationContainer) { - pk.messageTranslationKey = this.server.getLanguage().translateString(ev.getDeathMessage().getText(), ((TranslationContainer) ev.getDeathMessage()).getParameters(), null); - } else { - pk.messageTranslationKey = ev.getDeathMessage().getText(); + DeathInfoPacket pk = new DeathInfoPacket(); + if (ev.getDeathMessage() instanceof TranslationContainer) { + pk.messageTranslationKey = this.server.getLanguage().translateString(ev.getDeathMessage().getText(), ((TranslationContainer) ev.getDeathMessage()).getParameters(), null); + } else { + pk.messageTranslationKey = ev.getDeathMessage().getText(); + } + this.dataPacket(pk); } + + RespawnPacket pk = new RespawnPacket(); + Position pos = this.getSpawn(); + pk.x = (float) pos.x; + pk.y = (float) pos.y; + pk.z = (float) pos.z; + pk.respawnState = RespawnPacket.STATE_SEARCHING_FOR_SPAWN; this.dataPacket(pk); } - - RespawnPacket pk = new RespawnPacket(); - Position pos = this.getSpawn(); - pk.x = (float) pos.x; - pk.y = (float) pos.y; - pk.z = (float) pos.z; - pk.respawnState = RespawnPacket.STATE_SEARCHING_FOR_SPAWN; - - this.dataPacket(pk); } } @@ -4322,7 +5618,6 @@ protected void respawn() { return; } - this.craftingType = CRAFTING_SMALL; this.resetCraftingGridType(); PlayerRespawnEvent playerRespawnEvent = new PlayerRespawnEvent(this, this.getSpawn()); @@ -4330,32 +5625,56 @@ protected void respawn() { Position respawnPos = playerRespawnEvent.getRespawnPosition(); - this.sendExperience(); - this.sendExperienceLevel(); + this.teleport(respawnPos, null); + + this.sendBothExperience(this.exp, this.expLevel); - this.setSprinting(false); + this.setSprinting(false, false); this.setSneaking(false); + this.extinguish(); this.setDataProperty(new ShortEntityData(Player.DATA_AIR, 400), false); + this.airTicks = 400; this.deadTicks = 0; this.noDamageTicks = 60; + this.timeSinceRest = 0; this.removeAllEffects(); this.setHealth(this.getMaxHealth()); - this.getFoodData().setLevel(20, 20); + this.foodData.setLevel(20, 20); this.sendData(this); this.setMovementSpeed(DEFAULT_SPEED); - this.getAdventureSettings().update(); + this.adventureSettings.update(); this.inventory.sendContents(this); this.inventory.sendArmorContents(this); this.offhandInventory.sendContents(this); - this.teleport(respawnPos, null); this.spawnToAll(); this.scheduleUpdate(); + + if (this.spawnPosition instanceof BlockRespawnAnchor && this.spawnPosition.level.getProvider() != null) { + Block anchor = this.spawnPosition.level.getBlock(this.spawnPosition); + if (anchor instanceof BlockRespawnAnchor) { + int chargeLevel = anchor.getDamage(); + + if (chargeLevel > 0) { + anchor.setDamage(chargeLevel - 1); + anchor.level.setBlock(anchor, anchor); + + anchor.level.addLevelSoundEvent(anchor, LevelSoundEventPacket.SOUND_RESPAWN_ANCHOR_DEPLETE); + } + + if (chargeLevel <= 1) { + this.setSpawn(server.getDefaultLevel().getSafeSpawn()); + } + + } else { + this.setSpawn(server.getDefaultLevel().getSafeSpawn()); + } + } } @Override @@ -4365,11 +5684,12 @@ public void setHealth(float health) { } super.setHealth(health); - //TODO: Remove it in future! This a hack to solve the client-side absorption bug! WFT Mojang (Half a yellow heart cannot be shown, we can test it in local gaming) - Attribute attr = Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getAbsorption() % 2 != 0 ? this.getMaxHealth() + 1 : this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0); + + // HACK: solve the client-side absorption bug if (this.spawned) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); - pk.entries = new Attribute[]{attr}; + int max = this.getMaxHealth(); + pk.entries = new Attribute[]{Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(max).setValue(this.health > 0 ? (this.health < max ? this.health : max) : 0)}; pk.entityId = this.id; this.dataPacket(pk); } @@ -4379,53 +5699,76 @@ public void setHealth(float health) { public void setMaxHealth(int maxHealth) { super.setMaxHealth(maxHealth); - Attribute attr = Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(this.getAbsorption() % 2 != 0 ? this.getMaxHealth() + 1 : this.getMaxHealth()).setValue(health > 0 ? (health < getMaxHealth() ? health : getMaxHealth()) : 0); if (this.spawned) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); - pk.entries = new Attribute[]{attr}; + int max = this.getMaxHealth(); + pk.entries = new Attribute[]{Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(max).setValue(this.health > 0 ? (this.health < max ? this.health : max) : 0)}; pk.entityId = this.id; this.dataPacket(pk); } } + /** + * Get experience + * @return experience (non-full levels) + */ public int getExperience() { return this.exp; } + /** + * Get experience level + * @return experience level + */ public int getExperienceLevel() { return this.expLevel; } + /** + * Give the player more experience + * @param add experience to add + */ public void addExperience(int add) { if (add == 0) return; - int now = this.getExperience(); - int added = now + add; - int level = this.getExperienceLevel(); + int added = this.exp + add; + int level = this.expLevel; int most = calculateRequireExperience(level); - while (added >= most) { //Level Up! - added = added - most; + while (added >= most) { + added -= most; level++; most = calculateRequireExperience(level); } this.setExperience(added, level); } + /** + * Calculate experience required for the level + * @param level level + * @return required experience + */ public static int calculateRequireExperience(int level) { if (level >= 30) { return 112 + (level - 30) * 9; } else if (level >= 15) { return 37 + (level - 15) * 5; } else { - return 7 + level * 2; + return 7 + (level << 1); } } + /** + * Set player's experience + * @param exp experience (non-full levels) + */ public void setExperience(int exp) { - setExperience(exp, this.getExperienceLevel()); + setExperience(exp, this.expLevel); } - //todo something on performance, lots of exp orbs then lots of packets, could crash client - + /** + * Set player's experience and experience level + * @param exp experience (non-full levels) + * @param level experience level + */ public void setExperience(int exp, int level) { PlayerExperienceChangeEvent ev = new PlayerExperienceChangeEvent(this, this.exp, this.expLevel, exp, level); this.server.getPluginManager().callEvent(ev); @@ -4437,32 +5780,61 @@ public void setExperience(int exp, int level) { this.exp = ev.getNewExperience(); this.expLevel = ev.getNewExperienceLevel(); - this.sendExperienceLevel(this.expLevel); - this.sendExperience(this.exp); + this.sendBothExperience(this.exp, this.expLevel); } + /** + * Send experience (non-full levels) + */ public void sendExperience() { - sendExperience(this.getExperience()); + sendExperience(this.exp); } + /** + * Send experience (non-full levels) + * @param exp experience + */ public void sendExperience(int exp) { if (this.spawned) { - float percent = ((float) exp) / calculateRequireExperience(this.getExperienceLevel()); - percent = Math.max(0f, Math.min(1f, percent)); - this.setAttribute(Attribute.getAttribute(Attribute.EXPERIENCE).setValue(percent)); + this.setAttribute(Attribute.getAttribute(Attribute.EXPERIENCE).setValue(Math.max(0f, Math.min(1f, ((float) exp) / calculateRequireExperience(this.expLevel))))); } } + /** + * Send experience level + */ public void sendExperienceLevel() { - sendExperienceLevel(this.getExperienceLevel()); + sendExperienceLevel(this.expLevel); } + /** + * Send experience level + * @param level experience level + */ public void sendExperienceLevel(int level) { if (this.spawned) { this.setAttribute(Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(level)); } } + /** + * Send both player's experience and experience level in one packet + * @param exp experience (non-full levels) + * @param level experience level + */ + private void sendBothExperience(int exp, int level) { + if (this.spawned) { + UpdateAttributesPacket pk = new UpdateAttributesPacket(); + pk.entries = new Attribute[]{Attribute.getAttribute(Attribute.EXPERIENCE_LEVEL).setValue(level), Attribute.getAttribute(Attribute.EXPERIENCE).setValue(Math.max(0f, Math.min(1f, ((float) exp) / calculateRequireExperience(this.expLevel))))}; + pk.entityId = this.id; + this.dataPacket(pk); + } + } + + /** + * Send updated attribute + * @param attribute attribute + */ public void setAttribute(Attribute attribute) { UpdateAttributesPacket pk = new UpdateAttributesPacket(); pk.entries = new Attribute[]{attribute}; @@ -4475,50 +5847,59 @@ public void setMovementSpeed(float speed) { setMovementSpeed(speed, true); } + /** + * Set player's movement speed + * @param speed speed + * @param send send updated speed to player + */ public void setMovementSpeed(float speed, boolean send) { + if (speed < 0) { // Apparently effects can break this? + server.getLogger().debug("Invalid setMovementSpeed: " + speed); + return; + } super.setMovementSpeed(speed); if (this.spawned && send) { - this.sendMovementSpeed(speed); + this.setAttribute(Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(speed).setDefaultValue(speed)); } } - public void sendMovementSpeed(float speed){ + /** + * Send movement speed attribute + * @param speed speed + */ + public void sendMovementSpeed(float speed) { Attribute attribute = Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(speed); this.setAttribute(attribute); } + /** + * Get the entity which killed the player + * @return entity which killed the player or null + */ public Entity getKiller() { return killer; } @Override public boolean attack(EntityDamageEvent source) { - if (!this.isAlive()) { + if (!spawned || closed || !this.isAlive()) { return false; } if (this.isSpectator() || (this.isCreative() && source.getCause() != DamageCause.SUICIDE)) { - //source.setCancelled(); + source.setCancelled(); return false; - } else if (this.getAdventureSettings().get(Type.ALLOW_FLIGHT) && source.getCause() == DamageCause.FALL) { - //source.setCancelled(); + } else if (source.getCause() == DamageCause.FALL && this.getAllowFlight()) { + source.setCancelled(); return false; - } else if (source.getCause() == DamageCause.FALL) { - if (this.getLevel().getBlock(this.getPosition().floor().add(0.5, -1, 0.5)).getId() == Block.SLIME_BLOCK) { - if (!this.isSneaking()) { - //source.setCancelled(); - this.resetFallDistance(); - return false; - } - } } - if (super.attack(source)) { //!source.isCancelled() + if (super.attack(source)) { if (this.getLastDamageCause() == source && this.spawned) { if (source instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); if (damager instanceof Player) { - ((Player) damager).getFoodData().updateFoodExpLevel(0.1); + ((Player) damager).foodData.updateFoodExpLevel(0.1); } } EntityEventPacket pk = new EntityEventPacket(); @@ -4544,15 +5925,17 @@ public boolean dropItem(Item item) { } if (item.isNull()) { - this.server.getLogger().debug(this.getName() + " attempted to drop a null item (" + item + ")"); + this.server.getLogger().debug(this.username + " attempted to drop a null item (" + item + ')'); return true; - } - - Vector3 motion = this.getDirectionVector().multiply(0.4); + } - this.level.dropItem(this.add(0, 1.3, 0), item, motion, 40); + this.setUsingItem(false); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); + Vector3 motion = this.getDirectionVector().multiply(0.4); + EntityItem entityItem = this.level.dropAndGetItem(this.add(0, 1.3, 0), item, motion, 40); + if (entityItem != null) { + entityItem.droppedBy = this; + } return true; } @@ -4568,15 +5951,18 @@ public EntityItem dropAndGetItem(Item item) { } if (item.isNull()) { - this.server.getLogger().debug(this.getName() + " attempted to drop a null item (" + item + ")"); + this.server.getLogger().debug(this.getName() + " attempted to drop a null item (" + item + ')'); return null; } - Vector3 motion = this.getDirectionVector().multiply(0.4); - - this.setDataFlag(DATA_FLAGS, DATA_FLAG_ACTION, false); + this.setUsingItem(false); - return this.level.dropAndGetItem(this.add(0, 1.3, 0), item, motion, 40); + Vector3 motion = this.getDirectionVector().multiply(0.4); + EntityItem entityItem = this.level.dropAndGetItem(this.add(0, 1.3, 0), item, motion, 40); + if (entityItem != null) { + entityItem.droppedBy = this; + } + return entityItem; } public void sendPosition(Vector3 pos) { @@ -4595,47 +5981,90 @@ public void sendPosition(Vector3 pos, double yaw, double pitch, int mode) { this.sendPosition(pos, yaw, pitch, mode, null); } + /** + * Send player's position and rotation + * @param pos position + * @param yaw yaw + * @param pitch pitch + * @param mode movement mode + * @param targets receivers + */ public void sendPosition(Vector3 pos, double yaw, double pitch, int mode, Player[] targets) { MovePlayerPacket pk = new MovePlayerPacket(); pk.eid = this.getId(); pk.x = (float) pos.x; - pk.y = (float) (pos.y + this.getEyeHeight()); + pk.y = (float) (pos.y + this.getBaseOffset()); pk.z = (float) pos.z; pk.headYaw = (float) yaw; pk.pitch = (float) pitch; pk.yaw = (float) yaw; pk.mode = mode; + pk.onGround = this.onGround; + if (this.riding != null) { pk.ridingEid = this.riding.getId(); pk.mode = MovePlayerPacket.MODE_PITCH; } + this.ySize = 0; + if (targets != null) { Server.broadcastPacket(targets, pk); } else { + this.clientMovements.clear(); + this.dataPacket(pk); } } + /** + * Internal: Broadcast player movement to viewers + * @param x x + * @param y y + * @param z z + * @param yaw yaw + * @param pitch pitch + * @param headYaw headYaw + */ + private void sendPositionToViewers(double x, double y, double z, double yaw, double pitch, double headYaw) { + MovePlayerPacket pk = new MovePlayerPacket(); + pk.eid = this.getId(); + pk.x = (float) x; + pk.y = (float) (y + this.getBaseOffset()); + pk.z = (float) z; + pk.headYaw = (float) headYaw; + pk.pitch = (float) pitch; + pk.yaw = (float) yaw; + pk.mode = MovePlayerPacket.MODE_NORMAL; + pk.onGround = this.onGround; + + if (this.riding != null) { + pk.ridingEid = this.riding.getId(); + pk.mode = MovePlayerPacket.MODE_PITCH; + } + + this.ySize = 0; + + Server.broadcastPacket(this.getViewers().values(), pk); + } + @Override protected void checkChunks() { - if (this.chunk == null || (this.chunk.getX() != ((int) this.x >> 4) || this.chunk.getZ() != ((int) this.z >> 4))) { + if (this.chunk == null || (this.chunk.getX() != this.getChunkX() || this.chunk.getZ() != this.getChunkZ())) { if (this.chunk != null) { this.chunk.removeEntity(this); } - this.chunk = this.level.getChunk((int) this.x >> 4, (int) this.z >> 4, true); + this.chunk = this.level.getChunk(this.getChunkX(), this.getChunkZ(), true); if (!this.justCreated) { - Map newChunk = this.level.getChunkPlayers((int) this.x >> 4, (int) this.z >> 4); - newChunk.remove(this.getLoaderId()); + Map newChunk = this.level.getChunkPlayers(this.getChunkX(), this.getChunkZ()); + newChunk.remove(this.loaderId); - //List reload = new ArrayList<>(); for (Player player : new ArrayList<>(this.hasSpawned.values())) { - if (!newChunk.containsKey(player.getLoaderId())) { + if (!newChunk.containsKey(player.loaderId)) { this.despawnFrom(player); } else { - newChunk.remove(player.getLoaderId()); - //reload.add(player); + newChunk.remove(player.loaderId); } } @@ -4653,9 +6082,13 @@ protected void checkChunks() { } protected boolean checkTeleportPosition() { + return checkTeleportPosition(false); + } + + protected boolean checkTeleportPosition(boolean enderPearl) { if (this.teleportPosition != null) { - int chunkX = (int) this.teleportPosition.x >> 4; - int chunkZ = (int) this.teleportPosition.z >> 4; + int chunkX = this.teleportPosition.getChunkX(); + int chunkZ = this.teleportPosition.getChunkZ(); for (int X = -1; X <= 1; ++X) { for (int Z = -1; Z <= 1; ++Z) { @@ -4667,7 +6100,9 @@ protected boolean checkTeleportPosition() { } this.spawnToAll(); - this.forceMovement = this.teleportPosition; + if (!enderPearl) { + this.forceMovement = this.teleportPosition; + } this.teleportPosition = null; return true; } @@ -4676,13 +6111,8 @@ protected boolean checkTeleportPosition() { } protected void sendPlayStatus(int status) { - sendPlayStatus(status, false); - } - - protected void sendPlayStatus(int status, boolean immediate) { PlayStatusPacket pk = new PlayStatusPacket(); pk.status = status; - this.dataPacket(pk); } @@ -4692,89 +6122,75 @@ public boolean teleport(Location location, TeleportCause cause) { return false; } - Location from = this.getLocation(); Location to = location; if (cause != null) { + Location from = this.getLocation(); PlayerTeleportEvent event = new PlayerTeleportEvent(this, from, to, cause); this.server.getPluginManager().callEvent(event); if (event.isCancelled()) return false; to = event.getTo(); } - //TODO Remove it! A hack to solve the client-side teleporting bug! (inside into the block) + // HACK: solve the client-side teleporting bug (inside into the block) if (super.teleport(to.getY() == to.getFloorY() ? to.add(0, 0.00001, 0) : to, null)) { // null to prevent fire of duplicate EntityTeleportEvent this.removeAllWindows(); + this.formOpen = false; - this.teleportPosition = new Vector3(this.x, this.y, this.z); - this.forceMovement = this.teleportPosition; - this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_TELEPORT); + this.teleportPosition = this; + if (cause != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + this.forceMovement = this.teleportPosition; + } - this.checkTeleportPosition(); + if (this.dimensionChangeInProgress) { + this.dimensionChangeInProgress = false; + } else { + this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_TELEPORT); + this.checkTeleportPosition(cause == PlayerTeleportEvent.TeleportCause.ENDER_PEARL); + this.dummyBossBars.values().forEach(DummyBossBar::reshow); + } this.resetFallDistance(); this.nextChunkOrderRun = 0; this.resetClientMovement(); - //DummyBossBar - this.getDummyBossBars().values().forEach(DummyBossBar::reshow); - //Weather - this.getLevel().sendWeather(this); - //Update time - this.getLevel().sendTime(this); + this.stopFishing(false); return true; } return false; } - protected void forceSendEmptyChunks() { - int chunkPositionX = this.getFloorX() >> 4; - int chunkPositionZ = this.getFloorZ() >> 4; - for (int x = -chunkRadius; x < chunkRadius; x++) { - for (int z = -chunkRadius; z < chunkRadius; z++) { - LevelChunkPacket chunk = new LevelChunkPacket(); - chunk.chunkX = chunkPositionX + x; - chunk.chunkZ = chunkPositionZ + z; - chunk.data = new byte[0]; - this.dataPacket(chunk); - } - } - } - + /** + * Warning: Using teleportImmediate() may have unexpected side effects. Please use teleport() instead. + * Teleports the player immediately without calling PlayerTeleportEvent. + * @param location target location + */ public void teleportImmediate(Location location) { this.teleportImmediate(location, TeleportCause.PLUGIN); } + /** + * Warning: Using teleportImmediate() may have unexpected side effects. Please use teleport() instead. + * Teleports the player immediately without calling PlayerTeleportEvent. + * @param location target location + * @param cause teleport cause + */ public void teleportImmediate(Location location, TeleportCause cause) { - Location from = this.getLocation(); - if (super.teleport(location, cause)) { + // HACK: solve the client-side teleporting bug (inside into the block) + if (super.teleport(location.getY() == location.getFloorY() ? location.add(0, 0.00001, 0) : location, cause)) { this.removeAllWindows(); + this.formOpen = false; - if (from.getLevel().getId() != location.getLevel().getId()) { //Different level, update compass position - SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); - pk.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; - Position spawn = location.getLevel().getSpawnLocation(); - pk.x = spawn.getFloorX(); - pk.y = spawn.getFloorY(); - pk.z = spawn.getFloorZ(); - dataPacket(pk); - } - - this.forceMovement = new Vector3(this.x, this.y, this.z); - this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_RESET); + this.forceMovement = this; + this.sendPosition(this, this.yaw, this.pitch, MovePlayerPacket.MODE_TELEPORT); this.resetFallDistance(); this.orderChunks(); this.nextChunkOrderRun = 0; this.resetClientMovement(); - //DummyBossBar - this.getDummyBossBars().values().forEach(DummyBossBar::reshow); - //Weather - this.getLevel().sendWeather(this); - //Update time - this.getLevel().sendTime(this); + this.stopFishing(false); } } @@ -4798,12 +6214,13 @@ public int showFormWindow(FormWindow window) { * @return form id to use in {@link PlayerFormRespondedEvent} */ public int showFormWindow(FormWindow window, int id) { + if (formOpen) return 0; ModalFormRequestPacket packet = new ModalFormRequestPacket(); packet.formId = id; packet.data = window.getJSONData(); this.formWindows.put(packet.formId, window); - this.dataPacket(packet); + this.formOpen = true; return id; } @@ -4828,10 +6245,8 @@ public int addServerSettings(FormWindow window) { * @param length The BossBar percentage * @return bossBarId The BossBar ID, you should store it if you want to remove or update the BossBar later */ - @Deprecated public long createBossBar(String text, int length) { - DummyBossBar bossBar = new DummyBossBar.Builder(this).text(text).length(length).build(); - return this.createBossBar(bossBar); + return this.createBossBar(new DummyBossBar.Builder(this).text(text).length(length).build()); } /** @@ -4876,7 +6291,6 @@ public Map getDummyBossBars() { * @param length The new BossBar length * @param bossBarId The BossBar ID */ - @Deprecated public void updateBossBar(String text, int length, long bossBarId) { if (this.dummyBossBars.containsKey(bossBarId)) { DummyBossBar bossBar = this.dummyBossBars.get(bossBarId); @@ -4897,6 +6311,11 @@ public void removeBossBar(long bossBarId) { } } + /** + * Get window id of an open Inventory + * @param inventory inventory + * @return id of the inventory window or -1 if player doesn't have the window open + */ public int getWindowId(Inventory inventory) { if (this.windows.containsKey(inventory)) { return this.windows.get(inventory); @@ -4905,6 +6324,11 @@ public int getWindowId(Inventory inventory) { return -1; } + /** + * Get on open inventory by window id + * @param id window id + * @return inventory (if open) or null + */ public Inventory getWindowById(int id) { return this.windowIndex.get(id); } @@ -4965,13 +6389,14 @@ public void removeWindow(Inventory inventory) { protected void removeWindow(Inventory inventory, boolean isResponse) { inventory.close(this); - // TODO: This needs a proper fix - // Requiring isResponse here causes issues with inventory events and an item duplication glitch - if (/*isResponse &&*/ !this.permanentWindows.contains(this.getWindowId(inventory))) { + if (/*isResponse &&*/ !this.permanentWindows.contains(this.getWindowId(inventory))) { // Possible dupe this.windows.remove(inventory); } } + /** + * Send contents of all open inventories to the player + */ public void sendAllInventories() { for (Inventory inv : this.windows.keySet()) { inv.sendContents(this); @@ -4991,72 +6416,120 @@ protected void addDefaultWindows() { this.craftingGrid = this.playerUIInventory.getCraftingGrid(); this.addWindow(this.craftingGrid, ContainerIds.NONE); - - //TODO: more windows } + /** + * Get player's ui inventory + * @return ui inventory + */ public PlayerUIInventory getUIInventory() { return playerUIInventory; } + /** + * Get player's cursor inventory + * @return cursor inventory + */ public PlayerCursorInventory getCursorInventory() { return this.playerUIInventory.getCursorInventory(); } + /** + * Get player's crafting grid + * @return crafting grid + */ public CraftingGrid getCraftingGrid() { return this.craftingGrid; } + /** + * Set player's crafting grid + * @param grid crafting grid + */ public void setCraftingGrid(CraftingGrid grid) { this.craftingGrid = grid; this.addWindow(grid, ContainerIds.NONE); } + /** + * Resets crafting grid type and moves all UI inventory contents back to player inventory or drops them. + */ public void resetCraftingGridType() { - if (this.craftingGrid != null) { - Item[] drops = this.inventory.addItem(this.craftingGrid.getContents().values().toArray(new Item[0])); + if (this.playerUIInventory != null) { + Item[] drops; + + if (this.craftingGrid != null) { + drops = this.inventory.addItem(this.craftingGrid.getContents().values().toArray(new Item[0])); + this.craftingGrid.clearAll(); - if (drops.length > 0) { for (Item drop : drops) { - this.dropItem(drop); + this.level.dropItem(this, drop); } } - drops = this.inventory.addItem(this.getCursorInventory().getItem(0)); - if (drops.length > 0) { - for (Item drop : drops) { - this.dropItem(drop); - } + drops = this.inventory.addItem(this.playerUIInventory.getCursorInventory().getItemFast(0)); // cloned in addItem + this.playerUIInventory.getCursorInventory().clear(0); + + for (Item drop : drops) { + this.level.dropItem(this, drop); } + // Don't trust the client to handle this + this.moveBlockUIContents(Player.ANVIL_WINDOW_ID); // LOOM_WINDOW_ID is the same as ANVIL_WINDOW_ID? + this.moveBlockUIContents(Player.ENCHANT_WINDOW_ID); + this.moveBlockUIContents(Player.BEACON_WINDOW_ID); + this.moveBlockUIContents(Player.SMITHING_WINDOW_ID); this.playerUIInventory.clearAll(); - if (this.craftingGrid instanceof BigCraftingGrid) { + if (this.craftingGrid instanceof BigCraftingGrid && this.connected) { this.craftingGrid = this.playerUIInventory.getCraftingGrid(); this.addWindow(this.craftingGrid, ContainerIds.NONE); -// -// ContainerClosePacket pk = new ContainerClosePacket(); //be sure, big crafting is really closed -// pk.windowId = ContainerIds.NONE; -// this.dataPacket(pk); } + } + + this.craftingType = CRAFTING_SMALL; + } - this.craftingType = CRAFTING_SMALL; + /** + * Move all block UI contents back to player inventory or drop them + * @param window window id + */ + private void moveBlockUIContents(int window) { + Inventory inventory = this.getWindowById(window); + if (inventory != null) { + Item[] drops = this.inventory.addItem(inventory.getContents().values().toArray(new Item[0])); + inventory.clearAll(); + for (Item drop : drops) { + this.level.dropItem(this, drop); + } } } + /** + * Remove all windows + */ public void removeAllWindows() { removeAllWindows(false); } + /** + * Remove all windows + * @param permanent remove permanent windows + */ public void removeAllWindows(boolean permanent) { for (Entry entry : new ArrayList<>(this.windowIndex.entrySet())) { if (!permanent && this.permanentWindows.contains(entry.getKey())) { continue; } + this.removeWindow(entry.getValue()); } } + /** + * Get id of the window client has requested to be closed + * @return window id or Integer.MIN_VALUE if no window is being closed + */ public int getClosingWindowId() { return this.closingWindowId; } @@ -5086,24 +6559,22 @@ public void onChunkChanged(FullChunk chunk) { this.usedChunks.remove(Level.chunkHash(chunk.getX(), chunk.getZ())); } + /* Note: Update Level useChunkLoaderApi checks if more ChunkLoader API is ever used here */ + @Override public void onChunkLoaded(FullChunk chunk) { - } @Override public void onChunkPopulated(FullChunk chunk) { - } @Override public void onChunkUnloaded(FullChunk chunk) { - } @Override public void onBlockChanged(Vector3 block) { - } @Override @@ -5113,131 +6584,212 @@ public int getLoaderId() { @Override public boolean isLoaderActive() { - return this.isConnected(); + return this.connected; } - - public static BatchPacket getChunkCacheFromData(int chunkX, int chunkZ, int subChunkCount, byte[] payload) { + /** + * Get chunk cache from data + * @param chunkX chunk x + * @param chunkZ chunk z + * @param subChunkCount sub chunk count + * @param payload data + * @return BatchPacket + */ + public static BatchPacket getChunkCacheFromData(int chunkX, int chunkZ, int subChunkCount, byte[] payload, int dimension) { LevelChunkPacket pk = new LevelChunkPacket(); pk.chunkX = chunkX; pk.chunkZ = chunkZ; + pk.dimension = dimension; pk.subChunkCount = subChunkCount; pk.data = payload; - pk.encode(); + pk.tryEncode(); - BatchPacket batch = new BatchPacket(); - byte[][] batchPayload = new byte[2][]; byte[] buf = pk.getBuffer(); - batchPayload[0] = Binary.writeUnsignedVarInt(buf.length); - batchPayload[1] = buf; - byte[] data = Binary.appendBytes(batchPayload); + BinaryStream batched = new BinaryStream(new byte[5 + buf.length]).reset(); + batched.putUnsignedVarInt(buf.length); + batched.put(buf); try { - batch.payload = Network.deflateRaw(data, Server.getInstance().networkCompressionLevel); + byte[] bytes = batched.getBuffer(); + BatchPacket compress = new BatchPacket(); + if (Server.getInstance().useSnappy) { + compress.payload = SnappyCompression.compress(bytes); + } else { + compress.payload = Zlib.deflateRaw(bytes, Server.getInstance().networkCompressionLevel); + } + return compress; } catch (Exception e) { throw new RuntimeException(e); } - return batch; } - private boolean foodEnabled = true; - + /** + * Check whether food is enabled or not + * @return food enabled + */ public boolean isFoodEnabled() { return !(this.isCreative() || this.isSpectator()) && this.foodEnabled; } + /** + * Enable or disable food + * @param foodEnabled food enabled + */ public void setFoodEnabled(boolean foodEnabled) { this.foodEnabled = foodEnabled; } + /** + * Get player's food data + * @return food data + */ public PlayerFood getFoodData() { return this.foodData; } - //todo a lot on dimension + /** + * Send dimension change + * @param dimension dimension id + */ + public void setDimension(int dimension) { + if (!this.loggedIn) { + return; // Do not send ChangeDimensionPacket before StartGamePacket + } - private void setDimension(int dimension) { - ChangeDimensionPacket pk = new ChangeDimensionPacket(); - pk.dimension = dimension; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - this.dataPacket(pk); + this.dimensionChangeInProgress = true; + this.awaitingDimensionAck = true; + + ChangeDimensionPacket changeDimensionPacket = new ChangeDimensionPacket(); + changeDimensionPacket.dimension = dimension; + changeDimensionPacket.x = (float) this.x; + changeDimensionPacket.y = (float) this.y; + changeDimensionPacket.z = (float) this.z; + changeDimensionPacket.respawn = !this.isAlive(); + this.dataPacket(changeDimensionPacket); + + NetworkChunkPublisherUpdatePacket chunkPublisherUpdatePacket = new NetworkChunkPublisherUpdatePacket(); + chunkPublisherUpdatePacket.position = this.asBlockVector3(); + chunkPublisherUpdatePacket.radius = this.chunkRadius << 4; + this.dataPacket(chunkPublisherUpdatePacket); + + this.dimensionFix560 = true; } @Override - public boolean switchLevel(Level level) { - Level oldLevel = this.level; - if (super.switchLevel(level)) { - SetSpawnPositionPacket spawnPosition = new SetSpawnPositionPacket(); - spawnPosition.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; - Position spawn = level.getSpawnLocation(); - spawnPosition.x = spawn.getFloorX(); - spawnPosition.y = spawn.getFloorY(); - spawnPosition.z = spawn.getFloorZ(); - this.dataPacket(spawnPosition); - - // Remove old chunks - for (long index : new ArrayList<>(this.usedChunks.keySet())) { - int chunkX = Level.getHashX(index); - int chunkZ = Level.getHashZ(index); - this.unloadChunk(chunkX, chunkZ, oldLevel); - } - this.usedChunks.clear(); + protected void preSwitchLevel() { + // Make sure batch packets from the previous world gets through first + this.networkSession.flush(); - SetTimePacket setTime = new SetTimePacket(); - setTime.time = level.getTime(); - this.dataPacket(setTime); + // Remove old chunks + this.unloadChunks(true); + } - GameRulesChangedPacket gameRulesChanged = new GameRulesChangedPacket(); - gameRulesChanged.gameRules = level.getGameRules(); - this.dataPacket(gameRulesChanged); - return true; - } + @Override + protected void afterSwitchLevel() { + // Send spawn to update compass position + SetSpawnPositionPacket spawnPosition = new SetSpawnPositionPacket(); + spawnPosition.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; + Position spawn = level.getSpawnLocation(); + spawnPosition.x = spawn.getFloorX(); + spawnPosition.y = spawn.getFloorY(); + spawnPosition.z = spawn.getFloorZ(); + spawnPosition.dimension = level.getDimension(); + this.dataPacket(spawnPosition); + + // Update time and weather + level.sendTime(this); + level.sendWeather(this); + + // Update game rules + GameRulesChangedPacket packet = new GameRulesChangedPacket(); + packet.gameRulesMap = level.getGameRules().getGameRules(); + this.dataPacket(packet); - return false; + // Reset sleeping timer + this.timeSinceRest = 0; } + /** + * Enable or disable movement check + * @param checkMovement movement check enabled + */ public void setCheckMovement(boolean checkMovement) { this.checkMovement = checkMovement; } + /** + * @return player movement checks enabled + */ public boolean isCheckingMovement() { return this.checkMovement; } + /** + * Set locale + * @param locale locale + */ public synchronized void setLocale(Locale locale) { this.locale.set(locale); } + /** + * Get locale + * @return locale + */ public synchronized Locale getLocale() { return this.locale.get(); } @Override public void setSprinting(boolean value) { + this.setSprinting(value, true); + } + + /** + * Update movement speed to start/stop sprinting + * @param value sprinting + * @param send send updated speed to client + */ + public void setSprinting(boolean value, boolean send) { if (isSprinting() != value) { super.setSprinting(value); - - if(this.hasEffect(Effect.SPEED)) { - float movementSpeed = this.getMovementSpeed(); - this.sendMovementSpeed(value ? movementSpeed * 1.3f : movementSpeed); - } + this.setMovementSpeed(value ? getMovementSpeed() * 1.3f : getMovementSpeed() / 1.3f, send); } } + /** + * Transfer player to other server + * @param address target server address + */ public void transfer(InetSocketAddress address) { - String hostName = address.getAddress().getHostAddress(); - int port = address.getPort(); + transfer(address.getAddress().getHostAddress(), address.getPort()); + } + + /** + * Transfer player to other server + * @param hostName target server address + * @param port target server port + */ + public void transfer(String hostName, int port) { TransferPacket pk = new TransferPacket(); pk.address = hostName; pk.port = port; this.dataPacket(pk); } + /** + * Get player's LoginChainData + * @return login chain data + */ public LoginChainData getLoginChainData() { return this.loginChainData; } + /** + * Try to pick up an entity + * @param entity target + * @param near near + * @return success + */ public boolean pickupEntity(Entity entity, boolean near) { if (!this.spawned || !this.isAlive() || !this.isOnline() || this.isSpectator() || entity.isClosed()) { return false; @@ -5245,19 +6797,25 @@ public boolean pickupEntity(Entity entity, boolean near) { if (near) { if (entity instanceof EntityArrow && ((EntityArrow) entity).hadCollision) { - ItemArrow item = new ItemArrow(); + EntityArrow a = ((EntityArrow) entity); + ItemArrow item = (ItemArrow) Item.get(Item.ARROW, a.getData()); if (!this.isCreative() && !this.inventory.canAddItem(item)) { return false; } - InventoryPickupArrowEvent ev = new InventoryPickupArrowEvent(this.inventory, (EntityArrow) entity); + InventoryPickupArrowEvent ev = new InventoryPickupArrowEvent(this.inventory, a); - int pickupMode = ((EntityArrow) entity).getPickupMode(); - if (pickupMode == EntityArrow.PICKUP_NONE || (pickupMode == EntityArrow.PICKUP_CREATIVE && !this.isCreative())) { + int pickupMode = a.getPickupMode(); + if (pickupMode == EntityArrow.PICKUP_NONE_REMOVE || pickupMode == EntityArrow.PICKUP_NONE || (pickupMode == EntityArrow.PICKUP_CREATIVE && !this.isCreative())) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); + + if (pickupMode == EntityArrow.PICKUP_NONE_REMOVE) { + entity.close(); + } + if (ev.isCancelled()) { return false; } @@ -5269,12 +6827,31 @@ public boolean pickupEntity(Entity entity, boolean near) { this.dataPacket(pk); if (!this.isCreative()) { - this.inventory.addItem(item.clone()); + this.inventory.addItem(item); } entity.close(); return true; - } else if (entity instanceof EntityThrownTrident && ((EntityThrownTrident) entity).hadCollision) { + } else if (entity instanceof EntityThrownTrident) { + if (!((EntityThrownTrident) entity).hadCollision) { + if (entity.noClip) { + if (!this.equals(((EntityProjectile) entity).shootingEntity)) { + return false; + } + } else { + return false; + } + } + + if (!((EntityThrownTrident) entity).shotByPlayer()) { + return false; + } + Item item = ((EntityThrownTrident) entity).getItem(); + + if (item.hasEnchantment(Enchantment.ID_TRIDENT_LOYALTY) && !this.equals(((EntityProjectile) entity).shootingEntity)) { + return false; + } + if (!this.isCreative() && !this.inventory.canAddItem(item)) { return false; } @@ -5282,11 +6859,16 @@ public boolean pickupEntity(Entity entity, boolean near) { InventoryPickupTridentEvent ev = new InventoryPickupTridentEvent(this.inventory, (EntityThrownTrident) entity); int pickupMode = ((EntityThrownTrident) entity).getPickupMode(); - if (pickupMode == EntityThrownTrident.PICKUP_NONE || (pickupMode == EntityThrownTrident.PICKUP_CREATIVE && !this.isCreative())) { + if (pickupMode == EntityArrow.PICKUP_NONE_REMOVE || pickupMode == EntityThrownTrident.PICKUP_NONE || (pickupMode == EntityThrownTrident.PICKUP_CREATIVE && !this.isCreative())) { ev.setCancelled(); } this.server.getPluginManager().callEvent(ev); + + if (pickupMode == EntityArrow.PICKUP_NONE_REMOVE) { + entity.close(); + } + if (ev.isCancelled()) { return false; } @@ -5298,13 +6880,20 @@ public boolean pickupEntity(Entity entity, boolean near) { this.dataPacket(pk); if (!this.isCreative()) { - this.inventory.addItem(item.clone()); + int favSlot = ((EntityThrownTrident) entity).getFavoredSlot(); + if (favSlot != -1 && !this.isCreative() && inventory.getItemFast(favSlot).getId() == Item.AIR) { + inventory.setItem(favSlot, item.clone()); + } else { + inventory.addItem(item); // cloned in addItem + } } + entity.close(); return true; } else if (entity instanceof EntityItem) { - if (((EntityItem) entity).getPickupDelay() <= 0) { - Item item = ((EntityItem) entity).getItem(); + EntityItem entityItem = (EntityItem) entity; + if (entityItem.getPickupDelay() <= 0) { + Item item = entityItem.getItem(); if (item != null) { if (!this.isCreative() && !this.inventory.canAddItem(item)) { @@ -5312,7 +6901,7 @@ public boolean pickupEntity(Entity entity, boolean near) { } InventoryPickupItemEvent ev; - this.server.getPluginManager().callEvent(ev = new InventoryPickupItemEvent(this.inventory, (EntityItem) entity)); + this.server.getPluginManager().callEvent(ev = new InventoryPickupItemEvent(this.inventory, entityItem)); if (ev.isCancelled()) { return false; } @@ -5323,7 +6912,16 @@ public boolean pickupEntity(Entity entity, boolean near) { this.awardAchievement("mineWood"); break; case Item.DIAMOND: - this.awardAchievement("diamond"); + this.awardAchievement("diamonds"); + if (entityItem.droppedBy != null && entityItem.droppedBy != this) { + entityItem.droppedBy.awardAchievement("diamondsToYou"); + } + break; + case Item.LEATHER: + this.awardAchievement("killCow"); + break; + case Item.BLAZE_ROD: + this.awardAchievement("blazeRod"); break; } @@ -5333,7 +6931,7 @@ public boolean pickupEntity(Entity entity, boolean near) { Server.broadcastPacket(entity.getViewers().values(), pk); this.dataPacket(pk); - this.inventory.addItem(item.clone()); + this.inventory.addItem(item); // cloned in addItem entity.close(); return true; } @@ -5341,37 +6939,49 @@ public boolean pickupEntity(Entity entity, boolean near) { } } - int tick = this.getServer().getTick(); - if (pickedXPOrb < tick && entity instanceof EntityXPOrb && this.boundingBox.isVectorInside(entity)) { + if (pickedXPOrb < server.getTick() && entity instanceof EntityXPOrb && this.boundingBox.isVectorInside(entity)) { EntityXPOrb xpOrb = (EntityXPOrb) entity; if (xpOrb.getPickupDelay() <= 0) { int exp = xpOrb.getExp(); - entity.kill(); + entity.close(); this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_EXPERIENCE_ORB); - pickedXPOrb = tick; + pickedXPOrb = server.getTick(); - //Mending ArrayList itemsWithMending = new ArrayList<>(); for (int i = 0; i < 4; i++) { - if (inventory.getArmorItem(i).getEnchantment((short)Enchantment.ID_MENDING) != null) { + if (inventory.getArmorItem(i).hasEnchantment(Enchantment.ID_MENDING)) { itemsWithMending.add(inventory.getSize() + i); } } - if (inventory.getItemInHand().getEnchantment((short)Enchantment.ID_MENDING) != null) { + + if (inventory.getItemInHandFast().hasEnchantment(Enchantment.ID_MENDING)) { itemsWithMending.add(inventory.getHeldItemIndex()); } - if (itemsWithMending.size() > 0) { - Random rand = new Random(); - Integer itemToRepair = itemsWithMending.get(rand.nextInt(itemsWithMending.size())); - Item toRepair = inventory.getItem(itemToRepair); - if (toRepair instanceof ItemTool || toRepair instanceof ItemArmor) { - if (toRepair.getDamage() > 0) { - int dmg = toRepair.getDamage() - 2; + + Item offhand = this.getOffhandInventory().getItem(0); + if (offhand.getId() == Item.SHIELD && offhand.hasEnchantment(Enchantment.ID_MENDING)) { + itemsWithMending.add(-1); + } + + if (!itemsWithMending.isEmpty()) { + int itemToRepair = itemsWithMending.get(Utils.random.nextInt(itemsWithMending.size())); + boolean isOffhand = itemToRepair == -1; + + Item repaired = isOffhand ? offhand : inventory.getItem(itemToRepair); + if (repaired instanceof ItemTool || repaired instanceof ItemArmor) { + if (repaired.getDamage() > 0) { + int dmg = repaired.getDamage() - (exp << 1); // repair 2 points per xp if (dmg < 0) { dmg = 0; } - toRepair.setDamage(dmg); - inventory.setItem(itemToRepair, toRepair); + + repaired.setDamage(dmg); + + if (isOffhand) { + this.getOffhandInventory().setItem(0, repaired); + } else { + inventory.setItem(itemToRepair, repaired); + } return true; } } @@ -5403,6 +7013,11 @@ public boolean equals(Object obj) { return Objects.equals(this.getUniqueId(), other.getUniqueId()) && this.getId() == other.getId(); } + /** + * Check if the player is currently breaking a block + * + * @return is breaking a block + */ public boolean isBreakingBlock() { return this.breakingBlock != null; } @@ -5470,57 +7085,104 @@ public boolean doesTriggerPressurePlate() { return this.gamemode != SPECTATOR; } - @Override - public String toString() { - return "Player(name='" + getName() + - "', location=" + super.toString() + - ')'; - } - + /** + * Get ticks since sleeping in the current world last time + * + * @return ticks since sleeping + */ public int getTimeSinceRest() { return timeSinceRest; } - public void setTimeSinceRest(int timeSinceRest) { - this.timeSinceRest = timeSinceRest; + /** + * Set ticks since sleeping in the current world last time + * + * @param ticks ticks since sleeping + */ + public void setTimeSinceRest(int ticks) { + this.timeSinceRest = ticks; } - public NetworkPlayerSession getNetworkSession() { - return this.networkSession; + @Override + public String toString() { + return "Player(name='" + getName() + "', location=" + super.toString() + ')'; } - protected void processPreLogin() { - this.loginVerified = true; - final Player playerInstance = this; + @Override + public void setAirTicks(int ticks) { + if (this.airTicks != ticks) { + if (this.spawned || ticks > this.airTicks) { // Don't consume air before spawned + this.airTicks = ticks; + this.setDataPropertyAndSendOnlyToSelf(new ShortEntityData(DATA_AIR, ticks)); + } + } + } - this.preLoginEventTask = new AsyncTask() { - private PlayerAsyncPreLoginEvent event; + /** + * Send current held item to client + */ + private void syncHeldItem() { + InventorySlotPacket pk = new InventorySlotPacket(); + pk.slot = this.inventory.getHeldItemIndex(); + pk.item = this.inventory.getItem(pk.slot); + pk.inventoryId = ContainerIds.INVENTORY; + this.dataPacket(pk); + } - @Override - public void onRun() { - this.event = new PlayerAsyncPreLoginEvent(username, uuid, loginChainData, playerInstance.getSkin(), playerInstance.getAddress(), playerInstance.getPort()); - server.getPluginManager().callEvent(this.event); - } + /** + * Run every tick to send updated data if needed + */ + void resetPacketCounters() { + if (this.needSendAdventureSettings) { + this.needSendAdventureSettings = false; + this.adventureSettings.update(false); + } + if (this.needSendData) { + this.needSendData = false; + this.sendData(this); // Send data only once even if multiple actions fail + } + if (this.needSendFoodLevel) { + this.needSendFoodLevel = false; + this.foodData.sendFoodLevel(); + } + if (this.needSendInventory && this.spawned) { + this.needSendInventory = false; + this.getCursorInventory().sendContents(this); + this.sendAllInventories(); + } + if (this.needSendHeldItem && this.spawned) { + this.needSendHeldItem = false; + this.syncHeldItem(); + } + } - @Override - public void onCompletion(Server server) { - if (!playerInstance.connected) { - return; - } + /** + * Check whether player can eat (difficulty, gamemode, current food level) + * + * @param update send current food level to client + * @return can eat + */ + public boolean canEat(boolean update) { + if (this.foodData.getLevel() < this.foodData.getMaxLevel() || this.isCreative() || this.server.getDifficulty() == 0) { + return true; + } + if (update) { + this.needSendFoodLevel = true; + } + return false; + } - if (this.event.getLoginResult() == LoginResult.KICK) { - playerInstance.close(this.event.getKickMessage(), this.event.getKickMessage()); - } else if (playerInstance.shouldLogin) { - playerInstance.setSkin(this.event.getSkin()); - playerInstance.completeLoginSequence(); - for (Consumer action : this.event.getScheduledActions()) { - action.accept(server); - } - } - } - }; + /** + * Get Player's NetworkPlayerSession + * + * @return network session + */ + public NetworkPlayerSession getNetworkSession() { + return this.networkSession; + } - this.server.getScheduler().scheduleAsyncTask(this.preLoginEventTask); - this.processLogin(); + @Override + public final boolean canSaveToStorage() { + return false; } } diff --git a/src/main/java/cn/nukkit/PlayerFood.java b/src/main/java/cn/nukkit/PlayerFood.java index 90ff6a9a9ca..20a594980e1 100644 --- a/src/main/java/cn/nukkit/PlayerFood.java +++ b/src/main/java/cn/nukkit/PlayerFood.java @@ -9,14 +9,15 @@ import cn.nukkit.potion.Effect; /** + * This class handles player's food. + * * Created by funcraft on 2015/11/11. */ public class PlayerFood { - private int foodLevel = 20; - private final int maxFoodLevel; - private float foodSaturationLevel = 20f; - private int foodTickTimer = 0; + private int foodLevel; + private float foodSaturationLevel; + private short foodTickTimer = 0; private double foodExpLevel = 0; private final Player player; @@ -24,7 +25,6 @@ public class PlayerFood { public PlayerFood(Player player, int foodLevel, float foodSaturationLevel) { this.player = player; this.foodLevel = foodLevel; - this.maxFoodLevel = 20; this.foodSaturationLevel = foodSaturationLevel; } @@ -37,7 +37,7 @@ public int getLevel() { } public int getMaxLevel() { - return this.maxFoodLevel; + return 20; } public void setLevel(int foodLevel) { @@ -53,16 +53,16 @@ public void setLevel(int foodLevel, float saturationLevel) { foodLevel = 0; } - if (foodLevel <= 6 && !(this.getLevel() <= 6)) { - if (this.getPlayer().isSprinting()) { - this.getPlayer().setSprinting(false); + if (foodLevel <= 6 && !(this.foodLevel <= 6)) { + if (this.player.isSprinting()) { + this.player.setSprinting(false); } } - PlayerFoodLevelChangeEvent ev = new PlayerFoodLevelChangeEvent(this.getPlayer(), foodLevel, saturationLevel); - this.getPlayer().getServer().getPluginManager().callEvent(ev); + PlayerFoodLevelChangeEvent ev = new PlayerFoodLevelChangeEvent(this.player, foodLevel, saturationLevel); + this.player.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { - this.sendFoodLevel(this.getLevel()); + this.sendFoodLevel(this.foodLevel); return; } int foodLevel0 = ev.getFoodLevel(); @@ -81,10 +81,10 @@ public float getFoodSaturationLevel() { } public void setFoodSaturationLevel(float fsl) { - if (fsl > this.getLevel()) fsl = this.getLevel(); + if (fsl > this.foodLevel) fsl = this.foodLevel; if (fsl < 0) fsl = 0; - PlayerFoodLevelChangeEvent ev = new PlayerFoodLevelChangeEvent(this.getPlayer(), this.getLevel(), fsl); - this.getPlayer().getServer().getPluginManager().callEvent(ev); + PlayerFoodLevelChangeEvent ev = new PlayerFoodLevelChangeEvent(this.player, this.foodLevel, fsl); + this.player.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { return; } @@ -97,8 +97,8 @@ public void useHunger() { } public void useHunger(int amount) { - float sfl = this.getFoodSaturationLevel(); - int foodLevel = this.getLevel(); + float sfl = this.foodSaturationLevel; + int foodLevel = this.foodLevel; if (sfl > 0) { float newSfl = sfl - amount; if (newSfl < 0) newSfl = 0; @@ -113,11 +113,11 @@ public void addFoodLevel(Food food) { } public void addFoodLevel(int foodLevel, float fsl) { - this.setLevel(this.getLevel() + foodLevel, this.getFoodSaturationLevel() + fsl); + this.setLevel(this.foodLevel + foodLevel, this.foodSaturationLevel + fsl); } public void sendFoodLevel() { - this.sendFoodLevel(this.getLevel()); + this.sendFoodLevel(this.foodLevel); } public void reset() { @@ -129,51 +129,52 @@ public void reset() { } public void sendFoodLevel(int foodLevel) { - if (this.getPlayer().spawned) { - this.getPlayer().setAttribute(Attribute.getAttribute(Attribute.MAX_HUNGER).setValue(foodLevel)); + if (this.player.spawned) { + this.player.setAttribute(Attribute.getAttribute(Attribute.MAX_HUNGER).setValue(foodLevel).setDefaultValue(getMaxLevel())); } } public void update(int tickDiff) { - if (!this.getPlayer().isFoodEnabled()) return; - if (this.getPlayer().isAlive()) { + if (!this.player.isFoodEnabled()) return; + if (this.player.isAlive()) { int diff = Server.getInstance().getDifficulty(); - if (this.getLevel() > 17) { + if (this.foodLevel > 17 || diff == 0) { this.foodTickTimer += tickDiff; if (this.foodTickTimer >= 80) { - if (this.getPlayer().getHealth() < this.getPlayer().getMaxHealth()) { - EntityRegainHealthEvent ev = new EntityRegainHealthEvent(this.getPlayer(), 1, EntityRegainHealthEvent.CAUSE_EATING); - this.getPlayer().heal(ev); - //this.updateFoodExpLevel(3); + if (this.player.getHealth() < this.player.getRealMaxHealth()) { + EntityRegainHealthEvent ev = new EntityRegainHealthEvent(this.player, 1, EntityRegainHealthEvent.CAUSE_EATING); + this.player.heal(ev); + this.updateFoodExpLevel(6); } this.foodTickTimer = 0; } - } else if (this.getLevel() == 0) { + } else if (this.foodLevel == 0) { this.foodTickTimer += tickDiff; if (this.foodTickTimer >= 80) { - EntityDamageEvent ev = new EntityDamageEvent(this.getPlayer(), DamageCause.HUNGER, 1); - float now = this.getPlayer().getHealth(); + EntityDamageEvent ev = new EntityDamageEvent(this.player, DamageCause.HUNGER, 1); + float now = this.player.getHealth(); if (diff == 1) { - if (now > 10) this.getPlayer().attack(ev); + if (now > 10) this.player.attack(ev); } else if (diff == 2) { - if (now > 1) this.getPlayer().attack(ev); + if (now > 1) this.player.attack(ev); } else { - this.getPlayer().attack(ev); + this.player.attack(ev); } this.foodTickTimer = 0; } } - if (this.getPlayer().hasEffect(Effect.HUNGER)) { - this.updateFoodExpLevel(0.1 * (this.getPlayer().getEffect(Effect.HUNGER).getAmplifier() + 1)); + Effect hunger = this.player.getEffect(Effect.HUNGER); + if (hunger != null) { + this.updateFoodExpLevel(0.1 * (hunger.getAmplifier() + 1)); } } } public void updateFoodExpLevel(double use) { - if (!this.getPlayer().isFoodEnabled()) return; + if (!this.player.isFoodEnabled()) return; if (Server.getInstance().getDifficulty() == 0) return; - if (this.getPlayer().hasEffect(Effect.SATURATION)) return; + if (this.player.hasEffect(Effect.SATURATION)) return; this.foodExpLevel += use; if (this.foodExpLevel > 4) { this.useHunger(1); @@ -199,4 +200,9 @@ public void setFoodLevel(int foodLevel) { public void setFoodLevel(int foodLevel, float saturationLevel) { setLevel(foodLevel, saturationLevel); } + + @Override + public String toString() { + return "PlayerFood(player= " + player + ", foodLevel=" + foodLevel + ", foodSaturationLevel=" + foodSaturationLevel + ", foodTickTimer=" + foodTickTimer + ", foodExpLevel=" + foodExpLevel + ")"; + } } diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index fc95456c37c..169c7d69534 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -4,6 +4,8 @@ import cn.nukkit.blockentity.*; import cn.nukkit.command.*; import cn.nukkit.console.NukkitConsole; +import cn.nukkit.customblock.CustomBlockManager; +import cn.nukkit.dispenser.DispenseBehaviorRegister; import cn.nukkit.entity.Attribute; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityHuman; @@ -36,12 +38,8 @@ import cn.nukkit.level.format.LevelProvider; import cn.nukkit.level.format.LevelProviderManager; import cn.nukkit.level.format.anvil.Anvil; -import cn.nukkit.level.format.leveldb.LevelDB; -import cn.nukkit.level.format.mcregion.McRegion; -import cn.nukkit.level.generator.Flat; -import cn.nukkit.level.generator.Generator; -import cn.nukkit.level.generator.Nether; -import cn.nukkit.level.generator.Normal; +import cn.nukkit.level.format.leveldb.LevelDBProvider; +import cn.nukkit.level.generator.*; import cn.nukkit.math.NukkitMath; import cn.nukkit.metadata.EntityMetadataStore; import cn.nukkit.metadata.LevelMetadataStore; @@ -52,11 +50,10 @@ import cn.nukkit.nbt.tag.DoubleTag; import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.network.CompressBatchedTask; +import cn.nukkit.network.BatchingHelper; import cn.nukkit.network.Network; import cn.nukkit.network.RakNetInterface; import cn.nukkit.network.SourceInterface; -import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.protocol.PlayerListPacket; import cn.nukkit.network.protocol.ProtocolInfo; @@ -79,7 +76,6 @@ import cn.nukkit.scheduler.Task; import cn.nukkit.utils.*; import cn.nukkit.utils.bugreport.ExceptionHandler; -import co.aikar.timings.Timings; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import io.netty.buffer.ByteBuf; @@ -89,13 +85,8 @@ import org.iq80.leveldb.Options; import org.iq80.leveldb.impl.Iq80DBFactory; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; +import java.io.*; +import java.net.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; @@ -104,107 +95,75 @@ import java.util.regex.Pattern; /** + * The main server class + * * @author MagicDroidX * @author Box */ @Log4j2 public class Server { + /** + * Permission to receive admin broadcasts such as command usage. + */ public static final String BROADCAST_CHANNEL_ADMINISTRATIVE = "nukkit.broadcast.admin"; + /** + * Permission to receive common broadcasts such as join/quit/death/achievement messages. + */ public static final String BROADCAST_CHANNEL_USERS = "nukkit.broadcast.user"; - private static Server instance = null; - - private BanList banByName; - - private BanList banByIP; - - private Config operators; - - private Config whitelist; + private static Server instance; - private AtomicBoolean isRunning = new AtomicBoolean(true); + private final BanList banByName; + private final BanList banByIP; + private final Config operators; + private final Config whitelist; + private final Config properties; + private final Config config; - private boolean hasStopped = false; - - private PluginManager pluginManager; + private final String filePath; + private final String dataPath; + private final String pluginPath; - private ServerScheduler scheduler; + private final PluginManager pluginManager; + private final ServerScheduler scheduler; + private final BaseLang baseLang; + private final NukkitConsole console; + private final ConsoleThread consoleThread; + private final SimpleCommandMap commandMap; + private final CraftingManager craftingManager; + private final ResourcePackManager resourcePackManager; + private final ConsoleCommandSender consoleSender; + private boolean hasStopped; + private final AtomicBoolean isRunning = new AtomicBoolean(true); private int tickCounter; - private long nextTick; - private final float[] tickAverage = {20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20}; - private final float[] useAverage = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - private float maxTick = 20; - private float maxUse = 0; - - private final NukkitConsole console; - private final ConsoleThread consoleThread; - - private SimpleCommandMap commandMap; - - private CraftingManager craftingManager; - - private ResourcePackManager resourcePackManager; - - private ConsoleCommandSender consoleSender; - - private int maxPlayers; - - private boolean autoSave = true; - - private RCON rcon; - - private EntityMetadataStore entityMetadata; - - private PlayerMetadataStore playerMetadata; - - private LevelMetadataStore levelMetadata; - - private Network network; - - private boolean networkCompressionAsync; - public int networkCompressionLevel; - private int networkZlibProvider; - public int networkCompressionThreshold; - public boolean encryptionEnabled; - - private boolean autoTickRate; - private int autoTickRateLimit; - private boolean alwaysTickPlayers; private int baseTickRate; - private Boolean getAllowFlight = null; - private int difficulty = Integer.MAX_VALUE; - private int defaultGamemode = Integer.MAX_VALUE; - - private int autoSaveTicker = 0; - private int autoSaveTicks = 6000; - - private BaseLang baseLang; - - private boolean forceLanguage; - - private UUID serverID; - - private final String filePath; - private final String dataPath; - private final String pluginPath; - + private int autoSaveTicker; + private int maxPlayers; // setMaxPlayers + private boolean autoSave = true; // setAutoSave + private int difficulty; // setDifficulty + int c_s_spawnThreshold; + private String ip; + private int port; + private final UUID serverID; + private RCON rcon; + private final Network network; private QueryHandler queryHandler; - private QueryRegenerateEvent queryRegenerateEvent; - - private Config properties; - private Config config; + private final EntityMetadataStore entityMetadata; + private final PlayerMetadataStore playerMetadata; + private final LevelMetadataStore levelMetadata; private final Map players = new HashMap<>(); + final Map playerList = new HashMap<>(); - private final Map playerList = new HashMap<>(); + private static final Pattern uuidPattern = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}.dat$"); private final Map levels = new HashMap() { public Level put(Integer key, Level value) { @@ -212,13 +171,11 @@ public Level put(Integer key, Level value) { levelArray = levels.values().toArray(new Level[0]); return result; } - public boolean remove(Object key, Object value) { boolean result = super.remove(key, value); levelArray = levels.values().toArray(new Level[0]); return result; } - public Level remove(Object key) { Level result = super.remove(key); levelArray = levels.values().toArray(new Level[0]); @@ -227,22 +184,154 @@ public Level remove(Object key) { }; private Level[] levelArray = new Level[0]; - private final ServiceManager serviceManager = new NKServiceManager(); - - private Level defaultLevel = null; - - private boolean allowNether; - + private Level defaultLevel; private final Thread currentThread; - private Watchdog watchdog; - - private DB nameLookup; - + private final DB nameLookup; private PlayerDataSerializer playerDataSerializer; + private final BatchingHelper batchingHelper; - private final Set ignoredPackets = new HashSet<>(); + /** + * The server's MOTD. Remember to call network.setName() when updated. + */ + public String motd; + /** + * Default player data saving enabled. + */ + public boolean shouldSavePlayerData; + /** + * Anti fly checks enabled. + */ + public boolean allowFlight; + /** + * Hardcore mode enabled. + */ + public boolean isHardcore; + /** + * Force resource packs. + */ + public boolean forceResources; + /** + * Force player gamemode to default on every join. + */ + public boolean forceGamemode; + /** + * The nether dimension and portals enabled. + */ + public boolean netherEnabled; + /** + * Whitelist enabled. + */ + public boolean whitelistEnabled; + /** + * Xbox authentication enabled. + */ + public boolean xboxAuth; + /** + * Server side achievements enabled. + */ + public boolean achievementsEnabled; + /** + * The end dimension and portals enabled. + */ + public boolean endEnabled; + /** + * Pvp enabled. Can be changed per world using game rules. + */ + public boolean pvpEnabled; + /** + * Announce server side announcements to all players. + */ + public boolean announceAchievements; + /** + * How many chunks are sent to player per tick. + */ + public int chunksPerTick; + /** + * How many chunks needs to be sent before the player can spawn. + */ + public int spawnThreshold; + /** + * Zlib compression level for sent packets. + */ + public int networkCompressionLevel; + /** + * Maximum view distance in chunks. + */ + public int viewDistance; + /** + * Server's default gamemode. + */ + public int gamemode; + /** + * Minimum amount of time between player skin changes. + */ + public int skinChangeCooldown; + /** + * Spawn protection radius. + */ + public int spawnRadius; + /** + * How often auto save should happen. + */ + public int autoSaveTicks; + /** + * Limit automatic tick rate. + */ + public int autoTickRateLimit; + /** + * Set LevelDB cache size. + */ + public int levelDbCache; + /** + * Use native LevelDB implementation for better performance. + */ + public boolean useNativeLevelDB; + /** + * Showing plugins in query enabled. + */ + public boolean queryPlugins; + /** + * Chunk caching enabled. + */ + public boolean cacheChunks; + /** + * Whether attacking an entity should stop player from sprinting. + */ + public boolean attackStopSprint; + /** + * Enable automatic tick rate adjustments. + */ + public boolean autoTickRate; + /** + * Force server side translations. + */ + public boolean forceLanguage; + /** + * Always tick players. + */ + public boolean alwaysTickPlayers; + /** + * Don't disable client's own packs when force-resources is enabled. + */ + public boolean forceResourcesAllowOwnPacks; + /** + * Enable encryption. + */ + public boolean encryptionEnabled; + /** + * Use Snappy for packet compression for 1.19.30+ clients. + */ + public final boolean useSnappy; + /** + * Batch packets smaller than this will not be compressed. + */ + public int networkCompressionThreshold; + /** + * Temporary disable world saving to allow safe backup of leveldb worlds. + */ + public boolean holdWorldSave; Server(final String filePath, String dataPath, String pluginPath, String predefinedLanguage) { Preconditions.checkState(instance == null, "Already initialized!"); @@ -262,16 +351,14 @@ public Level remove(Object key) { new File(pluginPath).mkdirs(); } - this.dataPath = new File(dataPath).getAbsolutePath() + "/"; - this.pluginPath = new File(pluginPath).getAbsolutePath() + "/"; - - this.console = new NukkitConsole(this); - this.consoleThread = new ConsoleThread(); - this.consoleThread.start(); + this.dataPath = new File(dataPath).getAbsolutePath() + '/'; + this.pluginPath = new File(pluginPath).getAbsolutePath() + '/'; this.playerDataSerializer = new DefaultPlayerDataSerializer(this); - //todo: VersionString 现在不必要 + this.console = new NukkitConsole(); + this.consoleThread = new ConsoleThread(); + this.consoleThread.start(); if (!new File(this.dataPath + "nukkit.yml").exists()) { this.getLogger().info(TextFormat.GREEN + "Welcome! Please choose a language first!"); @@ -318,14 +405,12 @@ public Level remove(Object key) { } catch (IOException e) { throw new RuntimeException(e); } - } - this.console.setExecutingCommands(true); - - log.info("Loading {} ...", TextFormat.GREEN + "nukkit.yml" + TextFormat.WHITE); this.config = new Config(this.dataPath + "nukkit.yml", Config.YAML); + log.info("Loading server properties..."); + Nukkit.DEBUG = NukkitMath.clamp(this.getConfig("debug.level", 1), 1, 3); int logLevel = (Nukkit.DEBUG + 3) * 100; @@ -337,53 +422,22 @@ public Level remove(Object key) { } } - ignoredPackets.addAll(getConfig().getStringList("debug.ignored-packets")); - ignoredPackets.add("BatchPacket"); - - log.info("Loading {} ...", TextFormat.GREEN + "server.properties" + TextFormat.WHITE); - this.properties = new Config(this.dataPath + "server.properties", Config.PROPERTIES, new ConfigSection() { - { - put("motd", "A Nukkit Powered Server"); - put("sub-motd", "https://nukkitx.com"); - put("server-port", 19132); - put("server-ip", "0.0.0.0"); - put("view-distance", 10); - put("white-list", false); - put("achievements", true); - put("announce-player-achievements", true); - put("spawn-protection", 16); - put("max-players", 20); - put("allow-flight", false); - put("spawn-animals", true); - put("spawn-mobs", true); - put("gamemode", 0); - put("force-gamemode", false); - put("hardcore", false); - put("pvp", true); - put("difficulty", 1); - put("generator-settings", ""); - put("level-name", "world"); - put("level-seed", ""); - put("level-type", "DEFAULT"); - put("allow-nether", true); - put("enable-query", true); - put("enable-rcon", false); - put("rcon.password", Base64.getEncoder().encodeToString(UUID.randomUUID().toString().replace("-", "").getBytes()).substring(3, 13)); - put("auto-save", true); - put("force-resources", false); - put("xbox-auth", true); - } - }); + //ignoredPackets.addAll(getConfig().getStringList("debug.ignored-packets")); + //ignoredPackets.add("BatchPacket"); - // Allow Nether? (determines if we create a nether world if one doesn't exist on startup) - this.allowNether = this.properties.getBoolean("allow-nether", true); + this.properties = new Config(this.dataPath + "server.properties", Config.PROPERTIES, new ServerProperties()); + + // Should not be modified after startup + this.useSnappy = this.getConfig("network.compression-use-snappy", false); - this.forceLanguage = this.getConfig("settings.force-language", false); this.baseLang = new BaseLang(this.getConfig("settings.language", BaseLang.FALLBACK_LANGUAGE)); + + this.loadSettings(); + log.info(this.getLanguage().translateString("language.selected", new String[]{getLanguage().getName(), getLanguage().getLang()})); log.info(getLanguage().translateString("nukkit.server.start", TextFormat.AQUA + this.getVersion() + TextFormat.RESET)); - Object poolSize = this.getConfig("settings.async-workers", (Object) "auto"); + Object poolSize = this.getConfig("settings.async-workers", "auto"); if (!(poolSize instanceof Integer)) { try { poolSize = Integer.valueOf((String) poolSize); @@ -394,30 +448,17 @@ public Level remove(Object key) { ServerScheduler.WORKERS = (int) poolSize; - this.networkZlibProvider = this.getConfig("network.zlib-provider", 2); - Zlib.setProvider(this.networkZlibProvider); - - this.networkCompressionLevel = this.getConfig("network.compression-level", 7); - this.networkCompressionAsync = this.getConfig("network.async-compression", true); - this.networkCompressionThreshold = this.getConfig("network.batch-threshold", 256); - this.encryptionEnabled = this.getConfig("network.encryption", false); - - if (!this.encryptionEnabled) { - this.getLogger().warning("Encryption is not enabled. For better security, it's recommended to enable it (network.encryption=true in nukkit.yml) if you don't use a proxy software."); - } + this.scheduler = new ServerScheduler(); - this.autoTickRate = this.getConfig("level-settings.auto-tick-rate", true); - this.autoTickRateLimit = this.getConfig("level-settings.auto-tick-rate-limit", 20); - this.alwaysTickPlayers = this.getConfig("level-settings.always-tick-players", false); - this.baseTickRate = this.getConfig("level-settings.base-tick-rate", 1); + this.console.setExecutingCommands(true); // Scheduler needs to be ready - this.scheduler = new ServerScheduler(); + this.batchingHelper = new BatchingHelper(); if (this.getPropertyBoolean("enable-rcon", false)) { try { - this.rcon = new RCON(this, this.getPropertyString("rcon.password", ""), (!this.getIp().equals("")) ? this.getIp() : "0.0.0.0", this.getPropertyInt("rcon.port", this.getPort())); + this.rcon = new RCON(this, this.getPropertyString("rcon.password", ""), (!this.getIp().isEmpty()) ? this.getIp() : "0.0.0.0", this.getPropertyInt("rcon.port", this.getPort())); } catch (IllegalArgumentException e) { - log.error(getLanguage().translateString(e.getMessage(), e.getCause().getMessage())); + log.error(baseLang.translateString(e.getMessage(), e.getCause().getMessage())); } } @@ -432,64 +473,37 @@ public Level remove(Object key) { this.banByIP = new BanList(this.dataPath + "banned-ips.json"); this.banByIP.load(); - this.maxPlayers = this.getPropertyInt("max-players", 20); - this.setAutoSave(this.getPropertyBoolean("auto-save", true)); - - if (this.getPropertyBoolean("hardcore", false) && this.getDifficulty() < 3) { - this.setPropertyInt("difficulty", 3); - } - - boolean bugReport; - if (this.getConfig().exists("settings.bug-report")) { - bugReport = this.getConfig().getBoolean("settings.bug-report"); - this.getProperties().remove("bug-report"); - } else { - bugReport = this.getPropertyBoolean("bug-report", true); //backwards compat - } - if (bugReport) { - ExceptionHandler.registerExceptionHandler(); - } - - log.info(this.getLanguage().translateString("nukkit.server.networkStart", new String[]{this.getIp().equals("") ? "*" : this.getIp(), String.valueOf(this.getPort())})); - this.serverID = UUID.randomUUID(); - - this.network = new Network(this); - this.network.setName(this.getMotd()); - this.network.setSubName(this.getSubMotd()); - - log.info(this.getLanguage().translateString("nukkit.server.info", this.getName(), TextFormat.YELLOW + this.getNukkitVersion() + TextFormat.WHITE, TextFormat.AQUA + this.getCodename() + TextFormat.WHITE, this.getApiVersion())); - log.info(this.getLanguage().translateString("nukkit.server.license", this.getName())); - this.consoleSender = new ConsoleCommandSender(); this.commandMap = new SimpleCommandMap(this); - // Initialize metrics - new NukkitMetrics(this); - - this.registerEntities(); - this.registerBlockEntities(); + registerEntities(); + registerBlockEntities(); Block.init(); Enchantment.init(); + GlobalBlockPalette.init(); RuntimeItems.init(); Item.init(); - EnumBiome.values(); //load class, this also registers biomes + EnumBiome.values(); Effect.init(); Potion.init(); Attribute.init(); - GlobalBlockPalette.getOrCreateRuntimeId(0, 0); //Force it to load + DispenseBehaviorRegister.init(); + CustomBlockManager.init(this); - // Convert legacy data before plugins get the chance to mess with it. + // Convert legacy data before plugins get the chance to mess with it try { nameLookup = Iq80DBFactory.factory.open(new File(dataPath, "players"), new Options() - .createIfMissing(true) - .compressionType(CompressionType.ZLIB_RAW)); + .createIfMissing(true) + .compressionType(CompressionType.ZLIB_RAW)); } catch (IOException e) { throw new RuntimeException(e); } convertLegacyPlayerData(); + this.serverID = UUID.randomUUID(); + this.craftingManager = new CraftingManager(); this.resourcePackManager = new ResourcePackManager(new File(Nukkit.DATA_PATH, "resource_packs")); @@ -500,21 +514,49 @@ public Level remove(Object key) { this.queryRegenerateEvent = new QueryRegenerateEvent(this, 5); + log.info(this.baseLang.translateString("nukkit.server.networkStart", new String[]{this.getIp().isEmpty() ? "*" : this.getIp(), String.valueOf(this.getPort())})); + this.network = new Network(this); + this.network.setName(this.getMotd()); + this.network.setSubName(this.getSubMotd()); this.network.registerInterface(new RakNetInterface(this)); - this.pluginManager.loadPlugins(this.pluginPath); + if (!this.encryptionEnabled) { + this.getLogger().warning("Encryption is not enabled! For better security, it's recommended to enable it (network.encryption: true in nukkit.yml) if you don't use a proxy software."); + } + + if (!this.xboxAuth) { + this.getLogger().warning("Xbox authentication is not enabled! It's recommended to enable it (xbox-auth=on in server.properties) if you don't use a proxy software or an authentication plugin."); + } + + log.info(this.getLanguage().translateString("nukkit.server.info", this.getName(), TextFormat.YELLOW + this.getNukkitVersion() + TextFormat.WHITE, TextFormat.AQUA + this.getCodename() + TextFormat.WHITE, this.getApiVersion())); + log.info(this.getLanguage().translateString("nukkit.server.license", this.getName())); + + ExceptionHandler.registerExceptionHandler(); + this.pluginManager.loadPlugins(this.pluginPath); this.enablePlugins(PluginLoadOrder.STARTUP); + try { + if (CustomBlockManager.get().closeRegistry()) { + RuntimeItems.getMapping().generatePalette(); + } + + Item.initCreativeItems(); + } catch (Exception e) { + throw new IllegalStateException("Failed to init custom blocks", e); + } + + craftingManager.rebuildPacket(); + LevelProviderManager.addProvider(this, Anvil.class); - LevelProviderManager.addProvider(this, McRegion.class); - LevelProviderManager.addProvider(this, LevelDB.class); + LevelProviderManager.addProvider(this, LevelDBProvider.class); Generator.addGenerator(Flat.class, "flat", Generator.TYPE_FLAT); Generator.addGenerator(Normal.class, "normal", Generator.TYPE_INFINITE); Generator.addGenerator(Normal.class, "default", Generator.TYPE_INFINITE); Generator.addGenerator(Nether.class, "nether", Generator.TYPE_NETHER); - //todo: add old generator and hell generator + Generator.addGenerator(End.class, "the_end", Generator.TYPE_THE_END); + Generator.addGenerator(cn.nukkit.level.generator.Void.class, "void", Generator.TYPE_VOID); for (String name : this.getConfig("worlds", new HashMap()).keySet()) { if (!this.loadLevel(name)) { @@ -564,20 +606,22 @@ public Level remove(Object key) { this.setDefaultLevel(this.getLevelByName(defaultName)); } - this.properties.save(true); - - if (this.getDefaultLevel() == null) { - this.getLogger().emergency(this.getLanguage().translateString("nukkit.level.defaultError")); + if (this.defaultLevel == null) { + this.getLogger().emergency(this.baseLang.translateString("nukkit.level.defaultError")); this.forceShutdown(); - return; } - EnumLevel.initLevels(); + this.properties.save(true); - if (this.getConfig("ticks-per.autosave", 6000) > 0) { - this.autoSaveTicks = this.getConfig("ticks-per.autosave", 6000); - } + //for (Map.Entry entry : this.getLevels().entrySet()) { + Level level = this.defaultLevel;//entry.getValue(); + this.getLogger().debug("Preparing spawn region for level " + level.getName()); + Position spawn = level.getSpawnLocation(); + level.populateChunk(spawn.getChunkX(), spawn.getChunkZ(), true); + //} + + EnumLevel.initLevels(); this.enablePlugins(PluginLoadOrder.POSTWORLD); @@ -586,6 +630,9 @@ public Level remove(Object key) { this.watchdog.start(); } + // Initialize metrics + new NukkitMetrics(this); + this.start(); } @@ -658,81 +705,35 @@ public int broadcast(TextContainer message, String permissions) { } public static void broadcastPacket(Collection players, DataPacket packet) { - packet.tryEncode(); - for (Player player : players) { player.dataPacket(packet); } } public static void broadcastPacket(Player[] players, DataPacket packet) { - packet.tryEncode(); - for (Player player : players) { player.dataPacket(packet); } } - @Deprecated public void batchPackets(Player[] players, DataPacket[] packets) { - this.batchPackets(players, packets, false); - } - - @Deprecated - public void batchPackets(Player[] players, DataPacket[] packets, boolean forceSync) { if (players == null || packets == null || players.length == 0 || packets.length == 0) { return; } - BatchPacketsEvent ev = new BatchPacketsEvent(players, packets, forceSync); - getPluginManager().callEvent(ev); + BatchPacketsEvent ev = new BatchPacketsEvent(players, packets, true); + pluginManager.callEvent(ev); if (ev.isCancelled()) { return; } - Timings.playerNetworkSendTimer.startTiming(); - byte[][] payload = new byte[packets.length * 2][]; - for (int i = 0; i < packets.length; i++) { - DataPacket p = packets[i]; - int idx = i * 2; - p.tryEncode(); - byte[] buf = p.getBuffer(); - payload[idx] = Binary.writeUnsignedVarInt(buf.length); - payload[idx + 1] = buf; - packets[i] = null; - } - - List targets = new ArrayList<>(); - for (Player p : players) { - if (p.isConnected()) { - targets.add(p.getSocketAddress()); - } - } - - if (!forceSync && this.networkCompressionAsync) { - this.getScheduler().scheduleAsyncTask(new CompressBatchedTask(payload, targets, this.networkCompressionLevel)); - } else { - try { - byte[] data = Binary.appendBytes(payload); - this.broadcastPacketsCallback(Network.deflateRaw(data, this.networkCompressionLevel), targets); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - Timings.playerNetworkSendTimer.stopTiming(); - } - - public void broadcastPacketsCallback(byte[] data, List targets) { - BatchPacket pk = new BatchPacket(); - pk.payload = data; - - for (InetSocketAddress i : targets) { - if (this.players.containsKey(i)) { - this.players.get(i).dataPacket(pk); - } - } + this.batchingHelper.batchPackets(players, packets); } + /** + * Enable all plugins with matching load order + * @param type load order + */ public void enablePlugins(PluginLoadOrder type) { for (Plugin plugin : new ArrayList<>(this.pluginManager.getPlugins().values())) { if (!plugin.isEnabled() && type == plugin.getDescription().getOrder()) { @@ -746,21 +747,33 @@ public void enablePlugins(PluginLoadOrder type) { } } + /** + * Enable a plugin + * @param plugin plugin + */ public void enablePlugin(Plugin plugin) { this.pluginManager.enablePlugin(plugin); } + /** + * Disable all loaded plugins + */ public void disablePlugins() { this.pluginManager.disablePlugins(); } + /** + * Run a command as CommandSender. Use server.getConsoleSender() to run as CONSOLE. + * @param sender command sender + * @param commandLine command without slash + * @return command was found and attempted to be executed + */ public boolean dispatchCommand(CommandSender sender, String commandLine) throws ServerException { // First we need to check if this command is on the main thread or not, if not, warn the user if (!this.isPrimaryThread()) { - getLogger().warning("Command Dispatched Async: " + commandLine); - getLogger().warning("Please notify author of plugin causing this execution to fix this bug!", new Throwable()); - // TODO: We should sync the command to the main thread too! + getLogger().warning("Command dispatched asynchronously: " + commandLine); } + if (sender == null) { throw new ServerException("CommandSender is not valid"); } @@ -774,57 +787,68 @@ public boolean dispatchCommand(CommandSender sender, String commandLine) throws return false; } - //todo: use ticker to check console + /** + * Get server console CommandSender + * @return ConsoleCommandSender + */ public ConsoleCommandSender getConsoleSender() { return consoleSender; } + /** + * Reload the server. Notice: may cause issues with some plugins. + */ public void reload() { - log.info("Reloading..."); - log.info("Saving levels..."); - for (Level level : this.levelArray) { level.save(); } - this.pluginManager.disablePlugins(); this.pluginManager.clearPlugins(); this.commandMap.clearCommands(); - log.info("Reloading properties..."); + log.info("Reloading server properties..."); this.properties.reload(); - this.maxPlayers = this.getPropertyInt("max-players", 20); - - if (this.getPropertyBoolean("hardcore", false) && this.getDifficulty() < 3) { - this.setPropertyInt("difficulty", difficulty = 3); - } + this.loadSettings(); this.banByIP.load(); this.banByName.load(); this.reloadWhitelist(); this.operators.reload(); - for (BanEntry entry : this.getIPBans().getEntires().values()) { + for (BanEntry entry : this.banByIP.getEntires().values()) { try { - this.getNetwork().blockAddress(InetAddress.getByName(entry.getName()), -1); - } catch (UnknownHostException e) { - // ignore - } + this.network.blockAddress(InetAddress.getByName(entry.getName())); + } catch (UnknownHostException ignore) {} } + log.info("Reloading plugins..."); this.pluginManager.registerInterface(JavaPluginLoader.class); this.pluginManager.loadPlugins(this.pluginPath); this.enablePlugins(PluginLoadOrder.STARTUP); this.enablePlugins(PluginLoadOrder.POSTWORLD); - Timings.reset(); } + /** + * Mark the server to be shut down. + */ public void shutdown() { isRunning.compareAndSet(true, false); } + /** + * Shut down the server immediately. + */ public void forceShutdown() { + this.forceShutdown(this.getConfig("settings.shutdown-message", "Server closed")); + } + + /** + * Shut down the server immediately. + * + * @param reason message that shows to players on disconnect + */ + public void forceShutdown(String reason) { if (this.hasStopped) { return; } @@ -835,103 +859,117 @@ public void forceShutdown() { this.hasStopped = true; ServerStopEvent serverStopEvent = new ServerStopEvent(); - getPluginManager().callEvent(serverStopEvent); + pluginManager.callEvent(serverStopEvent); + + if (this.holdWorldSave) { + this.getLogger().warning("World save hold was not released! Any backup currently being taken may be invalid"); + } if (this.rcon != null) { + this.getLogger().debug("Closing RCON..."); this.rcon.close(); } + this.getLogger().debug("Disconnecting all players..."); for (Player player : new ArrayList<>(this.players.values())) { - player.close(player.getLeaveMessage(), this.getConfig("settings.shutdown-message", "Server closed")); + player.close(player.getLeaveMessage(), reason); } - this.getLogger().debug("Disabling all plugins"); - this.pluginManager.disablePlugins(); + this.getLogger().debug("Disabling all plugins..."); + this.disablePlugins(); - this.getLogger().debug("Removing event handlers"); + this.getLogger().debug("Removing event handlers..."); HandlerList.unregisterAll(); - this.getLogger().debug("Stopping all tasks"); + this.getLogger().debug("Stopping all tasks..."); this.scheduler.cancelAllTasks(); this.scheduler.mainThreadHeartbeat(Integer.MAX_VALUE); - this.getLogger().debug("Unloading all levels"); + this.getLogger().debug("Unloading all levels..."); for (Level level : this.levelArray) { this.unloadLevel(level, true); + this.nextTick = System.currentTimeMillis(); // Fix Watchdog killing the server while saving worlds } - this.getLogger().debug("Closing console"); + this.getLogger().debug("Closing console..."); this.consoleThread.interrupt(); - this.getLogger().debug("Stopping network interfaces"); + this.getLogger().debug("Closing BatchingHelper..."); + this.batchingHelper.shutdown(); + + this.getLogger().debug("Stopping network interfaces..."); for (SourceInterface interfaz : this.network.getInterfaces()) { interfaz.shutdown(); this.network.unregisterInterface(interfaz); } if (nameLookup != null) { + this.getLogger().debug("Closing name lookup DB..."); nameLookup.close(); } - this.getLogger().debug("Disabling timings"); - Timings.stopServer(); + this.getLogger().debug("Stopping Watchdog..."); if (this.watchdog != null) { this.watchdog.kill(); } - //todo other things } catch (Exception e) { log.fatal("Exception happened while shutting down, exiting the process", e); System.exit(1); } } + /** + * Internal: Start the server + */ public void start() { - if (this.getPropertyBoolean("enable-query", true)) { + if (this.getPropertyBoolean("enable-query", false)) { this.queryHandler = new QueryHandler(); } - for (BanEntry entry : this.getIPBans().getEntires().values()) { + for (BanEntry entry : this.banByIP.getEntires().values()) { try { - this.network.blockAddress(InetAddress.getByName(entry.getName()), -1); - } catch (UnknownHostException e) { - // ignore - } + this.network.blockAddress(InetAddress.getByName(entry.getName())); + } catch (UnknownHostException ignore) {} } - //todo send usage setting this.tickCounter = 0; - log.info(this.getLanguage().translateString("nukkit.server.defaultGameMode", getGamemodeString(this.getGamemode()))); + //log.info(this.getLanguage().translateString("nukkit.server.defaultGameMode", getGamemodeString(this.getGamemode()))); - log.info(this.getLanguage().translateString("nukkit.server.startFinished", String.valueOf((double) (System.currentTimeMillis() - Nukkit.START_TIME) / 1000))); + log.info(this.baseLang.translateString("nukkit.server.startFinished", String.valueOf((double) (System.currentTimeMillis() - Nukkit.START_TIME) / 1000))); this.tickProcessor(); this.forceShutdown(); } + private static final byte[] PREFIX = {(byte) 0xfe, (byte) 0xfd}; + + /** + * Internal: Handle query + * @param address sender address + * @param payload payload + */ public void handlePacket(InetSocketAddress address, ByteBuf payload) { try { - if (!payload.isReadable(3)) { + if (this.queryHandler == null || !payload.isReadable(3)) { return; } byte[] prefix = new byte[2]; payload.readBytes(prefix); - - if (!Arrays.equals(prefix, new byte[]{(byte) 0xfe, (byte) 0xfd})) { - return; - } - if (this.queryHandler != null) { + if (Arrays.equals(prefix, PREFIX)) { this.queryHandler.handle(address, payload); } } catch (Exception e) { log.error("Error whilst handling packet", e); - - this.network.blockAddress(address.getAddress(), -1); + this.network.blockAddress(address.getAddress(), 300); } } private int lastLevelGC; + /** + * Internal: Tick the server + */ public void tickProcessor() { this.nextTick = System.currentTimeMillis(); try { @@ -945,22 +983,25 @@ public void tickProcessor() { if (next - 0.1 > current) { long allocated = next - current - 1; - { // Instead of wasting time, do something potentially useful - int offset = 0; - for (int i = 0; i < levelArray.length; i++) { - offset = (i + lastLevelGC) % levelArray.length; - Level level = levelArray[offset]; + // Instead of wasting time, do something potentially useful + int offset = 0; + for (int i = 0; i < levelArray.length; i++) { + offset = (i + lastLevelGC) % levelArray.length; + Level level = levelArray[offset]; + if (!level.isBeingConverted) { level.doGarbageCollection(allocated - 1); - allocated = next - System.currentTimeMillis(); - if (allocated <= 0) { - break; - } } - lastLevelGC = offset + 1; + allocated = next - System.currentTimeMillis(); + if (allocated <= 0) break; } + lastLevelGC = offset + 1; if (allocated > 0) { - Thread.sleep(allocated, 900000); + try { + Thread.sleep(allocated, 900000); + } catch (Exception e) { + this.getLogger().logException(e); + } } } } catch (RuntimeException e) { @@ -974,11 +1015,8 @@ public void tickProcessor() { } public void onPlayerCompleteLoginSequence(Player player) { - this.sendFullPlayerListData(player); - } - - public void onPlayerLogin(Player player) { - + this.playerList.put(player.getUniqueId(), player); + this.updatePlayerListData(player.getUniqueId(), player.getId(), player.getDisplayName(), player.getSkin(), player.getLoginChainData().getXUID()); } public void addPlayer(InetSocketAddress socketAddress, Player player) { @@ -991,9 +1029,7 @@ public void addOnlinePlayer(Player player) { } public void removeOnlinePlayer(Player player) { - if (this.playerList.containsKey(player.getUniqueId())) { - this.playerList.remove(player.getUniqueId()); - + if (this.playerList.remove(player.getUniqueId()) != null) { PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_REMOVE; pk.entries = new PlayerListPacket.Entry[]{new PlayerListPacket.Entry(player.getUniqueId())}; @@ -1018,7 +1054,7 @@ public void updatePlayerListData(UUID uuid, long entityId, String name, Skin ski PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_ADD; pk.entries = new PlayerListPacket.Entry[]{new PlayerListPacket.Entry(uuid, entityId, name, skin, xboxUserId)}; - Server.broadcastPacket(players, pk); + this.batchPackets(players, new DataPacket[]{pk}); // This is sent "directly" so it always gets through before possible TYPE_REMOVE packet for NPCs etc. } public void updatePlayerListData(UUID uuid, long entityId, String name, Skin skin, String xboxUserId, Collection players) { @@ -1033,7 +1069,13 @@ public void removePlayerListData(UUID uuid, Player[] players) { PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_REMOVE; pk.entries = new PlayerListPacket.Entry[]{new PlayerListPacket.Entry(uuid)}; - Server.broadcastPacket(players, pk); + for (Player player : players) { + player.dataPacket(pk); + } + } + + public void removePlayerListData(UUID uuid, Collection players) { + this.removePlayerListData(uuid, players.toArray(new Player[0])); } public void removePlayerListData(UUID uuid, Player player) { @@ -1043,22 +1085,17 @@ public void removePlayerListData(UUID uuid, Player player) { player.dataPacket(pk); } - public void removePlayerListData(UUID uuid, Collection players) { - this.removePlayerListData(uuid, players.toArray(new Player[0])); - } - public void sendFullPlayerListData(Player player) { PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_ADD; pk.entries = this.playerList.values().stream() .map(p -> new PlayerListPacket.Entry( - p.getUniqueId(), - p.getId(), - p.getDisplayName(), - p.getSkin(), - p.getLoginChainData().getXUID())) + p.getUniqueId(), + p.getId(), + p.getDisplayName(), + p.getSkin(), + p.getLoginChainData().getXUID())) .toArray(PlayerListPacket.Entry[]::new); - player.dataPacket(pk); } @@ -1066,23 +1103,20 @@ public void sendRecipeList(Player player) { player.dataPacket(CraftingManager.packet); } - private void checkTickUpdates(int currentTick, long tickTime) { - for (Player p : new ArrayList<>(this.players.values())) { - /*if (!p.loggedIn && (tickTime - p.creationTime) >= 10000 && p.kick(PlayerKickEvent.Reason.LOGIN_TIMEOUT, "Login timeout")) { - continue; - } - - client freezes when applying resource packs - todo: fix*/ - - if (this.alwaysTickPlayers) { + private void checkTickUpdates(int currentTick) { + if (this.alwaysTickPlayers) { + for (Player p : new ArrayList<>(this.players.values())) { p.onUpdate(currentTick); } } - //Do level ticks + for (Player p : this.getOnlinePlayers().values()) { + p.resetPacketCounters(); + } + + // Do level ticks for (Level level : this.levelArray) { - if (level.getTickRate() > this.baseTickRate && --level.tickRateCounter > 0) { + if (level.isBeingConverted || (level.getTickRate() > this.baseTickRate && --level.tickRateCounter > 0)) { continue; } @@ -1103,74 +1137,72 @@ private void checkTickUpdates(int currentTick, long tickTime) { } else if (tickMs >= 50) { if (level.getTickRate() == this.baseTickRate) { level.setTickRate(Math.max(this.baseTickRate + 1, Math.min(this.autoTickRateLimit, tickMs / 50))); - this.getLogger().debug("Level \"" + level.getName() + "\" took " + NukkitMath.round(tickMs, 2) + "ms, setting tick rate to " + level.getTickRate() + " ticks"); + this.getLogger().debug("Level \"" + level.getName() + "\" took " + tickMs + "ms, setting tick rate to " + level.getTickRate() + " ticks"); } else if ((tickMs / level.getTickRate()) >= 50 && level.getTickRate() < this.autoTickRateLimit) { level.setTickRate(level.getTickRate() + 1); - this.getLogger().debug("Level \"" + level.getName() + "\" took " + NukkitMath.round(tickMs, 2) + "ms, setting tick rate to " + level.getTickRate() + " ticks"); + this.getLogger().debug("Level \"" + level.getName() + "\" took " + tickMs + "ms, setting tick rate to " + level.getTickRate() + " ticks"); } level.tickRateCounter = level.getTickRate(); } } } catch (Exception e) { - log.error(this.getLanguage().translateString("nukkit.level.tickError", - new String[]{level.getFolderName(), Utils.getExceptionMessage(e)})); + log.error(this.baseLang.translateString("nukkit.level.tickError", new String[]{level.getFolderName(), Utils.getExceptionMessage(e)})); } } } public void doAutoSave() { - if (this.getAutoSave()) { - Timings.levelSaveTimer.startTiming(); - for (Player player : new ArrayList<>(this.players.values())) { + if (this.autoSave) { + log.debug("Running auto save..."); + + for (Player player : this.players.values()) { if (player.isOnline()) { player.save(true); - } else if (!player.isConnected()) { - this.removePlayer(player); } } for (Level level : this.levelArray) { - level.save(); + if (level.getAutoSave()) { + if (level.getProvider() != null) { + try { + level.save(); + } catch (Exception ex) { + getLogger().error("Failed to auto save " + level.getName(), ex); + } + } + } } - Timings.levelSaveTimer.stopTiming(); } } - private boolean tick() { + private void tick() { long tickTime = System.currentTimeMillis(); - // TODO long time = tickTime - this.nextTick; if (time < -25) { try { Thread.sleep(Math.max(5, -time - 25)); } catch (InterruptedException e) { - Server.getInstance().getLogger().logException(e); + this.getLogger().logException(e); } } long tickTimeNano = System.nanoTime(); if ((tickTime - this.nextTick) < -25) { - return false; + return; } - Timings.fullServerTickTimer.startTiming(); - ++this.tickCounter; - 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); + this.checkTickUpdates(this.tickCounter); for (Player player : new ArrayList<>(this.players.values())) { player.checkNetwork(); @@ -1178,13 +1210,14 @@ private boolean tick() { if ((this.tickCounter & 0b1111) == 0) { this.titleTick(); - this.network.resetStatistics(); + + //this.network.resetStatistics(); // Unnecessary since addStatistics is not used in the new raknet this.maxTick = 20; this.maxUse = 0; if ((this.tickCounter & 0b111111111) == 0) { try { - this.getPluginManager().callEvent(this.queryRegenerateEvent = new QueryRegenerateEvent(this, 5)); + this.pluginManager.callEvent(this.queryRegenerateEvent = new QueryRegenerateEvent(this, 5)); if (this.queryHandler != null) { this.queryHandler.regenerateInfo(); } @@ -1193,25 +1226,23 @@ private boolean tick() { } } - this.getNetwork().updateName(); + this.network.updateName(); } - if (this.autoSave && ++this.autoSaveTicker >= this.autoSaveTicks) { + if (++this.autoSaveTicker >= this.autoSaveTicks) { this.autoSaveTicker = 0; this.doAutoSave(); } if (this.tickCounter % 100 == 0) { for (Level level : this.levelArray) { - level.doChunkGarbageCollection(); + if (!level.isBeingConverted) { + level.doChunkGarbageCollection(); + } } } - Timings.fullServerTickTimer.stopTiming(); - //long now = System.currentTimeMillis(); long nowNano = System.nanoTime(); - //float tick = Math.min(20, 1000 / Math.max(1, now - tickTime)); - //float use = Math.min(1, (now - tickTime) / 50); float tick = (float) Math.min(20, 1000000000 / Math.max(1000000, ((double) nowNano - tickTimeNano))); float use = (float) Math.min(1, ((double) (nowNano - tickTimeNano)) / 50000000); @@ -1235,36 +1266,26 @@ private boolean tick() { } else { this.nextTick += 50; } - - return true; } public long getNextTick() { return nextTick; } - // TODO: Fix title tick - public void titleTick() { - if (!Nukkit.ANSI || !Nukkit.TITLE) { + private void titleTick() { + if (!Nukkit.TITLE) { return; } - Runtime runtime = Runtime.getRuntime(); double used = NukkitMath.round((double) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024, 2); double max = NukkitMath.round(((double) runtime.maxMemory()) / 1024 / 1024, 2); - String usage = Math.round(used / max * 100) + "%"; - String title = (char) 0x1b + "]0;" + this.getName() + " " - + this.getNukkitVersion() - + " | Online " + this.players.size() + "/" + this.getMaxPlayers() - + " | Memory " + usage; - if (!Nukkit.shortTitle) { - title += " | U " + NukkitMath.round((this.network.getUpload() / 1024 * 1000), 2) - + " D " + NukkitMath.round((this.network.getDownload() / 1024 * 1000), 2) + " kB/s"; - } - title += " | TPS " + this.getTicksPerSecond() - + " | Load " + this.getTickUsage() + "%" + (char) 0x07; - - System.out.print(title); + System.out.print((char) 0x1b + "]0;Nukkit " + Nukkit.VERSION + + " | Online " + this.playerList.size() + '/' + this.maxPlayers + + " | Memory " + Math.round(used / max * 100) + '%' + + /*" | U " + NukkitMath.round((this.network.getUpload() / 1024 * 1000), 2) + + " D " + NukkitMath.round((this.network.getDownload() / 1024 * 1000), 2) + " kB/s" +*/ + " | TPS " + this.getTicksPerSecond() + + " | Load " + this.getTickUsage() + '%' + (char) 0x07); } public QueryRegenerateEvent getQueryInformation() { @@ -1284,7 +1305,7 @@ public String getNukkitVersion() { } public String getCodename() { - return Nukkit.CODENAME; + return ""; } public String getVersion() { @@ -1316,15 +1337,15 @@ public void setMaxPlayers(int maxPlayers) { } public int getPort() { - return this.getPropertyInt("server-port", 19132); + return port; } public int getViewDistance() { - return this.getPropertyInt("view-distance", 10); + return viewDistance; } public String getIp() { - return this.getPropertyString("server-ip", "0.0.0.0"); + return ip; } public UUID getServerUniqueId() { @@ -1343,23 +1364,15 @@ public void setAutoSave(boolean autoSave) { } public String getLevelType() { - return this.getPropertyString("level-type", "DEFAULT"); - } - - public boolean getGenerateStructures() { - return this.getPropertyBoolean("generate-structures", true); + return this.getPropertyString("level-type", "default"); } public int getGamemode() { - try { - return this.getPropertyInt("gamemode", 0) & 0b11; - } catch (NumberFormatException exception) { - return getGamemodeFromString(this.getPropertyString("gamemode")) & 0b11; - } + return gamemode; } public boolean getForceGamemode() { - return this.getPropertyBoolean("force-gamemode", false); + return this.forceGamemode; } public static String getGamemodeString(int mode) { @@ -1386,23 +1399,22 @@ public static int getGamemodeFromString(String str) { case "survival": case "s": return Player.SURVIVAL; - case "1": case "creative": case "c": return Player.CREATIVE; - case "2": case "adventure": case "a": return Player.ADVENTURE; - case "3": case "spectator": case "spc": case "view": case "v": return Player.SPECTATOR; + case "default": + return Server.getInstance().getDefaultGamemode(); } return -1; } @@ -1413,17 +1425,14 @@ public static int getDifficultyFromString(String str) { 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": @@ -1433,9 +1442,6 @@ public static int getDifficultyFromString(String str) { } public int getDifficulty() { - if (this.difficulty == Integer.MAX_VALUE) { - this.difficulty = getDifficultyFromString(this.getPropertyString("difficulty", "1")); - } return this.difficulty; } @@ -1448,45 +1454,45 @@ public void setDifficulty(int difficulty) { } public boolean hasWhitelist() { - return this.getPropertyBoolean("white-list", false); + return this.whitelistEnabled; } public int getSpawnRadius() { - return this.getPropertyInt("spawn-protection", 16); + return spawnRadius; } public boolean getAllowFlight() { - if (getAllowFlight == null) { - getAllowFlight = this.getPropertyBoolean("allow-flight", false); - } - return getAllowFlight; + return allowFlight; } public boolean isHardcore() { - return this.getPropertyBoolean("hardcore", false); + return this.isHardcore; } public int getDefaultGamemode() { - if (this.defaultGamemode == Integer.MAX_VALUE) { - this.defaultGamemode = this.getGamemode(); - } - return this.defaultGamemode; + return this.getGamemode(); } + /** + * Get MOTD + * @return motd + */ public String getMotd() { - return this.getPropertyString("motd", "A Nukkit Powered Server"); + return motd; } + /** + * Get Sub-MOTD (level name) + * @return sub-motd + */ public String getSubMotd() { - String subMotd = this.getPropertyString("sub-motd", "https://nukkitx.com"); - if (subMotd.isEmpty()) { - subMotd = "https://nukkitx.com"; // The client doesn't allow empty sub-motd in 1.16.210 - } - return subMotd; + String sub = this.getPropertyString("sub-motd", "Powered by Nukkit"); + if (sub.isEmpty()) sub = "Powered by Nukkit"; + return sub; } public boolean getForceResources() { - return this.getPropertyBoolean("force-resources", false); + return this.forceResources; } public MainLogger getLogger() { @@ -1521,14 +1527,29 @@ public ServerScheduler getScheduler() { return scheduler; } + /** + * Get current tick + * + * @return current tick + */ public int getTick() { return tickCounter; } + /** + * Get ticks per second + * + * @return TPS + */ public float getTicksPerSecond() { return ((float) Math.round(this.maxTick * 100)) / 100; } + /** + * Get average ticks per second + * + * @return average TPS + */ public float getTicksPerSecondAverage() { float sum = 0; int count = this.tickAverage.length; @@ -1538,36 +1559,81 @@ public float getTicksPerSecondAverage() { return (float) NukkitMath.round(sum / count, 2); } + /** + * Get main thread load + * + * @return tick usage % + */ public float getTickUsage() { return (float) NukkitMath.round(this.maxUse * 100, 2); } + /** + * Get average main thread load + * + * @return average main thread load + */ public float getTickUsageAverage() { float sum = 0; - int count = this.useAverage.length; for (float aUseAverage : this.useAverage) { sum += aUseAverage; } - return ((float) Math.round(sum / count * 100)) / 100; + return ((float) Math.round(sum / this.useAverage.length * 100)) / 100; } + /** + * Get command map + * + * @return command map + */ public SimpleCommandMap getCommandMap() { return commandMap; } + /** + * Get all online players + * + * @return online players + */ public Map getOnlinePlayers() { return ImmutableMap.copyOf(playerList); } + /** + * Get online player count + * + * @return online player count + */ + public int getOnlinePlayersCount() { + return this.playerList.size(); + } + + /** + * Register a recipe to CraftingManager. + * Please use getCraftingManager().registerRecipe(protocol, recipe) instead + * @param recipe recipe + */ public void addRecipe(Recipe recipe) { this.craftingManager.registerRecipe(recipe); } + /** + * Get an online player by uuid + * + * @param uuid uuid + * @return Optional Player + */ public Optional getPlayer(UUID uuid) { Preconditions.checkNotNull(uuid, "uuid"); return Optional.ofNullable(playerList.get(uuid)); } + /** + * Get known player uuid by player name + * + * @param name player name + * @return Optional UUID + */ public Optional lookupName(String name) { byte[] nameBytes = name.toLowerCase().getBytes(StandardCharsets.UTF_8); byte[] uuidBytes = nameLookup.get(nameBytes); @@ -1576,7 +1642,7 @@ public Optional lookupName(String name) { } if (uuidBytes.length != 16) { - log.warn("Invalid uuid in name lookup database detected! Removing"); + log.warn("Invalid uuid in name lookup database detected! Removing..."); nameLookup.delete(nameBytes); return Optional.empty(); } @@ -1597,12 +1663,12 @@ void updateName(UUID uuid, String name) { @Deprecated public IPlayer getOfflinePlayer(final String name) { - IPlayer result = this.getPlayerExact(name.toLowerCase()); + IPlayer result = this.getPlayerExact(name); if (result != null) { return result; } - return lookupName(name).map(uuid -> new OfflinePlayer(this, uuid)) + return lookupName(name).map(uuid -> new OfflinePlayer(this, uuid, name)) .orElse(new OfflinePlayer(this, name)); } @@ -1645,7 +1711,7 @@ private CompoundTag getOfflinePlayerDataInternal(String name, boolean runEvent, Optional dataStream = Optional.empty(); try { - dataStream = event.getSerializer().read(name, event.getUuid().orElse(null)); + dataStream = event.getSerializer().read(name, event.getUuid().orElse(null)); // TODO: should the name be lower case? if (dataStream.isPresent()) { return NBTIO.readCompressed(dataStream.get()); } @@ -1667,9 +1733,10 @@ private CompoundTag getOfflinePlayerDataInternal(String name, boolean runEvent, log.info(this.getLanguage().translateString("nukkit.data.playerNotFound", name)); } Position spawn = this.getDefaultLevel().getSafeSpawn(); + long time = System.currentTimeMillis(); nbt = new CompoundTag() - .putLong("firstPlayed", System.currentTimeMillis() / 1000) - .putLong("lastPlayed", System.currentTimeMillis() / 1000) + .putLong("firstPlayed", time / 1000) + .putLong("lastPlayed", time / 1000) .putList(new ListTag("Pos") .add(new DoubleTag("0", spawn.x)) .add(new DoubleTag("1", spawn.y)) @@ -1687,7 +1754,7 @@ private CompoundTag getOfflinePlayerDataInternal(String name, boolean runEvent, .add(new FloatTag("1", 0))) .putFloat("FallDistance", 0) .putShort("Fire", 0) - .putShort("Air", 300) + .putShort("Air", 400) .putBoolean("OnGround", true) .putBoolean("Invulnerable", false); @@ -1714,33 +1781,45 @@ public void saveOfflinePlayerData(String name, CompoundTag tag, boolean async) { } private void saveOfflinePlayerData(String name, CompoundTag tag, boolean async, boolean runEvent) { - String nameLower = name.toLowerCase(); if (this.shouldSavePlayerData()) { + String nameLower = name.toLowerCase(); PlayerDataSerializeEvent event = new PlayerDataSerializeEvent(nameLower, playerDataSerializer); if (runEvent) { pluginManager.callEvent(event); } - this.getScheduler().scheduleTask(new Task() { - boolean hasRun = false; + if (async) { + this.getScheduler().scheduleTask(new Task() { + private volatile boolean hasRun = false; - @Override - public void onRun(int currentTick) { - this.onCancel(); - } + @Override + public void onRun(int currentTick) { + this.onCancel(); + } - //doing it like this ensures that the playerdata will be saved in a server shutdown - @Override - public void onCancel() { - if (!this.hasRun) { - this.hasRun = true; - saveOfflinePlayerDataInternal(event.getSerializer(), tag, nameLower, event.getUuid().orElse(null)); + // Doing it like this ensures that the player data will be saved in a server shutdown + @Override + public void onCancel() { + if (!this.hasRun) { + this.hasRun = true; + saveOfflinePlayerDataInternal(event.getSerializer(), tag, nameLower, event.getUuid().orElse(null)); + } } - } - }, async); + }, true); + } else { + saveOfflinePlayerDataInternal(event.getSerializer(), tag, nameLower, event.getUuid().orElse(null)); + } } } + /** + * Internal: Save offline player data + * + * @param serializer serializer + * @param tag compound tag + * @param name player name + * @param uuid player uuid + */ private void saveOfflinePlayerDataInternal(PlayerDataSerializer serializer, CompoundTag tag, String name, UUID uuid) { try (OutputStream dataStream = serializer.write(name, uuid)) { NBTIO.writeGZIPCompressed(tag, dataStream, ByteOrder.BIG_ENDIAN); @@ -1749,9 +1828,11 @@ private void saveOfflinePlayerDataInternal(PlayerDataSerializer serializer, Comp } } + /** + * Internal: Convert legacy player saves to the uuid based saving + */ private void convertLegacyPlayerData() { File dataDirectory = new File(getDataPath(), "players/"); - Pattern uuidPattern = Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}.dat$"); File[] files = dataDirectory.listFiles(file -> { String name = file.getName(); @@ -1798,6 +1879,12 @@ private void convertLegacyPlayerData() { } } + /** + * Get an online player by name + * + * @param name player name + * @return Player or null + */ public Player getPlayer(String name) { Player found = null; name = name.toLowerCase(); @@ -1818,10 +1905,15 @@ public Player getPlayer(String name) { return found; } + /** + * Get an online player by exact player name + * + * @param name exact player name + * @return Player or null + */ public Player getPlayerExact(String name) { - name = name.toLowerCase(); for (Player player : this.getOnlinePlayers().values()) { - if (player.getName().toLowerCase().equals(name)) { + if (player.getName().equalsIgnoreCase(name)) { return player; } } @@ -1829,6 +1921,12 @@ public Player getPlayerExact(String name) { return null; } + /** + * Get players that match with the name + * + * @param partialName name + * @return matching players + */ public Player[] matchPlayer(String partialName) { partialName = partialName.toLowerCase(); List matchedPlayer = new ArrayList<>(); @@ -1843,46 +1941,79 @@ public Player[] matchPlayer(String partialName) { return matchedPlayer.toArray(new Player[0]); } + /** + * Internal: Remove a player from the server + * + * @param player player + */ public void removePlayer(Player player) { - Player toRemove = this.players.remove(player.getSocketAddress()); - if (toRemove != null) { + if (this.players.remove(player.getSocketAddress()) != null) { return; } for (InetSocketAddress socketAddress : new ArrayList<>(this.players.keySet())) { - Player p = this.players.get(socketAddress); - if (player == p) { + if (player == this.players.get(socketAddress)) { this.players.remove(socketAddress); break; } } } + /** + * Get all levels + * + * @return levels + */ public Map getLevels() { return levels; } + /** + * Get default level + * + * @return default level + */ public Level getDefaultLevel() { return defaultLevel; } + /** + * Change the default level + * + * @param defaultLevel new default level + */ public void setDefaultLevel(Level defaultLevel) { if (defaultLevel == null || (this.isLevelLoaded(defaultLevel.getFolderName()) && defaultLevel != this.defaultLevel)) { this.defaultLevel = defaultLevel; } } + /** + * Check whether a level is loaded + * + * @param name level name + * @return is loaded + */ public boolean isLevelLoaded(String name) { return this.getLevelByName(name) != null; } + /** + * Get a level by ID + * + * @param levelId level ID + * @return Level or null + */ public Level getLevel(int levelId) { - if (this.levels.containsKey(levelId)) { - return this.levels.get(levelId); - } - return null; + return this.levels.get(levelId); } + /** + * Get a level by name + * + * @param name level name + * @return Level or null + */ public Level getLevelByName(String name) { for (Level level : this.levelArray) { if (level.getFolderName().equalsIgnoreCase(name)) { @@ -1893,28 +2024,53 @@ public Level getLevelByName(String name) { return null; } + /** + * Unload a level. + * Notice that the default level cannot be unloaded without forceUnload=true + * + * @param level Level + * @return unloaded + */ public boolean unloadLevel(Level level) { return this.unloadLevel(level, false); } + /** + * Unload a level + * + * Notice: the default level cannot be unloaded without forceUnload=true + * + * @param level Level + * @param forceUnload force unload (ignore cancelled events and default level) + * @return unloaded + */ public boolean unloadLevel(Level level, boolean forceUnload) { - if (level == this.getDefaultLevel() && !forceUnload) { + if (level == this.defaultLevel && !forceUnload) { throw new IllegalStateException("The default level cannot be unloaded while running, please switch levels."); } return level.unload(forceUnload); - } + /** + * Load a level by name + * + * @param name level name + * @return loaded + */ public boolean loadLevel(String name) { if (Objects.equals(name.trim(), "")) { throw new LevelException("Invalid empty level name"); } + + if (!this.isPrimaryThread()) { + getLogger().warning("Level loaded asynchronously: " + name); + } + if (this.isLevelLoaded(name)) { return true; } else if (!this.isLevelGenerated(name)) { - log.warn(this.getLanguage().translateString("nukkit.level.notFound", name)); - + log.warn(this.baseLang.translateString("nukkit.level.notFound", name)); return false; } @@ -1923,14 +2079,13 @@ public boolean loadLevel(String name) { if (name.contains("/") || name.contains("\\")) { path = name; } else { - path = this.getDataPath() + "worlds/" + name + "/"; + path = this.dataPath + "worlds/" + name + '/'; } Class provider = LevelProviderManager.getProvider(path); if (provider == null) { - log.error(this.getLanguage().translateString("nukkit.level.loadError", new String[]{name, "Unknown provider"})); - + log.error(this.baseLang.translateString("nukkit.level.loadError", new String[]{name, "Unknown provider"})); return false; } @@ -1938,37 +2093,76 @@ public boolean loadLevel(String name) { try { level = new Level(this, name, path, provider); } catch (Exception e) { - log.error(this.getLanguage().translateString("nukkit.level.loadError", new String[]{name, e.getMessage()})); + log.error(this.baseLang.translateString("nukkit.level.loadError", new String[]{name, e.getMessage()})); return false; } - this.levels.put(level.getId(), level); - level.initLevel(); - this.getPluginManager().callEvent(new LevelLoadEvent(level)); + this.levels.put(level.getId(), level); level.setTickRate(this.baseTickRate); + this.pluginManager.callEvent(new LevelLoadEvent(level)); return true; } + /** + * Generate a new level + * + * @param name level name + * @return generated + */ public boolean generateLevel(String name) { - return this.generateLevel(name, new java.util.Random().nextLong()); + return this.generateLevel(name, Utils.random.nextLong()); } + /** + * Generate a new level + * + * @param name level name + * @param seed level seed + * @return generated + */ public boolean generateLevel(String name, long seed) { return this.generateLevel(name, seed, null); } + /** + * Generate a new level + * + * @param name level name + * @param seed level seed + * @param generator level generator + * @return generated + */ public boolean generateLevel(String name, long seed, Class generator) { return this.generateLevel(name, seed, generator, new HashMap<>()); } + /** + * Generate a new level + * + * @param name level name + * @param seed level seed + * @param generator level generator + * @param options level generator options + * @return generated + */ public boolean generateLevel(String name, long seed, Class generator, Map options) { return generateLevel(name, seed, generator, options, null); } + /** + * Generate a new level + * + * @param name level name + * @param seed level seed + * @param generator level generator + * @param options level generator options + * @param provider level provider + * @return generated + */ public boolean generateLevel(String name, long seed, Class generator, Map options, Class provider) { if (Objects.equals(name.trim(), "") || this.isLevelGenerated(name)) { return false; @@ -1983,7 +2177,7 @@ public boolean generateLevel(String name, long seed, Class } if (provider == null) { - provider = LevelProviderManager.getProviderByName(this.getConfig().get("level-settings.default-format", "anvil")); + provider = LevelProviderManager.getProviderByName("leveldb"); } String path; @@ -1991,7 +2185,7 @@ public boolean generateLevel(String name, long seed, Class if (name.contains("/") || name.contains("\\")) { path = name; } else { - path = this.getDataPath() + "worlds/" + name + "/"; + path = this.dataPath + "worlds/" + name + '/'; } Level level; @@ -1999,51 +2193,28 @@ public boolean generateLevel(String name, long seed, Class provider.getMethod("generate", String.class, String.class, long.class, Class.class, Map.class).invoke(null, path, name, seed, generator, options); level = new Level(this, name, path, provider); - this.levels.put(level.getId(), level); level.initLevel(); + + this.levels.put(level.getId(), level); + level.setTickRate(this.baseTickRate); } catch (Exception e) { - log.error(this.getLanguage().translateString("nukkit.level.generationError", new String[]{name, Utils.getExceptionMessage(e)})); + log.error(this.baseLang.translateString("nukkit.level.generationError", new String[]{name, Utils.getExceptionMessage(e)})); return false; } - this.getPluginManager().callEvent(new LevelInitEvent(level)); - - this.getPluginManager().callEvent(new LevelLoadEvent(level)); - - /*this.getLogger().notice(this.getLanguage().translateString("nukkit.level.backgroundGeneration", name)); - - int centerX = (int) level.getSpawnLocation().getX() >> 4; - int centerZ = (int) level.getSpawnLocation().getZ() >> 4; - - TreeMap order = new TreeMap<>(); - - for (int X = -3; X <= 3; ++X) { - for (int Z = -3; Z <= 3; ++Z) { - int distance = X * X + Z * Z; - int chunkX = X + centerX; - int chunkZ = Z + centerZ; - order.put(Level.chunkHash(chunkX, chunkZ), distance); - } - } - - List> sortList = new ArrayList<>(order.entrySet()); - - Collections.sort(sortList, new Comparator>() { - @Override - public int compare(Map.Entry o1, Map.Entry o2) { - return o2.getValue() - o1.getValue(); - } - }); - - for (String index : order.keySet()) { - Chunk.Entry entry = Level.getChunkXZ(index); - level.populateChunk(entry.chunkX, entry.chunkZ, true); - }*/ + this.pluginManager.callEvent(new LevelInitEvent(level)); + this.pluginManager.callEvent(new LevelLoadEvent(level)); return true; } + /** + * Check whether a level by name is generated + * + * @param name level name + * @return level found + */ public boolean isLevelGenerated(String name) { if (Objects.equals(name.trim(), "")) { return false; @@ -2055,7 +2226,7 @@ public boolean isLevelGenerated(String name) { if (name.contains("/") || name.contains("\\")) { path = name; } else { - path = this.getDataPath() + "worlds/" + name + "/"; + path = this.dataPath + "worlds/" + name + '/'; } return LevelProviderManager.getProvider(path) != null; @@ -2064,19 +2235,38 @@ public boolean isLevelGenerated(String name) { return true; } + /** + * Get BaseLang (server's default language) + * + * @return BaseLang + */ public BaseLang getLanguage() { return baseLang; } + /** + * Is forcing language enabled + * + * @return force-language enabled + */ public boolean isLanguageForced() { return forceLanguage; } + /** + * Get Network + * + * @return Network + */ public Network getNetwork() { return network; } - //Revising later... + /** + * Get nukkit.yml + * + * @return config + */ public Config getConfig() { return this.config; } @@ -2091,48 +2281,117 @@ public T getConfig(String variable, T defaultValue) { return value == null ? defaultValue : (T) value; } + /** + * Get server.properties + * + * @return server.properties as a Config + */ public Config getProperties() { return this.properties; } + /** + * Get a value from server.properties + * + * @param variable key + * @return value + */ public Object getProperty(String variable) { return this.getProperty(variable, null); } + /** + * Get a value from server.properties + * + * @param variable key + * @param defaultValue default value + * @return value + */ public Object getProperty(String variable, Object defaultValue) { return this.properties.exists(variable) ? this.properties.get(variable) : defaultValue; } + /** + * Set a string value in server.properties + * + * @param variable key + * @param value value + */ public void setPropertyString(String variable, String value) { this.properties.set(variable, value); this.properties.save(); } - public String getPropertyString(String variable) { - return this.getPropertyString(variable, null); + /** + * Get a string value from server.properties + * + * @param key key + * @return value + */ + public String getPropertyString(String key) { + return this.getPropertyString(key, null); } + /** + * Get a string value from server.properties + * + * @param key key + * @param defaultValue default value + * @return value + */ public String getPropertyString(String key, String defaultValue) { return this.properties.exists(key) ? this.properties.get(key).toString() : defaultValue; } + /** + * Get an int value from server.properties + * + * @param variable key + * @return value + */ public int getPropertyInt(String variable) { return this.getPropertyInt(variable, null); } + /** + * Get an int value from server.properties + * + * @param variable key + * @param defaultValue default value + * @return value + */ public int getPropertyInt(String variable, Integer defaultValue) { return this.properties.exists(variable) ? (!this.properties.get(variable).equals("") ? Integer.parseInt(String.valueOf(this.properties.get(variable))) : defaultValue) : defaultValue; } + /** + * Set an int value in server.properties + * + * @param variable key + * @param value value + */ public void setPropertyInt(String variable, int value) { this.properties.set(variable, value); this.properties.save(); } + /** + * Get a boolean value from server.properties + * + * @param variable key + * @return value + */ public boolean getPropertyBoolean(String variable) { return this.getPropertyBoolean(variable, null); } + /** + * Get a boolean value from server.properties + * + * @param variable key + * @param defaultValue default value + * @return value + */ public boolean getPropertyBoolean(String variable, Object defaultValue) { Object value = this.properties.exists(variable) ? this.properties.get(variable) : defaultValue; if (value instanceof Boolean) { @@ -2148,11 +2407,23 @@ public boolean getPropertyBoolean(String variable, Object defaultValue) { return false; } + /** + * Set a boolean value in server.properties + * + * @param variable key + * @param value value + */ public void setPropertyBoolean(String variable, boolean value) { this.properties.set(variable, value ? "1" : "0"); this.properties.save(); } + /** + * Get plugin commands + * + * @param name command name + * @return PluginIdentifiableCommand or null + */ public PluginIdentifiableCommand getPluginCommand(String name) { Command command = this.commandMap.getCommand(name); if (command instanceof PluginIdentifiableCommand) { @@ -2162,14 +2433,29 @@ public PluginIdentifiableCommand getPluginCommand(String name) { } } + /** + * Get list of banned players + * + * @return ban list + */ public BanList getNameBans() { return this.banByName; } + /** + * Get list of IP bans + * + * @return IP bans + */ public BanList getIPBans() { return this.banByIP; } + /** + * Give player the operator status + * + * @param name player name + */ public void addOp(String name) { this.operators.set(name.toLowerCase(), true); Player player = this.getPlayerExact(name); @@ -2179,6 +2465,11 @@ public void addOp(String name) { this.operators.save(true); } + /** + * Remove player's operator status + * + * @param name player name + */ public void removeOp(String name) { this.operators.remove(name.toLowerCase()); Player player = this.getPlayerExact(name); @@ -2188,43 +2479,78 @@ public void removeOp(String name) { this.operators.save(); } + /** + * Add a player to whitelist + * + * @param name player name + */ public void addWhitelist(String name) { this.whitelist.set(name.toLowerCase(), true); this.whitelist.save(true); } + /** + * Remove a player from whitelist + * + * @param name player name + */ public void removeWhitelist(String name) { this.whitelist.remove(name.toLowerCase()); this.whitelist.save(true); } + /** + * Check whether a player is whitelisted + * + * @param name player name + * @return is whitelisted or whitelist is not enabled + */ public boolean isWhitelisted(String name) { return !this.hasWhitelist() || this.operators.exists(name, true) || this.whitelist.exists(name, true); } + /** + * Check whether a player is an operator + * + * @param name player name + * @return is operator + */ public boolean isOp(String name) { - return name != null && this.operators.exists(name, true); + return this.operators.exists(name, true); } + /** + * Get whitelist config + * + * @return whitelist + */ public Config getWhitelist() { return whitelist; } + /** + * Get operator list config + * + * @return operators + */ public Config getOps() { return operators; } + /** + * Reload whitelist + */ public void reloadWhitelist() { this.whitelist.reload(); } - public ServiceManager getServiceManager() { - return serviceManager; - } - + /** + * Load command aliases from nukkit.yml + */ public Map> getCommandAliases() { Object section = this.getConfig("aliases"); Map> result = new LinkedHashMap<>(); + if (section instanceof Map) { for (Map.Entry entry : (Set) ((Map) section).entrySet()) { List commands = new ArrayList<>(); @@ -2241,51 +2567,84 @@ public Map> getCommandAliases() { } return result; + } + /** + * Get service manager + * + * @return service manager + */ + public ServiceManager getServiceManager() { + return serviceManager; } + /** + * Should player data saving be enabled + * + * @return player data saving enabled + */ public boolean shouldSavePlayerData() { - return this.getConfig("player.save-player-data", true); + return shouldSavePlayerData; } + /** + * How often player is allowed to change skin in game (in seconds) + * + * @return skin change cooldown + */ public int getPlayerSkinChangeCooldown() { - return this.getConfig("player.skin-change-cooldown", 30); + return skinChangeCooldown; } /** - * Checks the current thread against the expected primary thread for the - * server. - *

- * Note: this method should not be used to indicate the current - * synchronized state of the runtime. A current thread matching the main - * thread indicates that it is synchronized, but a mismatch does not - * preclude the same assumption. + * Checks the current thread against the expected primary thread for the server. + * + * Note: this method should not be used to indicate the current synchronized state of the runtime. A current thread matching the main thread indicates that it is synchronized, but a mismatch does not preclude the same assumption. * - * @return true if the current thread matches the expected primary thread, - * false otherwise + * @return true if the current thread matches the expected primary thread, false otherwise */ - public final boolean isPrimaryThread() { - return (Thread.currentThread() == currentThread); + public boolean isPrimaryThread() { + return Thread.currentThread() == currentThread; } + /** + * Get server's primary thread + * + * @return primary thread + */ public Thread getPrimaryThread() { return currentThread; } - private void registerEntities() { - Entity.registerEntity("Lightning", EntityLightning.class); - Entity.registerEntity("Arrow", EntityArrow.class); - Entity.registerEntity("EnderPearl", EntityEnderPearl.class); - Entity.registerEntity("FallingSand", EntityFallingBlock.class); - Entity.registerEntity("Firework", EntityFirework.class); + /** + * Internal method to register all default entities + */ + private static void registerEntities() { + //Items Entity.registerEntity("Item", EntityItem.class); Entity.registerEntity("Painting", EntityPainting.class); + Entity.registerEntity("XpOrb", EntityXPOrb.class); + Entity.registerEntity("ArmorStand", EntityArmorStand.class); + Entity.registerEntity("EndCrystal", EntityEndCrystal.class); + Entity.registerEntity("FallingSand", EntityFallingBlock.class); Entity.registerEntity("PrimedTnt", EntityPrimedTNT.class); + Entity.registerEntity("Firework", EntityFirework.class); + //Projectiles + Entity.registerEntity("Arrow", EntityArrow.class); Entity.registerEntity("Snowball", EntitySnowball.class); + Entity.registerEntity("EnderPearl", EntityEnderPearl.class); + Entity.registerEntity("ThrownExpBottle", EntityExpBottle.class); + Entity.registerEntity("ThrownPotion", EntityPotion.class); + Entity.registerEntity("Egg", EntityEgg.class); + Entity.registerEntity("ThrownLingeringPotion", EntityPotionLingering.class); + Entity.registerEntity("ThrownTrident", EntityThrownTrident.class); + Entity.registerEntity("FishingHook", EntityFishingHook.class); + Entity.registerEntity("EnderEye", EntityEnderEye.class); + Entity.registerEntity("AreaEffectCloud", EntityAreaEffectCloud.class); //Monsters Entity.registerEntity("Blaze", EntityBlaze.class); - Entity.registerEntity("CaveSpider", EntityCaveSpider.class); Entity.registerEntity("Creeper", EntityCreeper.class); + Entity.registerEntity("CaveSpider", EntityCaveSpider.class); Entity.registerEntity("Drowned", EntityDrowned.class); Entity.registerEntity("ElderGuardian", EntityElderGuardian.class); Entity.registerEntity("EnderDragon", EntityEnderDragon.class); @@ -2293,92 +2652,90 @@ private void registerEntities() { Entity.registerEntity("Endermite", EntityEndermite.class); Entity.registerEntity("Evoker", EntityEvoker.class); Entity.registerEntity("Ghast", EntityGhast.class); - Entity.registerEntity("GlowSquid", EntityGlowSquid.class); Entity.registerEntity("Guardian", EntityGuardian.class); - Entity.registerEntity("Hoglin", EntityHoglin.class); Entity.registerEntity("Husk", EntityHusk.class); Entity.registerEntity("MagmaCube", EntityMagmaCube.class); Entity.registerEntity("Phantom", EntityPhantom.class); - Entity.registerEntity("Piglin", EntityPiglin.class); - Entity.registerEntity("PiglinBrute", EntityPiglinBrute.class); - Entity.registerEntity("Pillager", EntityPillager.class); Entity.registerEntity("Ravager", EntityRavager.class); Entity.registerEntity("Shulker", EntityShulker.class); Entity.registerEntity("Silverfish", EntitySilverfish.class); Entity.registerEntity("Skeleton", EntitySkeleton.class); + Entity.registerEntity("SkeletonHorse", EntitySkeletonHorse.class); Entity.registerEntity("Slime", EntitySlime.class); - Entity.registerEntity("SnowGolem", EntitySnowGolem.class); Entity.registerEntity("Spider", EntitySpider.class); Entity.registerEntity("Stray", EntityStray.class); - Entity.registerEntity("Vex", EntityVex.class); Entity.registerEntity("Vindicator", EntityVindicator.class); - Entity.registerEntity("Warden", EntityWarden.class); - Entity.registerEntity("Witch", EntityWitch.class); - Entity.registerEntity("Wither", EntityWither.class); + Entity.registerEntity("Vex", EntityVex.class); Entity.registerEntity("WitherSkeleton", EntityWitherSkeleton.class); + Entity.registerEntity("Wither", EntityWither.class); + Entity.registerEntity("Witch", EntityWitch.class); + Entity.registerEntity("ZombiePigman", EntityZombiePigman.class); + Entity.registerEntity("ZombieVillager", EntityZombieVillagerV1.class); Entity.registerEntity("Zombie", EntityZombie.class); + Entity.registerEntity("Pillager", EntityPillager.class); + Entity.registerEntity("ZombieVillagerV2", EntityZombieVillager.class); + Entity.registerEntity("Hoglin", EntityHoglin.class); + Entity.registerEntity("Piglin", EntityPiglin.class); Entity.registerEntity("Zoglin", EntityZoglin.class); - Entity.registerEntity("ZombiePigman", EntityZombiePigman.class); - Entity.registerEntity("ZombieVillager", EntityZombieVillager.class); - Entity.registerEntity("ZombieVillagerV1", EntityZombieVillagerV1.class); + Entity.registerEntity("PiglinBrute", EntityPiglinBrute.class); + Entity.registerEntity("Warden", EntityWarden.class); //Passive - Entity.registerEntity("Allay", EntityAllay.class); - Entity.registerEntity("Axolotl", EntityAxolotl.class); Entity.registerEntity("Bat", EntityBat.class); - Entity.registerEntity("Bee", EntityBee.class); Entity.registerEntity("Cat", EntityCat.class); Entity.registerEntity("Chicken", EntityChicken.class); Entity.registerEntity("Cod", EntityCod.class); Entity.registerEntity("Cow", EntityCow.class); Entity.registerEntity("Dolphin", EntityDolphin.class); Entity.registerEntity("Donkey", EntityDonkey.class); - Entity.registerEntity("Fox", EntityFox.class); - Entity.registerEntity("Frog", EntityFrog.class); - Entity.registerEntity("Goat", EntityGoat.class); Entity.registerEntity("Horse", EntityHorse.class); + Entity.registerEntity("IronGolem", EntityIronGolem.class); Entity.registerEntity("Llama", EntityLlama.class); Entity.registerEntity("Mooshroom", EntityMooshroom.class); Entity.registerEntity("Mule", EntityMule.class); - Entity.registerEntity("Ocelot", EntityOcelot.class); Entity.registerEntity("Panda", EntityPanda.class); Entity.registerEntity("Parrot", EntityParrot.class); - Entity.registerEntity("Pig", EntityPig.class); Entity.registerEntity("PolarBear", EntityPolarBear.class); + Entity.registerEntity("Pig", EntityPig.class); Entity.registerEntity("Pufferfish", EntityPufferfish.class); Entity.registerEntity("Rabbit", EntityRabbit.class); Entity.registerEntity("Salmon", EntitySalmon.class); Entity.registerEntity("Sheep", EntitySheep.class); - Entity.registerEntity("SkeletonHorse", EntitySkeletonHorse.class); Entity.registerEntity("Squid", EntitySquid.class); - Entity.registerEntity("Strider", EntityStrider.class); - Entity.registerEntity("Tadpole", EntityTadpole.class); + Entity.registerEntity("SnowGolem", EntitySnowGolem.class); Entity.registerEntity("TropicalFish", EntityTropicalFish.class); Entity.registerEntity("Turtle", EntityTurtle.class); - Entity.registerEntity("Villager", EntityVillager.class); - Entity.registerEntity("VillagerV1", EntityVillagerV1.class); - Entity.registerEntity("WanderingTrader", EntityWanderingTrader.class); Entity.registerEntity("Wolf", EntityWolf.class); + Entity.registerEntity("Ocelot", EntityOcelot.class); + Entity.registerEntity("Villager", EntityVillagerV1.class); Entity.registerEntity("ZombieHorse", EntityZombieHorse.class); - //Projectile - Entity.registerEntity("Egg", EntityEgg.class); - Entity.registerEntity("ThrownExpBottle", EntityExpBottle.class); - Entity.registerEntity("ThrownPotion", EntityPotion.class); - Entity.registerEntity("ThrownTrident", EntityThrownTrident.class); - Entity.registerEntity("XpOrb", EntityXPOrb.class); - - Entity.registerEntity("Human", EntityHuman.class, true); - //Vehicle - Entity.registerEntity("Boat", EntityBoat.class); + Entity.registerEntity("WanderingTrader", EntityWanderingTrader.class); + Entity.registerEntity("VillagerV2", EntityVillager.class); + Entity.registerEntity("Fox", EntityFox.class); + Entity.registerEntity("Bee", EntityBee.class); + Entity.registerEntity("Strider", EntityStrider.class); + Entity.registerEntity("Goat", EntityGoat.class); + Entity.registerEntity("Axolotl", EntityAxolotl.class); + Entity.registerEntity("GlowSquid", EntityGlowSquid.class); + Entity.registerEntity("Allay", EntityAllay.class); + Entity.registerEntity("Frog", EntityFrog.class); + Entity.registerEntity("Tadpole", EntityTadpole.class); + Entity.registerEntity("Camel", EntityCamel.class); + //Vehicles + Entity.registerEntity("MinecartRideable", EntityMinecartEmpty.class); Entity.registerEntity("MinecartChest", EntityMinecartChest.class); Entity.registerEntity("MinecartHopper", EntityMinecartHopper.class); - Entity.registerEntity("MinecartRideable", EntityMinecartEmpty.class); Entity.registerEntity("MinecartTnt", EntityMinecartTNT.class); - - Entity.registerEntity("EndCrystal", EntityEndCrystal.class); - Entity.registerEntity("FishingHook", EntityFishingHook.class); + Entity.registerEntity("Boat", EntityBoat.class); + Entity.registerEntity("ChestBoat", EntityChestBoat.class); + //Others + Entity.registerEntity("Human", EntityHuman.class, true); + Entity.registerEntity("Lightning", EntityLightning.class); } - private void registerBlockEntities() { + /** + * Internal method to register all default block entities + */ + private static void registerBlockEntities() { BlockEntity.registerBlockEntity(BlockEntity.FURNACE, BlockEntityFurnace.class); BlockEntity.registerBlockEntity(BlockEntity.CHEST, BlockEntityChest.class); BlockEntity.registerBlockEntity(BlockEntity.SIGN, BlockEntitySign.class); @@ -2397,29 +2754,162 @@ private void registerBlockEntities() { BlockEntity.registerBlockEntity(BlockEntity.JUKEBOX, BlockEntityJukebox.class); BlockEntity.registerBlockEntity(BlockEntity.SHULKER_BOX, BlockEntityShulkerBox.class); BlockEntity.registerBlockEntity(BlockEntity.BANNER, BlockEntityBanner.class); + BlockEntity.registerBlockEntity(BlockEntity.DROPPER, BlockEntityDropper.class); + BlockEntity.registerBlockEntity(BlockEntity.DISPENSER, BlockEntityDispenser.class); + BlockEntity.registerBlockEntity(BlockEntity.MOB_SPAWNER, BlockEntitySpawner.class); BlockEntity.registerBlockEntity(BlockEntity.MUSIC, BlockEntityMusic.class); + BlockEntity.registerBlockEntity(BlockEntity.CAMPFIRE, BlockEntityCampfire.class); + BlockEntity.registerBlockEntity(BlockEntity.BARREL, BlockEntityBarrel.class); + BlockEntity.registerBlockEntity(BlockEntity.LECTERN, BlockEntityLectern.class); + BlockEntity.registerBlockEntity(BlockEntity.BLAST_FURNACE, BlockEntityBlastFurnace.class); + BlockEntity.registerBlockEntity(BlockEntity.SMOKER, BlockEntitySmoker.class); + BlockEntity.registerBlockEntity(BlockEntity.BELL, BlockEntityBell.class); + BlockEntity.registerBlockEntity(BlockEntity.PERSISTENT_CONTAINER, PersistentDataContainerBlockEntity.class); } + /** + * Is nether enabled on this server + * + * @return nether enabled + */ public boolean isNetherAllowed() { - return this.allowNether; + return this.netherEnabled; } + /** + * Get player data serializer that is used to save player data + * + * @return player data serializer + */ public PlayerDataSerializer getPlayerDataSerializer() { return playerDataSerializer; } + /** + * Set player data serializer that is used to save player data + * + * @param playerDataSerializer player data serializer + */ public void setPlayerDataSerializer(PlayerDataSerializer playerDataSerializer) { this.playerDataSerializer = Preconditions.checkNotNull(playerDataSerializer, "playerDataSerializer"); } - public boolean isIgnoredPacket(Class clazz) { - return this.ignoredPackets.contains(clazz.getSimpleName()); - } - + /** + * Get the Server instance + * + * @return Server + */ public static Server getInstance() { return instance; } + /** + * Load server config + */ + private void loadSettings() { + /* nukkit.yml */ + + this.forceLanguage = this.getConfig("settings.force-language", false); + this.queryPlugins = this.getConfig("settings.query-plugins", false); + + this.networkCompressionThreshold = this.getConfig("network.batch-threshold", 256); + this.networkCompressionLevel = Math.max(Math.min(this.getConfig("network.compression-level", 4), 9), 0); + this.encryptionEnabled = this.getConfig("network.encryption", false); + + this.autoTickRate = this.getConfig("level-settings.auto-tick-rate", true); + this.autoTickRateLimit = this.getConfig("level-settings.auto-tick-rate-limit", 20); + this.baseTickRate = this.getConfig("level-settings.base-tick-rate", 1); + this.alwaysTickPlayers = this.getConfig("level-settings.always-tick-players", false); + + this.useNativeLevelDB = this.getConfig("leveldb.use-native", false); + this.levelDbCache = this.getConfig("leveldb.cache-size-mb", 80); + + this.autoSaveTicks = this.getConfig("ticks-per.autosave", 6000); + + this.shouldSavePlayerData = this.getConfig("player.save-player-data", true); + this.skinChangeCooldown = this.getConfig("player.skin-change-cooldown", 15); + this.attackStopSprint = this.getConfig("player.attack-stop-sprint", true); + + this.chunksPerTick = this.getConfig("chunk-sending.per-tick", 4); + this.spawnThreshold = this.getConfig("spawn-threshold", 56); + this.cacheChunks = this.getConfig("cache-chunks", false); + + this.c_s_spawnThreshold = (int) Math.ceil(Math.sqrt(this.spawnThreshold)); + + /* server.properties */ + + this.maxPlayers = this.getPropertyInt("max-players", 20); + this.netherEnabled = this.getPropertyBoolean("allow-nether", true); + //this.endEnabled = this.getPropertyBoolean("allow-the-end", true); + this.xboxAuth = this.getPropertyBoolean("xbox-auth", true); + this.achievementsEnabled = this.getPropertyBoolean("achievements", true); + this.pvpEnabled = this.getPropertyBoolean("pvp", true); + this.announceAchievements = this.getPropertyBoolean("announce-player-achievements", true); + this.queryPlugins = this.getPropertyBoolean("query-plugins", false); + this.allowFlight = this.getPropertyBoolean("allow-flight", false); + this.isHardcore = this.getPropertyBoolean("hardcore", false); + this.forceResources = this.getPropertyBoolean("force-resources", false); + this.forceResourcesAllowOwnPacks = this.getPropertyBoolean("force-resources-allow-client-packs", false); + this.whitelistEnabled = this.getPropertyBoolean("white-list", false); + this.forceGamemode = this.getPropertyBoolean("force-gamemode", false); + this.motd = this.getPropertyString("motd", "A Minecraft Server"); + this.viewDistance = this.getPropertyInt("view-distance", 10); + this.port = this.getPropertyInt("server-port", 19132); + this.ip = this.getPropertyString("server-ip", "0.0.0.0"); + this.spawnRadius = this.getPropertyInt("spawn-protection", 16); + + this.setAutoSave(this.getPropertyBoolean("auto-save", true)); + + try { + this.gamemode = this.getPropertyInt("gamemode", 0) & 0b11; + } catch (NumberFormatException exception) { + this.gamemode = getGamemodeFromString(this.getPropertyString("gamemode")) & 0b11; + } + + if (this.isHardcore && this.difficulty < 3) { + this.setDifficulty(3); + } else { + this.setDifficulty(getDifficultyFromString(this.getPropertyString("difficulty", "2"))); + } + } + + /** + * This class contains all default server.properties values. + */ + private static class ServerProperties extends ConfigSection { + { + put("motd", "A Minecraft Server"); + put("sub-motd", "Powered by Nukkit"); + put("server-port", 19132); + put("server-ip", "0.0.0.0"); + put("view-distance", 10); + put("achievements", true); + put("announce-player-achievements", true); + put("spawn-protection", 16); + put("gamemode", 0); + put("force-gamemode", false); + put("difficulty", 2); + put("hardcore", false); + put("pvp", true); + put("white-list", false); + put("generator-settings", ""); + put("level-name", "world"); + put("level-seed", ""); + put("level-type", "default"); + put("enable-rcon", false); + put("rcon.password", Base64.getEncoder().encodeToString(UUID.randomUUID().toString().replace("-", "").getBytes()).substring(3, 13)); + put("force-resources", false); + put("force-resources-allow-client-packs", false); + put("xbox-auth", true); + put("auto-save", true); + put("force-language", false); + put("enable-query", false); + put("allow-flight", false); + put("allow-nether", true); + //put("allow-the-end", true); + } + } + private class ConsoleThread extends Thread implements InterruptibleThread { @Override diff --git a/src/main/java/cn/nukkit/api/API.java b/src/main/java/cn/nukkit/api/API.java deleted file mode 100644 index 9276ee5bbe4..00000000000 --- a/src/main/java/cn/nukkit/api/API.java +++ /dev/null @@ -1,130 +0,0 @@ -package cn.nukkit.api; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import static cn.nukkit.api.API.Definition.UNIVERSAL; -import static cn.nukkit.api.API.Usage.BLEEDING; - -/** - * Describes an API element. - * - * @author Lin Mulan, Nukkit Project - * @see Usage - * @see Definition - */ -@Retention(RetentionPolicy.SOURCE) -@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE}) -@API(usage = BLEEDING, definition = UNIVERSAL) -@SuppressWarnings("unused") -public @interface API { - - /** - * Indicates the level of stability of an API element. - * The stability also describes when to use this API element. - * - * @return The stability - * @see Usage - */ - Usage usage(); - - /** - * Indicates definition or the platforms this API element supports. - * - * @return The definition - * @see Definition - */ - Definition definition(); - - /** - * Enum constant for API usage. Indicates when to use this API element. - * - * @see #DEPRECATED - * @see #INCUBATING - * @see #BLEEDING - * @see #EXPERIMENTAL - * @see #MAINTAINED - * @see #STABLE - */ - enum Usage { - - /** - * Should no longer be used, might disappear in the next minor release. - */ - DEPRECATED, - - /** - * Intended for features in drafts. Should only be used for tests. - * - *

Might contains notable new features, but will be moved to a new package before remarking to {@link #BLEEDING}. - * Could be unsafe, might be removed without prior notice. Warnings will be send if used. - */ - INCUBATING, - - /** - * Intended for features in early development. Should only be used for tests. - * - *

Might be unwrapped, unsafe or have unchecked parameters. - * Further contribution was demanded to enhance, strengthen or simplify before remarking to {@link #EXPERIMENTAL}. - * Might be removed or modified without prior notice. - */ - BLEEDING, - - /** - * Intended for new, experimental features where we are looking for feedback. - * At least stable for development. - * - *

Use with caution, might be remarked to {@link #MAINTAINED} or {@link #STABLE} in the future, - * but also might be removed without prior notice. - */ - EXPERIMENTAL, - - /** - * Intended for features that was tested, documented and at least stable for production use. - * - *

These features will not be modified in a backwards-incompatible way for at least next minor release - * of the current major version. Will be remarked to {@link #DEPRECATED} first if scheduled for removal. - */ - MAINTAINED, - - /** - * Intended for features that was tested, documented and is preferred in production use. - * - *

Will not be changed in a backwards-incompatible way in the current version. - */ - STABLE - } - - /** - * Enum constant for API definition. Indicates which client platform this API element supports. - * - * @see #INTERNAL - * @see #PLATFORM_NATIVE - * @see #UNIVERSAL - */ - enum Definition { - - /** - * Intended for features should only be used by Nukkit itself. - * Should not be used in production. - */ - INTERNAL, - - /** - * Intended for features only available on one or several client platforms. - * - *

By using {@code PLATFORM_NATIVE} features, program will lose some cross-platform features provided. - * Might not available in some client platforms. Read the documents carefully before using this API element. - */ - PLATFORM_NATIVE, - - /** - * Intended for features implemented in all client platforms. - * - *

Preferred to use for production use, but sometimes be lack of platform-native features. - */ - UNIVERSAL - } -} diff --git a/src/main/java/cn/nukkit/block/Block.java b/src/main/java/cn/nukkit/block/Block.java index c5cc814492e..a687d2ded7a 100644 --- a/src/main/java/cn/nukkit/block/Block.java +++ b/src/main/java/cn/nukkit/block/Block.java @@ -2,6 +2,8 @@ import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.block.properties.BlockNotImplemented; +import cn.nukkit.customblock.CustomBlockManager; import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; @@ -10,6 +12,7 @@ import cn.nukkit.level.Level; import cn.nukkit.level.MovingObjectPosition; import cn.nukkit.level.Position; +import cn.nukkit.level.persistence.PersistentDataContainer; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; @@ -18,6 +21,8 @@ import cn.nukkit.plugin.Plugin; import cn.nukkit.potion.Effect; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.material.BlockType; +import cn.nukkit.utils.material.MaterialType; import java.lang.reflect.Constructor; import java.util.List; @@ -25,305 +30,97 @@ import java.util.Optional; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Block extends Position implements Metadatable, Cloneable, AxisAlignedBB, BlockID { - public static Class[] list = null; - public static Block[] fullList = null; - public static int[] light = null; - public static int[] lightFilter = null; - public static boolean[] solid = null; - public static double[] hardness = null; - public static boolean[] transparent = null; + + public static final int MAX_BLOCK_ID = 1024; + public static final int DATA_BITS = 6; + public static final int DATA_SIZE = 1 << DATA_BITS; + public static final int DATA_MASK = DATA_SIZE - 1; + + public static final BlockLayer LAYER_NORMAL = BlockLayer.NORMAL; + public static final BlockLayer LAYER_WATERLOGGED = BlockLayer.WATERLOGGED; + + @SuppressWarnings("rawtypes") + public static Class[] list; + public static Block[] fullList; + public static int[] light; + public static int[] lightFilter; + public static boolean[] solid; + public static double[] hardness; + public static boolean[] transparent; + public static boolean[] hasMeta; + + private BlockLayer layer = LAYER_NORMAL; + + private BlockType materialType; + /** - * if a block has can have variants + * A commonly used block face pattern */ - public static boolean[] hasMeta = null; + protected static final int[] faces2534 = {2, 5, 3, 4}; protected Block() {} - @SuppressWarnings("unchecked") public static void init() { if (list == null) { - list = new Class[256]; - fullList = new Block[4096]; - light = new int[256]; - lightFilter = new int[256]; - solid = new boolean[256]; - hardness = new double[256]; - transparent = new boolean[256]; - hasMeta = new boolean[256]; - - list[AIR] = BlockAir.class; //0 - list[STONE] = BlockStone.class; //1 - list[GRASS] = BlockGrass.class; //2 - list[DIRT] = BlockDirt.class; //3 - list[COBBLESTONE] = BlockCobblestone.class; //4 - list[PLANKS] = BlockPlanks.class; //5 - list[SAPLING] = BlockSapling.class; //6 - list[BEDROCK] = BlockBedrock.class; //7 - list[WATER] = BlockWater.class; //8 - list[STILL_WATER] = BlockWaterStill.class; //9 - list[LAVA] = BlockLava.class; //10 - list[STILL_LAVA] = BlockLavaStill.class; //11 - list[SAND] = BlockSand.class; //12 - list[GRAVEL] = BlockGravel.class; //13 - list[GOLD_ORE] = BlockOreGold.class; //14 - list[IRON_ORE] = BlockOreIron.class; //15 - list[COAL_ORE] = BlockOreCoal.class; //16 - list[WOOD] = BlockWood.class; //17 - list[LEAVES] = BlockLeaves.class; //18 - list[SPONGE] = BlockSponge.class; //19 - list[GLASS] = BlockGlass.class; //20 - list[LAPIS_ORE] = BlockOreLapis.class; //21 - list[LAPIS_BLOCK] = BlockLapis.class; //22 - list[DISPENSER] = BlockDispenser.class; //23 - list[SANDSTONE] = BlockSandstone.class; //24 - list[NOTEBLOCK] = BlockNoteblock.class; //25 - list[BED_BLOCK] = BlockBed.class; //26 - list[POWERED_RAIL] = BlockRailPowered.class; //27 - list[DETECTOR_RAIL] = BlockRailDetector.class; //28 - list[STICKY_PISTON] = BlockPistonSticky.class; //29 - list[COBWEB] = BlockCobweb.class; //30 - list[TALL_GRASS] = BlockTallGrass.class; //31 - list[DEAD_BUSH] = BlockDeadBush.class; //32 - list[PISTON] = BlockPiston.class; //33 - list[PISTON_HEAD] = BlockPistonHead.class; //34 - list[WOOL] = BlockWool.class; //35 - list[DANDELION] = BlockDandelion.class; //37 - list[FLOWER] = BlockFlower.class; //38 - list[BROWN_MUSHROOM] = BlockMushroomBrown.class; //39 - list[RED_MUSHROOM] = BlockMushroomRed.class; //40 - list[GOLD_BLOCK] = BlockGold.class; //41 - list[IRON_BLOCK] = BlockIron.class; //42 - list[DOUBLE_STONE_SLAB] = BlockDoubleSlabStone.class; //43 - list[STONE_SLAB] = BlockSlabStone.class; //44 - list[BRICKS_BLOCK] = BlockBricks.class; //45 - list[TNT] = BlockTNT.class; //46 - list[BOOKSHELF] = BlockBookshelf.class; //47 - list[MOSS_STONE] = BlockMossStone.class; //48 - list[OBSIDIAN] = BlockObsidian.class; //49 - list[TORCH] = BlockTorch.class; //50 - list[FIRE] = BlockFire.class; //51 - list[MONSTER_SPAWNER] = BlockMobSpawner.class; //52 - list[WOOD_STAIRS] = BlockStairsWood.class; //53 - list[CHEST] = BlockChest.class; //54 - list[REDSTONE_WIRE] = BlockRedstoneWire.class; //55 - list[DIAMOND_ORE] = BlockOreDiamond.class; //56 - list[DIAMOND_BLOCK] = BlockDiamond.class; //57 - list[WORKBENCH] = BlockCraftingTable.class; //58 - list[WHEAT_BLOCK] = BlockWheat.class; //59 - list[FARMLAND] = BlockFarmland.class; //60 - list[FURNACE] = BlockFurnace.class; //61 - list[BURNING_FURNACE] = BlockFurnaceBurning.class; //62 - list[SIGN_POST] = BlockSignPost.class; //63 - list[WOOD_DOOR_BLOCK] = BlockDoorWood.class; //64 - list[LADDER] = BlockLadder.class; //65 - list[RAIL] = BlockRail.class; //66 - list[COBBLESTONE_STAIRS] = BlockStairsCobblestone.class; //67 - list[WALL_SIGN] = BlockWallSign.class; //68 - list[LEVER] = BlockLever.class; //69 - list[STONE_PRESSURE_PLATE] = BlockPressurePlateStone.class; //70 - list[IRON_DOOR_BLOCK] = BlockDoorIron.class; //71 - list[WOODEN_PRESSURE_PLATE] = BlockPressurePlateWood.class; //72 - list[REDSTONE_ORE] = BlockOreRedstone.class; //73 - list[GLOWING_REDSTONE_ORE] = BlockOreRedstoneGlowing.class; //74 - list[UNLIT_REDSTONE_TORCH] = BlockRedstoneTorchUnlit.class; - list[REDSTONE_TORCH] = BlockRedstoneTorch.class; //76 - list[STONE_BUTTON] = BlockButtonStone.class; //77 - list[SNOW_LAYER] = BlockSnowLayer.class; //78 - list[ICE] = BlockIce.class; //79 - list[SNOW_BLOCK] = BlockSnow.class; //80 - list[CACTUS] = BlockCactus.class; //81 - list[CLAY_BLOCK] = BlockClay.class; //82 - list[SUGARCANE_BLOCK] = BlockSugarcane.class; //83 - list[JUKEBOX] = BlockJukebox.class; //84 - list[FENCE] = BlockFence.class; //85 - list[PUMPKIN] = BlockPumpkin.class; //86 - list[NETHERRACK] = BlockNetherrack.class; //87 - list[SOUL_SAND] = BlockSoulSand.class; //88 - list[GLOWSTONE_BLOCK] = BlockGlowstone.class; //89 - list[NETHER_PORTAL] = BlockNetherPortal.class; //90 - list[LIT_PUMPKIN] = BlockPumpkinLit.class; //91 - list[CAKE_BLOCK] = BlockCake.class; //92 - list[UNPOWERED_REPEATER] = BlockRedstoneRepeaterUnpowered.class; //93 - list[POWERED_REPEATER] = BlockRedstoneRepeaterPowered.class; //94 - list[INVISIBLE_BEDROCK] = BlockBedrockInvisible.class; //95 - list[TRAPDOOR] = BlockTrapdoor.class; //96 - list[MONSTER_EGG] = BlockMonsterEgg.class; //97 - list[STONE_BRICKS] = BlockBricksStone.class; //98 - list[BROWN_MUSHROOM_BLOCK] = BlockHugeMushroomBrown.class; //99 - list[RED_MUSHROOM_BLOCK] = BlockHugeMushroomRed.class; //100 - list[IRON_BARS] = BlockIronBars.class; //101 - list[GLASS_PANE] = BlockGlassPane.class; //102 - list[MELON_BLOCK] = BlockMelon.class; //103 - list[PUMPKIN_STEM] = BlockStemPumpkin.class; //104 - list[MELON_STEM] = BlockStemMelon.class; //105 - list[VINE] = BlockVine.class; //106 - list[FENCE_GATE] = BlockFenceGate.class; //107 - list[BRICK_STAIRS] = BlockStairsBrick.class; //108 - list[STONE_BRICK_STAIRS] = BlockStairsStoneBrick.class; //109 - list[MYCELIUM] = BlockMycelium.class; //110 - list[WATER_LILY] = BlockWaterLily.class; //111 - list[NETHER_BRICKS] = BlockBricksNether.class; //112 - list[NETHER_BRICK_FENCE] = BlockFenceNetherBrick.class; //113 - list[NETHER_BRICKS_STAIRS] = BlockStairsNetherBrick.class; //114 - list[NETHER_WART_BLOCK] = BlockNetherWart.class; //115 - list[ENCHANTING_TABLE] = BlockEnchantingTable.class; //116 - list[BREWING_STAND_BLOCK] = BlockBrewingStand.class; //117 - list[CAULDRON_BLOCK] = BlockCauldron.class; //118 - list[END_PORTAL] = BlockEndPortal.class; //119 - list[END_PORTAL_FRAME] = BlockEndPortalFrame.class; //120 - list[END_STONE] = BlockEndStone.class; //121 - list[DRAGON_EGG] = BlockDragonEgg.class; //122 - list[REDSTONE_LAMP] = BlockRedstoneLamp.class; //123 - list[LIT_REDSTONE_LAMP] = BlockRedstoneLampLit.class; //124 - //TODO: list[DROPPER] = BlockDropper.class; //125 - list[ACTIVATOR_RAIL] = BlockRailActivator.class; //126 - list[COCOA] = BlockCocoa.class; //127 - list[SANDSTONE_STAIRS] = BlockStairsSandstone.class; //128 - list[EMERALD_ORE] = BlockOreEmerald.class; //129 - list[ENDER_CHEST] = BlockEnderChest.class; //130 - list[TRIPWIRE_HOOK] = BlockTripWireHook.class; - list[TRIPWIRE] = BlockTripWire.class; //132 - list[EMERALD_BLOCK] = BlockEmerald.class; //133 - list[SPRUCE_WOOD_STAIRS] = BlockStairsSpruce.class; //134 - list[BIRCH_WOOD_STAIRS] = BlockStairsBirch.class; //135 - list[JUNGLE_WOOD_STAIRS] = BlockStairsJungle.class; //136 - - list[BEACON] = BlockBeacon.class; //138 - list[STONE_WALL] = BlockWall.class; //139 - list[FLOWER_POT_BLOCK] = BlockFlowerPot.class; //140 - list[CARROT_BLOCK] = BlockCarrot.class; //141 - list[POTATO_BLOCK] = BlockPotato.class; //142 - list[WOODEN_BUTTON] = BlockButtonWooden.class; //143 - list[SKULL_BLOCK] = BlockSkull.class; //144 - list[ANVIL] = BlockAnvil.class; //145 - list[TRAPPED_CHEST] = BlockTrappedChest.class; //146 - list[LIGHT_WEIGHTED_PRESSURE_PLATE] = BlockWeightedPressurePlateLight.class; //147 - list[HEAVY_WEIGHTED_PRESSURE_PLATE] = BlockWeightedPressurePlateHeavy.class; //148 - list[UNPOWERED_COMPARATOR] = BlockRedstoneComparatorUnpowered.class; //149 - list[POWERED_COMPARATOR] = BlockRedstoneComparatorPowered.class; //149 - list[DAYLIGHT_DETECTOR] = BlockDaylightDetector.class; //151 - list[REDSTONE_BLOCK] = BlockRedstone.class; //152 - list[QUARTZ_ORE] = BlockOreQuartz.class; //153 - list[HOPPER_BLOCK] = BlockHopper.class; //154 - list[QUARTZ_BLOCK] = BlockQuartz.class; //155 - list[QUARTZ_STAIRS] = BlockStairsQuartz.class; //156 - list[DOUBLE_WOOD_SLAB] = BlockDoubleSlabWood.class; //157 - list[WOOD_SLAB] = BlockSlabWood.class; //158 - list[STAINED_TERRACOTTA] = BlockTerracottaStained.class; //159 - list[STAINED_GLASS_PANE] = BlockGlassPaneStained.class; //160 - - list[LEAVES2] = BlockLeaves2.class; //161 - list[WOOD2] = BlockWood2.class; //162 - list[ACACIA_WOOD_STAIRS] = BlockStairsAcacia.class; //163 - list[DARK_OAK_WOOD_STAIRS] = BlockStairsDarkOak.class; //164 - list[SLIME_BLOCK] = BlockSlime.class; //165 - - list[IRON_TRAPDOOR] = BlockTrapdoorIron.class; //167 - list[PRISMARINE] = BlockPrismarine.class; //168 - list[SEA_LANTERN] = BlockSeaLantern.class; //169 - list[HAY_BALE] = BlockHayBale.class; //170 - list[CARPET] = BlockCarpet.class; //171 - list[TERRACOTTA] = BlockTerracotta.class; //172 - list[COAL_BLOCK] = BlockCoal.class; //173 - list[PACKED_ICE] = BlockIcePacked.class; //174 - list[DOUBLE_PLANT] = BlockDoublePlant.class; //175 - list[STANDING_BANNER] = BlockBanner.class; //176 - list[WALL_BANNER] = BlockWallBanner.class; //177 - list[DAYLIGHT_DETECTOR_INVERTED] = BlockDaylightDetectorInverted.class; //178 - list[RED_SANDSTONE] = BlockRedSandstone.class; //179 - list[RED_SANDSTONE_STAIRS] = BlockStairsRedSandstone.class; //180 - list[DOUBLE_RED_SANDSTONE_SLAB] = BlockDoubleSlabRedSandstone.class; //181 - list[RED_SANDSTONE_SLAB] = BlockSlabRedSandstone.class; //182 - list[FENCE_GATE_SPRUCE] = BlockFenceGateSpruce.class; //183 - list[FENCE_GATE_BIRCH] = BlockFenceGateBirch.class; //184 - list[FENCE_GATE_JUNGLE] = BlockFenceGateJungle.class; //185 - list[FENCE_GATE_DARK_OAK] = BlockFenceGateDarkOak.class; //186 - list[FENCE_GATE_ACACIA] = BlockFenceGateAcacia.class; //187 - - list[SPRUCE_DOOR_BLOCK] = BlockDoorSpruce.class; //193 - list[BIRCH_DOOR_BLOCK] = BlockDoorBirch.class; //194 - list[JUNGLE_DOOR_BLOCK] = BlockDoorJungle.class; //195 - list[ACACIA_DOOR_BLOCK] = BlockDoorAcacia.class; //196 - list[DARK_OAK_DOOR_BLOCK] = BlockDoorDarkOak.class; //197 - list[GRASS_PATH] = BlockGrassPath.class; //198 - list[ITEM_FRAME_BLOCK] = BlockItemFrame.class; //199 - list[CHORUS_FLOWER] = BlockChorusFlower.class; //200 - list[PURPUR_BLOCK] = BlockPurpur.class; //201 - - list[PURPUR_STAIRS] = BlockStairsPurpur.class; //203 - - list[UNDYED_SHULKER_BOX] = BlockUndyedShulkerBox.class; //205 - list[END_BRICKS] = BlockBricksEndStone.class; //206 - - list[END_ROD] = BlockEndRod.class; //208 - list[END_GATEWAY] = BlockEndGateway.class; //209 - - list[MAGMA] = BlockMagma.class; //213 - list[BLOCK_NETHER_WART_BLOCK] = BlockNetherWartBlock.class; //214 - list[RED_NETHER_BRICK] = BlockBricksRedNether.class; //215 - list[BONE_BLOCK] = BlockBone.class; //216 - - list[SHULKER_BOX] = BlockShulkerBox.class; //218 - list[PURPLE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedPurple.class; //219 - list[WHITE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedWhite.class; //220 - list[ORANGE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedOrange.class; //221 - list[MAGENTA_GLAZED_TERRACOTTA] = BlockTerracottaGlazedMagenta.class; //222 - list[LIGHT_BLUE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedLightBlue.class; //223 - list[YELLOW_GLAZED_TERRACOTTA] = BlockTerracottaGlazedYellow.class; //224 - list[LIME_GLAZED_TERRACOTTA] = BlockTerracottaGlazedLime.class; //225 - list[PINK_GLAZED_TERRACOTTA] = BlockTerracottaGlazedPink.class; //226 - list[GRAY_GLAZED_TERRACOTTA] = BlockTerracottaGlazedGray.class; //227 - list[SILVER_GLAZED_TERRACOTTA] = BlockTerracottaGlazedSilver.class; //228 - list[CYAN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedCyan.class; //229 - - list[BLUE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBlue.class; //231 - list[BROWN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBrown.class; //232 - list[GREEN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedGreen.class; //233 - list[RED_GLAZED_TERRACOTTA] = BlockTerracottaGlazedRed.class; //234 - list[BLACK_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBlack.class; //235 - list[CONCRETE] = BlockConcrete.class; //236 - list[CONCRETE_POWDER] = BlockConcretePowder.class; //237 - - list[CHORUS_PLANT] = BlockChorusPlant.class; //240 - list[STAINED_GLASS] = BlockGlassStained.class; //241 - list[PODZOL] = BlockPodzol.class; //243 - list[BEETROOT_BLOCK] = BlockBeetroot.class; //244 - list[STONECUTTER] = BlockStonecutter.class; //245 - list[GLOWING_OBSIDIAN] = BlockObsidianGlowing.class; //246 - //list[NETHER_REACTOR] = BlockNetherReactor.class; //247 Should not be removed - - //TODO: list[PISTON_EXTENSION] = BlockPistonExtension.class; //250 - - list[OBSERVER] = BlockObserver.class; //251 - - for (int id = 0; id < 256; id++) { - Class c = list[id]; + list = new Class[MAX_BLOCK_ID]; + fullList = new Block[MAX_BLOCK_ID * DATA_SIZE]; + light = new int[MAX_BLOCK_ID]; + lightFilter = new int[MAX_BLOCK_ID]; + solid = new boolean[MAX_BLOCK_ID]; + hardness = new double[MAX_BLOCK_ID]; + transparent = new boolean[MAX_BLOCK_ID]; + hasMeta = new boolean[MAX_BLOCK_ID]; + + Blocks.init(); + + for (int id = 0; id < MAX_BLOCK_ID; id++) { + Class c = list[id]; if (c != null) { Block block; try { - block = (Block) c.newInstance(); - try { - Constructor constructor = c.getDeclaredConstructor(int.class); + if (c.isAssignableFrom(BlockNotImplemented.class)) { + Constructor constructor = c.getDeclaredConstructor(int.class, int.class); constructor.setAccessible(true); - for (int data = 0; data < 16; ++data) { - fullList[(id << 4) | data] = (Block) constructor.newInstance(data); + block = (Block) constructor.newInstance(id, 0); + for (int data = 0; data < (1 << DATA_BITS); ++data) { + int fullId = (id << DATA_BITS) | data; + fullList[fullId] = (Block) constructor.newInstance(id, data); } hasMeta[id] = true; - } catch (NoSuchMethodException ignore) { - for (int data = 0; data < 16; ++data) { - fullList[(id << 4) | data] = block; + } else { + block = (Block) c.newInstance(); + try { + @SuppressWarnings("rawtypes") + Constructor constructor = c.getDeclaredConstructor(int.class); + constructor.setAccessible(true); + for (int data = 0; data < (1 << DATA_BITS); ++data) { + int fullId = (id << DATA_BITS) | data; + Block blockState; + try { + blockState = (Block) constructor.newInstance(data); + if (blockState.getDamage() != data) { + blockState = new BlockUnknown(id, data); + } + } catch (Exception e) { + Server.getInstance().getLogger().error("Error while registering " + c.getName(), e); + blockState = new BlockUnknown(id, data); + } + fullList[fullId] = blockState; + } + hasMeta[id] = true; + } catch (NoSuchMethodException ignore) { + for (int data = 0; data < DATA_SIZE; ++data) { + int fullId = (id << DATA_BITS) | data; + fullList[fullId] = block; + } } } } catch (Exception e) { - Server.getInstance().getLogger().error("Error while registering " + c.getName(), e); - for (int data = 0; data < 16; ++data) { - fullList[(id << 4) | data] = new BlockUnknown(id, data); - } - return; + throw new RuntimeException("Error while registering " + c.getName(), e); } solid[id] = block.isSolid(); @@ -338,7 +135,11 @@ public static void init() { } else { lightFilter[id] = 1; } - } else { + } else if (block instanceof BlockSlime) { + lightFilter[id] = 1; + } else if (id == CAULDRON_BLOCK) { + lightFilter[id] = 3; + }else { lightFilter[id] = 15; } } else { @@ -346,56 +147,182 @@ public static void init() { } } else { lightFilter[id] = 1; - for (int data = 0; data < 16; ++data) { - fullList[(id << 4) | data] = new BlockUnknown(id, data); + for (int data = 0; data < DATA_SIZE; ++data) { + fullList[(id << DATA_BITS) | data] = new BlockUnknown(id, data); } } } } } + public static Block get(MaterialType type) { + return get(type, 0); + } + + public static Block get(MaterialType type, Integer meta) { + if (!(type instanceof BlockType)) { + throw new IllegalArgumentException("Expected BlockType, got " + type.getClass().getSimpleName()); + } + return get(type.getLegacyId(), meta); + } + public static Block get(int id) { - return fullList[id << 4].clone(); + if (id < 0) { + id = 255 - id; + } + + if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return CustomBlockManager.get().getBlock(id, 0); + } + return fullList[id << DATA_BITS].clone(); } public static Block get(int id, Integer meta) { - if (meta != null) { - return fullList[(id << 4) + meta].clone(); - } else { - return fullList[id << 4].clone(); + if (id < 0) { + id = 255 - id; + } + + int fullId = meta == null ? (id << DATA_BITS ) : ((id << DATA_BITS) | meta); + if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return CustomBlockManager.get().getBlock(fullId); } + + return fullList[fullId].clone(); } public static Block get(int id, Integer meta, Position pos) { - Block block = fullList[(id << 4) | (meta == null ? 0 : meta)].clone(); + return get(id, meta, pos, LAYER_NORMAL); + } + + public static Block get(int id, Integer meta, Position pos, BlockLayer layer) { + if (id < 0) { + id = 255 - id; + } + + Block block; + if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + int fullId = (meta != null && meta > DATA_SIZE) ? (id << DATA_BITS) : ((id << DATA_BITS) | (meta == null ? 0 : meta)); + block = CustomBlockManager.get().getBlock(fullId); + } else if (meta != null && meta > DATA_SIZE) { + block = fullList[id << DATA_BITS].clone(); + block.setDamage(meta); + } else { + block = fullList[(id << DATA_BITS) | (meta == null ? 0 : meta)].clone(); + } + if (pos != null) { block.x = pos.x; block.y = pos.y; block.z = pos.z; block.level = pos.level; + block.layer = layer; } return block; } public static Block get(int id, int data) { - return fullList[(id << 4) + data].clone(); + if (id < 0) { + id = 255 - id; + } + + int fullId = (id << DATA_BITS ) | data; + if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return CustomBlockManager.get().getBlock(fullId); + } + return fullList[fullId].clone(); } public static Block get(int fullId, Level level, int x, int y, int z) { - Block block = fullList[fullId].clone(); + return get(fullId, level, x, y, z, LAYER_NORMAL); + } + + public static Block get(int fullId, Level level, int x, int y, int z, BlockLayer layer) { + Block block; + if ((fullId >> DATA_BITS) >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + block = CustomBlockManager.get().getBlock(fullId); + } else { + block = fullList[fullId].clone(); + } + block.x = x; block.y = y; block.z = z; block.level = level; + block.layer = layer; return block; } + public static Block getUnsafe(int fullId) { + if ((fullId >> DATA_BITS ) >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return CustomBlockManager.get().getBlock(fullId); + } + return fullList[fullId]; + } + + public static int getBlockLight(int blockId) { + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return light[0]; // TODO: just temporary + } + return light[blockId]; + } + + public static int getBlockLightFilter(int blockId) { + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return lightFilter[0]; // TODO: just temporary + } + return lightFilter[blockId]; + } + + public static boolean isBlockSolidById(int blockId) { + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return solid[1]; // TODO: just temporary + } + return solid[blockId]; + } + + public static boolean isBlockTransparentById(int blockId) { + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + return transparent[1]; // TODO: just temporary + } + return transparent[blockId]; + } + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - return this.getLevel().setBlock(this, this, true, true); + return this.canPlaceOn(block.down(), target) && this.getLevel().setBlock(this, this, true, true); } - //http://minecraft.gamepedia.com/Breaking - public boolean canHarvestWithHand() { //used for calculating breaking time + public boolean canPlaceOn(Block floor, Position pos) { + return this.canBePlaced(); + } + + public boolean canHarvest(Item item) { + return this.canHarvestWithHand() || this.getToolTier() == 0 || this.getToolType() == ItemTool.TYPE_NONE || correctTool0(this.getToolType(), item, this.getId()) && item.getTier() >= this.getToolType(); + } + + public WaterloggingType getWaterloggingType() { + return WaterloggingType.NO_WATERLOGGING; + } + + public enum WaterloggingType { + /** + * Block is not waterloggable + */ + NO_WATERLOGGING, + /** + * If possible, water will be set to second layer when the block is placed into water + */ + WHEN_PLACED_IN_WATER, + /** + * Water will flow into the block and water will be set to second layer + */ + FLOW_INTO_BLOCK + } + + public final boolean canWaterloggingFlowInto() { + return this.canBeFlowedInto() || this.getWaterloggingType() == WaterloggingType.FLOW_INTO_BLOCK; + } + + public boolean canHarvestWithHand() { return true; } @@ -407,6 +334,10 @@ public int tickRate() { return 10; } + public boolean onBreak(Item item, Player player) { + return this.onBreak(item); + } + public boolean onBreak(Item item) { return this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); } @@ -443,6 +374,10 @@ public int getToolType() { return ItemTool.TYPE_NONE; } + public int getToolTier() { + return 0; + } + public double getFrictionFactor() { return 0.6; } @@ -487,6 +422,10 @@ public boolean canBePushed() { return true; } + public boolean breakWhenPushed() { + return false; + } + public boolean hasComparatorInputOverride() { return false; } @@ -507,14 +446,30 @@ public BlockColor getColor() { public abstract int getId(); + public BlockType getBlockType() { + if (this.materialType == null) { + this.materialType = BlockTypes.getFromLegacy(this.getId()); + } + return this.materialType; + } + /** * The full id is a combination of the id and data. * @return full id */ public int getFullId() { - return (getId() << 4); + return getId() << DATA_BITS; } + public int getItemId() { + int id = this.getId(); + if (id > 255) { + return 255 - id; + } + return id; + } + + public void addVelocityToEntity(Entity entity, Vector3 vector) { } @@ -524,7 +479,6 @@ public int getDamage() { } public void setDamage(int meta) { - // Do nothing } public final void setDamage(Integer meta) { @@ -539,13 +493,14 @@ final public void position(Position v) { } public Item[] getDrops(Item item) { - if (this.getId() < 0 || this.getId() > list.length) { //Unknown blocks + if (this.getId() < 0 || this.getId() > list.length) { return new Item[0]; - } else { - return new Item[]{ - this.toItem() - }; } + + if (this.canHarvestWithHand() || this.canHarvest(item)) { + return new Item[]{this.toItem()}; + } + return new Item[0]; } private static double toolBreakTimeBonus0(int toolType, int toolTier, int blockId) { @@ -596,7 +551,20 @@ private static int toolType0(Item item) { return ItemTool.TYPE_NONE; } - private static boolean correctTool0(int blockToolType, Item item) { + private static boolean correctTool0(int blockToolType, Item item, int blockId) { + if (item.isShears() && (blockId == COBWEB || blockId == LEAVES || blockId == LEAVES2)) { + return true; + } + + if ((blockId == LEAVES && item.isHoe()) || + (blockId == LEAVES2 && item.isHoe())) { + return (blockToolType == ItemTool.TYPE_SHEARS && item.isHoe()); + } + + return correctTool(blockToolType, item); + } + + private static boolean correctTool(int blockToolType, Item item) { return (blockToolType == ItemTool.TYPE_SWORD && item.isSword()) || (blockToolType == ItemTool.TYPE_SHOVEL && item.isShovel()) || (blockToolType == ItemTool.TYPE_PICKAXE && item.isPickaxe()) || @@ -630,7 +598,7 @@ public double getBreakTime(Item item, Player player) { } int blockId = getId(); - boolean correctTool = correctTool0(getToolType(), item) + boolean correctTool = correctTool0(getToolType(), item, blockId) || item.isShears() && (blockId == COBWEB || blockId == LEAVES || blockId == LEAVES2); boolean canHarvestWithHand = canHarvestWithHand(); int itemToolType = toolType0(item); @@ -647,79 +615,28 @@ public double getBreakTime(Item item, Player player) { efficiencyLoreLevel, hasteEffectLevel, insideOfWaterWithoutAquaAffinity, outOfWaterButNotOnGround); } - /** - * @deprecated This function is lack of Player class and is not accurate enough, use #getBreakTime(Item, Player) - * @param item item used - * @return break time - */ - @Deprecated - public double getBreakTime(Item item) { - double base = this.getHardness() * 1.5; - if (this.canBeBrokenWith(item)) { - if (this.getToolType() == ItemTool.TYPE_SHEARS && item.isShears()) { - base /= 15; - } else if ( - (this.getToolType() == ItemTool.TYPE_PICKAXE && item.isPickaxe()) || - (this.getToolType() == ItemTool.TYPE_AXE && item.isAxe()) || - (this.getToolType() == ItemTool.TYPE_SHOVEL && item.isShovel()) || - (this.getToolType() == ItemTool.TYPE_HOE && item.isHoe()) - ) { - int tier = item.getTier(); - switch (tier) { - case ItemTool.TIER_WOODEN: - base /= 2; - break; - case ItemTool.TIER_STONE: - base /= 4; - break; - case ItemTool.TIER_IRON: - base /= 6; - break; - case ItemTool.TIER_DIAMOND: - base /= 8; - break; - case ItemTool.TIER_NETHERITE: - base /= 9; - break; - case ItemTool.TIER_GOLD: - base /= 12; - break; - } - } - } else { - base *= 3.33; - } - - if (item.isSword()) { - base *= 0.5; - } - - return base; - } - public boolean canBeBrokenWith(Item item) { return this.getHardness() != -1; } public Block getSide(BlockFace face) { - if (this.isValid()) { - return this.getLevel().getBlock((int) x + face.getXOffset(), (int) y + face.getYOffset(), (int) z + face.getZOffset()); - } - return this.getSide(face, 1); + return this.getSide(this.layer, face, 1); } public Block getSide(BlockFace face, int step) { + return this.getSide(this.layer, face, step); + } + + public Block getSide(BlockLayer layer, BlockFace face) { + return this.getSide(layer, face, 1); + } + + public Block getSide(BlockLayer layer, BlockFace face, int step) { if (this.isValid()) { - if (step == 1) { - return this.getLevel().getBlock((int) x + face.getXOffset(), (int) y + face.getYOffset(), (int) z + face.getZOffset()); - } else { - return this.getLevel().getBlock((int) x + face.getXOffset() * step, (int) y + face.getYOffset() * step, (int) z + face.getZOffset() * step); - } + return this.getLevel().getBlock(super.getSide(face, step), layer, true); } - Block block = Block.get(Item.AIR, 0); - block.x = (int) x + face.getXOffset() * step; - block.y = (int) y + face.getYOffset() * step; - block.z = (int) z + face.getZOffset() * step; + Block block = Block.get(Item.AIR, 0, Position.fromObject(new Vector3(this.x, this.y, this.z).getSide(face, step))); + block.layer = layer; return block; } @@ -731,6 +648,10 @@ public Block up(int step) { return getSide(BlockFace.UP, step); } + public Block up(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.UP, step); + } + public Block down() { return down(1); } @@ -739,6 +660,10 @@ public Block down(int step) { return getSide(BlockFace.DOWN, step); } + public Block down(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.DOWN, step); + } + public Block north() { return north(1); } @@ -747,6 +672,10 @@ public Block north(int step) { return getSide(BlockFace.NORTH, step); } + public Block north(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.NORTH, step); + } + public Block south() { return south(1); } @@ -755,6 +684,10 @@ public Block south(int step) { return getSide(BlockFace.SOUTH, step); } + public Block south(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.SOUTH, step); + } + public Block east() { return east(1); } @@ -763,6 +696,10 @@ public Block east(int step) { return getSide(BlockFace.EAST, step); } + public Block east(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.EAST, step); + } + public Block west() { return west(1); } @@ -771,9 +708,20 @@ public Block west(int step) { return getSide(BlockFace.WEST, step); } + public Block west(int step, BlockLayer layer) { + return this.getSide(layer, BlockFace.WEST, step); + } + + protected boolean isBlockAboveAir() { + if (this.level == null) { + return true; + } + return this.level.getBlockIdAt((int) this.x, (int) this.y + 1, (int) this.z) == AIR; + } + @Override public String toString() { - return "Block[" + this.getName() + "] (" + this.getId() + ":" + this.getDamage() + ")"; + return "Block[" + this.getName() + '|' + this.layer + "] (" + this.getId() + ':' + this.getDamage() + ')'; } public boolean collidesWithBB(AxisAlignedBB bb) { @@ -786,7 +734,6 @@ public boolean collidesWithBB(AxisAlignedBB bb, boolean collisionBB) { } public void onEntityCollide(Entity entity) { - } public AxisAlignedBB getBoundingBox() { @@ -801,6 +748,10 @@ protected AxisAlignedBB recalculateBoundingBox() { return this; } + protected AxisAlignedBB recalculateCollisionBoundingBox() { + return this.getBoundingBox(); + } + @Override public double getMinX() { return this.x; @@ -831,10 +782,6 @@ public double getMaxZ() { return this.z + 1; } - protected AxisAlignedBB recalculateCollisionBoundingBox() { - return getBoundingBox(); - } - public MovingObjectPosition calculateIntercept(Vector3 pos1, Vector3 pos2) { AxisAlignedBB bb = this.getBoundingBox(); if (bb == null) { @@ -967,7 +914,7 @@ public boolean isPowerSource() { } public String getLocationHash() { - return this.getFloorX() + ":" + this.getFloorY() + ":" + this.getFloorZ(); + return this.getFloorX() + ":" + this.getFloorY() + ':' + this.getFloorZ(); } public int getDropExp() { @@ -993,4 +940,78 @@ public Item toItem() { public boolean canSilkTouch() { return false; } + + public void setLayer(BlockLayer layer) { + this.layer = layer; + } + + public BlockLayer getLayer() { + return this.layer; + } + + protected static boolean canStayOnFullSolid(Block down) { + if (down.isTransparent()) { + switch (down.getId()) { + case BEACON: + case ICE: + case GLASS: + case STAINED_GLASS: + case HARD_GLASS: + case HARD_STAINED_GLASS: + case SCAFFOLDING: + case BARRIER: + case GLOWSTONE: + case SEA_LANTERN: + case HOPPER_BLOCK: + return true; + } + return false; + } + return true; + } + + protected static boolean canStayOnFullNonSolid(Block down) { + if (canStayOnFullSolid(down)) { + return true; + } + switch (down.getId()) { + case COMPOSTER: + case CAULDRON_BLOCK: + case LAVA_CAULDRON: + return true; + } + return false; + } + + public boolean alwaysDropsOnExplosion() { + return false; + } + + /** + * Check whether client will see a block as water (is water or uses fake waterlogging) + * @param id block id + * @return block has water + */ + public static boolean hasWater(int id) { + return id == WATER || id == STILL_WATER; + } + + public Block setUpdatePos(Vector3 pos) { + return this; // Only need to save this for observers + } + + public PersistentDataContainer getPersistentDataContainer() { + if (!this.isValid()) { + throw new IllegalStateException("Block does not have valid level"); + } + return this.level.getPersistentDataContainer(this); + } + + @SuppressWarnings("unused") + public boolean hasPersistentDataContainer() { + if (!this.isValid()) { + throw new IllegalStateException("Block does not have valid level"); + } + return this.level.hasPersistentDataContainer(this); + } } diff --git a/src/main/java/cn/nukkit/block/BlockAcaciaSignStanding.java b/src/main/java/cn/nukkit/block/BlockAcaciaSignStanding.java new file mode 100644 index 00000000000..da53929096d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAcaciaSignStanding.java @@ -0,0 +1,45 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockAcaciaSignStanding extends BlockSignPost { + + public BlockAcaciaSignStanding() { + this(0); + } + + public BlockAcaciaSignStanding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Acacia Sign Post"; + } + + @Override + public int getId() { + return ACACIA_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.ACACIA_SIGN); + } + + @Override + protected int getPostId() { + return ACACIA_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return ACACIA_WALL_SIGN; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAcaciaWallSign.java b/src/main/java/cn/nukkit/block/BlockAcaciaWallSign.java new file mode 100644 index 00000000000..05c1946614c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAcaciaWallSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockAcaciaWallSign extends BlockWallSign { + + public BlockAcaciaWallSign() { + this(0); + } + + public BlockAcaciaWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Acacia Wall Sign"; + } + + @Override + public int getId() { + return ACACIA_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.ACACIA_SIGN); + } + + @Override + protected int getPostId() { + return ACACIA_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return ACACIA_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAir.java b/src/main/java/cn/nukkit/block/BlockAir.java index 1fe3b4167ec..f19ca3032d5 100644 --- a/src/main/java/cn/nukkit/block/BlockAir.java +++ b/src/main/java/cn/nukkit/block/BlockAir.java @@ -5,13 +5,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockAir extends BlockTransparent { - public BlockAir() {} - @Override public int getId() { return AIR; diff --git a/src/main/java/cn/nukkit/block/BlockAllow.java b/src/main/java/cn/nukkit/block/BlockAllow.java new file mode 100644 index 00000000000..c3e71784320 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAllow.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockAllow extends BlockSolid { + + @Override + public int getId() { + return ALLOW; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Allow"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethyst.java b/src/main/java/cn/nukkit/block/BlockAmethyst.java new file mode 100644 index 00000000000..add8cd19147 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethyst.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockAmethyst extends BlockSolid { + + public BlockAmethyst() { + // Does nothing + } + + @Override + public String getName() { + return "Amethyst Block"; + } + + @Override + public int getId() { + return AMETHYST_BLOCK; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 1.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + // TODO: + /*@Override + public boolean isLavaResistant() { + return true; + }*/ +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethystBud.java b/src/main/java/cn/nukkit/block/BlockAmethystBud.java new file mode 100644 index 00000000000..2ded212179c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethystBud.java @@ -0,0 +1,111 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; +import cn.nukkit.utils.Faceable; + +public abstract class BlockAmethystBud extends BlockTransparentMeta implements Faceable { + + public BlockAmethystBud() { + this(0); + } + + public BlockAmethystBud(int meta) { + super(meta); + } + + protected abstract String getSizeName(); + + @Override + public String getName() { + return this.getSizeName() + " Amethyst Bud"; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(face.getIndex()); + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromIndex(this.getDamage()); + } + + public void setBlockFace(BlockFace face) { + this.setDamage(face.getIndex()); + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + switch (this.getBlockFace()) { + case UP: + return boundingBox(this.getCrystalOffset(), 0, this.getCrystalOffset(), 16 - this.getCrystalOffset(), + this.getCrystalHeight(), 16 - this.getCrystalOffset()); + case DOWN: + return boundingBox(this.getCrystalOffset(), 16 - this.getCrystalHeight(), this.getCrystalOffset(), + 16 - this.getCrystalOffset(), 16, 16 - this.getCrystalOffset()); + case NORTH: + return boundingBox(this.getCrystalOffset(), this.getCrystalOffset(), 16 - this.getCrystalHeight(), + 16 - this.getCrystalOffset(), 16 - this.getCrystalOffset(), 16); + case SOUTH: + return boundingBox(this.getCrystalOffset(), this.getCrystalOffset(), 0, + 16 - this.getCrystalOffset(), 16 - this.getCrystalOffset(), this.getCrystalHeight()); + case EAST: + return boundingBox(0, this.getCrystalOffset(), this.getCrystalOffset(), + this.getCrystalHeight(), 16 - this.getCrystalOffset(), 16 - this.getCrystalOffset()); + case WEST: + return boundingBox(16 - this.getCrystalHeight(), this.getCrystalOffset(), this.getCrystalHeight(), + 16, 16 - this.getCrystalOffset(), 16 - this.getCrystalOffset()); + } + return super.recalculateBoundingBox(); + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 1.5; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + protected abstract int getCrystalHeight(); + protected abstract int getCrystalOffset(); + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + protected static AxisAlignedBB boundingBox(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + return new SimpleAxisAlignedBB(minX / 16.0D, minY / 16.0D, minZ / 16.0D, maxX / 16.0D, maxY / 16.0D, maxZ / 16.0D); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethystBudLarge.java b/src/main/java/cn/nukkit/block/BlockAmethystBudLarge.java new file mode 100644 index 00000000000..ef9f7fea2e6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethystBudLarge.java @@ -0,0 +1,37 @@ +package cn.nukkit.block; + +public class BlockAmethystBudLarge extends BlockAmethystBud { + + public BlockAmethystBudLarge() { + this(0); + } + + public BlockAmethystBudLarge(int meta) { + super(meta); + } + + @Override + public int getId() { + return LARGE_AMETHYST_BUD; + } + + @Override + protected String getSizeName() { + return "Large"; + } + + @Override + protected int getCrystalHeight() { + return 5; + } + + @Override + protected int getCrystalOffset() { + return 3; + } + + @Override + public int getLightLevel() { + return 4; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethystBudMedium.java b/src/main/java/cn/nukkit/block/BlockAmethystBudMedium.java new file mode 100644 index 00000000000..5afdae1b0c5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethystBudMedium.java @@ -0,0 +1,37 @@ +package cn.nukkit.block; + +public class BlockAmethystBudMedium extends BlockAmethystBud { + + public BlockAmethystBudMedium() { + this(0); + } + + public BlockAmethystBudMedium(int meta) { + super(meta); + } + + @Override + public int getId() { + return MEDIUM_AMETHYST_BUD; + } + + @Override + protected String getSizeName() { + return "Medium"; + } + + @Override + protected int getCrystalHeight() { + return 4; + } + + @Override + protected int getCrystalOffset() { + return 3; + } + + @Override + public int getLightLevel() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethystBudSmall.java b/src/main/java/cn/nukkit/block/BlockAmethystBudSmall.java new file mode 100644 index 00000000000..434e4d9a8e3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethystBudSmall.java @@ -0,0 +1,37 @@ +package cn.nukkit.block; + +public class BlockAmethystBudSmall extends BlockAmethystBud { + + public BlockAmethystBudSmall() { + this(0); + } + + public BlockAmethystBudSmall(int meta) { + super(meta); + } + + @Override + public int getId() { + return SMALL_AMETHYST_BUD; + } + + @Override + protected String getSizeName() { + return "Small"; + } + + @Override + protected int getCrystalHeight() { + return 3; + } + + @Override + protected int getCrystalOffset() { + return 4; + } + + @Override + public int getLightLevel() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAmethystCluster.java b/src/main/java/cn/nukkit/block/BlockAmethystCluster.java new file mode 100644 index 00000000000..6a0c27a1757 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAmethystCluster.java @@ -0,0 +1,80 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.math.NukkitMath; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockAmethystCluster extends BlockAmethystBud { + + public BlockAmethystCluster() { + this(0); + } + + public BlockAmethystCluster(int meta) { + super(meta); + } + + @Override + public int getId() { + return AMETHYST_CLUSTER; + } + + @Override + protected int getCrystalHeight() { + return 7; + } + + @Override + protected int getCrystalOffset() { + return 7; + } + + @Override + protected String getSizeName() { + return "Cluster"; + } + + @Override + public String getName() { + return "Amethyst Cluster"; + } + + @Override + public int getLightLevel() { + return 5; + } + + @Override + public Item[] getDrops(Item item) { + if (!item.isPickaxe()) { + return new Item[]{Item.get(ItemID.AMETHYST_SHARD, 0, 2)}; + } + + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int amount = 4; + int fortuneLevel = NukkitMath.clamp(item.getEnchantmentLevel(Enchantment.ID_FORTUNE_DIGGING), 0, 3); + if (fortuneLevel > 0) { + int rand = ThreadLocalRandom.current().nextInt(100); + if (fortuneLevel == 1 && rand <= 33) { + amount = 8; + } else if (fortuneLevel == 2 && rand <= 25) { + amount = rand <= 12 ? 8 : 12; + } else if (fortuneLevel == 3 && rand <= 20) { + if (rand <= 6) { + amount = 8; + } else if (rand <= 13) { + amount = 12; + } else { + amount = 16; + } + } + } + return new Item[]{Item.get(ItemID.AMETHYST_SHARD, 0, amount)}; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAncientDebris.java b/src/main/java/cn/nukkit/block/BlockAncientDebris.java new file mode 100644 index 00000000000..990e3ee1bd6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAncientDebris.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockAncientDebris extends BlockSolid { + + public BlockAncientDebris() { + super(); + } + + @Override + public int getId() { + return ANCIENT_DEBRIS; + } + + @Override + public String getName() { + return "Ancient Debris"; + } + + @Override + public double getResistance() { + return 1200; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 30; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAnvil.java b/src/main/java/cn/nukkit/block/BlockAnvil.java index 6694f517c13..457cbf70241 100644 --- a/src/main/java/cn/nukkit/block/BlockAnvil.java +++ b/src/main/java/cn/nukkit/block/BlockAnvil.java @@ -6,15 +6,18 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; +import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; /** * Created by Pub4Game on 27.12.2015. */ -public class BlockAnvil extends BlockFallable implements Faceable { +public class BlockAnvil extends BlockFallableMeta implements Faceable { - private static final String[] NAMES = new String[]{ + private static final int[] faces = {1, 2, 3, 0}; + + private static final String[] NAMES = { "Anvil", "Anvil", "Anvil", @@ -29,29 +32,17 @@ public class BlockAnvil extends BlockFallable implements Faceable { "Very Damaged Anvil" }; - private int meta; - public BlockAnvil() { this(0); } public BlockAnvil(int meta) { - this.meta = meta; + super(meta); } @Override public int getFullId() { - return (getId() << 4) + getDamage(); - } - - @Override - public final int getDamage() { - return this.meta; - } - - @Override - public final void setDamage(int meta) { - this.meta = meta; + return (getId() << DATA_BITS) + getDamage(); } @Override @@ -91,19 +82,16 @@ public String getName() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (!target.isTransparent() || target.getId() == Block.SNOW_LAYER) { - int damage = this.getDamage(); - int[] faces = {1, 2, 3, 0}; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); - if (damage >= 4 && damage <= 7) { - this.setDamage(this.getDamage() | 0x04); - } else if (damage >= 8 && damage <= 11) { - this.setDamage(this.getDamage() | 0x08); - } - this.getLevel().setBlock(block, this, true); - return true; + int damage = this.getDamage(); + this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + if (damage >= 4 && damage <= 7) { + this.setDamage(this.getDamage() | 0x04); + } else if (damage >= 8 && damage <= 11) { + this.setDamage(this.getDamage() | 0x08); } - return false; + this.getLevel().setBlock(this, this, true, true); + this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ANVIL_FALL); + return true; } @Override @@ -128,7 +116,7 @@ public Item toItem() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ this.toItem() }; @@ -175,4 +163,9 @@ public double getMaxZ() { public boolean isSolid() { return false; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockAzalea.java b/src/main/java/cn/nukkit/block/BlockAzalea.java new file mode 100644 index 00000000000..396e7c5f88d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAzalea.java @@ -0,0 +1,100 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Position; +import cn.nukkit.math.AxisAlignedBB; + +public class BlockAzalea extends BlockTransparent { + + public BlockAzalea() { + // Does nothing + } + + @Override + public String getName() { + return "Azalea"; + } + + @Override + public int getId() { + return AZALEA; + } + + @Override + public boolean canPlaceOn(Block floor, Position pos) { + // Azaleas can be placed on grass blocks, dirt, coarse dirt, rooted dirt, podzol, moss blocks, farmland, mud, muddy mangrove roots and clay. + switch (floor.getId()) { + case GRASS: + case DIRT: + case ROOTED_DIRT: + case PODZOL: + case MOSS_BLOCK: + case FARMLAND: + case MUD: + case CLAY_BLOCK: + return true; + } + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_NONE; + } + + @Override + public boolean canHarvestWithHand() { + return true; + } + + @Override + public boolean canBeClimbed() { + return true; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 0; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return null; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAzaleaFlowering.java b/src/main/java/cn/nukkit/block/BlockAzaleaFlowering.java new file mode 100644 index 00000000000..abc85214985 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAzaleaFlowering.java @@ -0,0 +1,17 @@ +package cn.nukkit.block; + +public class BlockAzaleaFlowering extends BlockAzalea { + + public BlockAzaleaFlowering() { + } + + @Override + public String getName() { + return "Flowering Azalea"; + } + + @Override + public int getId() { + return FLOWERING_AZALEA; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAzaleaLeaves.java b/src/main/java/cn/nukkit/block/BlockAzaleaLeaves.java new file mode 100644 index 00000000000..6c7d7039cff --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAzaleaLeaves.java @@ -0,0 +1,93 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.math.BlockFace; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockAzaleaLeaves extends BlockLeaves { + + public BlockAzaleaLeaves() { + this(0); + } + + public BlockAzaleaLeaves(int meta) { + super(meta); + } + + @Override + public int getId() { + return AZALEA_LEAVES; + } + + @Override + public String getName() { + return "Azalea Leaves"; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setPersistent(true); + return super.place(item, block, target, face, fx, fy, fz, player); + } + + @Override + public Item toItem() { + return new ItemBlock(this, 0, 1); + } + + @Override + public Item[] getDrops(Item item) { + if (item.isShears() || item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{ this.toItem() }; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (random.nextInt(20) != 0) { + return new Item[0]; + } + + int chance = random.nextInt(4); + if (chance == 0 || chance == 1) { + return new Item[]{ Item.get(random.nextBoolean() ? Item.AZALEA : Item.FLOWERING_AZALEA, 0, 1) }; + } else if (chance == 2) { + return new Item[]{ Item.get(Item.SAPLING, random.nextInt(0, 6), 1) }; + } else { + return new Item[]{ Item.get(Item.STICK, 0, random.nextInt(1, 2)) }; + } + } + + @Override + protected void setOnDecayDamage() { + this.setCheckDecay(false); + } + + @Override + protected boolean canDropApple() { + return false; + } + + @Override + public boolean isPersistent() { + return (this.getDamage() & 2) >>> 1 == 1; + } + + @Override + public void setPersistent(boolean persistent) { + int value = (persistent ? 1 : 0) << 1; + this.setDamage(this.getDamage() & ~1 | (value & 2)); + } + + @Override + public boolean isCheckDecay() { + return (this.getDamage() & 1) == 1; + } + + @Override + public void setCheckDecay(boolean updateBit) { + this.setDamage(this.getDamage() & ~1 | ((updateBit ? 1 : 0) & 1)); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockAzaleaLeavesFlowered.java b/src/main/java/cn/nukkit/block/BlockAzaleaLeavesFlowered.java new file mode 100644 index 00000000000..b66f01f7a5d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockAzaleaLeavesFlowered.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockAzaleaLeavesFlowered extends BlockAzaleaLeaves { + + public BlockAzaleaLeavesFlowered() { + this(0); + } + + public BlockAzaleaLeavesFlowered(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Flowering Azalea Leaves"; + } + + @Override + public int getId() { + return AZALEA_LEAVES_FLOWERED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBamboo.java b/src/main/java/cn/nukkit/block/BlockBamboo.java new file mode 100644 index 00000000000..73ccadb7b22 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBamboo.java @@ -0,0 +1,318 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.*; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.MathHelper; +import cn.nukkit.network.protocol.AnimatePacket; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockBamboo extends BlockTransparentMeta { + + public static final int LEAF_SIZE_NONE = 0; + public static final int LEAF_SIZE_SMALL = 1; + public static final int LEAF_SIZE_LARGE = 2; + + public BlockBamboo() { + this(0); + } + + public BlockBamboo(int meta) { + super(meta); + } + + @Override + public int getId() { + return BAMBOO; + } + + @Override + public String getName() { + return "Bamboo"; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.isSupportInvalid()) { + this.level.scheduleUpdate(this, 0); + } + return type; + } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.level.useBreakOn(this, null, null, true); + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + Block up = this.up(); + if (this.getAge() == 0 && up.getId() == AIR && level.isAnimalSpawningAllowedByTime()/*this.level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { + this.grow(up); + } + return type; + } + return 0; + } + + public boolean grow(Block up) { + BlockBamboo newState = (BlockBamboo) Block.get(Block.BAMBOO); + if (this.isThick()) { + newState.setThick(true); + newState.setLeafSize(LEAF_SIZE_LARGE); + } else { + newState.setLeafSize(LEAF_SIZE_SMALL); + } + BlockGrowEvent blockGrowEvent = new BlockGrowEvent(up, newState); + level.getServer().getPluginManager().callEvent(blockGrowEvent); + if (!blockGrowEvent.isCancelled()) { + Block newState1 = blockGrowEvent.getNewState(); + newState1.x = x; + newState1.y = up.y; + newState1.z = z; + newState1.level = level; + newState1.place(this.toItem(), up, this, BlockFace.DOWN, 0.5, 0.5, 0.5, null); + return true; + } + return false; + } + + private int countHeight() { + int count = 0; + Block opt; + Block down = this; + while ((opt = down.down()).getId() == BAMBOO) { + down = opt; + if (++count >= 16) { + break; + } + } + return count; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + Block down = this.down(); + int downId = down.getId(); + if (downId != BAMBOO && downId != BAMBOO_SAPLING) { + BlockBambooSapling sampling = (BlockBambooSapling) Block.get(Block.BAMBOO_SAPLING); + sampling.x = x; + sampling.y = y; + sampling.z = z; + sampling.level = level; + return sampling.place(item, block, target, face, fx, fy, fz, player); + } + + boolean canGrow = true; + + if (downId == BAMBOO_SAPLING) { + if (player != null) { + AnimatePacket animatePacket = new AnimatePacket(); + animatePacket.action = AnimatePacket.Action.SWING_ARM; + animatePacket.eid = player.getId(); + this.getLevel().addChunkPacket(player.getChunkX(), player.getChunkZ(), animatePacket); + } + this.setLeafSize(LEAF_SIZE_SMALL); + } if (down instanceof BlockBamboo) { + BlockBamboo bambooDown = (BlockBamboo) down; + canGrow = bambooDown.getAge() == 0; + boolean thick = bambooDown.isThick(); + if (!thick) { + boolean setThick = true; + for (int i = 2; i <= 3; i++) { + if (this.getSide(BlockFace.DOWN, i).getId() != BAMBOO) { + setThick = false; + } + } + if (setThick) { + this.setThick(true); + this.setLeafSize(LEAF_SIZE_LARGE); + bambooDown.setLeafSize(LEAF_SIZE_SMALL); + bambooDown.setThick(true); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + while ((down = down.down()) instanceof BlockBamboo) { + bambooDown = (BlockBamboo) down; + bambooDown.setThick(true); + bambooDown.setLeafSize(LEAF_SIZE_NONE); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + } + } else { + this.setLeafSize(LEAF_SIZE_SMALL); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + } + } else { + this.setThick(true); + this.setLeafSize(LEAF_SIZE_LARGE); + this.setAge(0); + bambooDown.setLeafSize(LEAF_SIZE_LARGE); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + down = bambooDown.down(); + if (down instanceof BlockBamboo) { + bambooDown = (BlockBamboo) down; + bambooDown.setLeafSize(LEAF_SIZE_SMALL); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + down = bambooDown.down(); + if (down instanceof BlockBamboo) { + bambooDown = (BlockBamboo) down; + bambooDown.setLeafSize(LEAF_SIZE_NONE); + bambooDown.setAge(1); + this.level.setBlock(bambooDown, bambooDown, false, true); + } + } + } + } else if (this.isSupportInvalid()) { + return false; + } + + int height = canGrow? this.countHeight() : 0; + if (!canGrow || height >= 15 || height >= 11 && ThreadLocalRandom.current().nextFloat() < 0.25F) { + this.setAge(1); + } + + this.level.setBlock(this, this, false, true); + return true; + } + + @Override + public boolean onBreak(Item item) { + Block down = this.down(); + if (down instanceof BlockBamboo) { + BlockBamboo bambooDown = (BlockBamboo) down; + int height = bambooDown.countHeight(); + if (height < 15 && (height < 11 || !(ThreadLocalRandom.current().nextFloat() < 0.25F))) { + bambooDown.setAge(0); + this.level.setBlock(bambooDown, bambooDown, false, true); + } + } + return super.onBreak(item); + } + + @Override + public boolean canPassThrough() { + return true; + } + + private boolean isSupportInvalid() { + int downId = this.down().getId(); + return downId != BAMBOO && downId != DIRT && downId != GRASS && downId != SAND && downId != GRAVEL && downId != PODZOL && downId != BAMBOO_SAPLING; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.FOLIAGE_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 1; + } + + @Override + public double getResistance() { + return 2; + } + + public boolean isThick() { + return (this.getDamage() & 0x1) == 0x1; + } + + public void setThick(boolean thick) { + this.setDamage(this.getDamage() & (15 ^ 0x1) | (thick? 0x1 : 0x0)); + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + public int getLeafSize() { + return (this.getDamage() >> 1) & 0x3; + } + + public void setLeafSize(int leafSize) { + leafSize = MathHelper.clamp(leafSize, LEAF_SIZE_NONE, LEAF_SIZE_LARGE) & 0b11; + this.setDamage(this.getDamage() & (15 ^ 0b110) | (leafSize << 1)); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + int top = (int) y; + int count = 1; + + for (int i = 1; i <= 16; i++) { + int id = this.level.getBlockIdAt(this.getFloorX(), this.getFloorY() - i, this.getFloorZ()); + if (id == BAMBOO) { + count++; + } else { + break; + } + } + + for (int i = 1; i <= 16; i++) { + int id = this.level.getBlockIdAt(this.getFloorX(), this.getFloorY() + i, this.getFloorZ()); + if (id == BAMBOO) { + top++; + count++; + } else { + break; + } + } + + if (count >= 15) { + return false; + } + + boolean success = false; + + Block block = this.up(top - (int)y + 1); + if (block.getId() == BlockID.AIR) { + success = this.grow(block); + } + + if (success) { + if (player != null && !player.isCreative()) { + item.count--; + } + level.addParticle(new BoneMealParticle(this)); + } + + return true; + } + return false; + } + + public int getAge() { + return (this.getDamage() & 0x8) >> 3; + } + + public void setAge(int age) { + age = MathHelper.clamp(age, 0, 1) << 3; + this.setDamage(this.getDamage() & (15 ^ 0b1000) | age); + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBambooSapling.java b/src/main/java/cn/nukkit/block/BlockBambooSapling.java new file mode 100644 index 00000000000..3b1800267d7 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBambooSapling.java @@ -0,0 +1,172 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.MathHelper; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockBambooSapling extends BlockFlowable { + + public BlockBambooSapling() { + this(0); + } + + public BlockBambooSapling(int meta) { + super(meta); + } + + @Override + public int getId() { + return BAMBOO_SAPLING; + } + + @Override + public String getName() { + return "Bamboo Sapling"; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (isSupportInvalid()) { + level.useBreakOn(this, null, null, true); + } else { + Block up = up(); + if (up.getId() == BAMBOO) { + BlockBamboo upperBamboo = (BlockBamboo) up; + BlockBamboo newState = (BlockBamboo) Block.get(Block.BAMBOO); + newState.setThick(upperBamboo.isThick()); + level.setBlock(this, newState, true, true); + } + } + return type; + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + Block up = up(); + if (getAge() == 0 && up.getId() == AIR && level.isAnimalSpawningAllowedByTime()/*level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { + BlockBamboo newState = (BlockBamboo) Block.get(Block.BAMBOO); + newState.setLeafSize(BlockBamboo.LEAF_SIZE_SMALL); + BlockGrowEvent blockGrowEvent = new BlockGrowEvent(up, newState); + level.getServer().getPluginManager().callEvent(blockGrowEvent); + if (!blockGrowEvent.isCancelled()) { + Block newState1 = blockGrowEvent.getNewState(); + newState1.y = up.y; + newState1.x = x; + newState1.z = z; + newState1.level = level; + newState1.place(toItem(), up, this, BlockFace.DOWN, 0.5, 0.5, 0.5, null); + } + } + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (isSupportInvalid()) { + return false; + } + + if (this.getLevelBlock() instanceof BlockLiquid) { + return false; + } + + this.level.setBlock(this, this, true, true); + return true; + } + + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + boolean success = false; + Block block = this.up(); + if (block.getId() == AIR) { + success = this.grow(block); + } + + if (success) { + if (player != null && (player.gamemode & 0x01) == 0) { + item.count--; + } + + this.level.addParticle(new BoneMealParticle(this)); + } + + return true; + } + return false; + } + + public boolean grow(Block up) { + BlockBamboo bamboo = (BlockBamboo) Block.get(Block.BAMBOO); + bamboo.x = x; + bamboo.y = y; + bamboo.z = z; + bamboo.level = level; + return bamboo.grow(up); + } + + private boolean isSupportInvalid() { + int downId = down().getId(); + return downId != DIRT && downId != GRASS && downId != SAND && downId != GRAVEL && downId != PODZOL; + } + + public int getAge() { + return getDamage() & 0x1; + } + + public void setAge(int age) { + age = MathHelper.clamp(age, 0, 1) & 0x1; + setDamage(getDamage() & (15 ^ 0x1) | age); + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(BAMBOO), 0); + } + + @Override + public double getMinX() { + return this.x + 0.125; + } + + @Override + public double getMinZ() { + return this.z + 0.125; + } + + @Override + public double getMaxX() { + return this.x + 0.875; + } + + @Override + public double getMaxY() { + return this.y + 0.875; + } + + @Override + public double getMaxZ() { + return this.z + 0.875; + } + + @Override + public BlockColor getColor() { + return BlockColor.FOLIAGE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBanner.java b/src/main/java/cn/nukkit/block/BlockBanner.java index 2c4e293b72d..d870e12fdce 100644 --- a/src/main/java/cn/nukkit/block/BlockBanner.java +++ b/src/main/java/cn/nukkit/block/BlockBanner.java @@ -17,9 +17,6 @@ import cn.nukkit.utils.DyeColor; import cn.nukkit.utils.Faceable; -/** - * Created by PetteriM1 - */ public class BlockBanner extends BlockTransparentMeta implements Faceable { public BlockBanner() { @@ -56,7 +53,7 @@ public String getName() { } @Override - protected AxisAlignedBB recalculateBoundingBox() { + public AxisAlignedBB getBoundingBox() { return null; } @@ -71,29 +68,34 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl if (face == BlockFace.UP) { this.setDamage(NukkitMath.floorDouble(((player.yaw + 180) * 16 / 360) + 0.5) & 0x0f); this.getLevel().setBlock(block, this, true); + } else if (target.canBeReplaced()) { + this.setDamage(NukkitMath.floorDouble(((player.yaw + 180) * 16 / 360) + 0.5) & 0x0f); + this.getLevel().setBlock(target, this, true); } else { this.setDamage(face.getIndex()); - this.getLevel().setBlock(block, Block.get(BlockID.WALL_BANNER, this.getDamage()), true); + this.getLevel().setBlock(block, Block.get(WALL_BANNER, this.getDamage()), true); } CompoundTag nbt = BlockEntity.getDefaultCompound(this, BlockEntity.BANNER) .putInt("Base", item.getDamage() & 0xf); Tag type = item.getNamedTagEntry("Type"); + if (type instanceof IntTag) { nbt.put("Type", type); } + Tag patterns = item.getNamedTagEntry("Patterns"); if (patterns instanceof ListTag) { nbt.put("Patterns", patterns); } - BlockEntityBanner banner = (BlockEntityBanner) BlockEntity.createBlockEntity(BlockEntity.BANNER, this.getChunk(), nbt); - return banner != null; + BlockEntity.createBlockEntity(BlockEntity.BANNER, this.getChunk(), nbt); + return true; } return false; } - + @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { @@ -154,4 +156,14 @@ public DyeColor getDyeColor() { public boolean isSolid() { return false; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockBarrel.java b/src/main/java/cn/nukkit/block/BlockBarrel.java new file mode 100644 index 00000000000..97bb9fe49f3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBarrel.java @@ -0,0 +1,171 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityBarrel; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.nbt.tag.Tag; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; + +import java.util.Map; + +public class BlockBarrel extends BlockSolidMeta implements Faceable { + + public BlockBarrel() { + this(0); + } + + public BlockBarrel(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Barrel"; + } + + @Override + public int getId() { + return BARREL; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + double y = player.y + player.getEyeHeight(); + if (y - this.y > 2) { + this.setDamage(BlockFace.UP.getIndex()); + } else if (this.y - y > 0) { + this.setDamage(BlockFace.DOWN.getIndex()); + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + + this.level.setBlock(block, this, true, true); + + CompoundTag nbt = new CompoundTag("") + .putList(new ListTag<>("Items")) + .putString("id", BlockEntity.BARREL) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + if (item.hasCustomBlockData()) { + Map customData = item.getCustomBlockData().getTags(); + for (Map.Entry tag : customData.entrySet()) { + nbt.put(tag.getKey(), tag.getValue()); + } + } + + BlockEntity.createBlockEntity(BlockEntity.BARREL, this.getChunk(), nbt); + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player == null) { + return false; + } + + BlockEntity blockEntity = level.getBlockEntity(this); + if (!(blockEntity instanceof BlockEntityBarrel)) { + return false; + } + + BlockEntityBarrel barrel = (BlockEntityBarrel) blockEntity; + + if (barrel.namedTag.contains("Lock") && barrel.namedTag.get("Lock") instanceof StringTag) { + if (!barrel.namedTag.getString("Lock").equals(item.getCustomName())) { + return true; + } + } + + player.addWindow(barrel.getInventory()); + + return true; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public double getHardness() { + return 2.5; + } + + @Override + public double getResistance() { + return 12.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(BARREL)); + } + + @Override + public BlockFace getBlockFace() { + int index = getDamage() & 0x7; + return BlockFace.fromIndex(index); + } + + public void setBlockFace(BlockFace face) { + this.setDamage((this.getDamage() & 0x8) | (face.getIndex() & 0x7)); + } + + public boolean isOpen() { + return (this.getDamage() & 0x8) == 0x8; + } + + public void setOpen(boolean open) { + this.setDamage((this.getDamage() & 0x7) | (open? 0x8 : 0x0)); + } + + @Override + public boolean hasComparatorInputOverride() { + return true; + } + + @Override + public int getComparatorInputOverride() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (blockEntity instanceof BlockEntityBarrel) { + return ContainerInventory.calculateRedstone(((BlockEntityBarrel) blockEntity).getInventory()); + } + + return super.getComparatorInputOverride(); + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBarrier.java b/src/main/java/cn/nukkit/block/BlockBarrier.java new file mode 100644 index 00000000000..5d73f67917b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBarrier.java @@ -0,0 +1,57 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.utils.BlockColor; + +public class BlockBarrier extends BlockTransparent { + + @Override + public String getName() { + return "Barrier"; + } + + @Override + public int getId() { + return BARRIER; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.TRANSPARENT_BLOCK_COLOR; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBasalt.java b/src/main/java/cn/nukkit/block/BlockBasalt.java new file mode 100644 index 00000000000..f8a03e5475e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBasalt.java @@ -0,0 +1,97 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockBasalt extends BlockSolidMeta { + + public BlockBasalt() { + this(0); + } + + public BlockBasalt(int meta) { + super(meta); + } + + @Override + public double getHardness() { + return 1.25; + } + + @Override + public double getResistance() { + return 4.2; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setPillarAxis(face.getAxis()); + this.getLevel().setBlock(block, this, true, true); + return true; + } + + public void setPillarAxis(BlockFace.Axis axis) { + switch (axis) { + case Y: + this.setDamage(0); + break; + case X: + this.setDamage(1); + break; + case Z: + this.setDamage(2); + break; + } + } + + public BlockFace.Axis getPillarAxis() { + switch (this.getDamage() % 3) { + case 2: + return BlockFace.Axis.Z; + case 1: + return BlockFace.Axis.X; + case 0: + default: + return BlockFace.Axis.Y; + } + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Basalt"; + } + + @Override + public int getId() { + return BlockID.BASALT; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBasaltSmooth.java b/src/main/java/cn/nukkit/block/BlockBasaltSmooth.java new file mode 100644 index 00000000000..6e1394b16fe --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBasaltSmooth.java @@ -0,0 +1,60 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockBasaltSmooth extends BlockSolid { + + @Override + public String getName() { + return "Smooth Basalt"; + } + + @Override + public int getId() { + return SMOOTH_BASALT; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 1.25; + } + + @Override + public double getResistance() { + return 4.2; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBeacon.java b/src/main/java/cn/nukkit/block/BlockBeacon.java index 8a52c099ef9..6bba6e6309b 100644 --- a/src/main/java/cn/nukkit/block/BlockBeacon.java +++ b/src/main/java/cn/nukkit/block/BlockBeacon.java @@ -11,13 +11,10 @@ import cn.nukkit.utils.BlockColor; /** - * author: Angelic47 Nukkit Project + * @author Angelic47 Nukkit Project */ public class BlockBeacon extends BlockTransparent { - public BlockBeacon() { - } - @Override public int getId() { return BEACON; @@ -57,19 +54,8 @@ public boolean canBeActivated() { public boolean onActivate(Item item, Player player) { if (player != null) { BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityBeacon beacon; - if (t instanceof BlockEntityBeacon) { - beacon = (BlockEntityBeacon) t; - } else { - CompoundTag nbt = new CompoundTag("") - .putString("id", BlockEntity.BEACON) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - beacon = (BlockEntityBeacon) BlockEntity.createBlockEntity(BlockEntity.BEACON, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (beacon == null) { - return false; - } + if (!(t instanceof BlockEntityBeacon)) { + return false; } player.addWindow(new BeaconInventory(player.getUIInventory(), this), Player.BEACON_WINDOW_ID); @@ -79,21 +65,18 @@ public boolean onActivate(Item item, Player player) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - boolean blockSuccess = super.place(item, block, target, face, fx, fy, fz, player); - - if (blockSuccess) { + if (this.getLevel().setBlock(this, this, true, true)) { CompoundTag nbt = new CompoundTag("") .putString("id", BlockEntity.BEACON) .putInt("x", (int) this.x) .putInt("y", (int) this.y) .putInt("z", (int) this.z); - BlockEntityBeacon beacon = (BlockEntityBeacon) BlockEntity.createBlockEntity(BlockEntity.BEACON, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (beacon == null) { - return false; - } + BlockEntity.createBlockEntity(BlockEntity.BEACON, this.getChunk(), nbt); + + return true; } - return blockSuccess; + return false; } @Override @@ -105,4 +88,19 @@ public boolean canBePushed() { public BlockColor getColor() { return BlockColor.DIAMOND_BLOCK_COLOR; } -} + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean alwaysDropsOnExplosion() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockBed.java b/src/main/java/cn/nukkit/block/BlockBed.java index 482a7bd45c8..213641230e5 100644 --- a/src/main/java/cn/nukkit/block/BlockBed.java +++ b/src/main/java/cn/nukkit/block/BlockBed.java @@ -3,20 +3,27 @@ import cn.nukkit.Player; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityBed; -import cn.nukkit.entity.item.EntityPrimedTNT; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.mob.*; +import cn.nukkit.event.player.PlayerBedEnterEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBed; -import cn.nukkit.lang.TranslationContainer; +import cn.nukkit.level.Explosion; +import cn.nukkit.level.GameRule; import cn.nukkit.level.Level; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.DyeColor; import cn.nukkit.utils.Faceable; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockBed extends BlockTransparentMeta implements Faceable { @@ -39,11 +46,6 @@ public boolean canBeActivated() { return true; } - @Override - public double getResistance() { - return 1; - } - @Override public double getHardness() { return 0.2; @@ -59,26 +61,23 @@ public double getMaxY() { return this.y + 0.5625; } - @Override - public boolean onActivate(Item item) { - return this.onActivate(item, null); - } + /** + * List of mob network IDs which make players unable to sleep when nearby the bed. + */ + private static final IntSet MOB_IDS = new IntOpenHashSet(new int[]{EntityBlaze.NETWORK_ID, EntityCaveSpider.NETWORK_ID, EntityCreeper.NETWORK_ID, EntityDrowned.NETWORK_ID, EntityElderGuardian.NETWORK_ID, EntityEnderman.NETWORK_ID, EntityEndermite.NETWORK_ID, EntityEvoker.NETWORK_ID, EntityGhast.NETWORK_ID, EntityGuardian.NETWORK_ID, EntityHoglin.NETWORK_ID, EntityHusk.NETWORK_ID, EntityPiglinBrute.NETWORK_ID, EntityPillager.NETWORK_ID, EntityRavager.NETWORK_ID, EntityShulker.NETWORK_ID, EntitySilverfish.NETWORK_ID, EntitySkeleton.NETWORK_ID, EntitySlime.NETWORK_ID, EntitySpider.NETWORK_ID, EntityStray.NETWORK_ID, EntityVex.NETWORK_ID, EntityVindicator.NETWORK_ID, EntityWitch.NETWORK_ID, EntityWither.NETWORK_ID, EntityWitherSkeleton.NETWORK_ID, EntityZoglin.NETWORK_ID, EntityZombie.NETWORK_ID, EntityZombiePigman.NETWORK_ID, EntityZombieVillagerV1.NETWORK_ID, EntityZombieVillager.NETWORK_ID}); @Override public boolean onActivate(Item item, Player player) { + if (this.level.getDimension() != Level.DIMENSION_OVERWORLD) { + if (this.level.getGameRules().getBoolean(GameRule.RESPAWN_BLOCKS_EXPLODE)) { + if (this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true)) { + this.level.addParticle(new DestroyBlockParticle(this.add(0.5, 0.5, 0.5), this)); + } - if (this.level.getDimension() == Level.DIMENSION_NETHER || this.level.getDimension() == Level.DIMENSION_THE_END) { - CompoundTag tag = EntityPrimedTNT.getDefaultNBT(this).putShort("Fuse", 0); - new EntityPrimedTNT(this.level.getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), tag); - return true; - } - - int time = this.getLevel().getTime() % Level.TIME_FULL; - - boolean isNight = (time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE); - - if (player != null && !isNight) { - player.sendMessage(new TranslationContainer("tile.bed.noSleep")); + Explosion explosion = new Explosion(this.add(0.5, 0, 0.5), 5, this); + explosion.explodeA(); + explosion.explodeB(); + } return true; } @@ -91,43 +90,72 @@ public boolean onActivate(Item item, Player player) { if ((this.getDamage() & 0x08) == 0x08) { b = this; } else { - if (blockNorth.getId() == this.getId() && (blockNorth.getDamage() & 0x08) == 0x08) { + if (blockNorth.getId() == BED_BLOCK && (blockNorth.getDamage() & 0x08) == 0x08) { b = blockNorth; - } else if (blockSouth.getId() == this.getId() && (blockSouth.getDamage() & 0x08) == 0x08) { + } else if (blockSouth.getId() == BED_BLOCK && (blockSouth.getDamage() & 0x08) == 0x08) { b = blockSouth; - } else if (blockEast.getId() == this.getId() && (blockEast.getDamage() & 0x08) == 0x08) { + } else if (blockEast.getId() == BED_BLOCK && (blockEast.getDamage() & 0x08) == 0x08) { b = blockEast; - } else if (blockWest.getId() == this.getId() && (blockWest.getDamage() & 0x08) == 0x08) { + } else if (blockWest.getId() == BED_BLOCK && (blockWest.getDamage() & 0x08) == 0x08) { b = blockWest; } else { if (player != null) { - player.sendMessage(new TranslationContainer("tile.bed.notValid")); + player.sendMessage("§7%tile.bed.notValid", true); } return true; } } - if (player != null && !player.sleepOn(b)) { - player.sendMessage(new TranslationContainer("tile.bed.occupied")); - } + if (player != null) { + if (player.distanceSquared(this) > 36) { + player.sendMessage("§7%tile.bed.tooFar", true); + return true; + } + + if (!player.isCreative()) { + BlockFace secondPart = this.getBlockFace().getOpposite(); + AxisAlignedBB checkArea = new SimpleAxisAlignedBB(b.x - 8, b.y - 6.5, b.z - 8, b.x + 9, b.y + 5.5, b.z + 9).addCoord(secondPart.getXOffset(), 0, secondPart.getZOffset()); + for (Entity entity : this.getLevel().getCollidingEntities(checkArea)) { + if (!entity.isClosed() && MOB_IDS.contains(entity.getNetworkId())) { + player.sendMessage("§7%tile.bed.notSafe", true); + return true; + } + } + } + + int time = this.getLevel().getTime() % Level.TIME_FULL; + boolean isNight = time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE; + if (!isNight && !this.getLevel().isThundering()) { + if (!b.equals(player.getSpawnPosition())) { + PlayerBedEnterEvent ev = new PlayerBedEnterEvent(player, this, true); // TODO: Event for setting player respawn point? + player.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + player.setSpawn(b); + player.sendMessage("§7%tile.bed.respawnSet", true); + } + } + player.sendMessage("§7%tile.bed.noSleep", true); + } else if (!player.sleepOn(b)) { + player.sendMessage("§7%tile.bed.occupied", true); + } + } return true; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - Block down = this.down(); - if (!down.isTransparent()) { - Block next = this.getSide(player.getDirection()); - Block downNext = next.down(); + if (canStayOnFullNonSolid(this.down())) { + Block next = this.getSide(player.getHorizontalFacing()); - if (next.canBeReplaced() && !downNext.isTransparent()) { + if (next.canBeReplaced() && canStayOnFullNonSolid(next.down())) { int meta = player.getDirection().getHorizontalIndex(); - this.getLevel().setBlock(block, Block.get(this.getId(), meta), true, true); - this.getLevel().setBlock(next, Block.get(this.getId(), meta | 0x08), true, true); + this.getLevel().setBlock(block, Block.get(BED_BLOCK, meta), true, true); + + this.getLevel().setBlock(next, Block.get(BED_BLOCK, meta | 0x08), true, true); createBlockEntity(this, item.getDamage()); createBlockEntity(next, item.getDamage()); @@ -138,50 +166,84 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } + /** + * Internal: Can drop item when broken + */ + public boolean canDropItem = true; + @Override public boolean onBreak(Item item) { - Block blockNorth = this.north(); //Gets the blocks around them + Block blockNorth = this.north(); Block blockSouth = this.south(); Block blockEast = this.east(); Block blockWest = this.west(); - if ((this.getDamage() & 0x08) == 0x08) { //This is the Top part of bed - if (blockNorth.getId() == BED_BLOCK && (blockNorth.getDamage() & 0x08) != 0x08) { //Checks if the block ID&&meta are right - this.getLevel().setBlock(blockNorth, Block.get(BlockID.AIR), true, true); + Block secondPart = null; + if ((this.getDamage() & 0x08) == 0x08) { // Top part of the bed + if (blockNorth.getId() == BED_BLOCK && (blockNorth.getDamage() & 0x08) != 0x08) { // Check if the block ID & meta are right + secondPart = blockNorth; } else if (blockSouth.getId() == BED_BLOCK && (blockSouth.getDamage() & 0x08) != 0x08) { - this.getLevel().setBlock(blockSouth, Block.get(BlockID.AIR), true, true); + secondPart = blockSouth; } else if (blockEast.getId() == BED_BLOCK && (blockEast.getDamage() & 0x08) != 0x08) { - this.getLevel().setBlock(blockEast, Block.get(BlockID.AIR), true, true); + secondPart = blockEast; } else if (blockWest.getId() == BED_BLOCK && (blockWest.getDamage() & 0x08) != 0x08) { - this.getLevel().setBlock(blockWest, Block.get(BlockID.AIR), true, true); + secondPart = blockWest; + } + } else { // Bottom part of the bed + if (blockNorth.getId() == BED_BLOCK && (blockNorth.getDamage() & 0x08) == 0x08) { + secondPart = blockNorth; + } else if (blockSouth.getId() == BED_BLOCK && (blockSouth.getDamage() & 0x08) == 0x08) { + secondPart = blockSouth; + } else if (blockEast.getId() == BED_BLOCK && (blockEast.getDamage() & 0x08) == 0x08) { + secondPart = blockEast; + } else if (blockWest.getId() == BED_BLOCK && (blockWest.getDamage() & 0x08) == 0x08) { + secondPart = blockWest; } - } else { //Bottom Part of Bed - if (blockNorth.getId() == this.getId() && (blockNorth.getDamage() & 0x08) == 0x08) { - this.getLevel().setBlock(blockNorth, Block.get(BlockID.AIR), true, true); - } else if (blockSouth.getId() == this.getId() && (blockSouth.getDamage() & 0x08) == 0x08) { - this.getLevel().setBlock(blockSouth, Block.get(BlockID.AIR), true, true); - } else if (blockEast.getId() == this.getId() && (blockEast.getDamage() & 0x08) == 0x08) { - this.getLevel().setBlock(blockEast, Block.get(BlockID.AIR), true, true); - } else if (blockWest.getId() == this.getId() && (blockWest.getDamage() & 0x08) == 0x08) { - this.getLevel().setBlock(blockWest, Block.get(BlockID.AIR), true, true); + } + + if (secondPart != null) { + Item secondPartDrop = (secondPart.getDamage() & 0x08) == 0x08 ? secondPart.toItem() : null; // Get drops before block entity is destroyed to keep the color + if (this.getLevel().setBlock(secondPart, Block.get(BlockID.AIR), true, true)) { + if (secondPartDrop != null && this.canDropItem && this.getLevel().gameRules.getBoolean(GameRule.DO_TILE_DROPS)) { + this.getLevel().dropItem(this.add(0.5, 0.5, 0.5), secondPartDrop); // Drops only from the top part, prevent a dupe + } } } - this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, false); // Do not update both parts to prevent duplication bug if there is two fallable blocks top of the bed + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, secondPart == null); // Don't update both parts to prevent duplication bug if there are two fallable blocks on top of the bed + + for (Entity entity : this.level.getNearbyEntities(new SimpleAxisAlignedBB(this, this).grow(2, 1, 2))) { + if (!(entity instanceof Player)) continue; + Player player = (Player) entity; + + if (player.getSleepingPos() == null) continue; + if (!player.getSleepingPos().equals(this) && !player.getSleepingPos().equals(secondPart)) continue; + player.stopSleep(); + } + + if (level.getDimension() == Level.DIMENSION_OVERWORLD) { + Vector3 safeSpawn = null; + for (Player player : level.getServer().getOnlinePlayers().values()) { + if (player.getSpawnPosition() != null && (player.getSpawnPosition().equals(this) || player.getSpawnPosition().equals(secondPart))) { + player.setSpawn(safeSpawn == null ? (safeSpawn = level.getServer().getDefaultLevel().getSafeSpawn()) : safeSpawn); + } + } + } return true; } - private void createBlockEntity(Vector3 pos, int color) { + private void createBlockEntity(Block pos, int color) { CompoundTag nbt = BlockEntity.getDefaultCompound(pos, BlockEntity.BED); nbt.putByte("color", color); - BlockEntity.createBlockEntity(BlockEntity.BED, this.level.getChunk(pos.getFloorX() >> 4, pos.getFloorZ() >> 4), nbt); + BlockEntityBed be = (BlockEntityBed) BlockEntity.createBlockEntity(BlockEntity.BED, pos.getChunk(), nbt); + be.spawnToAll(); } @Override public Item toItem() { - return new ItemBed(this.getDyeColor().getWoolData()); + return Item.get(Item.BED, this.getDyeColor().getWoolData()); } @Override @@ -205,4 +267,14 @@ public DyeColor getDyeColor() { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + + /*@Override + public boolean breakWhenPushed() { + return true; + }*/ + + @Override + public boolean canBePushed() { + return false; // Temporary dupe patch + } } diff --git a/src/main/java/cn/nukkit/block/BlockBedrock.java b/src/main/java/cn/nukkit/block/BlockBedrock.java index 73e75f6ab2e..bde9f7bf001 100644 --- a/src/main/java/cn/nukkit/block/BlockBedrock.java +++ b/src/main/java/cn/nukkit/block/BlockBedrock.java @@ -3,14 +3,11 @@ import cn.nukkit.item.Item; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockBedrock extends BlockSolid { - public BlockBedrock() { - } - @Override public int getId() { return BEDROCK; diff --git a/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java b/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java index 3f586ca815f..a85caf29b6f 100644 --- a/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java +++ b/src/main/java/cn/nukkit/block/BlockBedrockInvisible.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.utils.BlockColor; /** @@ -9,9 +8,6 @@ */ public class BlockBedrockInvisible extends BlockSolid { - public BlockBedrockInvisible() { - } - @Override public int getId() { return INVISIBLE_BEDROCK; @@ -49,6 +45,11 @@ public boolean canBePushed() { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.AIR)); + return Item.get(0); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; } } diff --git a/src/main/java/cn/nukkit/block/BlockBeeNest.java b/src/main/java/cn/nukkit/block/BlockBeeNest.java new file mode 100644 index 00000000000..a325df1e5e9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBeeNest.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.utils.BlockColor; + +public class BlockBeeNest extends BlockBeehive { + + public BlockBeeNest() { + this(0); + } + + public BlockBeeNest(int meta) { + super(meta); + } + + @Override + public int getId() { + return BEE_NEST; + } + + @Override + public String getName() { + return "Bee Nest"; + } + + @Override + public int getBurnChance() { + return 30; + } + + @Override + public int getBurnAbility() { + return 60; + } + + @Override + public double getHardness() { + return 0.3; + } + + @Override + public double getResistance() { + return 0.3; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public BlockColor getColor() { + return BlockColor.YELLOW_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBeehive.java b/src/main/java/cn/nukkit/block/BlockBeehive.java new file mode 100644 index 00000000000..9698080edcc --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBeehive.java @@ -0,0 +1,77 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockBeehive extends BlockSolidMeta { + + public BlockBeehive() { + this(0); + } + + public BlockBeehive(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Beehive"; + } + + @Override + public int getId() { + return BEEHIVE; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public int getBurnChance() { + return 5; + } + + @Override + public int getBurnAbility() { + return 20; + } + + @Override + public double getHardness() { + return 0.6; + } + + @Override + public double getResistance() { + return 0.6; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + private static final short[] faces = {2, 3, 0, 1}; + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + return this.getLevel().setBlock(this, this, true, true); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBeetroot.java b/src/main/java/cn/nukkit/block/BlockBeetroot.java index 155f69f58b0..90ab0f6363f 100644 --- a/src/main/java/cn/nukkit/block/BlockBeetroot.java +++ b/src/main/java/cn/nukkit/block/BlockBeetroot.java @@ -1,7 +1,7 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsBeetroot; +import cn.nukkit.utils.Utils; /** * Created on 2015/11/22 by xtypr. @@ -28,7 +28,7 @@ public String getName() { @Override public Item toItem() { - return new ItemSeedsBeetroot(); + return Item.get(Item.BEETROOT_SEEDS); } @Override @@ -36,7 +36,7 @@ public Item[] getDrops(Item item) { if (this.getDamage() >= 0x07) { return new Item[]{ Item.get(Item.BEETROOT, 0, 1), - Item.get(Item.BEETROOT_SEEDS, 0, (int) (4d * Math.random())) + Item.get(Item.BEETROOT_SEEDS, 0, Utils.random.nextInt(0, 4)) }; } else { return new Item[]{ diff --git a/src/main/java/cn/nukkit/block/BlockBell.java b/src/main/java/cn/nukkit/block/BlockBell.java new file mode 100644 index 00000000000..83b5a302e99 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBell.java @@ -0,0 +1,401 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityBell; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.event.block.BellRingEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; + +public class BlockBell extends BlockTransparentMeta implements Faceable { + + public static final int TYPE_ATTACHMENT_STANDING = 0; + public static final int TYPE_ATTACHMENT_HANGING = 1; + public static final int TYPE_ATTACHMENT_SIDE = 2; + public static final int TYPE_ATTACHMENT_MULTIPLE = 3; + + public BlockBell() { + this(0); + } + + public BlockBell(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Bell"; + } + + @Override + public int getId() { + return BELL; + } + + private boolean isConnectedTo(BlockFace connectedFace, int attachmentType, BlockFace blockFace) { + BlockFace.Axis faceAxis = connectedFace.getAxis(); + switch (attachmentType) { + case TYPE_ATTACHMENT_STANDING: + if (faceAxis == BlockFace.Axis.Y) { + return connectedFace == BlockFace.DOWN; + } else { + return blockFace.getAxis() != faceAxis; + } + case TYPE_ATTACHMENT_HANGING: + return connectedFace == BlockFace.UP; + case TYPE_ATTACHMENT_SIDE: + return connectedFace == blockFace.getOpposite(); + case TYPE_ATTACHMENT_MULTIPLE: + return connectedFace == blockFace || connectedFace == blockFace.getOpposite(); + } + return false; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + int attachmentType = getAttachmentType(); + BlockFace blockFace = getBlockFace(); + boolean north = this.isConnectedTo(BlockFace.NORTH, attachmentType, blockFace); + boolean south = this.isConnectedTo(BlockFace.SOUTH, attachmentType, blockFace); + boolean west = this.isConnectedTo(BlockFace.WEST, attachmentType, blockFace); + boolean east = this.isConnectedTo(BlockFace.EAST, attachmentType, blockFace); + boolean up = this.isConnectedTo(BlockFace.UP, attachmentType, blockFace); + boolean down = this.isConnectedTo(BlockFace.DOWN, attachmentType, blockFace); + + double n = north ? 0 : 0.25; + double s = south ? 1 : 0.75; + double w = west ? 0 : 0.25; + double e = east ? 1 : 0.75; + double d = down ? 0 : 0.25; + double u = up ? 1 : 0.75; + + return new SimpleAxisAlignedBB(this.x + w, this.y + d, this.z + n, this.x + e, this.y + u, this.z + s); + } + + @Override + public void onEntityCollide(Entity entity) { + if (entity instanceof EntityItem && entity.getMotion().lengthSquared() > 0.01) { + AxisAlignedBB boundingBox = entity.getBoundingBox(); + AxisAlignedBB blockBoundingBox = this.getCollisionBoundingBox(); + if (boundingBox.intersectsWith(blockBoundingBox)) { + Vector3 entityCenter = new Vector3( + (boundingBox.getMaxX() - boundingBox.getMinX()) / 2, + (boundingBox.getMaxY() - boundingBox.getMinY()) / 2, + (boundingBox.getMaxZ() - boundingBox.getMinZ()) / 2 + ); + + Vector3 blockCenter = new Vector3( + (blockBoundingBox.getMaxX() - blockBoundingBox.getMinX()) / 2, + (blockBoundingBox.getMaxY() - blockBoundingBox.getMinY()) / 2, + (blockBoundingBox.getMaxZ() - blockBoundingBox.getMinZ()) / 2 + ); + Vector3 entityPos = entity.add(entityCenter); + Vector3 blockPos = this.add( + blockBoundingBox.getMinX() - x + blockCenter.x, + blockBoundingBox.getMinY() - y + blockCenter.y, + blockBoundingBox.getMinZ() - z + blockCenter.z + ); + + Vector3 entityVector = entityPos.subtract(blockPos); + entityVector = entityVector.normalize().multiply(0.4); + entityVector.y = Math.max(0.15, entityVector.y); + if (this.ring(entity, BellRingEvent.RingCause.DROPPED_ITEM)) { + entity.setMotion(entityVector); + } + } + } + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + protected AxisAlignedBB recalculateCollisionBoundingBox() { + return recalculateBoundingBox().expand(0.000001, 0.000001, 0.000001); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + return this.ring(player, player != null? BellRingEvent.RingCause.HUMAN_INTERACTION : BellRingEvent.RingCause.UNKNOWN); + } + + public boolean ring(Entity causeEntity, BellRingEvent.RingCause cause) { + return this.ring(causeEntity, cause, null); + } + + public boolean ring(Entity causeEntity, BellRingEvent.RingCause cause, BlockFace hitFace) { + BlockEntityBell bell = this.getOrCreateBlockEntity(); + if (bell == null) { + return true; + } + boolean addException = true; + BlockFace blockFace = getBlockFace(); + if (hitFace == null) { + if (causeEntity != null) { + if (causeEntity instanceof EntityItem) { + Position blockMid = this.add(0.5, 0.5, 0.5); + Vector3 vector = causeEntity.subtract(blockMid).normalize(); + int x = vector.x < 0? -1 : vector.x > 0? 1 : 0; + int z = vector.z < 0? -1 : vector.z > 0? 1 : 0; + if (x != 0 && z != 0) { + if (Math.abs(vector.x) < Math.abs(vector.z)) { + x = 0; + } else { + z = 0; + } + } + hitFace = blockFace; + for (BlockFace face : BlockFace.values()) { + if (face.getXOffset() == x && face.getZOffset() == z) { + hitFace = face; + break; + } + } + } else { + hitFace = causeEntity.getDirection(); + } + } else { + hitFace = blockFace; + } + } + switch (this.getAttachmentType()) { + case TYPE_ATTACHMENT_STANDING: + if (hitFace.getAxis() != blockFace.getAxis()) { + return false; + } + break; + case TYPE_ATTACHMENT_MULTIPLE: + if (hitFace.getAxis() == blockFace.getAxis()) { + return false; + } + break; + case TYPE_ATTACHMENT_SIDE: + if (hitFace.getAxis() == blockFace.getAxis()) { + addException = false; + } + break; + } + + BellRingEvent event = new BellRingEvent(this, cause, causeEntity); + this.level.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + + bell.setDirection(hitFace.getOpposite().getHorizontalIndex()); + bell.setTicks(0); + bell.setRinging(true); + if (addException && causeEntity instanceof Player) { + bell.spawnExceptions.add(causeEntity.getId()); + } + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_BELL_HIT); + return true; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean checkSupport() { + switch (this.getAttachmentType()) { + case TYPE_ATTACHMENT_STANDING: + if (this.checkSupport(this.down(), BlockFace.UP)) { + return true; + } + break; + case TYPE_ATTACHMENT_HANGING: + if (this.checkSupport(this.up(), BlockFace.DOWN)) { + return true; + } + break; + case TYPE_ATTACHMENT_MULTIPLE: + BlockFace blockFace = getBlockFace(); + if (this.checkSupport(this.getSide(blockFace), blockFace.getOpposite()) && + this.checkSupport(this.getSide(blockFace.getOpposite()), blockFace)) { + return true; + } + break; + case TYPE_ATTACHMENT_SIDE: + blockFace = getBlockFace(); + if (this.checkSupport(this.getSide(blockFace.getOpposite()), blockFace)) { + return true; + } + break; + } + + return false; + } + + private boolean checkSupport(Block support, BlockFace attachmentFace) { + if (!support.isTransparent()) { + return true; + } + + if (attachmentFace == BlockFace.DOWN) { + switch (support.getId()) { + case HOPPER_BLOCK: + case IRON_BARS: + return true; + default: + return support instanceof BlockFence; + } + } + + if (support instanceof BlockCauldron) { + return attachmentFace == BlockFace.UP; + } + return false; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!checkSupport()) { + this.level.useBreakOn(this); + } + return type; + } else if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (level.isBlockPowered(this)) { + if (!isToggled()) { + setToggled(true); + this.level.setBlock(this, this, true, true); + ring(null, BellRingEvent.RingCause.REDSTONE); + } + } else if (isToggled()) { + setToggled(false); + this.level.setBlock(this, this, true, true); + } + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block.canBeReplaced() && block.getId() != AIR && !(block instanceof BlockLiquid)) { + face = BlockFace.UP; + } + switch (face) { + case UP: + this.setAttachmentType(TYPE_ATTACHMENT_STANDING); + this.setBlockFace(player.getDirection().getOpposite()); + break; + case DOWN: + this.setAttachmentType(TYPE_ATTACHMENT_HANGING); + this.setBlockFace(player.getDirection().getOpposite()); + break; + default: + this.setBlockFace(face); + if (this.checkSupport(block.getSide(face), face.getOpposite())) { + this.setAttachmentType(TYPE_ATTACHMENT_MULTIPLE); + } else { + this.setAttachmentType(TYPE_ATTACHMENT_SIDE); + } + } + if (!this.checkSupport()) { + return false; + } + this.getLevel().setBlock(this, this, true, true); + this.createBlockEntity(); + return true; + } + + private BlockEntityBell createBlockEntity() { + CompoundTag nbt = BlockEntity.getDefaultCompound(this, BlockEntity.BELL); + return (BlockEntityBell) BlockEntity.createBlockEntity(BlockEntity.BELL, this.getChunk(), nbt); + } + + private BlockEntityBell getOrCreateBlockEntity() { + BlockEntity blockEntity = this.getLevel().getBlockEntity(this); + if (!(blockEntity instanceof BlockEntityBell)) { + blockEntity = this.createBlockEntity(); + } + return (BlockEntityBell) blockEntity; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(getDamage() & 0b11); + } + + public void setBlockFace(BlockFace face) { + if (face.getHorizontalIndex() == -1) { + return; + } + this.setDamage(this.getDamage() & (DATA_MASK ^ 0b11) | face.getHorizontalIndex()); + } + + public int getAttachmentType() { + return (this.getDamage() & 0b1100) >> 2 & 0b11; + } + + public void setAttachmentType(int attachmentType) { + attachmentType = attachmentType & 0b11; + this.setDamage(getDamage() & (DATA_MASK ^ 0b1100) | (attachmentType << 2)); + } + + public boolean isToggled() { + return (this.getDamage() & 0b010000) == 0b010000; + } + + public void setToggled(boolean toggled) { + this.setDamage(this.getDamage() & (DATA_MASK ^ 0b010000) | (toggled? 0b010000 : 0b000000)); + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 5; + } + + @Override + public double getResistance() { + return 5; + } + + @Override + public BlockColor getColor() { + return BlockColor.GOLD_BLOCK_COLOR; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockBirchSignStanding.java b/src/main/java/cn/nukkit/block/BlockBirchSignStanding.java new file mode 100644 index 00000000000..c45b9f2652e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBirchSignStanding.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockBirchSignStanding extends BlockSignPost { + + public BlockBirchSignStanding() { + this(0); + } + + public BlockBirchSignStanding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Birch Sign Post"; + } + + @Override + public int getId() { + return BIRCH_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.BIRCH_SIGN); + } + + @Override + protected int getPostId() { + return BIRCH_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return BIRCH_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBirchWallSign.java b/src/main/java/cn/nukkit/block/BlockBirchWallSign.java new file mode 100644 index 00000000000..8fd9e0c35dc --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBirchWallSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockBirchWallSign extends BlockWallSign { + + public BlockBirchWallSign() { + this(0); + } + + public BlockBirchWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Birch Wall Sign"; + } + + @Override + public int getId() { + return BIRCH_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.BIRCH_SIGN); + } + + @Override + protected int getPostId() { + return BIRCH_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return BIRCH_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlackstone.java b/src/main/java/cn/nukkit/block/BlockBlackstone.java new file mode 100644 index 00000000000..ace24d44ab7 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlackstone.java @@ -0,0 +1,57 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockBlackstone extends BlockSolid { + + public BlockBlackstone() { + } + + @Override + public int getId() { + return BLACKSTONE; + } + + @Override + public String getName() { + return "Blackstone"; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlackstoneGilded.java b/src/main/java/cn/nukkit/block/BlockBlackstoneGilded.java new file mode 100644 index 00000000000..6a7590a6997 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlackstoneGilded.java @@ -0,0 +1,87 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockBlackstoneGilded extends BlockSolid { + + @Override + public int getId() { + return GILDED_BLACKSTONE; + } + + @Override + public String getName() { + return "Gilded Blackstone"; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public Item[] getDrops(Item item) { + if (!item.isPickaxe()) { + return new Item[0]; + } + + int dropOdds; + int fortune = 0; + Enchantment enchantment = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); + if (enchantment != null) { + fortune = enchantment.getLevel(); + } + + switch (fortune) { + case 0: + dropOdds = 10; + break; + case 1: + dropOdds = 7; + break; + case 2: + dropOdds = 4; + break; + default: + dropOdds = 1; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (dropOdds > 1 && random.nextInt(dropOdds) != 0) { + return new Item[] { toItem() }; + } + + return new Item[] { Item.get(ItemID.GOLD_NUGGET, 0, random.nextInt(2, 6)) }; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 6; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockBlackstonePolished.java new file mode 100644 index 00000000000..764187eae64 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlackstonePolished.java @@ -0,0 +1,19 @@ +package cn.nukkit.block; + +public class BlockBlackstonePolished extends BlockBlackstone { + + @Override + public String getName() { + return "Polished Blackstone"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlackstonePolishedChiseled.java b/src/main/java/cn/nukkit/block/BlockBlackstonePolishedChiseled.java new file mode 100644 index 00000000000..c04d8014f10 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlackstonePolishedChiseled.java @@ -0,0 +1,14 @@ +package cn.nukkit.block; + +public class BlockBlackstonePolishedChiseled extends BlockBlackstonePolished { + + @Override + public int getId() { + return CHISELED_POLISHED_BLACKSTONE; + } + + @Override + public String getName() { + return "Chiseled Polished Blackstone"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlackstoneWall.java b/src/main/java/cn/nukkit/block/BlockBlackstoneWall.java new file mode 100644 index 00000000000..d4fd0797f2c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlackstoneWall.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockBlackstoneWall extends BlockWall { + + public BlockBlackstoneWall() { + this(0); + } + + public BlockBlackstoneWall(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Blackstone Wall"; + } + + @Override + public int getId() { + return BLACKSTONE_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlastFurnace.java b/src/main/java/cn/nukkit/block/BlockBlastFurnace.java new file mode 100644 index 00000000000..3110b37b0e7 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlastFurnace.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockBlastFurnace extends BlockBlastFurnaceLit { + + public BlockBlastFurnace() { + this(0); + } + + public BlockBlastFurnace(int meta) { + super(meta); + } + + @Override + public int getLightLevel() { + return 0; + } + + @Override + public String getName() { + return "Blast Furnace"; + } + + @Override + public int getId() { + return BLAST_FURNACE; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java b/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java new file mode 100644 index 00000000000..cf097f3d4b3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java @@ -0,0 +1,87 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityBlastFurnace; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.nbt.tag.Tag; + +import java.util.Map; + +public class BlockBlastFurnaceLit extends BlockFurnaceBurning { + + public BlockBlastFurnaceLit() { + this(0); + } + + public BlockBlastFurnaceLit(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Lit Blast Furnace"; + } + + @Override + public int getId() { + return LIT_BLAST_FURNACE; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(BLAST_FURNACE)); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.getLevel().setBlock(block, this, true, true); + CompoundTag nbt = new CompoundTag() + .putList(new ListTag<>("Items")) + .putString("id", BlockEntity.BLAST_FURNACE) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + if (item.hasCustomBlockData()) { + Map customData = item.getCustomBlockData().getTags(); + for (Map.Entry tag : customData.entrySet()) { + nbt.put(tag.getKey(), tag.getValue()); + } + } + + BlockEntityBlastFurnace furnace = (BlockEntityBlastFurnace) BlockEntity.createBlockEntity(BlockEntity.BLAST_FURNACE, this.getChunk(), nbt); + return furnace != null; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null) { + BlockEntity t = this.getLevel().getBlockEntity(this); + if (!(t instanceof BlockEntityBlastFurnace)) { + return false; + } + + BlockEntityBlastFurnace furnace = (BlockEntityBlastFurnace) t; + if (furnace.namedTag.contains("Lock") && furnace.namedTag.get("Lock") instanceof StringTag) { + if (!furnace.namedTag.getString("Lock").equals(item.getCustomName())) { + return true; + } + } + + player.addWindow(furnace.getInventory()); + } + + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBlueIce.java b/src/main/java/cn/nukkit/block/BlockBlueIce.java new file mode 100644 index 00000000000..41705f5864d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBlueIce.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +public class BlockBlueIce extends BlockIcePacked { + + @Override + public String getName() { + return "Blue Ice"; + } + + @Override + public int getId() { + return BLUE_ICE; + } + + @Override + public double getFrictionFactor() { + return 0.989; + } + + @Override + public double getHardness() { + return 2.8; + } + + @Override + public double getResistance() { + return 14; + } + + @Override + public int getLightLevel() { + return 4; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBone.java b/src/main/java/cn/nukkit/block/BlockBone.java index ae747a445b9..b8ee934f677 100644 --- a/src/main/java/cn/nukkit/block/BlockBone.java +++ b/src/main/java/cn/nukkit/block/BlockBone.java @@ -11,9 +11,9 @@ /** * @author CreeperFace */ -public class BlockBone extends BlockSolidMeta implements Faceable { +public class BlockBone extends BlockSolid implements Faceable { - private static final int[] FACES = { + private static final int[] faces = { 0, 0, 0b1000, @@ -22,14 +22,6 @@ public class BlockBone extends BlockSolidMeta implements Faceable { 0b0100 }; - public BlockBone() { - this(0); - } - - public BlockBone(int meta) { - super(meta); - } - @Override public int getId() { return BONE_BLOCK; @@ -57,7 +49,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{new ItemBlock(this)}; } @@ -71,7 +63,7 @@ public BlockFace getBlockFace() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(((this.getDamage() & 0x3) | FACES[face.getIndex()])); + this.setDamage(((this.getDamage() & 0x3) | faces[face.getIndex()])); this.getLevel().setBlock(block, this, true); return true; } diff --git a/src/main/java/cn/nukkit/block/BlockBookshelf.java b/src/main/java/cn/nukkit/block/BlockBookshelf.java index 9b931281d34..dba76c928e1 100644 --- a/src/main/java/cn/nukkit/block/BlockBookshelf.java +++ b/src/main/java/cn/nukkit/block/BlockBookshelf.java @@ -1,22 +1,13 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBook; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** * @author Nukkit Project Team */ -public class BlockBookshelf extends BlockSolidMeta { - - public BlockBookshelf(int meta) { - super(meta); - } - - public BlockBookshelf() { - this(0); - } +public class BlockBookshelf extends BlockSolid { @Override public String getName() { @@ -56,7 +47,7 @@ public int getBurnAbility() { @Override public Item[] getDrops(Item item) { return new Item[]{ - new ItemBook(0, 3) + Item.get(Item.BOOK, 0, 3) }; } diff --git a/src/main/java/cn/nukkit/block/BlockBorder.java b/src/main/java/cn/nukkit/block/BlockBorder.java new file mode 100644 index 00000000000..d866be7c309 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBorder.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockBorder extends BlockTransparent { + + @Override + public int getId() { + return BORDER_BLOCK; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Border"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBrewingStand.java b/src/main/java/cn/nukkit/block/BlockBrewingStand.java index b2d48d44645..73473046bfc 100644 --- a/src/main/java/cn/nukkit/block/BlockBrewingStand.java +++ b/src/main/java/cn/nukkit/block/BlockBrewingStand.java @@ -1,11 +1,11 @@ package cn.nukkit.block; + import cn.nukkit.Player; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityBrewingStand; import cn.nukkit.inventory.ContainerInventory; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBrewingStand; import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; @@ -63,53 +63,39 @@ public int getLightLevel() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (!block.down().isTransparent()) { - getLevel().setBlock(block, this, true, true); - - CompoundTag nbt = new CompoundTag() - .putList(new ListTag<>("Items")) - .putString("id", BlockEntity.BREWING_STAND) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - - if (item.hasCustomName()) { - nbt.putString("CustomName", item.getCustomName()); - } + this.getLevel().setBlock(this, this, true, true); - if (item.hasCustomBlockData()) { - Map customData = item.getCustomBlockData().getTags(); - for (Map.Entry tag : customData.entrySet()) { - nbt.put(tag.getKey(), tag.getValue()); - } - } + CompoundTag nbt = new CompoundTag() + .putList(new ListTag<>("Items")) + .putString("id", BlockEntity.BREWING_STAND) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); - BlockEntityBrewingStand brewing = (BlockEntityBrewingStand) BlockEntity.createBlockEntity(BlockEntity.BREWING_STAND, getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - return brewing != null; + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); } - return false; + + if (item.hasCustomBlockData()) { + Map customData = item.getCustomBlockData().getTags(); + for (Map.Entry tag : customData.entrySet()) { + nbt.put(tag.getKey(), tag.getValue()); + } + } + + BlockEntityBrewingStand brewing = (BlockEntityBrewingStand) BlockEntity.createBlockEntity(BlockEntity.BREWING_STAND, this.getChunk(), nbt); + return brewing != null; } @Override public boolean onActivate(Item item, Player player) { if (player != null) { BlockEntity t = getLevel().getBlockEntity(this); - BlockEntityBrewingStand brewing; - if (t instanceof BlockEntityBrewingStand) { - brewing = (BlockEntityBrewingStand) t; - } else { - CompoundTag nbt = new CompoundTag() - .putList(new ListTag<>("Items")) - .putString("id", BlockEntity.BREWING_STAND) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - brewing = (BlockEntityBrewingStand) BlockEntity.createBlockEntity(BlockEntity.BREWING_STAND, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (brewing == null) { - return false; - } + if (!(t instanceof BlockEntityBrewingStand)) { + return false; } + BlockEntityBrewingStand brewing = (BlockEntityBrewingStand) t; if (brewing.namedTag.contains("Lock") && brewing.namedTag.get("Lock") instanceof StringTag) { if (!brewing.namedTag.getString("Lock").equals(item.getCustomName())) { return false; @@ -124,12 +110,12 @@ public boolean onActivate(Item item, Player player) { @Override public Item toItem() { - return new ItemBrewingStand(); + return Item.get(Item.BREWING_STAND); } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -193,4 +179,14 @@ public int getComparatorInputOverride() { public boolean canHarvestWithHand() { return false; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } } diff --git a/src/main/java/cn/nukkit/block/BlockBricks.java b/src/main/java/cn/nukkit/block/BlockBricks.java index de1eb072dbd..fadb8d5526f 100644 --- a/src/main/java/cn/nukkit/block/BlockBricks.java +++ b/src/main/java/cn/nukkit/block/BlockBricks.java @@ -9,9 +9,6 @@ */ public class BlockBricks extends BlockSolid { - public BlockBricks() { - } - @Override public String getName() { return "Bricks"; @@ -39,7 +36,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ Item.get(Item.BRICKS_BLOCK, 0, 1) }; @@ -49,12 +46,12 @@ public Item[] getDrops(Item item) { } @Override - public BlockColor getColor() { - return BlockColor.RED_BLOCK_COLOR; + public boolean canHarvestWithHand() { + return false; } @Override - public boolean canHarvestWithHand() { - return false; + public BlockColor getColor() { + return BlockColor.RED_BLOCK_COLOR; } } diff --git a/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolished.java new file mode 100644 index 00000000000..6672abea2fa --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolished.java @@ -0,0 +1,14 @@ +package cn.nukkit.block; + +public class BlockBricksBlackstonePolished extends BlockBlackstonePolished { + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BRICKS; + } + + @Override + public String getName() { + return "Polished Blackstone Bricks"; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolishedCracked.java b/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolishedCracked.java new file mode 100644 index 00000000000..aeef4f683d9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksBlackstonePolishedCracked.java @@ -0,0 +1,14 @@ +package cn.nukkit.block; + +public class BlockBricksBlackstonePolishedCracked extends BlockBricksBlackstonePolished { + + @Override + public int getId() { + return CRACKED_POLISHED_BLACKSTONE_BRICKS; + } + + @Override + public String getName() { + return "Cracked Polished Blackstone Bricks"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBricksDeepslate.java b/src/main/java/cn/nukkit/block/BlockBricksDeepslate.java new file mode 100644 index 00000000000..0400c136f45 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksDeepslate.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockBricksDeepslate extends BlockSolid { + + public BlockBricksDeepslate() { + } + + @Override + public int getId() { + return DEEPSLATE_BRICKS; + } + + @Override + public String getName() { + return "Deepslate Bricks"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBricksDeepslateCracked.java b/src/main/java/cn/nukkit/block/BlockBricksDeepslateCracked.java new file mode 100644 index 00000000000..2fe2a6fc937 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksDeepslateCracked.java @@ -0,0 +1,18 @@ +package cn.nukkit.block; + +public class BlockBricksDeepslateCracked extends BlockBricksDeepslate { + + public BlockBricksDeepslateCracked() { + // Does nothing + } + + @Override + public int getId() { + return CRACKED_DEEPSLATE_BRICKS; + } + + @Override + public String getName() { + return "Cracked Deepslate Bricks"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBricksEndStone.java b/src/main/java/cn/nukkit/block/BlockBricksEndStone.java index 09684d99c3c..abc6095e4ec 100644 --- a/src/main/java/cn/nukkit/block/BlockBricksEndStone.java +++ b/src/main/java/cn/nukkit/block/BlockBricksEndStone.java @@ -6,9 +6,6 @@ public class BlockBricksEndStone extends BlockSolid { - public BlockBricksEndStone() { - } - @Override public String getName() { return "End Stone Bricks"; @@ -36,7 +33,7 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ Item.get(Item.END_BRICKS, 0, 1) }; diff --git a/src/main/java/cn/nukkit/block/BlockBricksNether.java b/src/main/java/cn/nukkit/block/BlockBricksNether.java index 2dab022be92..ac533f6f13c 100644 --- a/src/main/java/cn/nukkit/block/BlockBricksNether.java +++ b/src/main/java/cn/nukkit/block/BlockBricksNether.java @@ -10,9 +10,6 @@ */ public class BlockBricksNether extends BlockSolid { - public BlockBricksNether() { - } - @Override public String getName() { return "Nether Brick"; @@ -40,7 +37,7 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ Item.get(Item.NETHER_BRICKS, 0, 1) }; diff --git a/src/main/java/cn/nukkit/block/BlockBricksNetherChiseled.java b/src/main/java/cn/nukkit/block/BlockBricksNetherChiseled.java new file mode 100644 index 00000000000..e9fe3a42f5a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksNetherChiseled.java @@ -0,0 +1,14 @@ +package cn.nukkit.block; + +public class BlockBricksNetherChiseled extends BlockNetherBrick { + + @Override + public int getId() { + return CHISELED_NETHER_BRICKS; + } + + @Override + public String getName() { + return "Chiseled Nether Bricks"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBricksNetherCracked.java b/src/main/java/cn/nukkit/block/BlockBricksNetherCracked.java new file mode 100644 index 00000000000..b91c793f11a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBricksNetherCracked.java @@ -0,0 +1,14 @@ +package cn.nukkit.block; + +public class BlockBricksNetherCracked extends BlockNetherBrick { + + @Override + public int getId() { + return CRACKED_NETHER_BRICKS; + } + + @Override + public String getName() { + return "Cracked Nether Bricks"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBricksRedNether.java b/src/main/java/cn/nukkit/block/BlockBricksRedNether.java index d69d0358039..8ce2ad7cad1 100644 --- a/src/main/java/cn/nukkit/block/BlockBricksRedNether.java +++ b/src/main/java/cn/nukkit/block/BlockBricksRedNether.java @@ -1,14 +1,10 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; public class BlockBricksRedNether extends BlockNetherBrick { - public BlockBricksRedNether() { - } - @Override public String getName() { return "Red Nether Bricks"; @@ -21,7 +17,7 @@ public int getId() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ Item.get(Item.RED_NETHER_BRICK, 0, 1) }; diff --git a/src/main/java/cn/nukkit/block/BlockBricksStone.java b/src/main/java/cn/nukkit/block/BlockBricksStone.java index 358a5729928..d086f50da5b 100644 --- a/src/main/java/cn/nukkit/block/BlockBricksStone.java +++ b/src/main/java/cn/nukkit/block/BlockBricksStone.java @@ -4,7 +4,7 @@ import cn.nukkit.item.ItemTool; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockBricksStone extends BlockSolidMeta { @@ -13,6 +13,7 @@ public class BlockBricksStone extends BlockSolidMeta { public static final int CRACKED = 2; public static final int CHISELED = 3; + public BlockBricksStone() { this(0); } @@ -38,7 +39,7 @@ public double getResistance() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Stone Bricks", "Mossy Stone Bricks", "Cracked Stone Bricks", @@ -50,7 +51,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ Item.get(Item.STONE_BRICKS, this.getDamage() & 0x03, 1) }; diff --git a/src/main/java/cn/nukkit/block/BlockBubbleColumn.java b/src/main/java/cn/nukkit/block/BlockBubbleColumn.java new file mode 100644 index 00000000000..e91ba9ef164 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBubbleColumn.java @@ -0,0 +1,180 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityCreature; +import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.event.block.BlockFormEvent; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public class BlockBubbleColumn extends BlockTransparentMeta { + + public static final int DIRECTION_UP = 0; + public static final int DIRECTION_DOWN = 1; + + public BlockBubbleColumn() { + this(0); + } + + public BlockBubbleColumn(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Bubble Column"; + } + + @Override + public int getId() { + return BUBBLE_COLUMN; + } + + @Override + public double getResistance() { + return 100; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public Item toItem() { + return Item.get(0); + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (this.getLevel().setBlock(this, this, true, true)) { + this.getLevel().setBlock(this, Block.LAYER_WATERLOGGED, Block.get(Block.STILL_WATER), true, true); + return true; + } + return false; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + + if (down.getId() == BUBBLE_COLUMN) { + if (down.getDamage() != this.getDamage()) { + this.getLevel().setBlock(this, down, false, true); + } + } else if (down.getId() == SOUL_SAND) { + if (this.getDamage() != DIRECTION_UP) { + this.setDamage(DIRECTION_UP); + this.getLevel().setBlock(this, this, false, true); + } + } else if (down.getId() == MAGMA) { + if (this.getDamage() != DIRECTION_DOWN) { + this.setDamage(DIRECTION_DOWN); + this.getLevel().setBlock(this, this, false, true); + } + } else { + this.getLevel().setBlock(this, Block.get(WATER), false, true); + return type; + } + + Block up = this.up(); + if (up instanceof BlockWater && (up.getDamage() == 0 || up.getDamage() == 8)) { + BlockFormEvent event = new BlockFormEvent(up, Block.get(BUBBLE_COLUMN, this.getDamage())); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + this.getLevel().setBlock(up, event.getNewState(), false, true); + } + } + + return type; + } + + return 0; + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + if (entity.canBeMovedByCurrents()) { + if (this.isBlockAboveAir()) { + double motY = entity.motionY; + + if (this.getDamage() == 1) { + motY = Math.max(-0.9, motY - 0.03); + } else { + if ((entity instanceof EntityCreature) && motY < -0.64f) { + motY = -0.16f; + } + motY = Math.min(1.8, motY + 0.1); + } + + if (entity instanceof Player) { + ((Player) entity).setMotionLocally(entity.getMotion().setY(motY)); + } else { + entity.motionY = motY; + } + } else { + double motY = entity.motionY; + + if (this.getDamage() == 1) { + motY = Math.max(-0.3, motY - 0.3); + } else { + motY = Math.min(0.7, motY + 0.06); + } + + if (entity instanceof Player) { + ((Player) entity).setMotionLocally(entity.getMotion().setY(motY)); + } else { + entity.motionY = motY; + } + } + if (entity instanceof EntityItem) { + entity.collisionBlocks = null; + } + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockBuddingAmethyst.java b/src/main/java/cn/nukkit/block/BlockBuddingAmethyst.java new file mode 100644 index 00000000000..1ef794af177 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockBuddingAmethyst.java @@ -0,0 +1,96 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockSpreadEvent; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockBuddingAmethyst extends BlockSolid { + + public BlockBuddingAmethyst() { + } + + @Override + public int getId() { + return BUDDING_AMETHYST; + } + + @Override + public double getResistance() { + return 1.5; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public String getName() { + return "Budding Amethyst"; + } + + @Override + public int onUpdate(int type) { + if (type != Level.BLOCK_UPDATE_RANDOM || ThreadLocalRandom.current().nextInt(4) != 0) { + return type; + } + + BlockFace face = BlockFace.values()[ThreadLocalRandom.current().nextInt(BlockFace.values().length)]; + Block block = this.getSide(face); + + BlockAmethystBud targetBlock = null; + if (block.getId() == AIR || ((block.getId() == WATER || block.getId() == STILL_WATER) && block.getDamage() == 8)) { + targetBlock = (BlockAmethystBud) Block.get(SMALL_AMETHYST_BUD); + } else if (block.getId() == SMALL_AMETHYST_BUD && ((BlockAmethystBud) block).getBlockFace() == face) { + targetBlock = (BlockAmethystBud) Block.get(MEDIUM_AMETHYST_BUD); + } else if (block.getId() == MEDIUM_AMETHYST_BUD && ((BlockAmethystBud) block).getBlockFace() == face) { + targetBlock = (BlockAmethystBud) Block.get(LARGE_AMETHYST_BUD); + } else if (block.getId() == LARGE_AMETHYST_BUD && ((BlockAmethystBud) block).getBlockFace() == face) { + targetBlock = (BlockAmethystBud) Block.get(AMETHYST_CLUSTER); + } + + if (targetBlock != null) { + targetBlock.setBlockFace(face); + + BlockSpreadEvent event = new BlockSpreadEvent(block, this, targetBlock); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.getLevel().setBlock(block, event.getNewState(), false, true); + } + } + return type; + } + + @Override + public boolean onBreak(Item item, Player player) { + for (BlockFace face : BlockFace.values()) { + Block side = this.getSide(face); + if (side instanceof BlockAmethystBud && ((BlockAmethystBud) side).getBlockFace() == face) { + this.getLevel().setBlock(side, Block.get(BlockID.AIR), true, true); + this.getLevel().addParticle(new DestroyBlockParticle(side.add(0.5), side)); + } + } + return super.onBreak(item, player); + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.PURPLE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButton.java b/src/main/java/cn/nukkit/block/BlockButton.java index 7ce86e1beb3..ebc9a4ba3ca 100644 --- a/src/main/java/cn/nukkit/block/BlockButton.java +++ b/src/main/java/cn/nukkit/block/BlockButton.java @@ -3,10 +3,9 @@ import cn.nukkit.Player; import cn.nukkit.event.block.BlockRedstoneEvent; import cn.nukkit.item.Item; -import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.Faceable; @@ -40,7 +39,11 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } this.setDamage(face.getIndex()); - this.level.setBlock(block, this, true, true); + if (this.getSide(getFacing().getOpposite()).isTransparent()) { + return false; + } + + this.getLevel().setBlock(this, this, true, true); return true; } @@ -58,12 +61,11 @@ public boolean onActivate(Item item, Player player) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, 0, 15)); this.setDamage(this.getDamage() ^ 0x08); this.level.setBlock(this, this, true, false); - this.level.addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_POWER_ON, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_ON); this.level.scheduleUpdate(this, 30); - Vector3 pos = getLocation(); - level.updateAroundRedstone(pos, null); - level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + level.updateAroundRedstone(this, null); + level.updateAroundRedstone(getSideVec(getFacing().getOpposite()), null); return true; } @@ -80,11 +82,10 @@ public int onUpdate(int type) { this.setDamage(this.getDamage() ^ 0x08); this.level.setBlock(this, this, true, false); - this.level.addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_POWER_OFF, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_OFF); - Vector3 pos = getLocation(); - level.updateAroundRedstone(pos, null); - level.updateAroundRedstone(pos.getSide(getFacing().getOpposite()), null); + level.updateAroundRedstone(this, null); + level.updateAroundRedstone(getSideVec(getFacing().getOpposite()), null); } return Level.BLOCK_UPDATE_SCHEDULED; @@ -126,11 +127,26 @@ public boolean onBreak(Item item) { @Override public Item toItem() { - return Item.get(this.getId(), 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockButtonAcacia.java b/src/main/java/cn/nukkit/block/BlockButtonAcacia.java new file mode 100644 index 00000000000..e434650f9f8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonAcacia.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonAcacia extends BlockButtonWooden { + + public BlockButtonAcacia() { + this(0); + } + + public BlockButtonAcacia(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Acacia Button"; + } + + @Override + public int getId() { + return ACACIA_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonBirch.java b/src/main/java/cn/nukkit/block/BlockButtonBirch.java new file mode 100644 index 00000000000..2a80276e790 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonBirch.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonBirch extends BlockButtonWooden { + + public BlockButtonBirch() { + this(0); + } + + public BlockButtonBirch(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Birch Button"; + } + + @Override + public int getId() { + return BIRCH_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonCrimson.java b/src/main/java/cn/nukkit/block/BlockButtonCrimson.java new file mode 100644 index 00000000000..426e22e2b91 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonCrimson.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonCrimson extends BlockButtonWooden { + + public BlockButtonCrimson() { + this(0); + } + + public BlockButtonCrimson(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Button"; + } + + @Override + public int getId() { + return CRIMSON_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonDarkOak.java b/src/main/java/cn/nukkit/block/BlockButtonDarkOak.java new file mode 100644 index 00000000000..c886ca68b20 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonDarkOak.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonDarkOak extends BlockButtonWooden { + + public BlockButtonDarkOak() { + this(0); + } + + public BlockButtonDarkOak(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dark Oak Button"; + } + + @Override + public int getId() { + return DARK_OAK_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonJungle.java b/src/main/java/cn/nukkit/block/BlockButtonJungle.java new file mode 100644 index 00000000000..81f68c4e94e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonJungle.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonJungle extends BlockButtonWooden { + + public BlockButtonJungle() { + this(0); + } + + public BlockButtonJungle(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jungle Button"; + } + + @Override + public int getId() { + return JUNGLE_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonPolishedBlackstone.java b/src/main/java/cn/nukkit/block/BlockButtonPolishedBlackstone.java new file mode 100644 index 00000000000..53718653044 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonPolishedBlackstone.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonPolishedBlackstone extends BlockButtonStone { + + public BlockButtonPolishedBlackstone() { + this(0); + } + + public BlockButtonPolishedBlackstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Blackstone Button"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonSpruce.java b/src/main/java/cn/nukkit/block/BlockButtonSpruce.java new file mode 100644 index 00000000000..38c7a7ef929 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonSpruce.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonSpruce extends BlockButtonWooden { + + public BlockButtonSpruce() { + this(0); + } + + public BlockButtonSpruce(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Spruce Button"; + } + + @Override + public int getId() { + return SPRUCE_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockButtonStone.java b/src/main/java/cn/nukkit/block/BlockButtonStone.java index f591330ca4d..1657ad1f477 100644 --- a/src/main/java/cn/nukkit/block/BlockButtonStone.java +++ b/src/main/java/cn/nukkit/block/BlockButtonStone.java @@ -33,7 +33,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ this.toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockButtonWarped.java b/src/main/java/cn/nukkit/block/BlockButtonWarped.java new file mode 100644 index 00000000000..ddad2f9ee2e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockButtonWarped.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockButtonWarped extends BlockButtonWooden { + + public BlockButtonWarped() { + this(0); + } + + public BlockButtonWarped(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Button"; + } + + @Override + public int getId() { + return WARPED_BUTTON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCactus.java b/src/main/java/cn/nukkit/block/BlockCactus.java index 9cdc6921e43..2c3aca1a550 100644 --- a/src/main/java/cn/nukkit/block/BlockCactus.java +++ b/src/main/java/cn/nukkit/block/BlockCactus.java @@ -8,10 +8,8 @@ import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.item.Item; import cn.nukkit.level.Level; -import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.SimpleAxisAlignedBB; -import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; /** @@ -47,16 +45,11 @@ public boolean hasEntityCollision() { return true; } - @Override + /*@Override public double getMinX() { return this.x + 0.0625; } - @Override - public double getMinY() { - return this.y; - } - @Override public double getMinZ() { return this.z + 0.0625; @@ -67,19 +60,18 @@ public double getMaxX() { return this.x + 0.9375; } - @Override - public double getMaxY() { - return this.y + 0.9375; - } - @Override public double getMaxZ() { return this.z + 0.9375; - } + }*/ + + // Hack: Fix entity collisions + // No need for separate collision box + // Y-collisions need another fix anyway @Override - protected AxisAlignedBB recalculateCollisionBoundingBox() { - return new SimpleAxisAlignedBB(this.x, this.y, this.z, this.x + 1, this.y + 1, this.z + 1); + public double getMaxY() { + return this.y + 0.9375; } @Override @@ -104,13 +96,14 @@ public int onUpdate(int type) { } else if (type == Level.BLOCK_UPDATE_RANDOM) { if (down().getId() != CACTUS) { if (this.getDamage() == 0x0F) { + FullChunk chunk = this.level.getChunk((int) x >> 4, (int) z >> 4); for (int y = 1; y < 3; ++y) { - Block b = this.getLevel().getBlock(new Vector3(this.x, this.y + y, this.z)); + Block b = this.getLevel().getBlock(chunk, (int) this.x, (int) this.y + y, (int) this.z, true); if (b.getId() == AIR) { - BlockGrowEvent event = new BlockGrowEvent(b, Block.get(BlockID.CACTUS)); + BlockGrowEvent event = new BlockGrowEvent(b, Block.get(CACTUS)); Server.getInstance().getPluginManager().callEvent(event); if (!event.isCancelled()) { - this.getLevel().setBlock(b, event.getNewState(), true); + this.getLevel().setBlock(b, event.getNewState(), true, true); } break; } @@ -119,7 +112,7 @@ public int onUpdate(int type) { } else { this.setDamage(this.getDamage() + 1); } - this.getLevel().setBlock(this, this); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, true, false); // No need to send this to client } } @@ -135,8 +128,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl Block block2 = west(); Block block3 = east(); if (block0.canBeFlowedInto() && block1.canBeFlowedInto() && block2.canBeFlowedInto() && block3.canBeFlowedInto()) { - this.getLevel().setBlock(this, this, true); - + this.getLevel().setBlock(this, this, true, true); return true; } } @@ -152,11 +144,21 @@ public String getName() { public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } - + @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.CACTUS, 0, 1) + Item.get(Item.CACTUS, 0, 1) }; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCake.java b/src/main/java/cn/nukkit/block/BlockCake.java index 58c3bdfaa75..f2a9a56f7cd 100644 --- a/src/main/java/cn/nukkit/block/BlockCake.java +++ b/src/main/java/cn/nukkit/block/BlockCake.java @@ -2,10 +2,10 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemCake; import cn.nukkit.item.food.Food; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; +import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.BlockColor; /** @@ -43,17 +43,12 @@ public double getHardness() { @Override public double getResistance() { - return 2.5; + return 0.5; } @Override public double getMinX() { - return this.x + (1 + getDamage() * 2) / 16; - } - - @Override - public double getMinY() { - return this.y; + return this.x + ((1 + (getDamage() << 1)) >> 4); } @Override @@ -79,8 +74,7 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (down().getId() != Block.AIR) { - getLevel().setBlock(block, this, true, true); - + this.getLevel().setBlock(this, this, true, true); return true; } return false; @@ -106,12 +100,12 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemCake(); + return Item.get(Item.CAKE); } @Override public boolean onActivate(Item item, Player player) { - if (player != null && (player.getFoodData().getLevel() < player.getFoodData().getMaxLevel() || player.isCreative() || player.getServer().getDifficulty() == 0)) { + if (player != null && player.canEat(false)) { if (getDamage() <= 0x06) setDamage(getDamage() + 1); if (getDamage() >= 0x06) { getLevel().setBlock(this, Block.get(BlockID.AIR), true); @@ -119,6 +113,7 @@ public boolean onActivate(Item item, Player player) { Food.getByRelative(this).eatenBy(player); getLevel().setBlock(this, this, true); } + player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_BURP); return true; } return false; @@ -130,10 +125,20 @@ public BlockColor getColor() { } public int getComparatorInputOverride() { - return (7 - this.getDamage()) * 2; + return (7 - this.getDamage()) << 1; } public boolean hasComparatorInputOverride() { return true; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCalcite.java b/src/main/java/cn/nukkit/block/BlockCalcite.java new file mode 100644 index 00000000000..68866eb270c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCalcite.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockCalcite extends BlockSolid { + + public BlockCalcite() { + // Does nothing + } + + @Override + public String getName() { + return "Calcite"; + } + + @Override + public int getId() { + return CALCITE; + } + + @Override + public double getHardness() { + return 0.75; + } + + @Override + public double getResistance() { + return 0.75; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + // TODO: + /*@Override + public boolean isLavaResistant() { + return true; + }*/ +} diff --git a/src/main/java/cn/nukkit/block/BlockCampfire.java b/src/main/java/cn/nukkit/block/BlockCampfire.java new file mode 100644 index 00000000000..0b982455b10 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCampfire.java @@ -0,0 +1,262 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityCampfire; +import cn.nukkit.entity.Entity; +import cn.nukkit.event.entity.EntityDamageByBlockEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.inventory.CampfireInventory; +import cn.nukkit.inventory.CampfireRecipe; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.item.*; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.Tag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; + +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class BlockCampfire extends BlockTransparentMeta implements Faceable { + + public BlockCampfire() { + this(0); + } + + public BlockCampfire(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Campfire"; + } + + @Override + public int getId() { + return CAMPFIRE_BLOCK; + } + + @Override + public int getLightLevel() { + return isExtinguished() ? 0 : 15; + } + + @Override + public double getResistance() { + return 2; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[] {Item.get(ItemID.COAL, 0, 1 + ThreadLocalRandom.current().nextInt(1))}; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (this.down().getId() == CAMPFIRE_BLOCK) { + return false; + } + + this.setDamage(player != null ? player.getDirection().getOpposite().getHorizontalIndex() : 0); + Block layer1 = block.getLevelBlock(BlockLayer.WATERLOGGED); + + boolean defaultLayerCheck = (block instanceof BlockWater && block.getDamage() == 0 || block.getDamage() >= 8) || block instanceof BlockIceFrosted; + boolean layer1Check = (layer1 instanceof BlockWater && layer1.getDamage() == 0 || layer1.getDamage() >= 8) || layer1 instanceof BlockIceFrosted; + if (defaultLayerCheck || layer1Check) { + this.setExtinguished(true); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_FIZZ); + this.level.setBlock(this, BlockLayer.WATERLOGGED, defaultLayerCheck ? block : layer1, false, false); + } else { + this.level.setBlock(this, BlockLayer.WATERLOGGED, Block.get(Block.AIR), false, false); + } + + this.getLevel().setBlock(this, this, true, true); + this.createBlockEntity(item); + return true; + } + + private BlockEntityCampfire createBlockEntity(Item item) { + CompoundTag nbt = new CompoundTag() + .putString("id", BlockEntity.CAMPFIRE) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (item.hasCustomBlockData()) { + Map customData = item.getCustomBlockData().getTags(); + for (Map.Entry tag : customData.entrySet()) { + nbt.put(tag.getKey(), tag.getValue()); + } + } + + return (BlockEntityCampfire) BlockEntity.createBlockEntity(BlockEntity.CAMPFIRE, this.getChunk(), nbt); + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + if (!this.isExtinguished() && !entity.isSneaking()) { + entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.FIRE, this instanceof BlockCampfireSoul ? 2 : 1)); + } + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!this.isExtinguished()) { + Block layer1 = this.getLevelBlock(BlockLayer.WATERLOGGED); + if (layer1 instanceof BlockWater || layer1 instanceof BlockIceFrosted) { + this.setExtinguished(true); + this.level.setBlock(this, this, true, true); + this.level.addSound(this, Sound.RANDOM_FIZZ, 0.5f, 2.2f); + } + } + return type; + } + return 0; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == BlockID.AIR || item.getCount() <= 0) { + return false; + } + + BlockEntity entity = this.level.getBlockEntity(this); + if (!(entity instanceof BlockEntityCampfire)) { + return false; + } + + boolean itemUsed = false; + if (item.isShovel() && !this.isExtinguished()) { + this.setExtinguished(true); + this.level.setBlock(this, this, true, true); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_FIZZ); + itemUsed = true; + } else if (item.getId() == ItemID.FLINT_AND_STEEL || item.hasEnchantment(Enchantment.ID_FIRE_ASPECT)) { + item.useOn(this); + this.setExtinguished(false); + this.level.setBlock(this, this, true, true); + entity.scheduleUpdate(); + level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_IGNITE); + itemUsed = true; + } + + BlockEntityCampfire campfire = (BlockEntityCampfire) entity; + Item cloned = item.clone(); + cloned.setCount(1); + CampfireInventory inventory = campfire.getInventory(); + if (inventory.canAddItem(cloned)) { + CampfireRecipe recipe = this.level.getServer().getCraftingManager().matchCampfireRecipe(cloned); + if (recipe != null) { + inventory.addItem(cloned); + item.setCount(item.getCount() - 1); + return true; + } + } + + return itemUsed; + } + + @Override + public double getMaxY() { + return y + 0.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.SPRUCE_BLOCK_COLOR; + } + + public boolean isExtinguished() { + return (this.getDamage() & 0x4) == 0x4; + } + + public void setExtinguished(boolean extinguished) { + this.setDamage((this.getDamage() & 0x3) | (extinguished? 0x4 : 0x0)); + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(getDamage() & 0x3); + } + + public void setBlockFace(BlockFace face) { + if (face == BlockFace.UP || face == BlockFace.DOWN) { + return; + } + + this.setDamage((this.getDamage() & 0x4) | face.getHorizontalIndex()); + } + + @Override + public Item toItem() { + return Item.get(ItemID.CAMPFIRE); + } + + public boolean hasComparatorInputOverride() { + return true; + } + + @Override + public int getComparatorInputOverride() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (blockEntity instanceof BlockEntityCampfire) { + return ContainerInventory.calculateRedstone(((BlockEntityCampfire) blockEntity).getInventory()); + } + + return super.getComparatorInputOverride(); + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCampfireSoul.java b/src/main/java/cn/nukkit/block/BlockCampfireSoul.java new file mode 100644 index 00000000000..893cd1734b5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCampfireSoul.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemID; + +public class BlockCampfireSoul extends BlockCampfire { + + public BlockCampfireSoul() { + this(0); + } + + public BlockCampfireSoul(int meta) { + super(meta); + } + + @Override + public int getId() { + return SOUL_CAMPFIRE_BLOCK; + } + + @Override + public String getName() { + return "Soul Campfire"; + } + + @Override + public int getLightLevel() { + return isExtinguished()? 0 : 10; + } + + @Override + public Item toItem() { + return Item.get(ItemID.SOUL_CAMPFIRE); + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{ new ItemBlock(Block.get(Block.SOUL_SOIL, 0), 0) }; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCarpet.java b/src/main/java/cn/nukkit/block/BlockCarpet.java index 3cfa2db4e1d..9d46e451cd5 100644 --- a/src/main/java/cn/nukkit/block/BlockCarpet.java +++ b/src/main/java/cn/nukkit/block/BlockCarpet.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.level.Level; -import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.DyeColor; @@ -13,6 +12,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockCarpet extends BlockFlowable { + public BlockCarpet() { this(0); } @@ -30,11 +30,6 @@ public int getId() { return CARPET; } - @Override - public double getHardness() { - return 0.1; - } - @Override public double getResistance() { return 0.5; @@ -55,11 +50,6 @@ public boolean canPassThrough() { return false; } - @Override - protected AxisAlignedBB recalculateBoundingBox() { - return this; - } - @Override public double getMaxY() { return this.y + 0.0625; @@ -69,7 +59,7 @@ public double getMaxY() { public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { Block down = this.down(); if (down.getId() != Item.AIR) { - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } return false; @@ -97,4 +87,13 @@ public DyeColor getDyeColor() { return DyeColor.getByWoolData(getDamage()); } + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCarrot.java b/src/main/java/cn/nukkit/block/BlockCarrot.java index f86a429c4c6..66d00118a05 100644 --- a/src/main/java/cn/nukkit/block/BlockCarrot.java +++ b/src/main/java/cn/nukkit/block/BlockCarrot.java @@ -1,9 +1,7 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemCarrot; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** * @author Nukkit Project Team @@ -32,16 +30,16 @@ public int getId() { public Item[] getDrops(Item item) { if (getDamage() >= 0x07) { return new Item[]{ - new ItemCarrot(0, new Random().nextInt(3) + 1) + Item.get(Item.CARROT, 0, Utils.rand(1, 5)) }; } return new Item[]{ - new ItemCarrot() + Item.get(Item.CARROT) }; } @Override public Item toItem() { - return new ItemCarrot(); + return Item.get(Item.CARROT); } } diff --git a/src/main/java/cn/nukkit/block/BlockCartographyTable.java b/src/main/java/cn/nukkit/block/BlockCartographyTable.java new file mode 100644 index 00000000000..447373c3984 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCartographyTable.java @@ -0,0 +1,42 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockCartographyTable extends BlockSolid { + + @Override + public String getName() { + return "Cartography Table"; + } + + @Override + public int getId() { + return CARTOGRAPHY_TABLE; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public double getResistance() { + return 12.5; + } + + @Override + public double getHardness() { + return 2.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 5; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCauldron.java b/src/main/java/cn/nukkit/block/BlockCauldron.java index d9bae0a4acb..afb1f82d4ee 100644 --- a/src/main/java/cn/nukkit/block/BlockCauldron.java +++ b/src/main/java/cn/nukkit/block/BlockCauldron.java @@ -6,18 +6,32 @@ import cn.nukkit.event.player.PlayerBucketEmptyEvent; import cn.nukkit.event.player.PlayerBucketFillEvent; import cn.nukkit.item.*; +import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; +import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.particle.SmokeParticle; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.MathHelper; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.Tag; import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; /** - * author: CreeperFace + * @author CreeperFace * Nukkit Project */ -public class BlockCauldron extends BlockSolidMeta { +public class BlockCauldron extends BlockTransparentMeta { + + /** + * Used to cache biome check for freezing + * 1 = can't freeze, 2 = can freeze + */ + private byte freezing; public BlockCauldron() { super(0); @@ -57,13 +71,22 @@ public boolean canBeActivated() { } public boolean isFull() { - return this.getDamage() == 0x06; + return (this.getDamage() & 0x06) == 0x06; } public boolean isEmpty() { return this.getDamage() == 0x00; } + public int getFillLevel() { + return (getDamage() & 0x6) >> 1; + } + + public void setFillLevel(int fillLevel) { + fillLevel = MathHelper.clamp(fillLevel, 0, 3); + setDamage(fillLevel << 1); + } + @Override public boolean onActivate(Item item, Player player) { BlockEntity be = this.level.getBlockEntity(this); @@ -89,14 +112,13 @@ public boolean onActivate(Item item, Player player) { this.level.getServer().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { replaceBucket(item, player, ev.getItem()); - this.setDamage(0);//empty + this.setFillLevel(0);//empty this.level.setBlock(this, this, true); cauldron.clearCustomColor(); - this.getLevel().addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_CAULDRON_TAKE_WATER); + this.getLevel().addSound(this, Sound.CAULDRON_TAKEWATER); } - } else if (item.getDamage() == 8) {//water bucket - - if (isFull() && !cauldron.isCustomColor() && !cauldron.hasPotion()) { + } else if (item.getDamage() == 8 || item.getDamage() == 10) {//water and lava buckets + if (isFull() && !cauldron.isCustomColor() && !cauldron.hasPotion() && item.getDamage() == 8) { break; } @@ -107,71 +129,144 @@ public boolean onActivate(Item item, Player player) { PlayerBucketEmptyEvent ev = new PlayerBucketEmptyEvent(player, this, null, item, bucket); this.level.getServer().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { - replaceBucket(item, player, ev.getItem()); + if (player.isSurvival() || player.isAdventure()) { + replaceBucket(item, player, ev.getItem()); + } if (cauldron.hasPotion()) {//if has potion - this.setDamage(0);//empty - cauldron.setPotionId(0xffff);//reset potion - cauldron.setSplashPotion(false); - cauldron.clearCustomColor(); - this.level.setBlock(this, this, true); - this.level.addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_SOUND_EXPLODE); - } else { - this.setDamage(6);//fill + clearWithFizz(cauldron); + } else if (item.getDamage() == 8) { //water bucket + this.setFillLevel(3);//fill cauldron.clearCustomColor(); this.level.setBlock(this, this, true); - this.level.addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_SOUND_CAULDRON_FILL_WATER); + this.getLevel().addSound(this, Sound.CAULDRON_FILLWATER); + } else { // lava bucket + if (isEmpty()) { + BlockCauldronLava cauldronLava = new BlockCauldronLava(0xE); + cauldronLava.setFillLevel(3); + this.level.setBlock(this, cauldronLava, true, true); + cauldron.clearCustomColor(); + this.getLevel().addSound(this.add(0.5, 0.5, 0.5), Sound.BUCKET_EMPTY_LAVA); + } else { + clearWithFizz(cauldron); + } } //this.update(); } } break; - case Item.DYE: //TODO + case Item.DYE: + if (isEmpty() || cauldron.hasPotion()) { + break; + } + + if (player.isSurvival() || player.isAdventure()) { + item.setCount(item.getCount() - 1); + player.getInventory().setItemInHand(item); + } + + BlockColor color = new ItemDye(item.getDamage()).getDyeColor().getColor(); + if (!cauldron.isCustomColor()) { + cauldron.setCustomColor(color); + } else { + BlockColor current = cauldron.getCustomColor(); + BlockColor mixed = new BlockColor( + current.getRed() + ((color.getRed() - current.getRed()) >> 1), + current.getGreen() + ((color.getGreen() - current.getGreen()) >> 1), + current.getBlue() + ((color.getBlue() - current.getBlue()) >> 1) + ); + cauldron.setCustomColor(mixed); + } + this.level.addSound(this, Sound.CAULDRON_ADDDYE); break; case Item.LEATHER_CAP: case Item.LEATHER_TUNIC: case Item.LEATHER_PANTS: case Item.LEATHER_BOOTS: + case Item.LEATHER_HORSE_ARMOR: + if (isEmpty() || cauldron.hasPotion()) { + break; + } + + CompoundTag compoundTag = item.getNamedTag(); + if (compoundTag == null) compoundTag = new CompoundTag(); + if (cauldron.isCustomColor()) { + compoundTag.putInt("customColor", cauldron.getCustomColor().getRGB()); + } else { + compoundTag.remove("customColor"); + } + item.setCompoundTag(compoundTag); + player.getInventory().setItemInHand(item); + + setFillLevel(getFillLevel() - 1); + this.level.setBlock(this, this, true, true); + this.level.addSound(this, Sound.CAULDRON_DYEARMOR); break; case Item.POTION: + case Item.SPLASH_POTION: + case Item.LINGERING_POTION: + if (!isEmpty() && (cauldron.hasPotion() ? cauldron.getPotionId() != item.getDamage() : item.getDamage() != 0)) { + clearWithFizz(cauldron); + consumePotion(item, player); + break; + } if (isFull()) { break; } - this.setDamage(this.getDamage() + 1); - if (this.getDamage() > 0x06) - this.setDamage(0x06); - - if (item.getCount() == 1) { - player.getInventory().setItemInHand(new ItemBlock(Block.get(BlockID.AIR))); - } else if (item.getCount() > 1) { - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - - Item bottle = new ItemGlassBottle(); - if (player.getInventory().canAddItem(bottle)) { - player.getInventory().addItem(bottle); - } else { - player.getLevel().dropItem(player.add(0, 1.3, 0), bottle, player.getDirectionVector().multiply(0.4)); - } + if (item.getDamage() != 0 && isEmpty()) { + cauldron.setPotionId(item.getDamage()); } - - this.level.addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_CAULDRON_FILL_POTION); + cauldron.setPotionType( + item.getId() == Item.POTION ? BlockEntityCauldron.POTION_TYPE_NORMAL : + item.getId() == Item.SPLASH_POTION ? BlockEntityCauldron.POTION_TYPE_SPLASH : + BlockEntityCauldron.POTION_TYPE_LINGERING + ); + cauldron.spawnToAll(); + + setFillLevel(getFillLevel() + 1); + this.level.setBlock(this, this, true); + consumePotion(item, player); + this.getLevel().addSound(this, Sound.CAULDRON_FILLPOTION); break; case Item.GLASS_BOTTLE: if (isEmpty()) { break; } - this.setDamage(this.getDamage() - 1); - if (this.getDamage() < 0x00) - this.setDamage(0x00); + int meta = cauldron.hasPotion() ? cauldron.getPotionId() : 0; + Item potion; + if (meta == 0) { + potion = Item.get(Item.POTION); + } else { + switch (cauldron.getPotionType()) { + case BlockEntityCauldron.POTION_TYPE_SPLASH: + potion = Item.get(Item.SPLASH_POTION, meta); + break; + case BlockEntityCauldron.POTION_TYPE_LINGERING: + potion = Item.get(Item.LINGERING_POTION, meta); + break; + case BlockEntityCauldron.POTION_TYPE_NORMAL: + default: + potion = Item.get(Item.POTION, meta); + break; + } + } + + setFillLevel(getFillLevel() - 1); + if (isEmpty()) { + cauldron.setPotionId(0xffff);//reset potion + cauldron.clearCustomColor(); + } + this.level.setBlock(this, this, true); - if (item.getCount() == 1) { - player.getInventory().setItemInHand(new ItemPotion()); + boolean consumeBottle = player.isSurvival() || player.isAdventure(); + if (consumeBottle && item.getCount() == 1) { + player.getInventory().setItemInHand(potion); } else if (item.getCount() > 1) { - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); + if (consumeBottle) { + item.setCount(item.getCount() - 1); + player.getInventory().setItemInHand(item); + } - Item potion = new ItemPotion(); if (player.getInventory().canAddItem(potion)) { player.getInventory().addItem(potion); } else { @@ -179,7 +274,49 @@ public boolean onActivate(Item item, Player player) { } } - this.level.addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_CAULDRON_TAKE_POTION); + this.getLevel().addSound(this, Sound.CAULDRON_TAKEPOTION); + break; + case Item.ARROW: + if (item.getDamage() > 1 || !cauldron.hasPotion()) { + break; + } + + if (!player.isCreative() && item.getCount() == 1) { + item.setDamage(potion2arrow(cauldron.getPotionId())); + player.getInventory().setItemInHand(item); + } else if (item.getCount() > 1) { + Item newItem = item.clone(); + newItem.setCount(1); + newItem.setDamage(potion2arrow(cauldron.getPotionId())); + + if (!player.isCreative()) { + item.setCount(item.getCount() - 1); + player.getInventory().setItemInHand(item); + } + + if (player.getInventory().canAddItem(newItem)) { + player.getInventory().addItem(newItem); + } else { + player.getLevel().dropItem(player.add(0, 1.3, 0), newItem, player.getDirectionVector().multiply(0.4)); + } + } + + setFillLevel(getFillLevel() - 1); + if (isEmpty()) { + cauldron.setPotionId(0xffff); + cauldron.clearCustomColor(); + } + this.level.setBlock(this, this, true); + this.level.addLevelEvent(this.add(0.5, 0.375 + this.getDamage() * 0.125, 0.5), LevelEventPacket.EVENT_CAULDRON_DYE_ARMOR); + case BlockID.SHULKER_BOX: + if (isEmpty() || cauldron.isCustomColor() || cauldron.hasPotion()) { + break; + } + + player.getInventory().setItemInHand(Item.get(Item.UNDYED_SHULKER_BOX).setCompoundTag(item.getCompoundTag())); + setFillLevel(getFillLevel() - 1); + this.level.setBlock(this, this, true); + this.getLevel().addSound(this, Sound.CAULDRON_TAKEPOTION); break; default: return true; @@ -188,7 +325,13 @@ public boolean onActivate(Item item, Player player) { this.level.updateComparatorOutputLevel(this); return true; } - + + private static int potion2arrow(int potion) { + int id = potion & 0xffff; + if (id < 5 || id > 43) return 1; // if it fails don't create game crashing arrows + return id < 43 ? id + 1 : id; + } + protected void replaceBucket(Item oldBucket, Player player, Item newBucket) { if (player.isSurvival() || player.isAdventure()) { if (oldBucket.getCount() == 1) { @@ -203,7 +346,7 @@ protected void replaceBucket(Item oldBucket, Player player, Item newBucket) { } } } - + @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { CompoundTag nbt = new CompoundTag("") @@ -221,26 +364,39 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityCauldron cauldron = (BlockEntityCauldron) BlockEntity.createBlockEntity(BlockEntity.CAULDRON, this.level.getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - if (cauldron == null) { - return false; - } - this.getLevel().setBlock(block, this, true, true); + BlockEntity.createBlockEntity(BlockEntity.CAULDRON, this.getChunk(), nbt); + + this.getLevel().setBlock(this, this, true, true); return true; } @Override public Item[] getDrops(Item item) { if (item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{new ItemCauldron()}; + return new Item[]{Item.get(Item.CAULDRON)}; } return new Item[0]; } + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_RANDOM && level.isRaining() && !this.isFull()) { + if (freezing < 1) { + freezing = Biome.getBiome(level.getBiomeId((int) this.x, (int) this.z)).isFreezing() ? (byte) 2 : (byte) 1; + } + if (freezing == 1 && ThreadLocalRandom.current().nextInt(20) == 0 && level.canBlockSeeSky(this)) { + this.setFillLevel(this.getFillLevel() + 1); + this.getLevel().setBlock(this, this, true, true); + return Level.BLOCK_UPDATE_RANDOM; + } + } + return super.onUpdate(type); + } + @Override public Item toItem() { - return new ItemCauldron(); + return Item.get(Item.CAULDRON); } public boolean hasComparatorInputOverride() { @@ -248,11 +404,55 @@ public boolean hasComparatorInputOverride() { } public int getComparatorInputOverride() { - return this.getDamage(); + return getFillLevel(); } @Override public boolean canHarvestWithHand() { return false; } + + // Source: PN/#666 + private void consumePotion(Item item, Player player) { + if (player.isSurvival() || player.isAdventure()) { + if (item.getCount() == 1) { + player.getInventory().setItemInHand(new ItemBlock(Block.get(AIR))); + } else if (item.getCount() > 1) { + item.setCount(item.getCount() - 1); + player.getInventory().setItemInHand(item); + Item bottle = Item.get(Item.GLASS_BOTTLE); + if (player.getInventory().canAddItem(bottle)) { + player.getInventory().addItem(bottle); + } else { + player.getLevel().dropItem(player.add(0, 1.3, 0), bottle, player.getDirectionVector().multiply(0.4)); + } + } + } + } + + // Source: PN/#666 + public void clearWithFizz(BlockEntityCauldron cauldron) { + this.setFillLevel(0); + cauldron.setPotionId(0xffff); + cauldron.setSplashPotion(false); + cauldron.clearCustomColor(); + this.level.setBlock(this, Block.get(CAULDRON_BLOCK), true); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_FIZZ); + this.getLevel().addParticle(new SmokeParticle(add(Math.random(), 1.2, Math.random())), null, 8); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCauldronLava.java b/src/main/java/cn/nukkit/block/BlockCauldronLava.java new file mode 100644 index 00000000000..64e524b638c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCauldronLava.java @@ -0,0 +1,113 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityCauldron; +import cn.nukkit.entity.BaseEntity; +import cn.nukkit.entity.Entity; +import cn.nukkit.event.entity.EntityCombustByBlockEvent; +import cn.nukkit.event.entity.EntityDamageByBlockEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.event.player.PlayerBucketFillEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Sound; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.potion.Effect; + +public class BlockCauldronLava extends BlockCauldron { + + public BlockCauldronLava() { + this(0x8); + } + + public BlockCauldronLava(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Lava Cauldron"; + } + + @Override + public int getId() { + return LAVA_CAULDRON; + } + + @Override + public int getLightLevel() { + return 15; + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void setFillLevel(int fillLevel) { + super.setFillLevel(fillLevel); + setDamage(getDamage() | 0x8); + } + + @Override + public void onEntityCollide(Entity entity) { + if (!entity.fireProof || !entity.isOnFire() || !(entity instanceof BaseEntity)) { // Improve performance + if (!entity.fireProof || !entity.isOnFire()) { + + EntityCombustByBlockEvent ev = new EntityCombustByBlockEvent(this, entity, 8); + Server.getInstance().getPluginManager().callEvent(ev); + if (!ev.isCancelled() && entity.isAlive() && entity.noDamageTicks == 0) { + entity.setOnFire(ev.getDuration()); + } + } + + if (!entity.hasEffect(Effect.FIRE_RESISTANCE)) { + entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.LAVA, 4)); + } + } + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == ItemID.BUCKET) { + if (item.getDamage() == 0) { + if (!isFull()) { + return false; + } + + PlayerBucketFillEvent ev = new PlayerBucketFillEvent(player, this, null, item, Item.get(ItemID.BUCKET, 10, 1)); + this.level.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + replaceBucket(item, player, ev.getItem()); + if (!(this.level.getBlockEntity(this) instanceof BlockEntityCauldron)) { + BlockEntity.createBlockEntity(BlockEntity.CAULDRON, this.getChunk(), new CompoundTag("") + .putString("id", BlockEntity.CAULDRON) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putShort("PotionId", 0xffff) + .putByte("SplashPotion", 0)); + } + this.level.setBlock(this, Block.get(CAULDRON_BLOCK), true); + this.getLevel().addSound(this.add(0.5, 0.5, 0.5), Sound.BUCKET_FILL_LAVA); + } + } + } + + this.level.updateComparatorOutputLevel(this); + return true; + } + + @Override + public boolean isFull() { + return this.getDamage() == 14; + } + + @Override + public int onUpdate(int type) { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCaveVines.java b/src/main/java/cn/nukkit/block/BlockCaveVines.java new file mode 100644 index 00000000000..7cd9186958f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCaveVines.java @@ -0,0 +1,238 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockCaveVines extends BlockTransparentMeta { + + private static final float CHANCE_OF_BERRIES_ON_GROWTH = 0.11F * 1.2F; + + public BlockCaveVines() { + this(0); + } + + public BlockCaveVines(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Cave Vines"; + } + + @Override + public int getId() { + return CAVE_VINES; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + public boolean canPlaceOn(Block floor, Position pos) { + Block up = floor.up(2); + return (up.isSolid() || up instanceof BlockCaveVines) && super.canPlaceOn(floor, pos); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!this.canPlaceOn(block.down(), target)) { + return false; + } + + Block support = block.up(); + if (isCaveVine(support)) { + this.setVineAge(Math.min(this.getMaxAge(), ((BlockCaveVines) support).getVineAge() + 1)); + } else { + this.setVineAge(0); + } + return this.getLevel().setBlock(this, this, true, true); + } + + @Override + public int onUpdate(int type) { + switch (type) { + case Level.BLOCK_UPDATE_NORMAL: + Block up = this.up(); + if (!isCaveVine(up) && !up.isSolid()) { + this.getLevel().scheduleUpdate(this, 1); + } + break; + case Level.BLOCK_UPDATE_SCHEDULED: + this.getLevel().useBreakOn(this, null, null, true); + break; + case Level.BLOCK_UPDATE_RANDOM: + if (!this.tryGrowItself()) { + this.tryGrow(); + } + break; + } + return type; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (this.tryPickupBerries()) { + return true; + } + + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return false; + } + + Block bottom = this; + BlockCaveVines plantHead = null; + while (bottom instanceof BlockCaveVines) { + plantHead = (BlockCaveVines) bottom; + bottom = bottom.down(); + } + + if (!plantHead.tryGrow()) { + return false; + } + + this.level.addParticle(new BoneMealParticle(plantHead)); + if (player != null && !player.isCreative()) { + item.count--; + } + return true; + } + + @Override + public boolean canBeActivated() { + return true; + } + + private boolean tryPickupBerries() { + if (!this.hasBerries()) { + return false; + } + + BlockCaveVines blockCaveVines = this.getStateWithoutBerries(this); + this.getLevel().setBlock(this, blockCaveVines, false, true); + + Item item = Item.get(ItemID.GLOW_BERRIES, 0, 1); + this.getLevel().dropItem(this.add(0.5, 0.5, 0.5), item); + return true; + } + + private boolean tryGrow() { + if (this.getVineAge() >= this.getMaxAge()) { + return false; + } + + Block down = this.down(); + if (down.getY() <= this.getLevel().getMinBlockY() || down.getId() != Block.AIR) { + return false; + } + + Block topBlock = this; + while (topBlock instanceof BlockCaveVines) { + if (topBlock.getDamage() < this.getVineAge()) { + break; + } + topBlock = topBlock.up(); + } + + if (topBlock.getDamage() >= this.getMaxAge()) { + return false; + } + + boolean withBerries = ThreadLocalRandom.current().nextFloat() < CHANCE_OF_BERRIES_ON_GROWTH; + BlockCaveVines head = withBerries ? this.getStateWithBerries(down) : this.getStateWithoutBerries(down); + + BlockGrowEvent event = new BlockGrowEvent(this, head); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + + this.getLevel().setBlock(down, event.getNewState(), true, true); + + BlockCaveVines support = (BlockCaveVines) Block.get(this.hasBerries() ? CAVE_VINES_BODY_WITH_BERRIES : CAVE_VINES, this.getDamage()); + this.getLevel().setBlock(this, support, true, true); + return true; + } + + private boolean tryGrowItself() { + if (this.hasBerries() || ThreadLocalRandom.current().nextFloat() >= CHANCE_OF_BERRIES_ON_GROWTH) { + return false; + } + + BlockCaveVines blockCaveVines = this.getStateWithBerries(this); + this.getLevel().setBlock(this, blockCaveVines, true, true); + return true; + } + + public BlockCaveVines getStateWithBerries(Position position) { + if (this.getDamage() == 0) { + return (BlockCaveVines) Block.get(CAVE_VINES_HEAD_WITH_BERRIES, 0, position); + } + return (BlockCaveVines) Block.get(CAVE_VINES_HEAD_WITH_BERRIES, this.getDamage(), position); + } + + public BlockCaveVines getStateWithoutBerries(Position position) { + return (BlockCaveVines) Block.get(CAVE_VINES, this.getDamage(), position); + } + + @Override + public Item[] getDrops(Item item) { + if (!this.hasBerries()) { + return new Item[0]; + } + return new Item[]{ Item.get(ItemID.GLOW_BERRIES, 0, 1) }; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + public void setVineAge(int age) { + this.setDamage(age); + } + + public int getVineAge() { + return this.getDamage(); + } + + public int getMaxAge() { + return 25; + } + + public boolean hasBerries() { + return false; + } + + @Override + public boolean canBeClimbed() { + return true; + } + + public static boolean isCaveVine(Block block) { + switch (block.getId()) { + case CAVE_VINES: + case CAVE_VINES_BODY_WITH_BERRIES: + case CAVE_VINES_HEAD_WITH_BERRIES: + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesBody.java b/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesBody.java new file mode 100644 index 00000000000..f01f70b3bf8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesBody.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.level.Position; + +public class BlockCaveVinesBerriesBody extends BlockCaveVines { + + public BlockCaveVinesBerriesBody() { + this(0); + } + + public BlockCaveVinesBerriesBody(int meta) { + super(meta); + } + + @Override + public int getId() { + return CAVE_VINES_BODY_WITH_BERRIES; + } + + @Override + public BlockCaveVines getStateWithBerries(Position position) { + return (BlockCaveVines) Block.get(CAVE_VINES_HEAD_WITH_BERRIES, this.getDamage(), position); + } + + @Override + public BlockCaveVines getStateWithoutBerries(Position position) { + return (BlockCaveVines) Block.get(CAVE_VINES, this.getDamage(), position); + } + + @Override + public boolean hasBerries() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesHead.java b/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesHead.java new file mode 100644 index 00000000000..8424b9559ee --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCaveVinesBerriesHead.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.level.Position; + +public class BlockCaveVinesBerriesHead extends BlockCaveVines { + + public BlockCaveVinesBerriesHead() { + this(0); + } + + public BlockCaveVinesBerriesHead(int meta) { + super(meta); + } + + @Override + public int getId() { + return CAVE_VINES_HEAD_WITH_BERRIES; + } + + @Override + public BlockCaveVines getStateWithBerries(Position position) { + return (BlockCaveVines) Block.get(CAVE_VINES_HEAD_WITH_BERRIES, 0, position); + } + + @Override + public BlockCaveVines getStateWithoutBerries(Position position) { + return (BlockCaveVines) Block.get(CAVE_VINES, 0, position); + } + + @Override + public boolean hasBerries() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockChain.java b/src/main/java/cn/nukkit/block/BlockChain.java new file mode 100644 index 00000000000..d1f01d4798f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockChain.java @@ -0,0 +1,112 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; + +public class BlockChain extends BlockTransparentMeta { + + public BlockChain() { + this(0); + } + + public BlockChain(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Chain"; + } + + @Override + public int getId() { + return CHAIN_BLOCK; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setPillarAxis(face.getAxis()); + if (super.place(item, block, target, face, fx, fy, fz, player)) { + return true; + } + return false; + } + + public void setPillarAxis(BlockFace.Axis axis) { + switch (axis) { + case Y: + this.setDamage(0); + break; + case X: + this.setDamage(1); + break; + case Z: + this.setDamage(2); + break; + } + } + + public BlockFace.Axis getPillarAxis() { + switch (this.getDamage() % 3) { + case 2: + return BlockFace.Axis.Z; + case 1: + return BlockFace.Axis.X; + case 0: + default: + return BlockFace.Axis.Y; + } + } + + @Override + public double getHardness() { + return 5; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getMinX() { + return x + 7 / 16.0; + } + + @Override + public double getMaxX() { + return x + 9 / 16.0; + } + + @Override + public double getMinZ() { + return z + 7 / 16.0; + } + + @Override + public double getMaxZ() { + return z + 9 / 16.0; + } + + @Override + public Item toItem() { + return Item.get(Item.CHAIN); + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockChest.java b/src/main/java/cn/nukkit/block/BlockChest.java index e6620669812..0e32cdf1f2d 100644 --- a/src/main/java/cn/nukkit/block/BlockChest.java +++ b/src/main/java/cn/nukkit/block/BlockChest.java @@ -18,7 +18,7 @@ import java.util.Map; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockChest extends BlockTransparentMeta implements Faceable { @@ -66,11 +66,6 @@ public double getMinX() { return this.x + 0.0625; } - @Override - public double getMinY() { - return this.y; - } - @Override public double getMinZ() { return this.z + 0.0625; @@ -91,12 +86,10 @@ public double getMaxZ() { return this.z + 0.9375; } - @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { BlockEntityChest chest = null; - int[] faces = {2, 5, 3, 4}; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); for (int side = 2; side <= 5; ++side) { if ((this.getDamage() == 4 || this.getDamage() == 5) && (side == 4 || side == 5)) { @@ -114,7 +107,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); + CompoundTag nbt = new CompoundTag("") .putList(new ListTag<>("Items")) .putString("id", BlockEntity.CHEST) @@ -133,11 +127,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityChest blockEntity = (BlockEntityChest) BlockEntity.createBlockEntity(BlockEntity.CHEST, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - - if (blockEntity == null) { - return false; - } + BlockEntityChest blockEntity = (BlockEntityChest) BlockEntity.createBlockEntity(BlockEntity.CHEST, this.getChunk(), nbt); if (chest != null) { chest.pairWith(blockEntity); @@ -161,28 +151,17 @@ public boolean onBreak(Item item) { @Override public boolean onActivate(Item item, Player player) { if (player != null) { - Block top = up(); - if (!top.isTransparent()) { + Block top = this.up(); + if ((!(top instanceof BlockSlab) && !top.isTransparent()) || (top instanceof BlockSlab && top.isTransparent())) { // avoid issues with the slab hack return true; } BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityChest chest; - if (t instanceof BlockEntityChest) { - chest = (BlockEntityChest) t; - } else { - CompoundTag nbt = new CompoundTag("") - .putList(new ListTag<>("Items")) - .putString("id", BlockEntity.CHEST) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - chest = (BlockEntityChest) BlockEntity.createBlockEntity(BlockEntity.CHEST, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (chest == null) { - return false; - } + if (!(t instanceof BlockEntityChest)) { + return false; } + BlockEntityChest chest = (BlockEntityChest) t; if (chest.namedTag.contains("Lock") && chest.namedTag.get("Lock") instanceof StringTag) { if (!chest.namedTag.getString("Lock").equals(item.getCustomName())) { return true; @@ -216,11 +195,21 @@ public int getComparatorInputOverride() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockChorusFlower.java b/src/main/java/cn/nukkit/block/BlockChorusFlower.java index 46ad4e2a2c9..68308fd6b19 100644 --- a/src/main/java/cn/nukkit/block/BlockChorusFlower.java +++ b/src/main/java/cn/nukkit/block/BlockChorusFlower.java @@ -1,11 +1,30 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.entity.projectile.EntitySnowball; +import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; -public class BlockChorusFlower extends BlockTransparent { +import java.util.concurrent.ThreadLocalRandom; +public class BlockChorusFlower extends BlockTransparentMeta { + + // Version 7a3d8a5 public BlockChorusFlower() { + super(0); + } + + public BlockChorusFlower(int meta) { + super(meta); } @Override @@ -25,16 +44,206 @@ public double getHardness() { @Override public double getResistance() { - return 2; + return 0.4; } @Override public int getToolType() { - return ItemTool.TYPE_NONE; + return ItemTool.TYPE_AXE; + } + + private boolean isPositionValid() { + // Chorus flowers must be above end stone or chorus plant, or be above air and horizontally adjacent to exactly one chorus plant. + // If these conditions are not met, the block breaks without dropping anything. + Block down = down(); + if (down.getId() == CHORUS_PLANT || down.getId() == END_STONE) { + return true; + } + if (down.getId() != AIR) { + return false; + } + boolean foundPlant = false; + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + Block side = getSide(face); + if (side.getId() == CHORUS_PLANT) { + if (foundPlant) { + return false; + } + foundPlant = true; + } + } + + return foundPlant; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!isPositionValid()) { + this.getLevel().scheduleUpdate(this, 1); + return type; + } + } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.getLevel().useBreakOn(this, null, null, true); + return type; + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + // Check limit + Block up; + if (this.y < level.getMaxBlockY() && (up = this.up()).getId() == AIR) { + if (!isFullyAged()) { + boolean growUp = false; // Grow upward? + boolean ground = false; // Is on the ground directly? + Block down = this.down(); + if (down.getId() == AIR || down.getId() == END_STONE) { + growUp = true; + } else if (down.getId() == CHORUS_PLANT) { + int height = 1; + for (int y = 2; y < 6; y++) { + Block downY = this.down(y); + if (downY.getId() == CHORUS_PLANT) { + height++; + } else { + if (downY.getId() == END_STONE) { + ground = true; + } + break; + } + } + + if (height < 2 || height <= ThreadLocalRandom.current().nextInt(ground ? 5 : 4)) { + growUp = true; + } + } + + // Grow Upward + if (growUp && up.up().getId() == AIR && isHorizontalAir(up)) { + BlockChorusFlower block = (BlockChorusFlower) this.clone(); + block.y = this.y + 1; + BlockGrowEvent ev = new BlockGrowEvent(this, block); + Server.getInstance().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + this.getLevel().setBlock(this, Block.get(CHORUS_PLANT)); + this.getLevel().setBlock(block, ev.getNewState()); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_CHORUSGROW); + } else { + return Level.BLOCK_UPDATE_RANDOM; + } + // Grow Horizontally + } else if (!isFullyAged()) { + for (int i = 0; i < ThreadLocalRandom.current().nextInt(ground ? 5 : 4); i++) { + BlockFace face = BlockFace.Plane.HORIZONTAL.random(); + Block check = this.getSide(face); + if (check.getId() == AIR && check.down().getId() == AIR && isHorizontalAirExcept(check, face.getOpposite())) { + BlockChorusFlower block = (BlockChorusFlower) this.clone(); + block.x = check.x; + block.y = check.y; + block.z = check.z; + block.setAge(getAge() + 1); + BlockGrowEvent ev = new BlockGrowEvent(this, block); + Server.getInstance().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + this.getLevel().setBlock(this, Block.get(CHORUS_PLANT)); + this.getLevel().setBlock(block, ev.getNewState()); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_CHORUSGROW); + } else { + return Level.BLOCK_UPDATE_RANDOM; + } + } + } + // Death + } else { + BlockChorusFlower block = (BlockChorusFlower) this.clone(); + block.setAge(getMaxAge()); + BlockGrowEvent ev = new BlockGrowEvent(this, block); + Server.getInstance().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + this.getLevel().setBlock(block, ev.getNewState()); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_CHORUSDEATH); + } else { + return Level.BLOCK_UPDATE_RANDOM; + } + } + } + } else { + return Level.BLOCK_UPDATE_RANDOM; + } + } + + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!isPositionValid()) { + return false; + } + return super.place(item, block, target, face, fx, fy, fz, player); } @Override public Item[] getDrops(Item item) { return new Item[]{this.toItem()}; } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public void onEntityCollide(Entity entity) { + if (entity instanceof EntityArrow || entity instanceof EntitySnowball) { + entity.close(); + this.getLevel().useBreakOn(this); + } + } + + public int getMaxAge() { + return 5; + } + + public int getAge() { + return getDamage(); + } + + public void setAge(int age) { + this.setDamage(age); + } + + public boolean isFullyAged() { + return getAge() >= getMaxAge(); + } + + private boolean isHorizontalAir(Block block) { + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + if (block.getSide(face).getId() != AIR) { + return false; + } + } + return true; + } + + private boolean isHorizontalAirExcept(Block block, BlockFace except) { + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + if (face != except) { + if (block.getSide(face).getId() != AIR) { + return false; + } + } + } + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.PURPLE_BLOCK_COLOR; + } } diff --git a/src/main/java/cn/nukkit/block/BlockChorusPlant.java b/src/main/java/cn/nukkit/block/BlockChorusPlant.java index f4363f5ecf3..9424205aeaf 100644 --- a/src/main/java/cn/nukkit/block/BlockChorusPlant.java +++ b/src/main/java/cn/nukkit/block/BlockChorusPlant.java @@ -1,17 +1,16 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemID; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; public class BlockChorusPlant extends BlockTransparent { - public BlockChorusPlant() { - } - @Override public int getId() { return CHORUS_PLANT; @@ -29,21 +28,76 @@ public double getHardness() { @Override public double getResistance() { - return 2; + return 0.4; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!isSupportValid()) { + return false; + } + return super.place(item, block, target, face, fx, fy, fz, player); } @Override public int getToolType() { - return ItemTool.TYPE_NONE; + return ItemTool.TYPE_AXE; + } + + private boolean isSupportValid() { + // Must be on end stone or chorus plant, or be above air and horizontally adjacent to exactly one chorus plant, otherwise breaks without dropping anything + int down = down().getId(); + if (down == CHORUS_PLANT || down == END_STONE) { + return true; + } + + if (down != AIR) { + return false; + } + + boolean plantFound = false; + for (BlockFace face : BlockFace.Plane.HORIZONTAL) { + if (getSide(face).getId() == CHORUS_PLANT) { + if (plantFound) { + return false; + } + plantFound = true; + } + } + + return plantFound; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!isSupportValid()) { + this.getLevel().scheduleUpdate(this, 1); + return type; + } + } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.getLevel().useBreakOn(this, null, null, true); + return type; + } + + return 0; } @Override public Item[] getDrops(Item item) { - return ThreadLocalRandom.current().nextBoolean() ? new Item[]{Item.get(ItemID.CHORUS_FRUIT, 0, 1)} : new Item[0]; + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + return Utils.rand() ? new Item[]{Item.get(Item.CHORUS_FRUIT, 0, 1)} : new Item[0]; } @Override public BlockColor getColor() { return BlockColor.PURPLE_BLOCK_COLOR; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockClay.java b/src/main/java/cn/nukkit/block/BlockClay.java index b2cdee1d549..e0661d910cb 100644 --- a/src/main/java/cn/nukkit/block/BlockClay.java +++ b/src/main/java/cn/nukkit/block/BlockClay.java @@ -1,8 +1,8 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemClay; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; /** @@ -10,9 +10,6 @@ */ public class BlockClay extends BlockSolid { - public BlockClay() { - } - @Override public double getHardness() { return 0.6; @@ -40,8 +37,11 @@ public String getName() { @Override public Item[] getDrops(Item item) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[]{ - new ItemClay(0, 4) + Item.get(Item.CLAY, 0, 4) }; } diff --git a/src/main/java/cn/nukkit/block/BlockCoal.java b/src/main/java/cn/nukkit/block/BlockCoal.java index 48fc76c6b6b..9df6085b51c 100644 --- a/src/main/java/cn/nukkit/block/BlockCoal.java +++ b/src/main/java/cn/nukkit/block/BlockCoal.java @@ -9,8 +9,6 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockCoal extends BlockSolid { - public BlockCoal() { - } @Override public int getId() { @@ -49,7 +47,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockCobblestone.java b/src/main/java/cn/nukkit/block/BlockCobblestone.java index 223f38def5b..bdf270cb198 100644 --- a/src/main/java/cn/nukkit/block/BlockCobblestone.java +++ b/src/main/java/cn/nukkit/block/BlockCobblestone.java @@ -4,14 +4,11 @@ import cn.nukkit.item.ItemTool; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockCobblestone extends BlockSolid { - public BlockCobblestone() { - } - @Override public int getId() { return COBBLESTONE; @@ -39,7 +36,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockCobweb.java b/src/main/java/cn/nukkit/block/BlockCobweb.java index 16ca8a689d8..cca7d8d655c 100644 --- a/src/main/java/cn/nukkit/block/BlockCobweb.java +++ b/src/main/java/cn/nukkit/block/BlockCobweb.java @@ -2,8 +2,8 @@ import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemString; import cn.nukkit.item.ItemTool; +import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.utils.BlockColor; /** @@ -11,6 +11,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockCobweb extends BlockFlowable { + public BlockCobweb() { this(0); } @@ -44,6 +45,11 @@ public int getToolType() { return ItemTool.TYPE_SWORD; } + @Override + public boolean hasEntityCollision() { + return true; + } + @Override public void onEntityCollide(Entity entity) { entity.resetFallDistance(); @@ -57,7 +63,7 @@ public Item[] getDrops(Item item) { }; } else if (item.isSword()) { return new Item[]{ - new ItemString() + Item.get(Item.STRING) }; } else { return new Item[0]; @@ -73,4 +79,19 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return this; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCocoa.java b/src/main/java/cn/nukkit/block/BlockCocoa.java index 66faef60934..e97b43853de 100644 --- a/src/main/java/cn/nukkit/block/BlockCocoa.java +++ b/src/main/java/cn/nukkit/block/BlockCocoa.java @@ -13,19 +13,30 @@ import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.utils.DyeColor; import cn.nukkit.utils.Faceable; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created by CreeperFace on 27. 10. 2016. */ public class BlockCocoa extends BlockTransparentMeta implements Faceable { - protected static final AxisAlignedBB[] EAST = new SimpleAxisAlignedBB[]{new SimpleAxisAlignedBB(0.6875D, 0.4375D, 0.375D, 0.9375D, 0.75D, 0.625D), new SimpleAxisAlignedBB(0.5625D, 0.3125D, 0.3125D, 0.9375D, 0.75D, 0.6875D), new SimpleAxisAlignedBB(0.5625D, 0.3125D, 0.3125D, 0.9375D, 0.75D, 0.6875D)}; - protected static final AxisAlignedBB[] WEST = new SimpleAxisAlignedBB[]{new SimpleAxisAlignedBB(0.0625D, 0.4375D, 0.375D, 0.3125D, 0.75D, 0.625D), new SimpleAxisAlignedBB(0.0625D, 0.3125D, 0.3125D, 0.4375D, 0.75D, 0.6875D), new SimpleAxisAlignedBB(0.0625D, 0.3125D, 0.3125D, 0.4375D, 0.75D, 0.6875D)}; - protected static final AxisAlignedBB[] NORTH = new SimpleAxisAlignedBB[]{new SimpleAxisAlignedBB(0.375D, 0.4375D, 0.0625D, 0.625D, 0.75D, 0.3125D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.0625D, 0.6875D, 0.75D, 0.4375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.0625D, 0.6875D, 0.75D, 0.4375D)}; - protected static final AxisAlignedBB[] SOUTH = new SimpleAxisAlignedBB[]{new SimpleAxisAlignedBB(0.375D, 0.4375D, 0.6875D, 0.625D, 0.75D, 0.9375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.5625D, 0.6875D, 0.75D, 0.9375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.5625D, 0.6875D, 0.75D, 0.9375D)}; - protected static final AxisAlignedBB[] ALL = new AxisAlignedBB[12]; + protected static final AxisAlignedBB[] EAST = {new SimpleAxisAlignedBB(0.6875D, 0.4375D, 0.375D, 0.9375D, 0.75D, 0.625D), new SimpleAxisAlignedBB(0.5625D, 0.3125D, 0.3125D, 0.9375D, 0.75D, 0.6875D), new SimpleAxisAlignedBB(0.5625D, 0.3125D, 0.3125D, 0.9375D, 0.75D, 0.6875D)}; + protected static final AxisAlignedBB[] WEST = {new SimpleAxisAlignedBB(0.0625D, 0.4375D, 0.375D, 0.3125D, 0.75D, 0.625D), new SimpleAxisAlignedBB(0.0625D, 0.3125D, 0.3125D, 0.4375D, 0.75D, 0.6875D), new SimpleAxisAlignedBB(0.0625D, 0.3125D, 0.3125D, 0.4375D, 0.75D, 0.6875D)}; + protected static final AxisAlignedBB[] NORTH = {new SimpleAxisAlignedBB(0.375D, 0.4375D, 0.0625D, 0.625D, 0.75D, 0.3125D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.0625D, 0.6875D, 0.75D, 0.4375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.0625D, 0.6875D, 0.75D, 0.4375D)}; + protected static final AxisAlignedBB[] SOUTH = {new SimpleAxisAlignedBB(0.375D, 0.4375D, 0.6875D, 0.625D, 0.75D, 0.9375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.5625D, 0.6875D, 0.75D, 0.9375D), new SimpleAxisAlignedBB(0.3125D, 0.3125D, 0.5625D, 0.6875D, 0.75D, 0.9375D)}; + + private static final short[] faces = { + 0, + 0, + 0, + 2, + 3, + 1, + }; + + private static final short[] faces2 = { + 3, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 5 + }; public BlockCocoa() { this(0); @@ -46,52 +57,15 @@ public String getName() { } @Override - public void setDamage(int meta) { - super.setDamage(meta); - } - - - @Override - public double getMinX() { - return this.x + getRelativeBoundingBox().getMinX(); - } - - @Override - public double getMaxX() { - return this.x + getRelativeBoundingBox().getMaxX(); - } - - @Override - public double getMinY() { - return this.y + getRelativeBoundingBox().getMinY(); - } - - @Override - public double getMaxY() { - return this.y + getRelativeBoundingBox().getMaxY(); - } - - @Override - public double getMinZ() { - return this.z + getRelativeBoundingBox().getMinZ(); - } - - @Override - public double getMaxZ() { - return this.z + getRelativeBoundingBox().getMaxZ(); - } + protected AxisAlignedBB recalculateBoundingBox() { + AxisAlignedBB[] bbs; - private AxisAlignedBB getRelativeBoundingBox() { int damage = this.getDamage(); if (damage > 11) { - this.setDamage(damage = 11); + damage = 11; } - AxisAlignedBB boundingBox = ALL[damage]; - if (boundingBox != null) return boundingBox; - - AxisAlignedBB[] bbs; - switch (getDamage()) { + switch (damage) { case 1: case 5: case 9: @@ -112,22 +86,13 @@ private AxisAlignedBB getRelativeBoundingBox() { break; } - return ALL[damage] = bbs[this.getDamage() >> 2]; + return bbs[(damage >> 2)].getOffsetBoundingBox(x, y, z); } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (target.getId() == Block.WOOD && (target.getDamage() & 0x03) == BlockWood.JUNGLE) { if (face != BlockFace.DOWN && face != BlockFace.UP) { - int[] faces = new int[]{ - 0, - 0, - 0, - 2, - 3, - 1, - }; - this.setDamage(faces[face.getIndex()]); this.level.setBlock(block, this, true, true); return true; @@ -139,19 +104,15 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { - int[] faces = new int[]{ - 3, 4, 2, 5, 3, 4, 2, 5, 3, 4, 2, 5 - }; - - Block side = this.getSide(BlockFace.fromIndex(faces[this.getDamage()])); + Block side = this.getSide(BlockFace.fromIndex(faces2[this.getDamage()])); - if (side.getId() != Block.WOOD && side.getDamage() != BlockWood.JUNGLE) { + if (side.getId() != Block.WOOD && (side.getDamage() & 0x03) != BlockWood.JUNGLE) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; } } else if (type == Level.BLOCK_UPDATE_RANDOM) { - if (ThreadLocalRandom.current().nextInt(2) == 1) { - if (this.getDamage() / 4 < 2) { + if (Utils.random.nextInt(2) == 1) { + if (this.getDamage() >> 2 < 2) { BlockCocoa block = (BlockCocoa) this.clone(); block.setDamage(block.getDamage() + 4); BlockGrowEvent ev = new BlockGrowEvent(this, block); @@ -178,9 +139,9 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0f) { + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { Block block = this.clone(); - if (this.getDamage() / 4 < 2) { + if (this.getDamage() >> 2 < 2) { block.setDamage(block.getDamage() + 4); BlockGrowEvent ev = new BlockGrowEvent(this, block); Server.getInstance().getPluginManager().callEvent(ev); @@ -188,10 +149,11 @@ public boolean onActivate(Item item, Player player) { if (ev.isCancelled()) { return false; } + this.getLevel().setBlock(this, ev.getNewState(), true, true); this.level.addParticle(new BoneMealParticle(this)); - if (player != null && (player.gamemode & 0x01) == 0) { + if (player != null && !player.isCreative()) { item.count--; } } @@ -219,24 +181,39 @@ public int getToolType() { @Override public Item toItem() { - return new ItemDye(DyeColor.BROWN.getDyeData()); + return Item.get(Item.DYE, DyeColor.BROWN.getDyeData()); } @Override public Item[] getDrops(Item item) { if (this.getDamage() >= 8) { return new Item[]{ - new ItemDye(3, 3) + Item.get(Item.DYE, 3, Utils.rand(2, 3)) }; } else { return new Item[]{ - new ItemDye(3, 1) + Item.get(Item.DYE, 3, 1) }; } } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockCommandBlock.java b/src/main/java/cn/nukkit/block/BlockCommandBlock.java new file mode 100644 index 00000000000..4f727d3d0b5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCommandBlock.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockCommandBlock extends BlockSolid { + + @Override + public int getId() { + return COMMAND_BLOCK; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Command Block"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCommandBlockChain.java b/src/main/java/cn/nukkit/block/BlockCommandBlockChain.java new file mode 100644 index 00000000000..5f11fc8c5f9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCommandBlockChain.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockCommandBlockChain extends BlockSolid { + + @Override + public int getId() { + return CHAIN_COMMAND_BLOCK; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Chain Command Block"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCommandBlockRepeating.java b/src/main/java/cn/nukkit/block/BlockCommandBlockRepeating.java new file mode 100644 index 00000000000..179badce4ac --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCommandBlockRepeating.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockCommandBlockRepeating extends BlockSolid { + + @Override + public int getId() { + return REPEATING_COMMAND_BLOCK; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Repeating Command Block"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockComposter.java b/src/main/java/cn/nukkit/block/BlockComposter.java new file mode 100644 index 00000000000..de45220f3e8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockComposter.java @@ -0,0 +1,214 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.ComposterEmptyEvent; +import cn.nukkit.event.block.ComposterFillEvent; +import cn.nukkit.item.*; +import cn.nukkit.level.Sound; +import cn.nukkit.utils.DyeColor; +import cn.nukkit.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; + +public class BlockComposter extends BlockTransparentMeta implements ItemID { + + private static final Int2IntOpenHashMap items = new Int2IntOpenHashMap(); + + static { + registerItems(30, KELP, BEETROOT_SEEDS, DRIED_KELP, MELON_SEEDS, PUMPKIN_SEEDS, SWEET_BERRIES, WHEAT_SEEDS); + registerItems(50, MELON_SLICE, SUGAR_CANE); + registerItems(65, APPLE, BEETROOT, CARROT, COCOA, POTATO, WHEAT); + registerItems(85, BAKED_POTATOES, BREAD, COOKIE); + registerItems(100, CAKE, PUMPKIN_PIE); + registerBlocks(30, BLOCK_KELP, LEAVES, LEAVES2, SAPLINGS, SEAGRASS, SWEET_BERRY_BUSH); + registerBlocks(50, GRASS, CACTUS, DRIED_KELP_BLOCK, VINES, NETHER_SPROUTS_BLOCK); + registerBlocks(65, DANDELION, RED_FLOWER, DOUBLE_PLANT, WITHER_ROSE, LILY_PAD, MELON_BLOCK, PUMPKIN, CARVED_PUMPKIN, SEA_PICKLE, BROWN_MUSHROOM, RED_MUSHROOM, SHROOMLIGHT, CRIMSON_FUNGUS, WARPED_FUNGUS); + registerBlocks(85, HAY_BALE, BROWN_MUSHROOM_BLOCK, RED_MUSHROOM_BLOCK, MUSHROOM_STEW, BLOCK_NETHER_WART_BLOCK, WARPED_WART_BLOCK); + registerBlocks(100, CAKE_BLOCK); + registerBlock(50, TALL_GRASS, 0); + registerBlock(50, TALL_GRASS, 1); + registerBlock(65, TALL_GRASS, 2); + registerBlock(65, TALL_GRASS, 3); + } + + public BlockComposter() { + this(0); + } + + public BlockComposter(int meta) { + super(meta); + } + + @Override + public int getId() { + return COMPOSTER; + } + + @Override + public String getName() { + return "Composter"; + } + + @Override + public double getHardness() { + return 0.6; + } + + @Override + public double getResistance() { + return 0.6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public boolean hasComparatorInputOverride() { + return true; + } + + @Override + public int getComparatorInputOverride() { + return getDamage(); + } + + public boolean incrementLevel() { + int fillLevel = getDamage() + 1; + setDamage(fillLevel); + this.level.setBlock(this, this, true, true); + return fillLevel == 8; + } + + public boolean isFull() { + return getDamage() == 8; + } + + public boolean isEmpty() { + return getDamage() == 0; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getCount() <= 0 || item.getId() == Item.AIR) { + return false; + } + + if (isFull()) { + ComposterEmptyEvent event = new ComposterEmptyEvent(this, player, item, new ItemDye(DyeColor.WHITE), 0); + this.level.getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.setDamage(event.getNewLevel()); + this.level.setBlock(this, this, true, true); + this.level.dropItem(add(0.5, 0.85, 0.5), event.getDrop()); + this.level.addSound(add(0.5 , 0.5, 0.5), Sound.BLOCK_COMPOSTER_EMPTY); + } + return true; + } + + int chance = getChance(item); + if (chance <= 0) { + return false; + } + + boolean success = Utils.random.nextInt(100) < chance; + ComposterFillEvent event = new ComposterFillEvent(this, player, item, chance, success); + this.level.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return true; + } + + if (player != null && !player.isCreative()) { + item.setCount(item.getCount() - 1); + } + + if (event.isSuccess()) { + if (incrementLevel()) { + level.addSound(this.add(0.5, 0.5, 0.5), Sound.BLOCK_COMPOSTER_READY); + } else { + level.addSound(this.add(0.5, 0.5, 0.5), Sound.BLOCK_COMPOSTER_FILL_SUCCESS); + } + } else { + level.addSound(this.add(0.5, 0.5, 0.5), Sound.BLOCK_COMPOSTER_FILL); + } + + return true; + } + + public Item empty() { + return empty(null, null); + } + + public Item empty(Player player) { + return this.empty(null, player); + } + + public Item empty(Item item, Player player) { + ComposterEmptyEvent event = new ComposterEmptyEvent(this, player, item, new ItemDye(DyeColor.WHITE), 0); + this.level.getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.setDamage(event.getNewLevel()); + this.level.setBlock(this, this, true, true); + if (item != null) { + this.level.dropItem(add(0.5, 0.85, 0.5), event.getDrop()); + } + this.level.addSound(add(0.5 , 0.5, 0.5), Sound.BLOCK_COMPOSTER_EMPTY); + return event.getDrop(); + } + return null; + } + + public static void registerItem(int chance, int itemId) { + registerItem(chance, itemId, 0); + } + + public static void registerItem(int chance, int itemId, int meta) { + items.put(itemId << 6 | meta & 0x3F, chance); + } + + public static void registerItems(int chance, int... itemIds) { + for (int itemId : itemIds) { + registerItem(chance, itemId, 0); + } + } + + public static void registerBlocks(int chance, int... blockIds) { + for (int blockId : blockIds) { + registerBlock(chance, blockId, 0); + } + } + + public static void registerBlock(int chance, int blockId) { + registerBlock(chance, blockId, 0); + } + + public static void registerBlock(int chance, int blockId, int meta) { + if (blockId > 255) { + blockId = 255 - blockId; + } + registerItem(chance, blockId, meta); + } + + public static void register(int chance, Item item) { + registerItem(chance, item.getId(), item.getDamage()); + } + + public static int getChance(Item item) { + int chance = items.get(item.getId() << 6 | item.getDamage()); + if (chance == 0) { + chance = items.get(item.getId() << 6); + } + return chance; + } + + @Override + public boolean canBeActivated() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockConcretePowder.java b/src/main/java/cn/nukkit/block/BlockConcretePowder.java index 20811ae3608..87e4fbb0671 100644 --- a/src/main/java/cn/nukkit/block/BlockConcretePowder.java +++ b/src/main/java/cn/nukkit/block/BlockConcretePowder.java @@ -11,30 +11,19 @@ /** * Created by CreeperFace on 2.6.2017. */ -public class BlockConcretePowder extends BlockFallable { - private int meta; +public class BlockConcretePowder extends BlockFallableMeta { public BlockConcretePowder() { - this(0); + super(0); } public BlockConcretePowder(int meta) { - this.meta = meta; + super(meta); } @Override public int getFullId() { - return (getId() << 4) + getDamage(); - } - - @Override - public final int getDamage() { - return this.meta; - } - - @Override - public final void setDamage(int meta) { - this.meta = meta; + return (this.getId() << Block.DATA_BITS) + getDamage(); } @Override @@ -70,7 +59,7 @@ public int onUpdate(int type) { for (int side = 1; side <= 5; side++) { Block block = this.getSide(BlockFace.fromIndex(side)); if (block.getId() == Block.WATER || block.getId() == Block.STILL_WATER) { - this.level.setBlock(this, Block.get(Block.CONCRETE, this.meta), true, true); + this.level.setBlock(this, Block.get(Block.CONCRETE, this.getDamage()), true, true); } } diff --git a/src/main/java/cn/nukkit/block/BlockConduit.java b/src/main/java/cn/nukkit/block/BlockConduit.java new file mode 100644 index 00000000000..41c3c4c7ce5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockConduit.java @@ -0,0 +1,59 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockConduit extends BlockSolidMeta { + + public BlockConduit() { + this(0); + } + + public BlockConduit(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Conduit"; + } + + @Override + public int getId() { + return CONDUIT; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public int getLightLevel() { + return 15; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean alwaysDropsOnExplosion() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopper.java b/src/main/java/cn/nukkit/block/BlockCopper.java new file mode 100644 index 00000000000..ef174b80ad8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopper.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopper extends BlockCopperBase { + + public BlockCopper() { + // Does nothing + } + + @Override + public String getName() { + return "Block of Copper"; + } + + @Override + public int getId() { + return COPPER_BLOCK; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.UNAFFECTED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperBase.java b/src/main/java/cn/nukkit/block/BlockCopperBase.java new file mode 100644 index 00000000000..1e28a64571a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperBase.java @@ -0,0 +1,98 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; + +public abstract class BlockCopperBase extends BlockSolid implements Oxidizable, Waxable { + + public BlockCopperBase() { + // Does nothing + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + @Override + public boolean onActivate(Item item, Player player) { + return Waxable.super.onActivate(item, player) + || Oxidizable.super.onActivate(item, player); + } + + @Override + public int onUpdate(int type) { + return Oxidizable.super.onUpdate(type); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Block getStateWithOxidizationLevel(OxidizationLevel oxidizationLevel) { + return Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel), this.getDamage()); + } + + @Override + public boolean setOxidizationLevel(OxidizationLevel oxidizationLevel) { + if (this.getOxidizationLevel().equals(oxidizationLevel)) { + return true; + } + return this.level.setBlock(this, Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel))); + } + + @Override + public boolean setWaxed(boolean waxed) { + if (this.isWaxed() == waxed) { + return true; + } + return this.level.setBlock(this, Block.get(getCopperId(waxed, getOxidizationLevel()))); + } + + @Override + public boolean isWaxed() { + return false; + } + + protected int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel) { + if (oxidizationLevel == null) { + return this.getId(); + } + switch (oxidizationLevel) { + case UNAFFECTED: + return waxed ? WAXED_COPPER : COPPER_BLOCK; + case EXPOSED: + return waxed ? WAXED_EXPOSED_COPPER : EXPOSED_COPPER; + case WEATHERED: + return waxed ? WAXED_WEATHERED_COPPER : WEATHERED_COPPER; + case OXIDIZED: + return waxed ? WAXED_OXIDIZED_COPPER : OXIDIZED_COPPER; + default: + return this.getId(); + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCut.java b/src/main/java/cn/nukkit/block/BlockCopperCut.java new file mode 100644 index 00000000000..8dbcb53b40b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCut.java @@ -0,0 +1,51 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperCut extends BlockCopperBase { + + public BlockCopperCut() { + // Does nothing + } + + @Override + public String getName() { + return "Cut Copper"; + } + + @Override + public int getId() { + return CUT_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.UNAFFECTED; + } + + @Override + protected int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel) { + if (oxidizationLevel == null) { + return this.getId(); + } + + switch (oxidizationLevel) { + case UNAFFECTED: + return waxed ? WAXED_CUT_COPPER : CUT_COPPER; + case EXPOSED: + return waxed ? WAXED_EXPOSED_CUT_COPPER : EXPOSED_CUT_COPPER; + case WEATHERED: + return waxed ? WAXED_WEATHERED_CUT_COPPER : WEATHERED_CUT_COPPER; + case OXIDIZED: + return waxed ? WAXED_OXIDIZED_CUT_COPPER : OXIDIZED_CUT_COPPER; + default: + return this.getId(); + } + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutExposed.java b/src/main/java/cn/nukkit/block/BlockCopperCutExposed.java new file mode 100644 index 00000000000..b77eefd1b45 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutExposed.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperCutExposed extends BlockCopperCut { + + public BlockCopperCutExposed() { + // Does nothing + } + + @Override + public String getName() { + return "Exposed Cut Copper"; + } + + @Override + public int getId() { + return EXPOSED_CUT_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_TERRACOTA_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.EXPOSED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutExposedWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperCutExposedWaxed.java new file mode 100644 index 00000000000..a36ab3d4bde --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutExposedWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperCutExposedWaxed extends BlockCopperCutExposed { + + public BlockCopperCutExposedWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Exposed Cut Copper"; + } + + @Override + public int getId() { + return WAXED_EXPOSED_CUT_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutOxidized.java b/src/main/java/cn/nukkit/block/BlockCopperCutOxidized.java new file mode 100644 index 00000000000..790a6435c11 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutOxidized.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperCutOxidized extends BlockCopperCut { + + public BlockCopperCutOxidized() { + // Does nothing + } + + @Override + public String getName() { + return "Oxidized Cut Copper"; + } + + @Override + public int getId() { + return OXIDIZED_CUT_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.OXIDIZED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutOxidizedWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperCutOxidizedWaxed.java new file mode 100644 index 00000000000..13a29499f6c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutOxidizedWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperCutOxidizedWaxed extends BlockCopperCutOxidized { + + public BlockCopperCutOxidizedWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Oxidized Cut Copper"; + } + + @Override + public int getId() { + return WAXED_OXIDIZED_CUT_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperCutWaxed.java new file mode 100644 index 00000000000..52c8b0d2348 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperCutWaxed extends BlockCopperCut { + + public BlockCopperCutWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Cut Copper"; + } + + @Override + public int getId() { + return WAXED_CUT_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutWeathered.java b/src/main/java/cn/nukkit/block/BlockCopperCutWeathered.java new file mode 100644 index 00000000000..e8885c59d12 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutWeathered.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperCutWeathered extends BlockCopperCut { + + public BlockCopperCutWeathered() { + // Does nothing + } + + @Override + public String getName() { + return "Weathered Cut Copper"; + } + + @Override + public int getId() { + return WEATHERED_CUT_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.WEATHERED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperCutWeatheredWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperCutWeatheredWaxed.java new file mode 100644 index 00000000000..0ffd40655b5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperCutWeatheredWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperCutWeatheredWaxed extends BlockCopperCutWeathered { + + public BlockCopperCutWeatheredWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Weathered Cut Copper"; + } + + @Override + public int getId() { + return WAXED_WEATHERED_CUT_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperExposed.java b/src/main/java/cn/nukkit/block/BlockCopperExposed.java new file mode 100644 index 00000000000..4a6568800b2 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperExposed.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperExposed extends BlockCopper { + + public BlockCopperExposed() { + // Does nothing + } + + @Override + public String getName() { + return "Exposed Copper"; + } + + @Override + public int getId() { + return EXPOSED_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_TERRACOTA_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.EXPOSED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperExposedWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperExposedWaxed.java new file mode 100644 index 00000000000..7b24a095109 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperExposedWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperExposedWaxed extends BlockCopperExposed { + + public BlockCopperExposedWaxed( ) { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Exposed Copper"; + } + + @Override + public int getId() { + return WAXED_EXPOSED_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperOxidized.java b/src/main/java/cn/nukkit/block/BlockCopperOxidized.java new file mode 100644 index 00000000000..2df9499a8a3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperOxidized.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperOxidized extends BlockCopper { + + public BlockCopperOxidized() { + // Does nothing + } + + @Override + public String getName() { + return "Oxidized Copper"; + } + + @Override + public int getId() { + return OXIDIZED_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.OXIDIZED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperOxidizedWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperOxidizedWaxed.java new file mode 100644 index 00000000000..35bf2facce3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperOxidizedWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperOxidizedWaxed extends BlockCopperOxidized { + + public BlockCopperOxidizedWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Oxidized Copper"; + } + + @Override + public int getId() { + return WAXED_OXIDIZED_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperWaxed.java new file mode 100644 index 00000000000..b28423eddf1 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperWaxed extends BlockCopper { + + public BlockCopperWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Block of Copper"; + } + + @Override + public int getId() { + return WAXED_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperWeathered.java b/src/main/java/cn/nukkit/block/BlockCopperWeathered.java new file mode 100644 index 00000000000..05a62e1e7a1 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperWeathered.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockCopperWeathered extends BlockCopper { + + public BlockCopperWeathered() { + // Does nothing + } + + @Override + public String getName() { + return "Weathered Copper"; + } + + @Override + public int getId() { + return WEATHERED_COPPER; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.WEATHERED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCopperWeatheredWaxed.java b/src/main/java/cn/nukkit/block/BlockCopperWeatheredWaxed.java new file mode 100644 index 00000000000..dc381145457 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCopperWeatheredWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockCopperWeatheredWaxed extends BlockCopperWeathered { + + public BlockCopperWeatheredWaxed() { + // Does nothing + } + + @Override + public String getName() { + return "Waxed Weathered Copper"; + } + + @Override + public int getId() { + return WAXED_WEATHERED_COPPER; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoral.java b/src/main/java/cn/nukkit/block/BlockCoral.java new file mode 100644 index 00000000000..15da506b542 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoral.java @@ -0,0 +1,98 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public class BlockCoral extends BlockTransparentMeta { + + public static final int TYPE_TUBE = 0; + public static final int TYPE_BRAIN = 1; + public static final int TYPE_BUBBLE = 2; + public static final int TYPE_FIRE = 3; + public static final int TYPE_HORN = 4; + + private static final String[] names = new String[] { + "Tube Coral", + "Brain Coral", + "Bubble Coral", + "Fire Coral", + "Horn Coral" + }; + + public BlockCoral() { + this(0); + } + + public BlockCoral(int meta) { + super(meta); + } + + @Override + public String getName() { + int variant = this.getDamage() & 0x7; + if (variant >= names.length) { + return names[0]; + } + return names[variant]; + } + + @Override + public int getId() { + return CORAL; + } + + public double getHardness() { + return 0; + } + + public double getResistance() { + return 0; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.down().isTransparent()) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (this.down().isTransparent()) { + return false; + } + if (this.getLevel().setBlock(this, this, true, true)) { + if (block instanceof BlockWater) { + this.getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, Block.LAYER_WATERLOGGED, Block.get(Block.STILL_WATER), true, true); + } + return true; + } + return false; + } + + @Override + public Item[] getDrops(Item item) { + if (item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null) { + return super.getDrops(item); + } else { + return new Item[0]; + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralBlock.java b/src/main/java/cn/nukkit/block/BlockCoralBlock.java new file mode 100644 index 00000000000..25443c1009c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralBlock.java @@ -0,0 +1,123 @@ +package cn.nukkit.block; + +import cn.nukkit.event.block.BlockFadeEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockCoralBlock extends BlockSolidMeta { + + private static final String[] names = new String[] { + "Tube Coral Block", + "Brain Coral Block", + "Bubble Coral Block", + "Fire Coral Block", + "Horn Coral Block", + }; + + public BlockCoralBlock() { + this(0); + } + + public BlockCoralBlock(int meta) { + super(meta); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!this.isDead()) { + this.getLevel().scheduleUpdate(this, 60 + ThreadLocalRandom.current().nextInt(40)); + } + return type; + } + + if (type != Level.BLOCK_UPDATE_SCHEDULED) { + return 0; + } + + if (this.isDead()) { + return type; + } + + for (BlockFace face : BlockFace.values()) { + if (this.getSide(BlockLayer.NORMAL, face) instanceof BlockWater || this.getSide(BlockLayer.WATERLOGGED, face) instanceof BlockWater + || this.getSide(BlockLayer.NORMAL, face) instanceof BlockIceFrosted || this.getSide(BlockLayer.WATERLOGGED, face) instanceof BlockIceFrosted) { + return type; + } + } + + BlockFadeEvent event = new BlockFadeEvent(this, Block.get(CORAL_BLOCK, this.getDamage() | 0x8)); + if (!event.isCancelled()) { + this.setDead(true); + this.getLevel().setBlock(this, event.getNewState(), true, true); + } + return type; + } + + public double getHardness() { + return 1.5; + } + + public double getResistance() { + return 6; + } + + @Override + public String getName() { + int variant = this.getDamage() & 0x7; + String name; + if (variant >= names.length) { + name = names[0]; + } else { + name = names[variant]; + } + return this.isDead() ? "Dead" + name : name; + } + + @Override + public int getId() { + return CORAL_BLOCK; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null) { + return new Item[]{this.toItem() }; + } else { + return new Item[]{ new ItemBlock(this.clone(), this.getDamage() | 0x8) }; + } + } else { + return new Item[0]; + } + } + + public boolean isDead() { + return (this.getDamage() & 0x8) == 0x8; + } + + public void setDead(boolean dead) { + if (dead) { + this.setDamage(this.getDamage() | 0x8); + } else { + this.setDamage(this.getDamage() ^ 0x8); + } + } + +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralFan.java b/src/main/java/cn/nukkit/block/BlockCoralFan.java new file mode 100644 index 00000000000..570dc433958 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralFan.java @@ -0,0 +1,194 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockFadeEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockCoralFan extends BlockCoral implements Faceable { + + private static final String[] names = new String[] { + "Tube Coral Fan", + "Brain Coral Fan", + "Bubble Coral Fan", + "Fire Coral Fan", + "Horn Coral Fan" + }; + + public BlockCoralFan() { + this(0); + } + + public BlockCoralFan(int meta) { + super(meta); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block side = this.getSide(this.getRootsFace()); + if (!side.isSolid() || side.getId() == MAGMA || side.getId() == SOUL_SAND) { + this.getLevel().useBreakOn(this); + } else { + this.getLevel().scheduleUpdate(this, 60 + ThreadLocalRandom.current().nextInt(40)); + } + return type; + } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { + Block side = this.getSide(this.getRootsFace()); + if (side.getId() == ICE) { + this.getLevel().useBreakOn(this); + return type; + } + + if (!this.isDead() && !(this.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockWater) && !(this.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockIceFrosted)) { + BlockFadeEvent event = new BlockFadeEvent(this, Block.get(CORAL_FAN_DEAD, this.getDamage())); + if (!event.isCancelled()) { + this.getLevel().setBlock(this, event.getNewState(), true, true); + } + } + return type; + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + if ((this.getDamage() & 0x8) == 0) { + this.setDamage(this.getDamage() | 0x8); + } else { + this.setDamage(this.getDamage() ^ 0x8); + } + this.getLevel().setBlock(this, this, true, true); + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (face == BlockFace.DOWN) { + return false; + } + + Block layer1 = block.getLevelBlock(BlockLayer.WATERLOGGED); + boolean hasWater = layer1 instanceof BlockWater; + if (layer1.getId() != Block.AIR && (!hasWater || layer1.getDamage() != 0 && layer1.getDamage() != 8)) { + return false; + } + + if (hasWater && layer1.getDamage() == 8) { + this.getLevel().setBlock(this, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + } + + if (!target.isSolid() || target.getId() == MAGMA || target.getId() == SOUL_SAND) { + return false; + } + + if (face == BlockFace.UP) { + double rotation = player.yaw % 360; + if (rotation < 0) { + rotation += 360.0; + } + int axisBit = rotation >= 0 && rotation < 12 || (342 <= rotation && rotation < 360)? 0x0 : 0x8; + this.setDamage(this.getDamage() & 0x7 | axisBit); + this.getLevel().setBlock(this, BlockLayer.WATERLOGGED, hasWater? new BlockCoralFan(this.getDamage()) : new BlockCoralFanDead(this.getDamage()), true, true); + } else { + int type = this.getType(); + int typeBit = type % 2; + int deadBit = this.isDead()? 0x1 : 0; + int faceBit; + switch (face) { + case WEST: + faceBit = 0; + break; + case EAST: + faceBit = 1; + break; + case NORTH: + faceBit = 2; + break; + default: + case SOUTH: + faceBit = 3; + break; + } + int deadData = faceBit << 2 | deadBit << 1 | typeBit; + int deadBlockId; + switch (type) { + default: + case BlockCoral.TYPE_TUBE: + case BlockCoral.TYPE_BRAIN: + deadBlockId = CORAL_FAN_HANG; + break; + case BlockCoral.TYPE_BUBBLE: + case BlockCoral.TYPE_FIRE: + deadBlockId = CORAL_FAN_HANG2; + break; + case BlockCoral.TYPE_HORN: + deadBlockId = CORAL_FAN_HANG3; + break; + } + this.getLevel().setBlock(this, BlockLayer.WATERLOGGED, Block.get(deadBlockId, deadData), true, true); + } + return true; + } + + + @Override + public String getName() { + int variant = this.getType(); + String name; + if (variant >= names.length) { + name = names[0]; + } else { + name = names[variant]; + } + return name; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public int getId() { + return CORAL_FAN; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public Item toItem() { + return Item.get(this.getItemId(), this.getDamage() ^ 0x8); + } + + @Override + public Item[] getDrops(Item item) { + if (item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null) { + return super.getDrops(item); + } else { + return new Item[0]; + } + } + + public boolean isDead() { + return false; + } + + public int getType() { + return this.getDamage() & 0x7; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + public BlockFace getRootsFace() { + return BlockFace.DOWN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralFanDead.java b/src/main/java/cn/nukkit/block/BlockCoralFanDead.java new file mode 100644 index 00000000000..1bffaded712 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralFanDead.java @@ -0,0 +1,48 @@ +package cn.nukkit.block; + +import cn.nukkit.level.Level; +import cn.nukkit.utils.BlockColor; + +public class BlockCoralFanDead extends BlockCoralFan { + + public BlockCoralFanDead() { + this(0); + } + + public BlockCoralFanDead(int meta) { + super(meta); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!this.getSide(this.getRootsFace()).isSolid()) { + this.getLevel().useBreakOn(this); + } + return type; + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + return super.onUpdate(type); + } + return 0; + } + + @Override + public String getName() { + return "Dead " + super.getName(); + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } + + @Override + public int getId() { + return CORAL_FAN_DEAD; + } + + @Override + public boolean isDead() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralFanHang.java b/src/main/java/cn/nukkit/block/BlockCoralFanHang.java new file mode 100644 index 00000000000..394218ff3d9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralFanHang.java @@ -0,0 +1,83 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public class BlockCoralFanHang extends BlockCoralFan { + + public BlockCoralFanHang() { + this(0); + } + + public BlockCoralFanHang(int meta) { + super(meta); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_RANDOM) { + return type; + } else { + return super.onUpdate(type); + } + } + + @Override + public int getType() { + if ((this.getDamage() & 0b1) == 0) { + return BlockCoral.TYPE_TUBE; + } else { + return BlockCoral.TYPE_BRAIN; + } + } + + @Override + public BlockFace getBlockFace() { + int face = this.getDamage() >> 2 & 0x3; + switch (face) { + case 0: + return BlockFace.WEST; + case 1: + return BlockFace.EAST; + case 2: + return BlockFace.NORTH; + default: + case 3: + return BlockFace.SOUTH; + } + } + + + @Override + public String getName() { + String name = super.getName(); + name = name.substring(0, name.length() - 4); + if (isDead()) { + return "Dead " + name + " Wall Fan"; + } else { + return name + " Wall Fan"; + } + } + + @Override + public boolean isDead() { + return (this.getDamage() & 0b10) == 0b10; + } + + @Override + public BlockFace getRootsFace() { + return this.getBlockFace().getOpposite(); + } + + @Override + public int getId() { + return CORAL_FAN_HANG; + } + + @Override + public Item toItem() { + return new ItemBlock(this.isDead()? new BlockCoralFanDead() : new BlockCoralFan(), this.getType()); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralFanHang2.java b/src/main/java/cn/nukkit/block/BlockCoralFanHang2.java new file mode 100644 index 00000000000..3dfeb9fa72c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralFanHang2.java @@ -0,0 +1,26 @@ +package cn.nukkit.block; + +public class BlockCoralFanHang2 extends BlockCoralFanHang { + + public BlockCoralFanHang2() { + this(0); + } + + public BlockCoralFanHang2(int meta) { + super(meta); + } + + @Override + public int getId() { + return CORAL_FAN_HANG2; + } + + @Override + public int getType() { + if ((this.getDamage() & 0b1) == 0) { + return BlockCoral.TYPE_BUBBLE; + } else { + return BlockCoral.TYPE_FIRE; + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCoralFanHang3.java b/src/main/java/cn/nukkit/block/BlockCoralFanHang3.java new file mode 100644 index 00000000000..434341e0783 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCoralFanHang3.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockCoralFanHang3 extends BlockCoralFanHang { + + public BlockCoralFanHang3() { + this(0); + } + + public BlockCoralFanHang3(int meta) { + super(meta); + } + + @Override + public int getId() { + return CORAL_FAN_HANG3; + } + + @Override + public int getType() { + return BlockCoral.TYPE_HORN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCraftingTable.java b/src/main/java/cn/nukkit/block/BlockCraftingTable.java index aa5a9039dde..a5e4e0fb5cd 100644 --- a/src/main/java/cn/nukkit/block/BlockCraftingTable.java +++ b/src/main/java/cn/nukkit/block/BlockCraftingTable.java @@ -1,6 +1,7 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.event.player.CraftingTableOpenEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; import cn.nukkit.network.protocol.ContainerOpenPacket; @@ -11,8 +12,6 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockCraftingTable extends BlockSolid { - public BlockCraftingTable() { - } @Override public String getName() { @@ -47,16 +46,25 @@ public int getToolType() { @Override public boolean onActivate(Item item, Player player) { if (player != null) { - player.craftingType = Player.CRAFTING_BIG; - player.setCraftingGrid(player.getUIInventory().getBigCraftingGrid()); - ContainerOpenPacket pk = new ContainerOpenPacket(); - pk.windowId = -1; - pk.type = 1; - pk.x = (int) x; - pk.y = (int) y; - pk.z = (int) z; - pk.entityId = player.getId(); - player.dataPacket(pk); + CraftingTableOpenEvent ev = new CraftingTableOpenEvent(player, this); + player.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + if (player.craftingType == Player.CRAFTING_BIG) { + player.getServer().getLogger().debug(player.getName() + " tried to activate crafting table but craftingType is already CRAFTING_BIG"); + return true; + } + player.craftingType = Player.CRAFTING_BIG; + player.setCraftingGrid(player.getUIInventory().getBigCraftingGrid()); + + ContainerOpenPacket pk = new ContainerOpenPacket(); + pk.windowId = -1; + pk.type = 1; + pk.x = (int) x; + pk.y = (int) y; + pk.z = (int) z; + pk.entityId = player.getId(); + player.dataPacket(pk); + } } return true; } @@ -65,4 +73,9 @@ public boolean onActivate(Item item, Player player) { public BlockColor getColor() { return BlockColor.WOOD_BLOCK_COLOR; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonDoor.java b/src/main/java/cn/nukkit/block/BlockCrimsonDoor.java new file mode 100644 index 00000000000..55e14d4c778 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonDoor.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockCrimsonDoor extends BlockDoor { + + public BlockCrimsonDoor() { + this(0); + } + + public BlockCrimsonDoor(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Door"; + } + + @Override + public int getId() { + return CRIMSON_DOOR_BLOCK; + } + + @Override + public Item toItem() { + return Item.get(Item.CRIMSON_DOOR); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonFungus.java b/src/main/java/cn/nukkit/block/BlockCrimsonFungus.java new file mode 100644 index 00000000000..cecfac16215 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonFungus.java @@ -0,0 +1,97 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.level.generator.object.tree.ObjectCrimsonTree; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.level.Position; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; +import cn.nukkit.utils.DyeColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockCrimsonFungus extends BlockFungus { + + public BlockCrimsonFungus() { + // Does nothing + } + + @Override + public int getId() { + return CRIMSON_FUNGUS; + } + + @Override + public String getName() { + return "Crimson Fungus"; + } + + @Override + protected boolean canGrowOn(Block support) { + return support.getId() == CRIMSON_NYLIUM; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.down().isTransparent()) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } + return 0; + } + + @Override + public boolean grow(Player cause) { + // TODO: + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + + public boolean canPlaceOn(Block floor, Position pos) { + switch (floor.getId()) { + case BlockID.GRASS: + case BlockID.DIRT: + case BlockID.PODZOL: + case BlockID.FARMLAND: + case BlockID.CRIMSON_NYLIUM: + case BlockID.WARPED_NYLIUM: + case BlockID.MYCELIUM: + case BlockID.SOUL_SOIL: + return true; + default: + return false; + } + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == Item.DYE && item.getDamage() == DyeColor.WHITE.getDyeData()) { + if (player != null && (player.gamemode & 0x01) == 0) { + item.count--; + } + + if (ThreadLocalRandom.current().nextFloat() < 0.4 && this.level.getBlockIdAt((int) this.x, (int) this.y - 1, (int) this.z) == CRIMSON_NYLIUM) { + new ObjectCrimsonTree().placeObject(this.level, (int) this.x, (int) this.y, (int) this.z, new NukkitRandom()); + this.level.setBlock(new Vector3((int) this.x, (int) this.y - 1, (int) this.z), Block.get(NETHERRACK), false, true); + } + + this.level.addParticle(new BoneMealParticle(this)); + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonNylium.java b/src/main/java/cn/nukkit/block/BlockCrimsonNylium.java new file mode 100644 index 00000000000..d9a92cd64f6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonNylium.java @@ -0,0 +1,25 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockCrimsonNylium extends BlockNylium { + + public BlockCrimsonNylium() { + // Does nothing + } + + @Override + public String getName() { + return "Crimson Nylium"; + } + + @Override + public int getId() { + return CRIMSON_NYLIUM; + } + + @Override + public BlockColor getColor() { + return BlockColor.CRIMSON_NYLIUM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonPlanks.java b/src/main/java/cn/nukkit/block/BlockCrimsonPlanks.java new file mode 100644 index 00000000000..29fedb8ca1d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonPlanks.java @@ -0,0 +1,45 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockCrimsonPlanks extends BlockSolid { + + public BlockCrimsonPlanks() { + this(0); + } + + public BlockCrimsonPlanks(int meta) { + // super(meta); + } + + @Override + public String getName() { + return "Crimson Planks"; + } + + @Override + public int getId() { + return CRIMSON_PLANKS; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.CRIMSON_STEM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonRoots.java b/src/main/java/cn/nukkit/block/BlockCrimsonRoots.java new file mode 100644 index 00000000000..b583589db2f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonRoots.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockCrimsonRoots extends BlockRoots { + + public BlockCrimsonRoots() { + this(0); + } + + public BlockCrimsonRoots(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Roots"; + } + + @Override + public int getId() { + return CRIMSON_ROOTS; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonSign.java b/src/main/java/cn/nukkit/block/BlockCrimsonSign.java new file mode 100644 index 00000000000..5276bbd4e1a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockCrimsonSign extends BlockSignPost { + + public BlockCrimsonSign() { + this(0); + } + + public BlockCrimsonSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Sign"; + } + + @Override + public int getId() { + return CRIMSON_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.CRIMSON_SIGN); + } + + @Override + protected int getPostId() { + return CRIMSON_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return CRIMSON_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonStairs.java b/src/main/java/cn/nukkit/block/BlockCrimsonStairs.java new file mode 100644 index 00000000000..6c5f3d0f059 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonStairs.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockCrimsonStairs extends BlockStairsWood { + + public BlockCrimsonStairs() { + this(0); + } + + public BlockCrimsonStairs(int meta) { + super(meta); + } + + @Override + public int getId() { + return CRIMSON_STAIRS; + } + + @Override + public String getName() { + return "Crimson Wood Stairs"; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonStem.java b/src/main/java/cn/nukkit/block/BlockCrimsonStem.java new file mode 100644 index 00000000000..067f8fd6cf8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonStem.java @@ -0,0 +1,51 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockCrimsonStem extends BlockStem { + + public BlockCrimsonStem() { + this(0); + } + + public BlockCrimsonStem(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Stem"; + } + + @Override + public int getId() { + return CRIMSON_STEM; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public int getStrippedId() { + return STRIPPED_CRIMSON_STEM; + } + + @Override + public BlockColor getColor() { + return BlockColor.CRIMSON_STEM_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonTrapdoor.java b/src/main/java/cn/nukkit/block/BlockCrimsonTrapdoor.java new file mode 100644 index 00000000000..e58c80ca0f4 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonTrapdoor.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockCrimsonTrapdoor extends BlockTrapdoor { + + public BlockCrimsonTrapdoor() { + this(0); + } + + public BlockCrimsonTrapdoor(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Trapdoor"; + } + + @Override + public int getId() { + return CRIMSON_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrimsonWallSign.java b/src/main/java/cn/nukkit/block/BlockCrimsonWallSign.java new file mode 100644 index 00000000000..757663c0778 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockCrimsonWallSign.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockCrimsonWallSign extends BlockWallSign { + + public BlockCrimsonWallSign() { + this(0); + } + + public BlockCrimsonWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson WallSign"; + } + + @Override + public int getId() { + return CRIMSON_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(Item.CRIMSON_SIGN); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockCrops.java b/src/main/java/cn/nukkit/block/BlockCrops.java index f02c05df693..028aaca5d27 100644 --- a/src/main/java/cn/nukkit/block/BlockCrops.java +++ b/src/main/java/cn/nukkit/block/BlockCrops.java @@ -4,15 +4,15 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; import cn.nukkit.level.Level; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockCrops extends BlockFlowable { @@ -26,7 +26,6 @@ public boolean canBeActivated() { return true; } - @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (block.down().getId() == FARMLAND) { @@ -38,11 +37,10 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public boolean onActivate(Item item, Player player) { - //Bone meal - if (item.getId() == Item.DYE && item.getDamage() == 0x0f) { + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { if (this.getDamage() < 7) { BlockCrops block = (BlockCrops) this.clone(); - block.setDamage(block.getDamage() + ThreadLocalRandom.current().nextInt(3) + 2); + block.setDamage(block.getDamage() + Utils.random.nextInt(3) + 2); if (block.getDamage() > 7) { block.setDamage(7); } @@ -54,9 +52,10 @@ public boolean onActivate(Item item, Player player) { } this.getLevel().setBlock(this, ev.getNewState(), false, true); + this.level.addParticle(new BoneMealParticle(this)); - if (player != null && (player.gamemode & 0x01) == 0) { + if (player != null && !player.isCreative()) { item.count--; } } @@ -75,7 +74,7 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_NORMAL; } } else if (type == Level.BLOCK_UPDATE_RANDOM) { - if (ThreadLocalRandom.current().nextInt(2) == 1) { + if (Utils.random.nextInt(2) == 1) { if (this.getDamage() < 0x07) { BlockCrops block = (BlockCrops) this.clone(); block.setDamage(block.getDamage() + 1); diff --git a/src/main/java/cn/nukkit/block/BlockDandelion.java b/src/main/java/cn/nukkit/block/BlockDandelion.java index acfe445c8ab..965f7ff4d01 100644 --- a/src/main/java/cn/nukkit/block/BlockDandelion.java +++ b/src/main/java/cn/nukkit/block/BlockDandelion.java @@ -5,6 +5,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockDandelion extends BlockFlower { + public BlockDandelion() { this(0); } diff --git a/src/main/java/cn/nukkit/block/BlockDarkOakSignStanding.java b/src/main/java/cn/nukkit/block/BlockDarkOakSignStanding.java new file mode 100644 index 00000000000..255056a8490 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDarkOakSignStanding.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockDarkOakSignStanding extends BlockSignPost { + + public BlockDarkOakSignStanding() { + this(0); + } + + public BlockDarkOakSignStanding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dark Oak Sign Post"; + } + + @Override + public int getId() { + return DARK_OAK_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.DARKOAK_SIGN); + } + + @Override + protected int getPostId() { + return DARK_OAK_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return DARK_OAK_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDarkOakWallSign.java b/src/main/java/cn/nukkit/block/BlockDarkOakWallSign.java new file mode 100644 index 00000000000..3a28af6e740 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDarkOakWallSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockDarkOakWallSign extends BlockWallSign { + + public BlockDarkOakWallSign() { + this(0); + } + + public BlockDarkOakWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dark Oak Wall Sign"; + } + + @Override + public int getId() { + return DARK_OAK_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.DARKOAK_SIGN); + } + + @Override + protected int getPostId() { + return DARK_OAK_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return DARK_OAK_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDaylightDetector.java b/src/main/java/cn/nukkit/block/BlockDaylightDetector.java index 8e196e50c94..647630c9021 100644 --- a/src/main/java/cn/nukkit/block/BlockDaylightDetector.java +++ b/src/main/java/cn/nukkit/block/BlockDaylightDetector.java @@ -1,7 +1,10 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; /** @@ -10,9 +13,6 @@ */ public class BlockDaylightDetector extends BlockTransparent { - public BlockDaylightDetector() { - } - @Override public int getId() { return DAYLIGHT_DETECTOR; @@ -33,9 +33,35 @@ public BlockColor getColor() { return BlockColor.WOOD_BLOCK_COLOR; } + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + this.getLevel().setBlock(this, Block.get(DAYLIGHT_DETECTOR_INVERTED)); + return true; + } + @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public boolean isPowerSource() { + return true; + } + + @Override + public int getWeakPower(BlockFace face) { + return this.level.isAnimalSpawningAllowedByTime() ? 15 : 0; + } + + @Override + public boolean canBePushed() { + return false; } @Override @@ -48,11 +74,30 @@ public double getMaxY() { return this.y + 0.625; } - //This function is a suggestion that can be renamed or deleted - protected boolean invertDetect() { - return false; + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_SCHEDULED) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.level.updateAroundRedstone(this, null); + } + this.level.scheduleUpdate(this, 40); + } + return 0; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; } - //todo redstone + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (this.getLevel().setBlock(this, this, true, true)) { + this.level.scheduleUpdate(this, 40); + + return true; + } + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java b/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java index 3d0307915b6..dfb25d16c9f 100644 --- a/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java +++ b/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java @@ -1,7 +1,9 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; /** * Created on 2015/11/22 by CreeperFace. @@ -9,9 +11,6 @@ */ public class BlockDaylightDetectorInverted extends BlockDaylightDetector { - public BlockDaylightDetectorInverted() { - } - @Override public int getId() { return DAYLIGHT_DETECTOR_INVERTED; @@ -22,13 +21,24 @@ public String getName() { return "Daylight Detector Inverted"; } + @Override + public boolean onActivate(Item item, Player player) { + this.getLevel().setBlock(this, Block.get(DAYLIGHT_DETECTOR)); + return true; + } + @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.DAYLIGHT_DETECTOR), 0); + return new ItemBlock(Block.get(DAYLIGHT_DETECTOR), 0); } - protected boolean invertDetect() { + @Override + public boolean isPowerSource() { return true; } + @Override + public int getWeakPower(BlockFace face) { + return this.level.isAnimalSpawningAllowedByTime() ? 0 : 15; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDeadBush.java b/src/main/java/cn/nukkit/block/BlockDeadBush.java index 23b4c9e2296..4c467a012e1 100644 --- a/src/main/java/cn/nukkit/block/BlockDeadBush.java +++ b/src/main/java/cn/nukkit/block/BlockDeadBush.java @@ -2,24 +2,22 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemStick; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/2 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockDeadBush extends BlockFlowable { + public BlockDeadBush() { this(0); } public BlockDeadBush(int meta) { - // Dead bushes can't have meta. Also stops the server from throwing an exception with the block palette. super(0); } @@ -70,7 +68,7 @@ public Item[] getDrops(Item item) { }; } else { return new Item[]{ - new ItemStick(0, ThreadLocalRandom.current().nextInt(3)) + Item.get(Item.STICK, 0, Utils.random.nextInt(3)) }; } } @@ -78,4 +76,14 @@ public Item[] getDrops(Item item) { public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDeepslate.java b/src/main/java/cn/nukkit/block/BlockDeepslate.java new file mode 100644 index 00000000000..803cf8bc4a2 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDeepslate.java @@ -0,0 +1,106 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockDeepslate extends BlockSolidMeta { + + public BlockDeepslate() { + this(0); + } + + public BlockDeepslate(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Deepslate"; + } + + @Override + public int getId() { + return DEEPSLATE; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setPillarAxis(face.getAxis()); + this.getLevel().setBlock(block, this, true, true); + return true; + } + + public void setPillarAxis(BlockFace.Axis axis) { + switch (axis) { + case Y: + this.setDamage(0); + break; + case X: + this.setDamage(1); + break; + case Z: + this.setDamage(2); + break; + } + } + + public BlockFace.Axis getPillarAxis() { + switch (this.getDamage() % 3) { + case 2: + return BlockFace.Axis.Z; + case 1: + return BlockFace.Axis.X; + case 0: + default: + return BlockFace.Axis.Y; + } + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(DEEPSLATE), 0); + } + + @Override + public Item[] getDrops(Item item) { + if (!this.canHarvest(item)) { + return new Item[0]; + } + + return new Item[]{new ItemBlock(Block.get(COBBLED_DEEPSLATE), 0)}; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDeepslateChiseled.java b/src/main/java/cn/nukkit/block/BlockDeepslateChiseled.java new file mode 100644 index 00000000000..ec1bc34b5cf --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDeepslateChiseled.java @@ -0,0 +1,17 @@ +package cn.nukkit.block; + +public class BlockDeepslateChiseled extends BlockDeepslateCobbled { + + public BlockDeepslateChiseled() { + } + + @Override + public int getId() { + return CHISELED_DEEPSLATE; + } + + @Override + public String getName() { + return "Chiseled Deepslate"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDeepslateCobbled.java b/src/main/java/cn/nukkit/block/BlockDeepslateCobbled.java new file mode 100644 index 00000000000..3c7dfaf4dfd --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDeepslateCobbled.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockDeepslateCobbled extends BlockSolid { + + public BlockDeepslateCobbled() { + } + + @Override + public String getName() { + return "Cobbled Deepslate"; + } + + @Override + public int getId() { + return COBBLED_DEEPSLATE; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6.0; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDeepslatePolished.java b/src/main/java/cn/nukkit/block/BlockDeepslatePolished.java new file mode 100644 index 00000000000..76ed536817c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDeepslatePolished.java @@ -0,0 +1,18 @@ +package cn.nukkit.block; + +public class BlockDeepslatePolished extends BlockDeepslateCobbled { + + public BlockDeepslatePolished() { + // Does nothing + } + + @Override + public String getName() { + return "Polished Deepslate"; + } + + @Override + public int getId() { + return POLISHED_DEEPSLATE; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDeny.java b/src/main/java/cn/nukkit/block/BlockDeny.java new file mode 100644 index 00000000000..26c60adca9b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDeny.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockDeny extends BlockSolid { + + @Override + public int getId() { + return DENY; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Deny"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDiamond.java b/src/main/java/cn/nukkit/block/BlockDiamond.java index 53073452789..61f95a42c96 100644 --- a/src/main/java/cn/nukkit/block/BlockDiamond.java +++ b/src/main/java/cn/nukkit/block/BlockDiamond.java @@ -9,9 +9,6 @@ */ public class BlockDiamond extends BlockSolid { - public BlockDiamond() { - } - @Override public double getHardness() { return 5; @@ -34,7 +31,7 @@ public int getId() { @Override public String getName() { - return "Diamond Block"; + return "Block of Diamond"; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDirt.java b/src/main/java/cn/nukkit/block/BlockDirt.java index dd87fb80c7e..fc94cf24c09 100644 --- a/src/main/java/cn/nukkit/block/BlockDirt.java +++ b/src/main/java/cn/nukkit/block/BlockDirt.java @@ -3,11 +3,15 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Sound; +import cn.nukkit.level.generator.object.ObjectTallGrass; +import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * AMAZING COARSE DIRT added by kvetinac97 * Nukkit Project */ @@ -17,7 +21,7 @@ public BlockDirt() { this(0); } - public BlockDirt(int meta){ + public BlockDirt(int meta) { super(meta); } @@ -54,15 +58,35 @@ public String getName() { @Override public boolean onActivate(Item item, Player player) { if (item.isHoe()) { - if (this.up() instanceof BlockAir) { + Block up = this.up(); + if (up instanceof BlockAir || up instanceof BlockFlowable) { item.useOn(this); this.getLevel().setBlock(this, this.getDamage() == 0 ? get(FARMLAND) : get(DIRT), true); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } return true; } } else if (item.isShovel()) { - if (this.up() instanceof BlockAir) { + Block up = this.up(); + if (up instanceof BlockAir || up instanceof BlockFlowable) { item.useOn(this); - this.getLevel().setBlock(this, get(GRASS_PATH)); + this.getLevel().setBlock(this, Block.get(GRASS_PATH)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + return true; + } + } else if (player != null && item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + Block up = this.up(); + if (up instanceof BlockWater) { + if (!player.isCreative()) { + item.count--; + } + this.level.addParticle(new BoneMealParticle(this)); + if (up.up() instanceof BlockWater) { + ObjectTallGrass.growSeagrass(this.getLevel(), this); + } return true; } } @@ -72,7 +96,8 @@ public boolean onActivate(Item item, Player player) { @Override public Item[] getDrops(Item item) { - return new Item[]{new ItemBlock(Block.get(BlockID.DIRT))}; + int damage = this.getDamage() & 0x01; + return new Item[]{new ItemBlock(Block.get(BlockID.DIRT, damage), damage)}; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDirtRooted.java b/src/main/java/cn/nukkit/block/BlockDirtRooted.java new file mode 100644 index 00000000000..4a888b24c70 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDirtRooted.java @@ -0,0 +1,81 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.Sound; +import cn.nukkit.level.particle.BoneMealParticle; + +public class BlockDirtRooted extends BlockDirt { + + public BlockDirtRooted() { + this(0); + } + + public BlockDirtRooted(int meta) { + super(0); // no different states + } + + @Override + public int getId() { + return ROOTED_DIRT; + } + + @Override + public String getName() { + return "Rooted Dirt"; + } + + @Override + public boolean onActivate(Item item, Player player) { + Block up = this.up(); + if (item.isShovel() && (up instanceof BlockAir || up instanceof BlockFlowable)) { + item.useOn(this); + this.getLevel().setBlock(this, Block.get(GRASS_PATH)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + return true; + } + + if (item.isHoe() && (up instanceof BlockAir || up instanceof BlockFlowable)) { + item.useOn(this); + this.getLevel().setBlock(this, Block.get(DIRT)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + this.getLevel().dropItem(this.add(0.5, 0.8, 0.5), new ItemBlock(Block.get(HANGING_ROOTS), 0)); + return true; + } + + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return false; + } + + Block down = this.down(); + BlockGrowEvent event = new BlockGrowEvent(down, Block.get(BlockID.HANGING_ROOTS, 0, down)); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + + if (down.getId() == AIR || down.canBeReplaced()) { + this.getLevel().setBlock(down, Block.get(HANGING_ROOTS)); + this.level.addParticle(new BoneMealParticle(down)); + return true; + } + return false; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{ this.toItem() }; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDispenser.java b/src/main/java/cn/nukkit/block/BlockDispenser.java index 3bd7bbd75f5..0976b60b37b 100644 --- a/src/main/java/cn/nukkit/block/BlockDispenser.java +++ b/src/main/java/cn/nukkit/block/BlockDispenser.java @@ -1,10 +1,23 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityDispenser; +import cn.nukkit.dispenser.DispenseBehavior; +import cn.nukkit.dispenser.DispenseBehaviorRegister; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.inventory.Inventory; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.Faceable; +import cn.nukkit.utils.Utils; + +import java.util.Map.Entry; /** * Created by CreeperFace on 15.4.2017. @@ -36,22 +49,18 @@ public int getId() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(Block.DISPENSER)); } @Override public int getComparatorInputOverride() { - /*BlockEntity blockEntity = this.level.getBlockEntity(this); - - if(blockEntity instanceof BlockEntityDispenser) { - //return ContainerInventory.calculateRedstone(((BlockEntityDispenser) blockEntity).getInventory()); TODO: dispenser - }*/ + BlockEntity blockEntity = this.level.getBlockEntity(this); - return super.getComparatorInputOverride(); - } + if (blockEntity instanceof BlockEntityDispenser) { + return ContainerInventory.calculateRedstone(((BlockEntityDispenser) blockEntity).getInventory()); + } - public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage() & 7); + return 0; } public boolean isTriggered() { @@ -60,7 +69,7 @@ public boolean isTriggered() { public void setTriggered(boolean value) { int i = 0; - i |= getFacing().getIndex(); + i |= getBlockFace().getIndex(); if (value) { i |= 8; @@ -70,20 +79,136 @@ public void setTriggered(boolean value) { } @Override - public boolean canHarvestWithHand() { - return false; + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player == null) { + return false; + } + + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDispenser)) { + return false; + } + + if (blockEntity.namedTag.contains("Lock") && blockEntity.namedTag.get("Lock") instanceof StringTag) { + if (!blockEntity.namedTag.getString("Lock").equals(item.getCustomName())) { + return true; + } + } + + player.addWindow(((BlockEntityDispenser) blockEntity).getInventory()); + return true; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (player != null) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + double y = player.y + player.getEyeHeight(); + + if (y - this.y > 2) { + this.setDamage(BlockFace.UP.getIndex()); + } else if (this.y - y > 0) { + this.setDamage(BlockFace.DOWN.getIndex()); + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } + + this.getLevel().setBlock(block, this, true); + + BlockEntity.createBlockEntity(BlockEntity.DISPENSER, this.getChunk(), BlockEntity.getDefaultCompound(this, BlockEntity.DISPENSER)); + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.setTriggered(false); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + dispense(); + return type; + } else if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (!isTriggered() && (level.isBlockPowered(this) || level.isBlockPowered(this.getSideVec(BlockFace.UP)))) { + this.setTriggered(true); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + level.scheduleUpdate(this, this, 4); + } + + return type; + } + + return 0; + } + + public void dispense() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDispenser)) { + return; + } + + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_CLICK); + + int r = 1; + int slot = -1; + Item target = null; + + Inventory inv = ((BlockEntityDispenser) blockEntity).getInventory(); + for (Entry entry : inv.getContents().entrySet()) { + Item item = entry.getValue(); + + if (!item.isNull() && Utils.random.nextInt(r++) == 0) { + target = item; + slot = entry.getKey(); + } + } + + if (target == null) { + return; + } + target = target.clone(); + + DispenseBehavior behavior = DispenseBehaviorRegister.getBehavior(target.getId()); + Item result = behavior.dispense(this, getBlockFace(), target); + + if (result == null) { + target.count--; + inv.setItem(slot, target); + } else if (!result.equals(target)) { + inv.setItem(slot, result); + } } public Vector3 getDispensePosition() { - BlockFace facing = getFacing(); - double x = this.getX() + 0.7 * facing.getXOffset(); - double y = this.getY() + 0.7 * facing.getYOffset(); - double z = this.getZ() + 0.7 * facing.getZOffset(); - return new Vector3(x, y, z); + BlockFace facing = getBlockFace(); + return this.add( + 0.5 + 0.7 * facing.getXOffset(), + 0.5 + 0.7 * facing.getYOffset(), + 0.5 + 0.7 * facing.getZOffset() + ); } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromIndex(this.getDamage() & 0x07); + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation } } diff --git a/src/main/java/cn/nukkit/block/BlockDoor.java b/src/main/java/cn/nukkit/block/BlockDoor.java index 53995465ecb..08579d94e56 100644 --- a/src/main/java/cn/nukkit/block/BlockDoor.java +++ b/src/main/java/cn/nukkit/block/BlockDoor.java @@ -5,22 +5,24 @@ import cn.nukkit.event.block.DoorToggleEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.math.SimpleAxisAlignedBB; -import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.Faceable; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockDoor extends BlockTransparentMeta implements Faceable { - public static int DOOR_OPEN_BIT = 0x04; - public static int DOOR_TOP_BIT = 0x08; - public static int DOOR_HINGE_BIT = 0x01; - public static int DOOR_POWERED_BIT = 0x02; + public static final int DOOR_OPEN_BIT = 0x04; + public static final int DOOR_TOP_BIT = 0x08; + public static final int DOOR_HINGE_BIT = 0x01; + public static final int DOOR_POWERED_BIT = 0x02; + + private static final int[] faces = {1, 2, 3, 0}; protected BlockDoor(int meta) { super(meta); @@ -36,21 +38,27 @@ public boolean isSolid() { return false; } - public int getFullDamage() { - int meta; - + private int getFullDamage() { + int up; + int down; if (isTop()) { - meta = this.down().getDamage(); + down = this.down().getDamage(); + up = this.getDamage(); } else { - meta = this.getDamage(); + down = this.getDamage(); + up = this.up().getDamage(); } - return (this.getId() << 5 ) + (meta & 0x07 | (isTop() ? 0x08 : 0) | (isRightHinged() ? 0x10 :0)); + + boolean isRight = (up & DOOR_HINGE_BIT) > 0; + + return down & 0x07 | (isTop() ? 0x08 : 0) | (isRight ? 0x10 : 0); } @Override protected AxisAlignedBB recalculateBoundingBox() { double f = 0.1875; + int damage = this.getFullDamage(); AxisAlignedBB bb = new SimpleAxisAlignedBB( this.x, @@ -61,9 +69,9 @@ protected AxisAlignedBB recalculateBoundingBox() { this.z + 1 ); - int j = isTop() ? (this.down().getDamage() & 0x03) : getDamage() & 0x03; - boolean isOpen = isOpen(); - boolean isRight = isRightHinged(); + int j = damage & 0x03; + boolean isOpen = ((damage & 0x04) > 0); + boolean isRight = ((damage & 0x10) > 0); if (j == 0) { if (isOpen) { @@ -210,7 +218,8 @@ public int onUpdate(int type) { } if (type == Level.BLOCK_UPDATE_REDSTONE) { - if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { + boolean powered = this.level.isBlockPowered(this); + if ((!isOpen() && powered) || (isOpen() && !powered)) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, isOpen() ? 15 : 0, isOpen() ? 0 : 15)); this.toggle(null); @@ -222,14 +231,13 @@ public int onUpdate(int type) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (this.y > 254) return false; + if (this.y > target.getLevel().getMaxBlockY() - 1) return false; if (face == BlockFace.UP) { Block blockUp = this.up(); - Block blockDown = this.down(); - if (!blockUp.canBeReplaced() || blockDown.isTransparent()) { + if (!blockUp.canBeReplaced() || !canStayOnFullNonSolid(this.down())) { return false; } - int[] faces = {1, 2, 3, 0}; + int direction = faces[player != null ? player.getDirection().getHorizontalIndex() : 0]; Block left = this.getSide(player.getDirection().rotateYCCW()); @@ -240,15 +248,12 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } this.setDamage(direction); - this.getLevel().setBlock(block, this, true, false); //Bottom - this.getLevel().setBlock(blockUp, Block.get(this.getId(), metaUp), true, true); //Top - if (!this.isOpen() && this.level.isBlockPowered(this.getLocation())) { - this.toggle(null); - metaUp |= DOOR_POWERED_BIT; - this.getLevel().setBlockDataAt(blockUp.getFloorX(), blockUp.getFloorY(), blockUp.getFloorZ(), metaUp); - } + //Bottom + this.getLevel().setBlock(this, this, true, true); + //Top + this.getLevel().setBlock(blockUp, Block.get(this.getId(), metaUp), true); return true; } @@ -273,19 +278,9 @@ public boolean onBreak(Item item) { return true; } - @Override - public boolean onActivate(Item item) { - return this.onActivate(item, null); - } - @Override public boolean onActivate(Item item, Player player) { - if (!this.toggle(player)) { - return false; - } - - this.getLevel().addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_DOOR); - return true; + return this.toggle(player); } public boolean toggle(Player player) { @@ -297,19 +292,37 @@ public boolean toggle(Player player) { } Block down; - if (isTop()) { + Block up; + if (isTop(this.getDamage())) { down = this.down(); + up = this; } else { down = this; + up = this.up(); } - if (down.up().getId() != down.getId()) { + + if (up.getId() != down.getId()) { return false; } - down.setDamage(down.getDamage() ^ DOOR_OPEN_BIT); - getLevel().setBlock(down, down, true, true); + + int data = down.getDamage() ^ 0x04; + this.level.setBlockDataAt(down.getFloorX(), down.getFloorY(), down.getFloorZ(), data); + if (this.isOpenAfter(data)) { + this.level.addSound(this, Sound.RANDOM_DOOR_OPEN); + } else { + this.level.addSound(this, Sound.RANDOM_DOOR_CLOSE); + } return true; } + private boolean isOpenAfter(int data) { + if (isTop(data)) { + return (this.down().getDamage() & DOOR_OPEN_BIT) > 0; + } else { + return (data & DOOR_OPEN_BIT) > 0; + } + } + public boolean isOpen() { if (isTop(this.getDamage())) { return (this.down().getDamage() & DOOR_OPEN_BIT) > 0; @@ -317,6 +330,7 @@ public boolean isOpen() { return (this.getDamage() & DOOR_OPEN_BIT) > 0; } } + public boolean isTop() { return isTop(this.getDamage()); } @@ -334,6 +348,16 @@ public boolean isRightHinged() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockDoorAcacia.java b/src/main/java/cn/nukkit/block/BlockDoorAcacia.java index 0ca8279bbf0..46503b1c544 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorAcacia.java +++ b/src/main/java/cn/nukkit/block/BlockDoorAcacia.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorAcacia; import cn.nukkit.utils.BlockColor; public class BlockDoorAcacia extends BlockDoorWood { @@ -26,7 +25,7 @@ public int getId() { @Override public Item toItem() { - return new ItemDoorAcacia(); + return Item.get(Item.ACACIA_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorBirch.java b/src/main/java/cn/nukkit/block/BlockDoorBirch.java index 6556596f898..66cf813df2a 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorBirch.java +++ b/src/main/java/cn/nukkit/block/BlockDoorBirch.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorBirch; import cn.nukkit.utils.BlockColor; public class BlockDoorBirch extends BlockDoorWood { @@ -26,7 +25,7 @@ public int getId() { @Override public Item toItem() { - return new ItemDoorBirch(); + return Item.get(Item.BIRCH_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorDarkOak.java b/src/main/java/cn/nukkit/block/BlockDoorDarkOak.java index 4da32f31c85..38c90229867 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorDarkOak.java +++ b/src/main/java/cn/nukkit/block/BlockDoorDarkOak.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorDarkOak; import cn.nukkit.utils.BlockColor; public class BlockDoorDarkOak extends BlockDoorWood { @@ -26,7 +25,7 @@ public int getId() { @Override public Item toItem() { - return new ItemDoorDarkOak(); + return Item.get(Item.DARK_OAK_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorIron.java b/src/main/java/cn/nukkit/block/BlockDoorIron.java index 74df6c75bd8..2a7a572c6b7 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorIron.java +++ b/src/main/java/cn/nukkit/block/BlockDoorIron.java @@ -2,12 +2,11 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorIron; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockDoorIron extends BlockDoor { @@ -30,11 +29,6 @@ public int getId() { return IRON_DOOR_BLOCK; } - @Override - public boolean canBeActivated() { - return true; - } - @Override public double getHardness() { return 5; @@ -52,7 +46,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -63,7 +57,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemDoorIron(); + return Item.get(Item.IRON_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorJungle.java b/src/main/java/cn/nukkit/block/BlockDoorJungle.java index 5f6cb1fed5a..2af2d291a69 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorJungle.java +++ b/src/main/java/cn/nukkit/block/BlockDoorJungle.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorJungle; import cn.nukkit.utils.BlockColor; public class BlockDoorJungle extends BlockDoorWood { @@ -26,7 +25,7 @@ public int getId() { @Override public Item toItem() { - return new ItemDoorJungle(); + return Item.get(Item.JUNGLE_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorSpruce.java b/src/main/java/cn/nukkit/block/BlockDoorSpruce.java index 1f9accf6ebe..05b3f0f66a1 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorSpruce.java +++ b/src/main/java/cn/nukkit/block/BlockDoorSpruce.java @@ -1,7 +1,6 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorSpruce; import cn.nukkit.utils.BlockColor; public class BlockDoorSpruce extends BlockDoorWood { @@ -26,7 +25,7 @@ public int getId() { @Override public Item toItem() { - return new ItemDoorSpruce(); + return Item.get(Item.SPRUCE_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoorWood.java b/src/main/java/cn/nukkit/block/BlockDoorWood.java index ad34c77d4d7..27a79562fa8 100644 --- a/src/main/java/cn/nukkit/block/BlockDoorWood.java +++ b/src/main/java/cn/nukkit/block/BlockDoorWood.java @@ -1,12 +1,11 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDoorWood; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockDoorWood extends BlockDoor { @@ -46,7 +45,7 @@ public int getToolType() { @Override public Item toItem() { - return new ItemDoorWood(); + return Item.get(Item.WOODEN_DOOR); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDoubleMudBrickSlab.java b/src/main/java/cn/nukkit/block/BlockDoubleMudBrickSlab.java new file mode 100644 index 00000000000..ff60c220548 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleMudBrickSlab.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +public class BlockDoubleMudBrickSlab extends BlockDoubleSlabBase { + + public BlockDoubleMudBrickSlab() { + this(0); + } + + public BlockDoubleMudBrickSlab(int meta) { + super(meta); + } + + @Override + public int getId() { + return MUD_BRICK_DOUBLE_SLAB; + } + + @Override + public String getSlabName() { + return "Double Mud Brick Slab"; + } + @Override + public int getSingleSlabId() { + return MUD_BRICK_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoublePlant.java b/src/main/java/cn/nukkit/block/BlockDoublePlant.java index a5686af96d8..306779e0f87 100644 --- a/src/main/java/cn/nukkit/block/BlockDoublePlant.java +++ b/src/main/java/cn/nukkit/block/BlockDoublePlant.java @@ -2,19 +2,20 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsWheat; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; import cn.nukkit.level.Level; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created on 2015/11/23 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockDoublePlant extends BlockFlowable { + public static final int SUNFLOWER = 0; public static final int LILAC = 1; public static final int TALL_GRASS = 2; @@ -23,7 +24,7 @@ public class BlockDoublePlant extends BlockFlowable { public static final int PEONY = 5; public static final int TOP_HALF_BITMASK = 0x8; - private static final String[] NAMES = new String[]{ + private static final String[] NAMES = { "Sunflower", "Lilac", "Double Tallgrass", @@ -47,7 +48,8 @@ public int getId() { @Override public boolean canBeReplaced() { - return this.getDamage() == TALL_GRASS || this.getDamage() == LARGE_FERN; + int damage = this.getDamage() & 0x7; + return damage == TALL_GRASS || damage == LARGE_FERN; } @Override @@ -83,8 +85,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl int id = down.getId(); if (up.getId() == AIR && (id == GRASS || id == DIRT || id == PODZOL || id == FARMLAND || id == MYCELIUM)) { - this.getLevel().setBlock(block, this, true, false); // If we update the bottom half, it will drop the item because there isn't a flower block above - this.getLevel().setBlock(up, Block.get(BlockID.DOUBLE_PLANT, getDamage() ^ TOP_HALF_BITMASK), true, true); + // Place top half first in order to call block updates on bottom part, but do not update to prevent breaking. + this.getLevel().setBlock(up, Block.get(DOUBLE_PLANT, getDamage() ^ TOP_HALF_BITMASK), true, false); + this.getLevel().setBlock(block, this, true, true); return true; } @@ -93,10 +96,11 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public boolean onBreak(Item item) { - Block down = down(); - if ((this.getDamage() & TOP_HALF_BITMASK) == TOP_HALF_BITMASK) { // Top half - this.getLevel().useBreakOn(down); + Block down = down(); + if (down instanceof BlockDoublePlant) { + this.getLevel().useBreakOn(down); + } } else { this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); } @@ -107,27 +111,27 @@ public boolean onBreak(Item item) { @Override public Item[] getDrops(Item item) { if ((this.getDamage() & TOP_HALF_BITMASK) != TOP_HALF_BITMASK) { - switch (this.getDamage() & 0x07) { + int type = this.getDamage() & 0x07; + switch (type) { case TALL_GRASS: case LARGE_FERN: - boolean dropSeeds = ThreadLocalRandom.current().nextInt(10) == 0; + boolean dropSeeds = Utils.random.nextInt(10) == 0; if (item.isShears()) { - //todo enchantment if (dropSeeds) { return new Item[]{ - new ItemSeedsWheat(0, 1), - toItem() + Item.get(Item.WHEAT_SEEDS), + Item.get(Item.TALL_GRASS, type == LARGE_FERN ? 2 : 1, 2) }; } else { return new Item[]{ - toItem() + Item.get(Item.TALL_GRASS, type == LARGE_FERN ? 2 : 1, 2) }; } } if (dropSeeds) { return new Item[]{ - new ItemSeedsWheat() + Item.get(Item.WHEAT_SEEDS) }; } else { return new Item[0]; @@ -152,22 +156,23 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0f) { //Bone meal - switch (this.getDamage() & 0x07) { - case SUNFLOWER: - case LILAC: - case ROSE_BUSH: - case PEONY: - if (player != null && (player.gamemode & 0x01) == 0) { - item.count--; - } - this.level.addParticle(new BoneMealParticle(this)); - this.level.dropItem(this, this.toItem()); - } + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + int type = this.getDamage() & 0x07; + if (type == SUNFLOWER || type == LILAC || type == ROSE_BUSH || type == PEONY) { // Flower + if (player != null && !player.isCreative()) { + item.count--; + } + this.level.addParticle(new BoneMealParticle(this)); + this.level.dropItem(this, this.toItem()); + } return true; } return false; } + + public Item toItem() { + return new ItemBlock(this, this.getDamage() & 0x07, 1); + } } diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlab.java b/src/main/java/cn/nukkit/block/BlockDoubleSlab.java index c23819a90cc..4cb9f38000e 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlab.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlab.java @@ -1,14 +1,11 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; -import cn.nukkit.utils.BlockColor; - /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class BlockDoubleSlab extends BlockSolidMeta { +public class BlockDoubleSlab extends BlockDoubleSlabStone { + public static final int STONE = 0; public static final int SANDSTONE = 1; public static final int WOODEN = 2; @@ -25,67 +22,4 @@ public BlockDoubleSlab() { public BlockDoubleSlab(int meta) { super(meta); } - - @Override - public int getId() { - return DOUBLE_SLAB; - } - - //todo hardness and residence - - @Override - public double getHardness() { - return 2; - } - - @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; - } - - @Override - public String getName() { - String[] names = new String[]{ - "Stone", - "Sandstone", - "Wooden", - "Cobblestone", - "Brick", - "Stone Brick", - "Quartz", - "Nether Brick" - }; - return "Double " + names[this.getDamage() & 0x07] + " Slab"; - } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{ - Item.get(Item.SLAB, this.getDamage() & 0x07, 2) - }; - } else { - return new Item[0]; - } - } - - @Override - public BlockColor getColor() { - switch (this.getDamage() & 0x07) { - case BlockDoubleSlab.WOODEN: - return BlockColor.WOOD_BLOCK_COLOR; - default: - case BlockDoubleSlab.COBBLESTONE: - case BlockDoubleSlab.BRICK: - case BlockDoubleSlab.STONE_BRICK: - case BlockDoubleSlab.STONE: - return BlockColor.STONE_BLOCK_COLOR; - case BlockDoubleSlab.SANDSTONE: - return BlockColor.SAND_BLOCK_COLOR; - case BlockDoubleSlab.QUARTZ: - return BlockColor.QUARTZ_BLOCK_COLOR; - case BlockDoubleSlab.NETHER_BRICK: - return BlockColor.NETHERRACK_BLOCK_COLOR; - } - } } diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabBase.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabBase.java new file mode 100644 index 00000000000..92f341b7832 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabBase.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public abstract class BlockDoubleSlabBase extends BlockSolidMeta { + + public BlockDoubleSlabBase() { + this(0); + } + + public BlockDoubleSlabBase(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Double " + this.getSlabName() + " Slab"; + } + + public abstract String getSlabName(); + + public abstract int getSingleSlabId(); + + public abstract int getItemDamage(); + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getSingleSlabId(), this.getItemDamage()), this.getItemDamage(), 1); + } + + protected boolean isCorrectTool(Item item) { + return canHarvestWithHand() || canHarvest(item); + } + + @Override + public Item[] getDrops(Item item) { + if (isCorrectTool(item)) { + Item slab = toItem(); + slab.setCount(2); + return new Item[]{ slab }; + } else { + return new Item[0]; + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstone.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstone.java new file mode 100644 index 00000000000..b0a74134e98 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstone.java @@ -0,0 +1,60 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabBlackstone extends BlockDoubleSlabBase { + + public BlockDoubleSlabBlackstone() { + this(0); + } + + protected BlockDoubleSlabBlackstone(int meta) { + super(meta); + } + + @Override + public String getSlabName() { + return "Double Blackstone Slab"; + } + + @Override + public int getId() { + return BLACKSTONE_DOUBLE_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getSingleSlabId() { + return BLACKSTONE_SLAB; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstonePolished.java new file mode 100644 index 00000000000..cc9da667057 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabBlackstonePolished.java @@ -0,0 +1,60 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabBlackstonePolished extends BlockDoubleSlabBase { + + public BlockDoubleSlabBlackstonePolished() { + this(0); + } + + public BlockDoubleSlabBlackstonePolished(int meta) { + super(meta); + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_DOUBLE_SLAB; + } + + @Override + public int getSingleSlabId() { + return POLISHED_BLACKSTONE_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public String getSlabName() { + return "Polished Blackstone"; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 6.0; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickBlackstonePolished.java new file mode 100644 index 00000000000..689d81e3813 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickBlackstonePolished.java @@ -0,0 +1,32 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabBrickBlackstonePolished extends BlockDoubleSlabBlackstonePolished { + + public BlockDoubleSlabBrickBlackstonePolished() { + this(0); + } + + public BlockDoubleSlabBrickBlackstonePolished(int meta) { + super(meta); + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB; + } + + @Override + public int getSingleSlabId() { + return POLISHED_BLACKSTONE_BRICK_SLAB; + } + + @Override + public String getSlabName() { + return "Polished Blackstone Brick"; + } + + @Override + public double getHardness() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickDeepslate.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickDeepslate.java new file mode 100644 index 00000000000..1ef60c38691 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabBrickDeepslate.java @@ -0,0 +1,65 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabBrickDeepslate extends BlockDoubleSlabBase { + + public BlockDoubleSlabBrickDeepslate() { + this(0); + } + + protected BlockDoubleSlabBrickDeepslate(int meta) { + super(meta); + } + + @Override + public int getId() { + return DEEPSLATE_BRICK_DOUBLE_SLAB; + } + + @Override + public int getSingleSlabId() { + return DEEPSLATE_BRICK_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public String getSlabName() { + return "Double Deepslate Brick Slab"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperBase.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperBase.java new file mode 100644 index 00000000000..a5489b9b554 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperBase.java @@ -0,0 +1,88 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public abstract class BlockDoubleSlabCopperBase extends BlockDoubleSlabBase implements Waxable, Oxidizable { + + public BlockDoubleSlabCopperBase(int meta) { + super(meta); + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean onActivate(Item item, Player player) { + return Waxable.super.onActivate(item, player) + || Oxidizable.super.onActivate(item, player); + } + + @Override + public int onUpdate(int type) { + return Oxidizable.super.onUpdate(type); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public Block getStateWithOxidizationLevel(OxidizationLevel oxidizationLevel) { + return Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel), this.getDamage()); + } + + @Override + public boolean setOxidizationLevel(OxidizationLevel oxidizationLevel) { + if (this.getOxidizationLevel().equals(oxidizationLevel)) { + return true; + } + return this.level.setBlock(this, Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel))); + } + + @Override + public boolean setWaxed(boolean waxed) { + if (this.isWaxed() == waxed) { + return true; + } + return this.level.setBlock(this, Block.get(getCopperId(waxed, getOxidizationLevel()))); + } + + @Override + public boolean isWaxed() { + return false; + } + + protected abstract int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel); +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCut.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCut.java new file mode 100644 index 00000000000..6982605b262 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCut.java @@ -0,0 +1,68 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; + +public class BlockDoubleSlabCopperCut extends BlockDoubleSlabCopperBase { + + public BlockDoubleSlabCopperCut() { + this(0); + } + + public BlockDoubleSlabCopperCut(int meta) { + super(meta); + } + + @Override + public int getId() { + return DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public String getSlabName() { + String name = ""; + if (this.isWaxed()) { + name += "Waxed "; + } + + OxidizationLevel oxidizationLevel = this.getOxidizationLevel(); + if (oxidizationLevel != OxidizationLevel.UNAFFECTED) { + String oxidationName = oxidizationLevel.name(); + name += oxidationName.charAt(0) + oxidationName.substring(1).toLowerCase(); + } + return name + " Cut Copper"; + } + + @Override + public int getSingleSlabId() { + return CUT_COPPER_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + protected int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel) { + if (oxidizationLevel == null) { + return getId(); + } + switch (oxidizationLevel) { + case UNAFFECTED: + return waxed? WAXED_DOUBLE_CUT_COPPER_SLAB : DOUBLE_CUT_COPPER_SLAB; + case EXPOSED: + return waxed? WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB : EXPOSED_DOUBLE_CUT_COPPER_SLAB; + case WEATHERED: + return waxed? WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB : WEATHERED_DOUBLE_CUT_COPPER_SLAB; + case OXIDIZED: + return waxed? WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB : OXIDIZED_DOUBLE_CUT_COPPER_SLAB; + default: + return getId(); + } + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.UNAFFECTED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposed.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposed.java new file mode 100644 index 00000000000..85cec004911 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposed.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabCopperCutExposed extends BlockDoubleSlabCopperCut { + + public BlockDoubleSlabCopperCutExposed() { + this(0); + } + + public BlockDoubleSlabCopperCutExposed(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return EXPOSED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return EXPOSED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.EXPOSED; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_TERRACOTA_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposedWaxed.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposedWaxed.java new file mode 100644 index 00000000000..52f8b6ee48c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutExposedWaxed.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabCopperCutExposedWaxed extends BlockDoubleSlabCopperCutExposed { + + public BlockDoubleSlabCopperCutExposedWaxed() { + this(0); + } + + public BlockDoubleSlabCopperCutExposedWaxed(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return WAXED_EXPOSED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidized.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidized.java new file mode 100644 index 00000000000..aa69ae2dcab --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidized.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabCopperCutOxidized extends BlockDoubleSlabCopperCut { + + public BlockDoubleSlabCopperCutOxidized() { + this(0); + } + + public BlockDoubleSlabCopperCutOxidized(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return OXIDIZED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return OXIDIZED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.OXIDIZED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidizedWaxed.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidizedWaxed.java new file mode 100644 index 00000000000..feb03bff0ad --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutOxidizedWaxed.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabCopperCutOxidizedWaxed extends BlockDoubleSlabCopperCutOxidized { + + public BlockDoubleSlabCopperCutOxidizedWaxed() { + this(0); + } + + public BlockDoubleSlabCopperCutOxidizedWaxed(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return WAXED_OXIDIZED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWaxed.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWaxed.java new file mode 100644 index 00000000000..233a09a151c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWaxed.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabCopperCutWaxed extends BlockDoubleSlabCopperCut { + + public BlockDoubleSlabCopperCutWaxed() { + this(0); + } + + public BlockDoubleSlabCopperCutWaxed(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return WAXED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return WAXED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeathered.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeathered.java new file mode 100644 index 00000000000..ba6ea011121 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeathered.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabCopperCutWeathered extends BlockDoubleSlabCopperCut { + + public BlockDoubleSlabCopperCutWeathered() { + this(0); + } + + public BlockDoubleSlabCopperCutWeathered(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return WEATHERED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return WEATHERED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.WEATHERED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeatheredWaxed.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeatheredWaxed.java new file mode 100644 index 00000000000..e5d05579f49 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCopperCutWeatheredWaxed.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabCopperCutWeatheredWaxed extends BlockDoubleSlabCopperCutWeathered { + + public BlockDoubleSlabCopperCutWeatheredWaxed() { + this(0); + } + + public BlockDoubleSlabCopperCutWeatheredWaxed(int meta) { + super(meta); + } + + @Override + public int getSingleSlabId() { + return WAXED_WEATHERED_CUT_COPPER_SLAB; + } + + @Override + public int getId() { + return WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabCrimson.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabCrimson.java new file mode 100644 index 00000000000..34673b88eff --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabCrimson.java @@ -0,0 +1,65 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabCrimson extends BlockDoubleSlabBase { + + public BlockDoubleSlabCrimson() { + super(0); + } + + public BlockDoubleSlabCrimson(int meta) { + super(meta); + } + + @Override + public int getId() { + return CRIMSON_DOUBLE_SLAB; + } + + @Override + public String getSlabName() { + return "Crimson"; + } + + @Override + public int getSingleSlabId() { + return CRIMSON_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslateCobbled.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslateCobbled.java new file mode 100644 index 00000000000..7c420775846 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslateCobbled.java @@ -0,0 +1,32 @@ +package cn.nukkit.block; + +public class BlockDoubleSlabDeepslateCobbled extends BlockDoubleSlabBase { + + public BlockDoubleSlabDeepslateCobbled() { + this(0); + } + + public BlockDoubleSlabDeepslateCobbled(int meta) { + super(meta); + } + + @Override + public int getId() { + return COBBLED_DEEPSLATE_DOUBLE_SLAB; + } + + @Override + public String getSlabName() { + return "Cobbled Deepslate"; + } + + @Override + public int getSingleSlabId() { + return COBBLED_DEEPSLATE_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslatePolished.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslatePolished.java new file mode 100644 index 00000000000..09eb2fc4387 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabDeepslatePolished.java @@ -0,0 +1,65 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabDeepslatePolished extends BlockDoubleSlabBase { + + public BlockDoubleSlabDeepslatePolished() { + this(0); + } + + public BlockDoubleSlabDeepslatePolished(int meta) { + super(meta); + } + + @Override + public int getId() { + return POLISHED_DEEPSLATE_DOUBLE_SLAB; + } + + @Override + public int getSingleSlabId() { + return POLISHED_DEEPSLATE_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public String getSlabName() { + return "Double Polished Deepslate Slab"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabRedSandstone.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabRedSandstone.java index 176c645a37e..3c559dfe7b5 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlabRedSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabRedSandstone.java @@ -1,14 +1,23 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** * Created by CreeperFace on 26. 11. 2016. */ -public class BlockDoubleSlabRedSandstone extends BlockSolidMeta { +public class BlockDoubleSlabRedSandstone extends BlockDoubleSlabBase { + + private static final String[] NAMES = new String[]{ + "Red Sandstone", + "Purpur", + "", + "", + "", + "", + "", + "" + }; public BlockDoubleSlabRedSandstone() { this(0); @@ -23,6 +32,11 @@ public int getId() { return DOUBLE_RED_SANDSTONE_SLAB; } + @Override + public int getSingleSlabId() { + return RED_SANDSTONE_SLAB; + } + @Override public double getResistance() { return 30; @@ -39,35 +53,13 @@ public int getToolType() { } @Override - public String getName() { - String[] names = new String[]{ - "Red Sandstone", - "Purpur", - "", - "", - "", - "", - "", - "" - }; - - return "Double " + names[this.getDamage() & 0x07] + " Slab"; + public String getSlabName() { + return NAMES[this.getDamage() & 0x07]; } @Override - public Item toItem() { - return new ItemBlock(Block.get(BlockID.RED_SANDSTONE_SLAB), this.getDamage() & 0x07); - } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{ - Item.get(Item.RED_SANDSTONE_SLAB, this.getDamage() & 0x07, 2) - }; - } else { - return new Item[0]; - } + public int getItemDamage() { + return this.getDamage() & 0x07; } @Override @@ -86,4 +78,4 @@ public BlockColor getColor() { return BlockColor.STONE_BLOCK_COLOR; } } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java index 6828f735a1e..f2f47b4b547 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone.java @@ -1,15 +1,14 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class BlockDoubleSlabStone extends BlockSolidMeta { +public class BlockDoubleSlabStone extends BlockDoubleSlabBase { + public static final int STONE = 0; public static final int SANDSTONE = 1; public static final int WOODEN = 2; @@ -19,6 +18,17 @@ public class BlockDoubleSlabStone extends BlockSolidMeta { public static final int QUARTZ = 6; public static final int NETHER_BRICK = 7; + private static final String[] NAMES = new String[]{ + "Stone", + "Sandstone", + "Wooden", + "Cobblestone", + "Brick", + "Stone Brick", + "Quartz", + "Nether Brick" + }; + public BlockDoubleSlabStone() { this(0); } @@ -32,6 +42,11 @@ public int getId() { return DOUBLE_SLAB; } + @Override + public int getSingleSlabId() { + return STONE_SLAB; + } + @Override public double getResistance() { return getToolType() > ItemTool.TIER_WOODEN ? 30 : 15; @@ -48,45 +63,24 @@ public int getToolType() { } @Override - public String getName() { - String[] names = new String[]{ - "Stone", - "Sandstone", - "Wooden", - "Cobblestone", - "Brick", - "Stone Brick", - "Quartz", - "Nether Brick" - }; - return "Double " + names[this.getDamage() & 0x07] + " Slab"; + public String getSlabName() { + return NAMES[this.getDamage() & 0x07]; } @Override - public Item toItem() { - return new ItemBlock(Block.get(BlockID.STONE_SLAB), this.getDamage() & 0x07); - } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{ - Item.get(Item.SLAB, this.getDamage() & 0x07, 2) - }; - } else { - return new Item[0]; - } + public int getItemDamage() { + return this.getDamage() & 0x07; } @Override public BlockColor getColor() { switch (this.getDamage() & 0x07) { default: - case BlockDoubleSlabStone.STONE: - case BlockDoubleSlabStone.COBBLESTONE: - case BlockDoubleSlabStone.BRICK: - case BlockDoubleSlabStone.STONE_BRICK: - return BlockColor.STONE_BLOCK_COLOR; + case BlockDoubleSlabStone.STONE: + case BlockDoubleSlabStone.COBBLESTONE: + case BlockDoubleSlabStone.BRICK: + case BlockDoubleSlabStone.STONE_BRICK: + return BlockColor.STONE_BLOCK_COLOR; case BlockDoubleSlabStone.SANDSTONE: return BlockColor.SAND_BLOCK_COLOR; case BlockDoubleSlabStone.WOODEN: @@ -102,4 +96,4 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone3.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone3.java new file mode 100644 index 00000000000..42ddea58f7d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone3.java @@ -0,0 +1,79 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabStone3 extends BlockDoubleSlabBase { + + public static final int END_STONE_BRICKS = 0; + public static final int SMOOTH_RED_SANDSTONE = 1; + public static final int POLISHED_ANDESITE = 2; + public static final int ANDESITE = 3; + public static final int DIORITE = 4; + public static final int POLISHED_DIORITE = 5; + public static final int GRANITE = 6; + public static final int POLISHED_GRANITE = 7; + + private static final String[] NAMES = new String[]{ + "End Stone Brick", + "Smooth Red Sandstone", + "Polished Andesite", + "Andesite", + "Diorite", + "Polished Diorite", + "Granite", + "Polisehd Granite" + }; + + public BlockDoubleSlabStone3() { + this(0); + } + + public BlockDoubleSlabStone3(int meta) { + super(meta); + } + + @Override + public String getSlabName() { + return NAMES[this.getDamage() & 0x07]; + } + + @Override + public int getId() { + return DOUBLE_STONE_SLAB3; + } + + @Override + public int getSingleSlabId() { + return STONE_SLAB3; + } + + @Override + public int getItemDamage() { + return this.getDamage() & 0x07; + } + + @Override + public BlockColor getColor() { + switch (this.getDamage() & 0x07) { + case END_STONE_BRICKS: + return BlockColor.SAND_BLOCK_COLOR; + case SMOOTH_RED_SANDSTONE: + return BlockColor.ORANGE_BLOCK_COLOR; + default: + case POLISHED_ANDESITE: + case ANDESITE: + return BlockColor.STONE_BLOCK_COLOR; + case DIORITE: + case POLISHED_DIORITE: + return BlockColor.QUARTZ_BLOCK_COLOR; + case GRANITE: + case POLISHED_GRANITE: + return BlockColor.DIRT_BLOCK_COLOR; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabStone4.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone4.java new file mode 100644 index 00000000000..d18509f2f30 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabStone4.java @@ -0,0 +1,89 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabStone4 extends BlockDoubleSlabBase { + + private static final String[] NAMES = new String[]{ + "Mossy Stone Brick", + "Smooth Quartz", + "Stone", + "Cut Sandstone", + "Cut Red Sandstone", + }; + + public static final int MOSSY_STONE_BRICKS = 0; + public static final int SMOOTH_QUARTZ = 1; + public static final int STONE = 2; + public static final int CUT_SANDSTONE = 3; + public static final int CUT_RED_SANDSTONE = 4; + + public BlockDoubleSlabStone4() { + this(0); + } + + public BlockDoubleSlabStone4(int meta) { + super(meta); + } + + @Override + public int getId() { + return DOUBLE_STONE_SLAB4; + } + + @Override + public int getSingleSlabId() { + return STONE_SLAB4; + } + + @Override + public double getResistance() { + return this.getToolType() > ItemTool.TIER_WOODEN ? 30 : 15; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getSlabName() { + int variant = this.getDamage() & 0x07; + if (variant >= NAMES.length) { + return NAMES[0]; + } + return NAMES[variant]; + } + + @Override + public int getItemDamage() { + return this.getDamage() & 0x07; + } + + @Override + public BlockColor getColor() { + switch (this.getDamage() & 0x07) { + default: + case MOSSY_STONE_BRICKS: + case STONE: + return BlockColor.STONE_BLOCK_COLOR; + case SMOOTH_QUARTZ: + return BlockColor.QUARTZ_BLOCK_COLOR; + case CUT_SANDSTONE: + return BlockColor.SAND_BLOCK_COLOR; + case CUT_RED_SANDSTONE: + return BlockColor.ORANGE_BLOCK_COLOR; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabTileDeepslate.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabTileDeepslate.java new file mode 100644 index 00000000000..4ae1646a3eb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabTileDeepslate.java @@ -0,0 +1,65 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabTileDeepslate extends BlockDoubleSlabBase { + + public BlockDoubleSlabTileDeepslate() { + this(0); + } + + protected BlockDoubleSlabTileDeepslate(int meta) { + super(meta); + } + + @Override + public int getId() { + return DEEPSLATE_TILE_DOUBLE_SLAB; + } + + @Override + public int getSingleSlabId() { + return DEEPSLATE_TILE_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public String getSlabName() { + return "Double Deepslate Tile Slab"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabWarped.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabWarped.java new file mode 100644 index 00000000000..fea73e3c109 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabWarped.java @@ -0,0 +1,65 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDoubleSlabWarped extends BlockDoubleSlabBase { + + public BlockDoubleSlabWarped() { + super(0); + } + + public BlockDoubleSlabWarped(int meta) { + super(meta); + } + + @Override + public int getId() { + return WARPED_DOUBLE_SLAB; + } + + @Override + public String getSlabName() { + return "Warped"; + } + + @Override + public int getSingleSlabId() { + return WARPED_SLAB; + } + + @Override + public int getItemDamage() { + return 0; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java b/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java index ab83b36c568..98eeb0d8f2b 100644 --- a/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java +++ b/src/main/java/cn/nukkit/block/BlockDoubleSlabWood.java @@ -1,7 +1,5 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; @@ -9,7 +7,18 @@ * Created on 2015/12/2 by xtypr. * Package cn.nukkit.block in project Nukkit . */ -public class BlockDoubleSlabWood extends BlockSolidMeta { +public class BlockDoubleSlabWood extends BlockDoubleSlabBase { + + private static final String[] NAMES = new String[]{ + "Oak", + "Spruce", + "Birch", + "Jungle", + "Acacia", + "Dark Oak", + "", + "" + }; public BlockDoubleSlabWood() { this(0); @@ -24,6 +33,11 @@ public int getId() { return DOUBLE_WOOD_SLAB; } + @Override + public int getSingleSlabId() { + return WOOD_SLAB; + } + @Override public double getHardness() { return 2; @@ -40,37 +54,21 @@ public int getToolType() { } @Override - public String getName() { - String[] names = new String[]{ - "Oak", - "Spruce", - "Birch", - "Jungle", - "Acacia", - "Dark Oak", - "", - "" - }; - return "Double " + names[this.getDamage() & 0x07] + " Slab"; + public String getSlabName() { + return NAMES[this.getDamage() & 0x07]; } @Override - public Item toItem() { - return new ItemBlock(Block.get(BlockID.WOODEN_SLAB), this.getDamage() & 0x07); - } - - public Item[] getDrops(Item item) { - return new Item[]{ - Item.get(Item.WOOD_SLAB, this.getDamage() & 0x07, 2) - }; + public int getItemDamage() { + return this.getDamage() & 0x07; } @Override public BlockColor getColor() { - switch(this.getDamage() & 0x07){ + switch (this.getDamage() & 0x07) { default: - case 0: //OAK - return BlockColor.WOOD_BLOCK_COLOR; + case 0: //OAK + return BlockColor.WOOD_BLOCK_COLOR; case 1: //SPRUCE return BlockColor.SPRUCE_BLOCK_COLOR; case 2: //BIRCH diff --git a/src/main/java/cn/nukkit/block/BlockDragonEgg.java b/src/main/java/cn/nukkit/block/BlockDragonEgg.java index 8e02b46d3b6..dc6465562c6 100644 --- a/src/main/java/cn/nukkit/block/BlockDragonEgg.java +++ b/src/main/java/cn/nukkit/block/BlockDragonEgg.java @@ -4,14 +4,10 @@ import cn.nukkit.level.Level; import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; public class BlockDragonEgg extends BlockFallable { - public BlockDragonEgg() { - } - @Override public String getName() { return "Dragon Egg"; @@ -29,7 +25,7 @@ public double getHardness() { @Override public double getResistance() { - return 45; + return 9; } @Override @@ -39,7 +35,7 @@ public int getLightLevel() { @Override public BlockColor getColor() { - return BlockColor.OBSIDIAN_BLOCK_COLOR; + return BlockColor.BLACK_BLOCK_COLOR; } @Override @@ -56,15 +52,13 @@ public int onUpdate(int type) { } public void teleport() { - ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < 1000; ++i) { - Block to = this.getLevel().getBlock(this.add(random.nextInt(-16, 16), random.nextInt(-16, 16), random.nextInt(-16, 16))); + Block to = this.getLevel().getBlock(this.add(Utils.random.nextInt(-16, 16), Utils.random.nextInt(-16, 16), Utils.random.nextInt(-16, 16))); if (to.getId() == AIR) { BlockFromToEvent event = new BlockFromToEvent(this, to); this.level.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) return; to = event.getTo(); - int diffX = this.getFloorX() - to.getFloorX(); int diffY = this.getFloorY() - to.getFloorY(); int diffZ = this.getFloorZ() - to.getFloorZ(); @@ -74,11 +68,26 @@ public void teleport() { pk.x = this.getFloorX(); pk.y = this.getFloorY(); pk.z = this.getFloorZ(); - this.getLevel().addChunkPacket(this.getFloorX() >> 4, this.getFloorZ() >> 4, pk); + this.getLevel().addChunkPacket(this.getChunkX(), this.getChunkZ(), pk); this.getLevel().setBlock(this, get(AIR), true); this.getLevel().setBlock(to, this, true); return; } } } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean alwaysDropsOnExplosion() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockDriedKelpBlock.java b/src/main/java/cn/nukkit/block/BlockDriedKelpBlock.java new file mode 100644 index 00000000000..eb26d53c09b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDriedKelpBlock.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockDriedKelpBlock extends Block { + + @Override + public String getName() { + return "Dried Kelp Block"; + } + + @Override + public int getId() { + return DRIED_KELP_BLOCK; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + + public double getHardness() { + return 0.5; + } + + public double getResistance() { + return 2.5; + } + + @Override + public int getBurnAbility() { + return 30; + } + + @Override + public BlockColor getColor() { + return BlockColor.GREEN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDripleafBig.java b/src/main/java/cn/nukkit/block/BlockDripleafBig.java new file mode 100644 index 00000000000..56bd4307239 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDripleafBig.java @@ -0,0 +1,287 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.BlockPropertiesHelper; +import cn.nukkit.block.properties.DripleafTilt; +import cn.nukkit.block.properties.VanillaProperties; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.customblock.properties.BlockProperty; +import cn.nukkit.customblock.properties.BooleanBlockProperty; +import cn.nukkit.customblock.properties.EnumBlockProperty; +import cn.nukkit.entity.Entity; +import cn.nukkit.event.Event; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.event.player.PlayerInteractEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.level.Sound; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + +public class BlockDripleafBig extends BlockSolidMeta implements BlockPropertiesHelper, Faceable { + + public static final BlockProperty TILT_PROPERTY = new EnumBlockProperty<>("big_dripleaf_tilt", false, DripleafTilt.class); + public static final BlockProperty HEAD_PROPERTY = new BooleanBlockProperty("big_dripleaf_head", false); + + private static final BlockProperties PROPERTIES = new BlockProperties(TILT_PROPERTY, HEAD_PROPERTY, VanillaProperties.DIRECTION); + + public BlockDripleafBig() { + this(0); + } + + public BlockDripleafBig(int meta) { + super(meta); + } + + @Override + public BlockProperties getBlockProperties() { + return PROPERTIES; + } + + @Override + public boolean canPlaceOn(Block floor, Position pos) { + switch (floor.getId()) { + case CLAY_BLOCK: + case DIRT: + case FARMLAND: + case GRASS: + case MOSS_BLOCK: + case MYCELIUM: + case BIG_DRIPLEAF: + case WATER: + case STILL_WATER: + return super.canPlaceOn(floor, pos); + } + + if (floor.getLayer() == LAYER_WATERLOGGED && floor.getId() == AIR) { + return super.canPlaceOn(floor, pos); + } + return false; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + Block down = block.down(); + if (!this.canPlaceOn(down, target)) { + return false; + } + + if (down.getId() == BIG_DRIPLEAF) { + BlockDripleafBig floor = (BlockDripleafBig) down; + floor.setHasHead(false); + this.getLevel().setBlock(floor, floor, true, true); + this.setDirection(floor.getDirection()); + } else { + this.setDirection(player.getDirection().getOpposite()); + } + + this.setTilt(DripleafTilt.NONE); + this.setHasHead(true); + return this.getLevel().setBlock(this, this, true, true); + } + + @Override + public boolean onBreak(Item item, Player player) { + Block down = this.down(); + while (down instanceof BlockDripleafBig) { + this.getLevel().setBlock(down, Block.get(BlockID.AIR), true, true); + this.getLevel().addParticle(new DestroyBlockParticle(down.add(0.5), down)); + down = down.down(); + } + + Block up = this.up(); + while (up instanceof BlockDripleafBig) { + this.getLevel().setBlock(up, Block.get(BlockID.AIR), true, true); + this.getLevel().addParticle(new DestroyBlockParticle(up.add(0.5), up)); + up = up.up(); + } + + return super.onBreak(item, player); + } + + @Override + public void onEntityCollide(Entity entity) { + if (!(entity instanceof Player) || !this.hasHead() || this.getTilt() != DripleafTilt.NONE) { + return; + } + + Event event = new PlayerInteractEvent((Player) entity, null, this, null, PlayerInteractEvent.Action.PHYSICAL); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + this.setTiltAndScheduleTick(DripleafTilt.UNSTABLE, false); + } + + @Override + public int onUpdate(int type) { + if (type != Level.BLOCK_UPDATE_SCHEDULED) { + return super.onUpdate(type); + } + + DripleafTilt tilt = this.getTilt(); + if (tilt == DripleafTilt.UNSTABLE) { + this.setTiltAndScheduleTick(DripleafTilt.PARTIAL_TILT, true); + } else if (tilt == DripleafTilt.PARTIAL_TILT) { + this.setTiltAndScheduleTick(DripleafTilt.FULL_TILT, true); + } else if (tilt == DripleafTilt.FULL_TILT) { + this.resetTilt(); + } + return 0; + } + + private void setTiltAndScheduleTick(DripleafTilt tilt, boolean sound) { + this.setTilt(tilt); + this.getLevel().setBlock(this, this, true, true); + + if (sound) { + this.getLevel().addSound(this, Sound.TILT_DOWN_BIG_DRIPLEAF); + } + + int delay = tilt.getNetxStateDelay(); + if (delay != -1) { + this.getLevel().scheduleUpdate(this, delay); + } + } + + private void resetTilt() { + this.setTilt(DripleafTilt.NONE); + this.getLevel().setBlock(this, this, true, true); + this.getLevel().addSound(this, Sound.TILT_UP_BIG_DRIPLEAF); + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return false; + } + + BlockGrowEvent event = new BlockGrowEvent(this, Block.get(BlockID.BIG_DRIPLEAF, 0, this)); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + + Block up = this; + BlockDripleafBig highestPart = null; + while (up instanceof BlockDripleafBig) { + highestPart = (BlockDripleafBig) up; + up = up.up(); + } + + if (highestPart == null) { + return false; + } + + highestPart.setHasHead(false); + this.getLevel().setBlock(highestPart, highestPart, false, true); + + BlockDripleafBig block = (BlockDripleafBig) this.clone(); + block.setHasHead(true); + + this.getLevel().setBlock(highestPart.up(), block, false, true); + + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && !player.isCreative()) { + item.count--; + } + return true; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return super.recalculateBoundingBox(); // TODO: + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public String getName() { + return "Big Dripleaf"; + } + + @Override + public int getId() { + return BIG_DRIPLEAF; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public double getResistance() { + return 0.1; + } + + @Override + public double getHardness() { + return 0.1; + } + + public void setTilt(DripleafTilt tilt) { + this.setPropertyValue(TILT_PROPERTY, tilt); + } + + public DripleafTilt getTilt() { + return this.getPropertyValue(TILT_PROPERTY); + } + + public void setHasHead(boolean value) { + this.setBooleanValue(HEAD_PROPERTY, value); + } + + public boolean hasHead() { + return this.getBooleanValue(HEAD_PROPERTY); + } + + public void setDirection(BlockFace blockFace) { + this.setPropertyValue(VanillaProperties.DIRECTION, blockFace); + } + + public BlockFace getDirection() { + return this.getPropertyValue(VanillaProperties.DIRECTION); + } + + @Override + public BlockFace getBlockFace() { + return this.getDirection(); + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean isTransparent() { + return !this.hasHead() || !this.getTilt().isStable(); + } + + @Override + public boolean isSolid() { + return this.hasHead() && this.getTilt().isStable(); + } + + @Override + public boolean canPassThrough() { + return !this.hasHead() || !this.getTilt().isStable(); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDripleafSmall.java b/src/main/java/cn/nukkit/block/BlockDripleafSmall.java new file mode 100644 index 00000000000..df084d9909a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDripleafSmall.java @@ -0,0 +1,197 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.BlockPropertiesHelper; +import cn.nukkit.block.properties.VanillaProperties; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + +public class BlockDripleafSmall extends BlockFlowable implements BlockPropertiesHelper, Faceable { + + private static final BlockProperties PROPERTIES = new BlockProperties(VanillaProperties.UPPER_BLOCK, VanillaProperties.DIRECTION); + + public BlockDripleafSmall() { + this(0); + } + + public BlockDripleafSmall(int meta) { + super(meta); + } + + @Override + public BlockProperties getBlockProperties() { + return PROPERTIES; + } + + @Override + public boolean canPlaceOn(Block floor, Position pos) { + switch (floor.getId()) { + case CLAY_BLOCK: + case DIRT: + case FARMLAND: + case GRASS: + case MOSS_BLOCK: + case MYCELIUM: + case SMALL_DRIPLEAF: + case WATER: + case STILL_WATER: + return super.canPlaceOn(floor, pos); + } + + if (floor.getLayer() == LAYER_WATERLOGGED && floor.getId() == AIR) { + return super.canPlaceOn(floor, pos); + } + return false; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + Block down = block.down(); + if (!this.canPlaceOn(down, target)) { + return false; + } + + if (down.getId() == SMALL_DRIPLEAF) { + BlockDripleafSmall floor = (BlockDripleafSmall) down; + floor.setHasHead(false); + this.getLevel().setBlock(floor, floor, true, true); + this.setDirection(floor.getDirection()); + } else { + this.setDirection(player.getDirection().getOpposite()); + } + + this.setHasHead(true); + return this.getLevel().setBlock(this, this, true, true); + } + + @Override + public boolean onBreak(Item item, Player player) { + Block down = this.down(); + while (down instanceof BlockDripleafSmall) { + this.getLevel().setBlock(down, Block.get(BlockID.AIR), true, true); + this.getLevel().addParticle(new DestroyBlockParticle(down.add(0.5), down)); + down = down.down(); + } + + Block up = this.up(); + while (up instanceof BlockDripleafSmall) { + this.getLevel().setBlock(up, Block.get(BlockID.AIR), true, true); + this.getLevel().addParticle(new DestroyBlockParticle(up.add(0.5), up)); + up = up.up(); + } + + return super.onBreak(item, player); + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return false; + } + + BlockGrowEvent event = new BlockGrowEvent(this, Block.get(BlockID.BIG_DRIPLEAF, 0, this)); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + + Block down = this.down(); + while (down instanceof BlockDripleafSmall) { + BlockDripleafBig block = (BlockDripleafBig) Block.get(BlockID.BIG_DRIPLEAF); + block.setDirection(this.getDirection()); + this.getLevel().setBlock(down, block, false, true); + down = down.down(); + } + + Block up = this; + while (up instanceof BlockDripleafSmall) { + BlockDripleafBig block = (BlockDripleafBig) Block.get(BlockID.BIG_DRIPLEAF); + block.setDirection(this.getDirection()); + this.getLevel().setBlock(up, block, false, true); + up = up.up(); + } + + Block highestPart = this.getLevel().getBlock(up.down()); + if (highestPart instanceof BlockDripleafBig) { + ((BlockDripleafBig) highestPart).setHasHead(true); + this.getLevel().setBlock(highestPart, highestPart, false, true); + } + + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && !player.isCreative()) { + item.count--; + } + return true; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return null; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public String getName() { + return "Small Dripleaf"; + } + + @Override + public int getId() { + return SMALL_DRIPLEAF; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + public void setHasHead(boolean value) { + this.setBooleanValue(VanillaProperties.UPPER_BLOCK, value); + } + + public boolean hasHead() { + return this.getBooleanValue(VanillaProperties.UPPER_BLOCK); + } + + public void setDirection(BlockFace blockFace) { + this.setPropertyValue(VanillaProperties.DIRECTION, blockFace); + } + + public BlockFace getDirection() { + return this.getPropertyValue(VanillaProperties.DIRECTION); + } + + @Override + public BlockFace getBlockFace() { + return this.getDirection(); + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean canPassThrough() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockDripstone.java b/src/main/java/cn/nukkit/block/BlockDripstone.java new file mode 100644 index 00000000000..4a5be4bde27 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDripstone.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockDripstone extends BlockSolid { + + public BlockDripstone() { + // Does nothing + } + + @Override + public String getName() { + return "Dripstone Block"; + } + + @Override + public int getId() { + return DRIPSTONE_BLOCK; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 1; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + // TODO: + /*@Override + public boolean isLavaResistant() { + return true; + }*/ +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockDropper.java b/src/main/java/cn/nukkit/block/BlockDropper.java new file mode 100644 index 00000000000..07efd6f5e2b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockDropper.java @@ -0,0 +1,242 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityDropper; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.Faceable; +import cn.nukkit.utils.Utils; + +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class BlockDropper extends BlockSolidMeta implements Faceable { + + public BlockDropper() { + this(0); + } + + public BlockDropper(int meta) { + super(meta); + } + + @Override + public int getId() { + return DROPPER; + } + + @Override + public String getName() { + return "Dropper"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 17.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (player != null) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + double y = player.y + player.getEyeHeight(); + + if (y - this.y > 2) { + this.setDamage(BlockFace.UP.getIndex()); + } else if (this.y - y > 0) { + this.setDamage(BlockFace.DOWN.getIndex()); + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } + + this.getLevel().setBlock(block, this, true); + + BlockEntity.createBlockEntity(BlockEntity.DROPPER, this.getChunk(), BlockEntity.getDefaultCompound(this, BlockEntity.DROPPER)); + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player == null) { + return false; + } + + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDropper)) { + return false; + } + + if (blockEntity.namedTag.contains("Lock") && blockEntity.namedTag.get("Lock") instanceof StringTag) { + if (!blockEntity.namedTag.getString("Lock").equals(item.getCustomName())) { + return true; + } + } + + player.addWindow(((BlockEntityDropper) blockEntity).getInventory()); + return true; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromIndex(this.getDamage() & 0x7); + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(Block.DROPPER)); + } + + public Vector3 getDispensePosition() { + BlockFace facing = getBlockFace(); + return this.add( + 0.5 + 0.7 * facing.getXOffset(), + 0.5 + 0.7 * facing.getYOffset(), + 0.5 + 0.7 * facing.getZOffset() + ); + } + + public void dispense() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (!(blockEntity instanceof BlockEntityDropper)) { + return; + } + + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_CLICK); + + int r = 1; + int slot = -1; + Item target = null; + + Inventory inv = ((BlockEntityDropper) blockEntity).getInventory(); + for (Map.Entry entry : inv.getContents().entrySet()) { + Item item = entry.getValue(); + + if (!item.isNull() && Utils.random.nextInt(r++) == 0) { + target = item; + slot = entry.getKey(); + } + } + + if (target != null) { + target = target.clone(); + drop(target); + + target.count--; + inv.setItem(slot, target); + } + } + + public void drop(Item item) { + BlockFace face = this.getBlockFace(); + Vector3 dispensePos = this.getDispensePosition(); + + if (face.getAxis() == BlockFace.Axis.Y) { + dispensePos.y -= 0.125; + } else { + dispensePos.y -= 0.15625; + } + + ThreadLocalRandom rand = ThreadLocalRandom.current(); + Vector3 motion = new Vector3(); + + double offset = rand.nextDouble() * 0.1 + 0.2; + + motion.x = face.getXOffset() * offset; + motion.y = 0.1; + motion.z = face.getZOffset() * offset; + + motion.x += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.y += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.z += rand.nextGaussian() * 0.007499999832361937 * 6; + + Item i = item.clone(); + i.setCount(1); + this.level.dropItem(dispensePos, i, motion); + } + + @Override + public boolean hasComparatorInputOverride() { + return true; + } + + @Override + public int getComparatorInputOverride() { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (blockEntity instanceof BlockEntityDropper) { + return ContainerInventory.calculateRedstone(((BlockEntityDropper) blockEntity).getInventory()); + } + + return 0; + } + + public boolean isTriggered() { + return (this.getDamage() & 8) > 0; + } + + public void setTriggered(boolean value) { + int i = 0; + i |= getBlockFace().getIndex(); + + if (value) { + i |= 8; + } + + this.setDamage(i); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.setTriggered(false); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + dispense(); + return type; + } else if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (!isTriggered() && (level.isBlockPowered(this) || level.isBlockPowered(this.getSideVec(BlockFace.UP)))) { + this.setTriggered(true); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + level.scheduleUpdate(this, this, 4); + } + + return type; + } + + return 0; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} diff --git a/src/main/java/cn/nukkit/block/BlockEmerald.java b/src/main/java/cn/nukkit/block/BlockEmerald.java index 92e949c544a..e31af8631e1 100644 --- a/src/main/java/cn/nukkit/block/BlockEmerald.java +++ b/src/main/java/cn/nukkit/block/BlockEmerald.java @@ -10,12 +10,9 @@ */ public class BlockEmerald extends BlockSolid { - public BlockEmerald() { - } - @Override public String getName() { - return "Emerald Block"; + return "Block of Emerald"; } @Override @@ -40,7 +37,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockEnchantingTable.java b/src/main/java/cn/nukkit/block/BlockEnchantingTable.java index e8c61f58fd3..4de0edde00e 100644 --- a/src/main/java/cn/nukkit/block/BlockEnchantingTable.java +++ b/src/main/java/cn/nukkit/block/BlockEnchantingTable.java @@ -8,7 +8,6 @@ import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.StringTag; import cn.nukkit.nbt.tag.Tag; import cn.nukkit.utils.BlockColor; @@ -20,8 +19,6 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockEnchantingTable extends BlockTransparent { - public BlockEnchantingTable() { - } @Override public int getId() { @@ -60,7 +57,7 @@ public boolean canBeActivated() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -71,7 +68,7 @@ public Item[] getDrops(Item item) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); CompoundTag nbt = new CompoundTag() .putString("id", BlockEntity.ENCHANT_TABLE) @@ -90,37 +87,27 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityEnchantTable enchantTable = (BlockEntityEnchantTable) BlockEntity.createBlockEntity(BlockEntity.ENCHANT_TABLE, getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - return enchantTable != null; + BlockEntity.createBlockEntity(BlockEntity.ENCHANT_TABLE, this.getChunk(), nbt); + + return true; } @Override public boolean onActivate(Item item, Player player) { if (player != null) { BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityEnchantTable enchantTable; - if (t instanceof BlockEntityEnchantTable) { - enchantTable = (BlockEntityEnchantTable) t; - } else { - CompoundTag nbt = new CompoundTag() - .putList(new ListTag<>("Items")) - .putString("id", BlockEntity.ENCHANT_TABLE) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - enchantTable = (BlockEntityEnchantTable) BlockEntity.createBlockEntity(BlockEntity.ENCHANT_TABLE, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (enchantTable == null) { - return false; - } + if (!(t instanceof BlockEntityEnchantTable)) { + return false; } + BlockEntityEnchantTable enchantTable = (BlockEntityEnchantTable) t; if (enchantTable.namedTag.contains("Lock") && enchantTable.namedTag.get("Lock") instanceof StringTag) { if (!enchantTable.namedTag.getString("Lock").equals(item.getCustomName())) { return true; } } - player.addWindow(new EnchantInventory(player.getUIInventory(), this.getLocation()), Player.ENCHANT_WINDOW_ID); + player.addWindow(new EnchantInventory(player.getUIInventory(), this), Player.ENCHANT_WINDOW_ID); } return true; @@ -135,4 +122,9 @@ public boolean canHarvestWithHand() { public BlockColor getColor() { return BlockColor.RED_BLOCK_COLOR; } + + @Override + public boolean canBePushed() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockEndGateway.java b/src/main/java/cn/nukkit/block/BlockEndGateway.java index 826d95eaa9d..dfe2f026a64 100644 --- a/src/main/java/cn/nukkit/block/BlockEndGateway.java +++ b/src/main/java/cn/nukkit/block/BlockEndGateway.java @@ -9,9 +9,6 @@ */ public class BlockEndGateway extends BlockSolid { - public BlockEndGateway() { - } - @Override public String getName() { return "End Gateway"; @@ -62,4 +59,13 @@ public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); } + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean isSolid() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockEndPortal.java b/src/main/java/cn/nukkit/block/BlockEndPortal.java index 8d2cf1ac1ee..e2ae2683e1e 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortal.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortal.java @@ -2,6 +2,7 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.utils.BlockColor; public class BlockEndPortal extends BlockFlowable { @@ -24,11 +25,6 @@ public int getId() { return END_PORTAL; } - @Override - public boolean canPassThrough() { - return true; - } - @Override public boolean isBreakable(Item item) { return false; @@ -74,6 +70,11 @@ public boolean canBeFlowedInto() { return false; } + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return this; + } + @Override public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); diff --git a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java index a8e45f5c75f..fc1a33b31d2 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java @@ -4,20 +4,16 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; -import java.util.ArrayList; -import java.util.List; - /** * Created by Pub4Game on 26.12.2015. */ public class BlockEndPortalFrame extends BlockTransparentMeta implements Faceable { - private static final int[] FACES = {2, 3, 0, 1}; + private static final int[] faces = {2, 3, 0, 1}; public BlockEndPortalFrame() { this(0); @@ -82,91 +78,27 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if((this.getDamage() & 0x04) == 0 && player != null && item.getId() == Item.ENDER_EYE) { + if ((this.getDamage() & 0x04) == 0 && player != null && item.getId() == Item.ENDER_EYE && !player.isSneaking()) { this.setDamage(this.getDamage() + 4); - this.getLevel().setBlock(this, this, true, true); + this.getLevel().setBlock(this, this, true, false); this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_END_PORTAL_FRAME_FILL); - this.createPortal(); - return true; - } - return false; - } - - public void createPortal() { - Vector3 centerSpot = this.searchCenter(new ArrayList<>()); - if(centerSpot != null) { - for(int x = -2; x <= 2; x++) { - for(int z = -2; z <= 2; z++) { - if((x == -2 || x == 2) && (z == -2 || z == 2)) - continue; - if(x == -2 || x == 2 || z == -2 || z == 2) { - if(!this.checkFrame(this.getLevel().getBlock(centerSpot.add(x, 0, z)), x, z)) { - return; + for (int i = 0; i < 4; i++) { + for (int j = -1; j <= 1; j++) { + Block t = this.getSide(BlockFace.fromHorizontalIndex(i), 2).getSide(BlockFace.fromHorizontalIndex((i + 1) % 4), j); + if (isCompletedPortal(t)) { + for (int k = -1; k <= 1; k++) { + for (int l = -1; l <= 1; l++) { + this.getLevel().setBlock(t.add(k, 0, l), Block.get(Block.END_PORTAL), true); + } } + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_END_PORTAL_SPAWN); + return true; } } } - - for(int x = -1; x <= 1; x++) { - for(int z = -1; z <= 1; z++) { - Vector3 vector3 = centerSpot.add(x, 0, z); - if(this.getLevel().getBlock(vector3).getId() != Block.AIR) { - this.getLevel().useBreakOn(vector3); - } - this.getLevel().setBlock(vector3, Block.get(Block.END_PORTAL)); - } - } - } - } - - private Vector3 searchCenter(List visited) { - for(int x = -2; x <= 2; x++) { - if(x == 0) - continue; - Block block = this.getLevel().getBlock(this.add(x, 0, 0)); - Block iBlock = this.getLevel().getBlock(this.add(x * 2, 0, 0)); - if(this.checkFrame(block) && !visited.contains(block)) { - visited.add(block); - if((x == -1 || x == 1) && this.checkFrame(iBlock)) - return ((BlockEndPortalFrame) block).searchCenter(visited); - for(int z = -4; z <= 4; z++) { - if(z == 0) - continue; - block = this.getLevel().getBlock(this.add(x, 0, z)); - if(this.checkFrame(block)) { - return this.add(x / 2, 0, z / 2); - } - } - } - } - for(int z = -2; z <= 2; z++) { - if(z == 0) - continue; - Block block = this.getLevel().getBlock(this.add(0, 0, z)); - Block iBlock = this.getLevel().getBlock(this.add(0, 0, z * 2)); - if(this.checkFrame(block) && !visited.contains(block)) { - visited.add(block); - if((z == -1 || z == 1) && this.checkFrame(iBlock)) - return ((BlockEndPortalFrame) block).searchCenter(visited); - for(int x = -4; x <= 4; x++) { - if(x == 0) - continue; - block = this.getLevel().getBlock(this.add(x, 0, z)); - if(this.checkFrame(block)) { - return this.add(x / 2, 0, z / 2); - } - } - } + return true; } - return null; - } - - private boolean checkFrame(Block block) { - return block.getId() == this.getId() && (block.getDamage() & 4) == 4; - } - - private boolean checkFrame(Block block, int x, int z) { - return block.getId() == this.getId() && (block.getDamage() - 4) == (x == -2 ? 3 : x == 2 ? 1 : z == -2 ? 0 : z == 2 ? 2 : -1); + return false; } @Override @@ -176,18 +108,31 @@ public boolean canHarvestWithHand() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(FACES[player != null ? player.getDirection().getHorizontalIndex() : 0]); - this.getLevel().setBlock(block, this, true); + this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + + this.getLevel().setBlock(this, this, true, true); + return true; + } + + private static boolean isCompletedPortal(Block center) { + for (int i = 0; i < 4; i++) { + for (int j = -1; j <= 1; j++) { + Block block = center.getSide(BlockFace.fromHorizontalIndex(i), 2).getSide(BlockFace.fromHorizontalIndex((i + 1) % 4), j); + if (block.getId() != Block.END_PORTAL_FRAME || (block.getDamage() & 0x4) == 0) { + return false; + } + } + } return true; } @@ -195,4 +140,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl public BlockColor getColor() { return BlockColor.GREEN_BLOCK_COLOR; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockEndRod.java b/src/main/java/cn/nukkit/block/BlockEndRod.java index 2c4da5a0106..b4794dfa369 100644 --- a/src/main/java/cn/nukkit/block/BlockEndRod.java +++ b/src/main/java/cn/nukkit/block/BlockEndRod.java @@ -14,6 +14,8 @@ */ public class BlockEndRod extends BlockTransparentMeta implements Faceable { + private static final int[] faces = {0, 1, 3, 2, 5, 4}; + public BlockEndRod() { this(0); } @@ -47,11 +49,6 @@ public int getLightLevel() { return 14; } - @Override - public boolean canBePushed() { - return true; - } - @Override public int getToolType() { return ItemTool.TYPE_PICKAXE; @@ -79,7 +76,6 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = {0, 1, 3, 2, 5, 4}; this.setDamage(faces[player != null ? face.getIndex() : 0]); this.getLevel().setBlock(block, this, true, true); @@ -88,12 +84,21 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockEndStone.java b/src/main/java/cn/nukkit/block/BlockEndStone.java index 1695fa12b8a..1956362e918 100644 --- a/src/main/java/cn/nukkit/block/BlockEndStone.java +++ b/src/main/java/cn/nukkit/block/BlockEndStone.java @@ -10,9 +10,6 @@ */ public class BlockEndStone extends BlockSolid { - public BlockEndStone() { - } - @Override public String getName() { return "End Stone"; @@ -40,7 +37,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockEnderChest.java b/src/main/java/cn/nukkit/block/BlockEnderChest.java index 0cac7913762..158deb864c0 100644 --- a/src/main/java/cn/nukkit/block/BlockEnderChest.java +++ b/src/main/java/cn/nukkit/block/BlockEnderChest.java @@ -6,6 +6,7 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.StringTag; @@ -19,7 +20,7 @@ public class BlockEnderChest extends BlockTransparentMeta implements Faceable { - private Set viewers = new HashSet<>(); + private final Set viewers = new HashSet<>(); public BlockEnderChest() { this(0); @@ -91,10 +92,10 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = {2, 5, 3, 4}; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + + this.getLevel().setBlock(this, this, true, true); - this.getLevel().setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag("") .putString("id", BlockEntity.ENDER_CHEST) .putInt("x", (int) this.x) @@ -112,34 +113,24 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityEnderChest ender = (BlockEntityEnderChest) BlockEntity.createBlockEntity(BlockEntity.ENDER_CHEST, this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - return ender != null; + BlockEntity.createBlockEntity(BlockEntity.ENDER_CHEST, this.getChunk(), nbt); + return true; } @Override public boolean onActivate(Item item, Player player) { if (player != null) { Block top = this.up(); - if (!top.isTransparent()) { + if (!top.isTransparent() && !(top instanceof BlockSlab && (top.getDamage() & 0x07) <= 0)) { // avoid issues with the slab hack return true; } BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityEnderChest chest; - if (t instanceof BlockEntityEnderChest) { - chest = (BlockEntityEnderChest) t; - } else { - CompoundTag nbt = new CompoundTag("") - .putString("id", BlockEntity.ENDER_CHEST) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - chest = (BlockEntityEnderChest) BlockEntity.createBlockEntity(BlockEntity.ENDER_CHEST, this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - if (chest == null) { - return false; - } + if (!(t instanceof BlockEntityEnderChest)) { + return false; } + BlockEntityEnderChest chest = (BlockEntityEnderChest) t; if (chest.namedTag.contains("Lock") && chest.namedTag.get("Lock") instanceof StringTag) { if (!chest.namedTag.getString("Lock").equals(item.getCustomName())) { return true; @@ -155,7 +146,10 @@ public boolean onActivate(Item item, Player player) { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[]{ Item.get(Item.OBSIDIAN, 0, 8) }; @@ -190,11 +184,16 @@ public boolean canSilkTouch() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; } } diff --git a/src/main/java/cn/nukkit/block/BlockFallable.java b/src/main/java/cn/nukkit/block/BlockFallable.java index 04740226643..571249cc1d2 100644 --- a/src/main/java/cn/nukkit/block/BlockFallable.java +++ b/src/main/java/cn/nukkit/block/BlockFallable.java @@ -1,6 +1,5 @@ package cn.nukkit.block; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityFallingBlock; import cn.nukkit.event.block.BlockFallEvent; import cn.nukkit.level.Level; @@ -9,9 +8,8 @@ import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; - /** - * author: rcsuperman + * @author rcsuperman * Nukkit Project */ public abstract class BlockFallable extends BlockSolid { @@ -28,7 +26,6 @@ public int onUpdate(int type) { if (event.isCancelled()) { return type; } - this.level.setBlock(this, Block.get(Block.AIR), true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") @@ -46,11 +43,9 @@ public int onUpdate(int type) { .putInt("TileID", this.getId()) .putByte("Data", this.getDamage()); - EntityFallingBlock fall = (EntityFallingBlock) Entity.createEntity("FallingSand", this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); + EntityFallingBlock fall = new EntityFallingBlock(this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - if (fall != null) { - fall.spawnToAll(); - } + fall.spawnToAll(); } } return type; diff --git a/src/main/java/cn/nukkit/block/BlockFallableMeta.java b/src/main/java/cn/nukkit/block/BlockFallableMeta.java new file mode 100644 index 00000000000..de05e801648 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFallableMeta.java @@ -0,0 +1,54 @@ +package cn.nukkit.block; + +import cn.nukkit.entity.item.EntityFallingBlock; +import cn.nukkit.event.block.BlockFallEvent; +import cn.nukkit.level.Level; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +/** + * @author rcsuperman + * Nukkit Project + */ +public abstract class BlockFallableMeta extends BlockSolidMeta { + + protected BlockFallableMeta(int meta) { + super(meta); + } + + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + if (down.getId() == AIR || down instanceof BlockLiquid || down instanceof BlockFire) { + BlockFallEvent event = new BlockFallEvent(this); + this.level.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return type; + } + this.level.setBlock(this, Block.get(Block.AIR), true, true); + CompoundTag nbt = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", this.x + 0.5)) + .add(new DoubleTag("", this.y)) + .add(new DoubleTag("", this.z + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + + .putList(new ListTag("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0))) + .putInt("TileID", this.getId()) + .putByte("Data", this.getDamage()); + + EntityFallingBlock fall = new EntityFallingBlock(this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); + + fall.spawnToAll(); + } + } + return type; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFarmland.java b/src/main/java/cn/nukkit/block/BlockFarmland.java index 7301b15da03..ce2c05dab8a 100644 --- a/src/main/java/cn/nukkit/block/BlockFarmland.java +++ b/src/main/java/cn/nukkit/block/BlockFarmland.java @@ -4,7 +4,7 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; -import cn.nukkit.math.Vector3; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.utils.BlockColor; /** @@ -48,21 +48,20 @@ public int getToolType() { @Override public double getMaxY() { - return this.y + 1; + return this.y + 0.9375; } @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_RANDOM) { - Vector3 v = new Vector3(); + Block up = this.up(); - if (this.level.getBlock(v.setComponents(x, this.y + 1, z)) instanceof BlockCrops) { + if (up instanceof BlockCrops) { return 0; } - if (this.level.getBlock(v.setComponents(x, this.y + 1, z)).isSolid()) { + if (up.isSolid()) { this.level.setBlock(this, Block.get(BlockID.DIRT), false, true); - return Level.BLOCK_UPDATE_RANDOM; } @@ -78,10 +77,10 @@ public int onUpdate(int type) { continue; } - v.setComponents(x, y, z); - int block = this.level.getBlockIdAt(v.getFloorX(), v.getFloorY(), v.getFloorZ()); + FullChunk chunk = this.level.getChunk(x >> 4, z >> 4); - if (block == WATER || block == STILL_WATER) { + int block = this.level.getBlockIdAt(chunk, x, y, z); + if (Block.hasWater(block) || block == FROSTED_ICE || this.level.isBlockWaterloggedAt(chunk, x, y, z)) { found = true; break; } @@ -90,23 +89,28 @@ public int onUpdate(int type) { } } - Block block = this.level.getBlock(v.setComponents(x, y - 1, z)); - if (found || block instanceof BlockWater) { + Block block; + if (found || (block = this.down()) instanceof BlockWater || block instanceof BlockIceFrosted) { if (this.getDamage() < 7) { this.setDamage(7); - this.level.setBlock(this, this, false, false); + this.level.setBlock(this, this, false, true); } return Level.BLOCK_UPDATE_RANDOM; } if (this.getDamage() > 0) { this.setDamage(this.getDamage() - 1); - this.level.setBlock(this, this, false, false); + this.level.setBlock(this, this, false, true); } else { this.level.setBlock(this, Block.get(Block.DIRT), false, true); } return Level.BLOCK_UPDATE_RANDOM; + } else if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.up().isSolid()) { + this.level.setBlock(this, Block.get(DIRT), false, true); + return Level.BLOCK_UPDATE_NORMAL; + } } return 0; diff --git a/src/main/java/cn/nukkit/block/BlockFence.java b/src/main/java/cn/nukkit/block/BlockFence.java index 3c6356511de..4a0a6e1a5ad 100644 --- a/src/main/java/cn/nukkit/block/BlockFence.java +++ b/src/main/java/cn/nukkit/block/BlockFence.java @@ -50,7 +50,7 @@ public int getToolType() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak Fence", "Spruce Fence", "Birch Fence", @@ -63,7 +63,6 @@ public String getName() { return names[this.getDamage() & 0x07]; } - @Override protected AxisAlignedBB recalculateBoundingBox() { boolean north = this.canConnect(this.north()); boolean south = this.canConnect(this.south()); @@ -99,19 +98,19 @@ public boolean canConnect(Block block) { @Override public BlockColor getColor() { - switch(this.getDamage() & 0x07){ + switch (this.getDamage() & 0x07) { default: - case BlockFence.FENCE_OAK: //OAK + case FENCE_OAK: //OAK return BlockColor.WOOD_BLOCK_COLOR; - case BlockFence.FENCE_SPRUCE: //SPRUCE + case FENCE_SPRUCE: //SPRUCE return BlockColor.SPRUCE_BLOCK_COLOR; - case BlockFence.FENCE_BIRCH: //BIRCH + case FENCE_BIRCH: //BIRCH return BlockColor.SAND_BLOCK_COLOR; - case BlockFence.FENCE_JUNGLE: //JUNGLE + case FENCE_JUNGLE: //JUNGLE return BlockColor.DIRT_BLOCK_COLOR; - case BlockFence.FENCE_ACACIA: //ACACIA + case FENCE_ACACIA: //ACACIA return BlockColor.ORANGE_BLOCK_COLOR; - case BlockFence.FENCE_DARK_OAK: //DARK OAK + case FENCE_DARK_OAK: //DARK OAK return BlockColor.BROWN_BLOCK_COLOR; } } @@ -120,4 +119,9 @@ public BlockColor getColor() { public Item toItem() { return new ItemBlock(this, this.getDamage()); } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockFenceCrimson.java b/src/main/java/cn/nukkit/block/BlockFenceCrimson.java new file mode 100644 index 00000000000..055c65ebfdf --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFenceCrimson.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockFenceCrimson extends BlockFence { + + public BlockFenceCrimson() { + this(0); + } + + public BlockFenceCrimson(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Fence"; + } + + @Override + public int getId() { + return CRIMSON_FENCE; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFenceGate.java b/src/main/java/cn/nukkit/block/BlockFenceGate.java index 15c18e7b44a..a776602fd42 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGate.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGate.java @@ -5,8 +5,8 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; import cn.nukkit.math.BlockFace; -import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -104,8 +104,8 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { this.setDamage(player != null ? player.getDirection().getHorizontalIndex() : 0); - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } @@ -119,7 +119,12 @@ public boolean onActivate(Item item, Player player) { return false; } - this.getLevel().addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_DOOR); + this.getLevel().setBlock(this, this, true); + if (this.isOpen()) { + this.level.addSound(this, Sound.RANDOM_DOOR_OPEN); + } else { + this.level.addSound(this, Sound.RANDOM_DOOR_CLOSE); + } return true; } @@ -174,7 +179,12 @@ public boolean toggle(Player player) { } this.setDamage(direction | ((~this.getDamage()) & 0x04)); - this.level.setBlock(this, this, false, false); + this.level.setBlock(this, this, true, false); + if (this.isOpen()) { + this.level.addSound(this, Sound.RANDOM_DOOR_OPEN); + } else { + this.level.addSound(this, Sound.RANDOM_DOOR_CLOSE); + } return true; } @@ -185,7 +195,8 @@ public boolean isOpen() { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_REDSTONE) { - if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { + boolean powered = this.level.isBlockPowered(this); + if ((!isOpen() && powered) || (isOpen() && !powered)) { this.toggle(null); return type; } @@ -193,7 +204,7 @@ public int onUpdate(int type) { return 0; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE, 0, 1); @@ -201,6 +212,16 @@ public Item toItem() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public boolean canPassThrough() { + return this.isOpen(); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateAcacia.java b/src/main/java/cn/nukkit/block/BlockFenceGateAcacia.java index db80aa78a0f..69663971f3b 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGateAcacia.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGateAcacia.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockFenceGateAcacia extends BlockFenceGate { + public BlockFenceGateAcacia() { this(0); } @@ -25,7 +26,7 @@ public int getId() { public String getName() { return "Acacia Fence Gate"; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE_ACACIA, 0, 1); diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateBirch.java b/src/main/java/cn/nukkit/block/BlockFenceGateBirch.java index 9b0723112bf..97654be29f5 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGateBirch.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGateBirch.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockFenceGateBirch extends BlockFenceGate { + public BlockFenceGateBirch() { this(0); } @@ -25,7 +26,7 @@ public int getId() { public String getName() { return "Birch Fence Gate"; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE_BIRCH, 0, 1); diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateCrimson.java b/src/main/java/cn/nukkit/block/BlockFenceGateCrimson.java new file mode 100644 index 00000000000..e0c76702ebf --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFenceGateCrimson.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockFenceGateCrimson extends BlockFenceGate { + + public BlockFenceGateCrimson() { + this(0); + } + + public BlockFenceGateCrimson(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Fence Gate"; + } + + @Override + public int getId() { + return CRIMSON_FENCE_GATE; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateDarkOak.java b/src/main/java/cn/nukkit/block/BlockFenceGateDarkOak.java index 9f03cf39859..04ca651b283 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGateDarkOak.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGateDarkOak.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockFenceGateDarkOak extends BlockFenceGate { + public BlockFenceGateDarkOak() { this(0); } @@ -25,7 +26,7 @@ public int getId() { public String getName() { return "Dark Oak Fence Gate"; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE_DARK_OAK, 0, 1); diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateJungle.java b/src/main/java/cn/nukkit/block/BlockFenceGateJungle.java index fe8c23fa047..eb5e176489d 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGateJungle.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGateJungle.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockFenceGateJungle extends BlockFenceGate { + public BlockFenceGateJungle() { this(0); } @@ -25,7 +26,7 @@ public int getId() { public String getName() { return "Jungle Fence Gate"; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE_JUNGLE, 0, 1); diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateSpruce.java b/src/main/java/cn/nukkit/block/BlockFenceGateSpruce.java index d9809efd834..8b38f74dfd1 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceGateSpruce.java +++ b/src/main/java/cn/nukkit/block/BlockFenceGateSpruce.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockFenceGateSpruce extends BlockFenceGate { + public BlockFenceGateSpruce() { this(0); } @@ -25,7 +26,7 @@ public int getId() { public String getName() { return "Spruce Fence Gate"; } - + @Override public Item toItem() { return Item.get(Item.FENCE_GATE_SPRUCE,0, 1); diff --git a/src/main/java/cn/nukkit/block/BlockFenceGateWarped.java b/src/main/java/cn/nukkit/block/BlockFenceGateWarped.java new file mode 100644 index 00000000000..e916d49396d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFenceGateWarped.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockFenceGateWarped extends BlockFenceGate { + + public BlockFenceGateWarped() { + this(0); + } + + public BlockFenceGateWarped(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Fence Gate"; + } + + @Override + public int getId() { + return WARPED_FENCE_GATE; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFenceNetherBrick.java b/src/main/java/cn/nukkit/block/BlockFenceNetherBrick.java index f8ca8847192..baa16ba4c74 100644 --- a/src/main/java/cn/nukkit/block/BlockFenceNetherBrick.java +++ b/src/main/java/cn/nukkit/block/BlockFenceNetherBrick.java @@ -33,11 +33,6 @@ public int getId() { return NETHER_BRICK_FENCE; } - @Override - public double getHardness() { - return 2; - } - @Override public double getResistance() { return 10; @@ -45,7 +40,7 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockFenceWarped.java b/src/main/java/cn/nukkit/block/BlockFenceWarped.java new file mode 100644 index 00000000000..5e9a3841594 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFenceWarped.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockFenceWarped extends BlockFence { + + public BlockFenceWarped() { + this(0); + } + + public BlockFenceWarped(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Fence"; + } + + @Override + public int getId() { + return WARPED_FENCE; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFire.java b/src/main/java/cn/nukkit/block/BlockFire.java index 4d1eeb3458f..6d9b7bd3505 100644 --- a/src/main/java/cn/nukkit/block/BlockFire.java +++ b/src/main/java/cn/nukkit/block/BlockFire.java @@ -1,7 +1,9 @@ package cn.nukkit.block; import cn.nukkit.Server; +import cn.nukkit.entity.BaseEntity; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.item.EntityPotion; import cn.nukkit.entity.projectile.EntityArrow; import cn.nukkit.event.block.BlockBurnEvent; @@ -16,16 +18,13 @@ import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; import cn.nukkit.potion.Effect; import cn.nukkit.potion.Potion; import cn.nukkit.utils.BlockColor; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockFire extends BlockFlowable { @@ -81,17 +80,24 @@ public void onEntityCollide(Entity entity) { } } - if (!entity.hasEffect(Effect.FIRE_RESISTANCE)) { - entity.attack(new EntityDamageByBlockEvent(this, entity, DamageCause.FIRE, 1)); - } + if (!entity.fireProof || !entity.isOnFire() || !(entity instanceof BaseEntity)) { // Improve performance - EntityCombustByBlockEvent ev = new EntityCombustByBlockEvent(this, entity, 8); - if (entity instanceof EntityArrow) { - ev.setCancelled(); - } - Server.getInstance().getPluginManager().callEvent(ev); - if (!ev.isCancelled() && entity.isAlive() && entity.noDamageTicks == 0) { - entity.setOnFire(ev.getDuration()); + if (!(entity instanceof EntityLiving) || (!entity.hasEffect(Effect.FIRE_RESISTANCE) && this.level.getGameRules().getBoolean(GameRule.FIRE_DAMAGE))) { + entity.attack(new EntityDamageByBlockEvent(this, entity, DamageCause.FIRE, 1)); + } + + if (!entity.fireProof || !entity.isOnFire()) { + EntityCombustByBlockEvent ev = new EntityCombustByBlockEvent(this, entity, 8); + if (entity instanceof EntityArrow) { + ev.setCancelled(); + } + + Server.getInstance().getPluginManager().callEvent(ev); + + if (!ev.isCancelled() && entity.isAlive() && entity.noDamageTicks == 0) { + entity.setOnFire(ev.getDuration()); + } + } } } @@ -104,28 +110,15 @@ public Item[] getDrops(Item item) { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_RANDOM) { if (!this.isBlockTopFacingSurfaceSolid(this.down()) && !this.canNeighborBurn()) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); - } + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); + } else if (this.level.gameRules.getBoolean(GameRule.DO_FIRE_TICK) && !level.isUpdateScheduled(this, this)) { + level.scheduleUpdate(this, tickRate()); } return Level.BLOCK_UPDATE_NORMAL; } else if (type == Level.BLOCK_UPDATE_SCHEDULED && this.level.gameRules.getBoolean(GameRule.DO_FIRE_TICK)) { - boolean forever = this.down().getId() == Block.NETHERRACK || this.down().getId() == Block.MAGMA; - - ThreadLocalRandom random = ThreadLocalRandom.current(); - - //TODO: END - - if (!this.isBlockTopFacingSurfaceSolid(this.down()) && !this.canNeighborBurn()) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); - } - } + Block down = this.down(); + boolean forever = this.getId() == SOUL_FIRE || down.getId() == NETHERRACK || down.getId() == MAGMA || (down.getId() == BEDROCK && level.getDimension() == Level.DIMENSION_THE_END); if (!forever && this.getLevel().isRaining() && (this.getLevel().canBlockSeeSky(this) || @@ -134,80 +127,75 @@ public int onUpdate(int type) { this.getLevel().canBlockSeeSky(this.south()) || this.getLevel().canBlockSeeSky(this.north())) ) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); + + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); + } + + if (!this.isBlockTopFacingSurfaceSolid(down) && !this.canNeighborBurn()) { + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); + return 0; + } + + int meta = this.getDamage(); + + if (meta < 15) { + int newMeta = meta + Utils.random.nextInt(3); + if (newMeta > 15) newMeta = 15; + this.setDamage(newMeta); + this.getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, true, false); // No need to send this to client + } + + this.getLevel().scheduleUpdate(this, this.tickRate() + Utils.random.nextInt(10)); + + if (!forever && !this.canNeighborBurn()) { + if (!this.isBlockTopFacingSurfaceSolid(this.down()) || meta > 3) { + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); } + } else if (!forever && !(this.down().getBurnAbility() > 0) && meta == 15 && Utils.random.nextInt(4) == 0) { + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); } else { - int meta = this.getDamage(); + int o = 0; - if (meta < 15) { - int newMeta = meta + random.nextInt(3); - this.setDamage(Math.min(newMeta, 15)); - this.getLevel().setBlock(this, this, true); - } + //TODO: decrease the o if the rainfall values are high - this.getLevel().scheduleUpdate(this, this.tickRate() + random.nextInt(10)); + this.tryToCatchBlockOnFire(this.east(), 300 + o, meta); + this.tryToCatchBlockOnFire(this.west(), 300 + o, meta); + this.tryToCatchBlockOnFire(this.down(), 250 + o, meta); + this.tryToCatchBlockOnFire(this.up(), 250 + o, meta); + this.tryToCatchBlockOnFire(this.south(), 300 + o, meta); + this.tryToCatchBlockOnFire(this.north(), 300 + o, meta); - if (!forever && !this.canNeighborBurn()) { - if (!this.isBlockTopFacingSurfaceSolid(this.down()) || meta > 3) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); - } - } - } else if (!forever && !(this.down().getBurnAbility() > 0) && meta == 15 && random.nextInt(4) == 0) { - BlockFadeEvent event = new BlockFadeEvent(this, get(AIR)); - level.getServer().getPluginManager().callEvent(event); - if (!event.isCancelled()) { - level.setBlock(this, event.getNewState(), true); - } - } else { - int o = 0; - - //TODO: decrease the o if the rainfall values are high - - this.tryToCatchBlockOnFire(this.east(), 300 + o, meta); - this.tryToCatchBlockOnFire(this.west(), 300 + o, meta); - this.tryToCatchBlockOnFire(this.down(), 250 + o, meta); - this.tryToCatchBlockOnFire(this.up(), 250 + o, meta); - this.tryToCatchBlockOnFire(this.south(), 300 + o, meta); - this.tryToCatchBlockOnFire(this.north(), 300 + o, meta); - - for (int x = (int) (this.x - 1); x <= (int) (this.x + 1); ++x) { - for (int z = (int) (this.z - 1); z <= (int) (this.z + 1); ++z) { - for (int y = (int) (this.y - 1); y <= (int) (this.y + 4); ++y) { - if (x != (int) this.x || y != (int) this.y || z != (int) this.z) { - int k = 100; - - if (y > this.y + 1) { - k += (y - (this.y + 1)) * 100; - } + for (int x = (int) (this.x - 1); x <= (int) (this.x + 1); ++x) { + for (int z = (int) (this.z - 1); z <= (int) (this.z + 1); ++z) { + for (int y = (int) (this.y - 1); y <= (int) (this.y + 4); ++y) { + if (x != (int) this.x || y != (int) this.y || z != (int) this.z) { + int k = 100; + + if (y > this.y + 1) { + k += (y - (this.y + 1)) * 100; + } - Block block = this.getLevel().getBlock(new Vector3(x, y, z)); - int chance = this.getChanceOfNeighborsEncouragingFire(block); + Block block = this.getLevel().getBlock(x, y, z); + int chance = getChanceOfNeighborsEncouragingFire(block); - if (chance > 0) { - int t = (chance + 40 + this.getLevel().getServer().getDifficulty() * 7) / (meta + 30); + if (chance > 0) { + int t = (chance + 40 + this.getLevel().getServer().getDifficulty() * 7) / (meta + 30); - //TODO: decrease the t if the rainfall values are high + //TODO: decrease the t if the rainfall values are high - if (t > 0 && random.nextInt(k) <= t) { - int damage = meta + random.nextInt(5) / 4; + if (t > 0 && Utils.random.nextInt(k) <= t) { + int damage = meta + (Utils.random.nextInt(5) >> 2); - if (damage > 15) { - damage = 15; - } + if (damage > 15) { + damage = 15; + } - BlockIgniteEvent e = new BlockIgniteEvent(block, this, null, BlockIgniteEvent.BlockIgniteCause.SPREAD); - this.level.getServer().getPluginManager().callEvent(e); + BlockIgniteEvent e = new BlockIgniteEvent(block, this, null, BlockIgniteEvent.BlockIgniteCause.SPREAD); + this.level.getServer().getPluginManager().callEvent(e); - if (!e.isCancelled()) { - this.getLevel().setBlock(block, Block.get(BlockID.FIRE, damage), true); - this.getLevel().scheduleUpdate(block, this.tickRate()); - } + if (!e.isCancelled()) { + this.getLevel().setBlock(block, Block.get(FIRE, damage), true); + this.getLevel().scheduleUpdate(block, this.tickRate()); } } } @@ -222,14 +210,10 @@ public int onUpdate(int type) { } private void tryToCatchBlockOnFire(Block block, int bound, int damage) { - int burnAbility = block.getBurnAbility(); - - Random random = ThreadLocalRandom.current(); - - if (random.nextInt(bound) < burnAbility) { + if (Utils.random.nextInt(bound) < block.getBurnAbility()) { - if (random.nextInt(damage + 10) < 5) { - int meta = damage + random.nextInt(5) / 4; + if (Utils.random.nextInt(damage + 10) < 5) { + int meta = damage + (Utils.random.nextInt(5) >> 2); if (meta > 15) { meta = 15; @@ -239,7 +223,7 @@ private void tryToCatchBlockOnFire(Block block, int bound, int damage) { this.level.getServer().getPluginManager().callEvent(e); if (!e.isCancelled()) { - this.getLevel().setBlock(block, Block.get(BlockID.FIRE, meta), true); + this.getLevel().setBlock(block, Block.get(FIRE, meta), true); this.getLevel().scheduleUpdate(block, this.tickRate()); } } else { @@ -257,7 +241,7 @@ private void tryToCatchBlockOnFire(Block block, int bound, int damage) { } } - private int getChanceOfNeighborsEncouragingFire(Block block) { + private static int getChanceOfNeighborsEncouragingFire(Block block) { if (block.getId() != AIR) { return 0; } else { @@ -284,23 +268,31 @@ public boolean canNeighborBurn() { public boolean isBlockTopFacingSurfaceSolid(Block block) { if (block != null) { - if (block.isSolid()) { + if (block instanceof BlockStairs && (block.getDamage() & 4) == 4) { return true; - } else { - if (block instanceof BlockStairs && - (block.getDamage() & 4) == 4) { - - return true; - } else if (block instanceof BlockSlab && - (block.getDamage() & 8) == 8) { - - return true; - } else if (block instanceof BlockSnowLayer && - (block.getDamage() & 7) == 7) { - - return true; - } - } + } else if (block instanceof BlockSlab && (this.getDamage() & 0x08) > 0) { + return true; + } else if (block instanceof BlockSnowLayer && (block.getDamage() & 7) == 7) { + return true; + } else if (block instanceof BlockGlass) { + return false; + } else if (block instanceof BlockHopper || block instanceof BlockBeacon) { + return false; + } else if (block instanceof BlockShulkerBox || block instanceof BlockChest || block instanceof BlockEnderChest) { + return false; + } else if (block instanceof BlockAnvil || block instanceof BlockEnchantingTable || block instanceof BlockBrewingStand) { + return false; + } else if (block instanceof BlockCampfire) { + return false; + } else if (block instanceof BlockCactus) { + return false; + } else if (block instanceof BlockDaylightDetector) { + return false; + } else if (block instanceof BlockIce) { + return false; + } else if (block instanceof BlockCake) { + return false; + } else return block.isSolid(); } return false; @@ -313,7 +305,7 @@ public int tickRate() { @Override public BlockColor getColor() { - return BlockColor.LAVA_BLOCK_COLOR; + return BlockColor.AIR_BLOCK_COLOR; } @Override @@ -325,4 +317,9 @@ protected AxisAlignedBB recalculateCollisionBoundingBox() { public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockFletchingTable.java b/src/main/java/cn/nukkit/block/BlockFletchingTable.java new file mode 100644 index 00000000000..cf57ce7b062 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFletchingTable.java @@ -0,0 +1,42 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockFletchingTable extends BlockSolid { + + @Override + public String getName() { + return "Fletching Table"; + } + + @Override + public int getId() { + return FLETCHING_TABLE; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public double getResistance() { + return 12.5; + } + + @Override + public double getHardness() { + return 2.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 5; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockFlowable.java b/src/main/java/cn/nukkit/block/BlockFlowable.java index a0f2d7dd5b8..0dbc59e6d8f 100644 --- a/src/main/java/cn/nukkit/block/BlockFlowable.java +++ b/src/main/java/cn/nukkit/block/BlockFlowable.java @@ -3,7 +3,7 @@ import cn.nukkit.math.AxisAlignedBB; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockFlowable extends BlockTransparentMeta { @@ -41,4 +41,9 @@ public boolean isSolid() { protected AxisAlignedBB recalculateBoundingBox() { return null; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockFlower.java b/src/main/java/cn/nukkit/block/BlockFlower.java index 9f5e60536bc..a21d7c8b502 100644 --- a/src/main/java/cn/nukkit/block/BlockFlower.java +++ b/src/main/java/cn/nukkit/block/BlockFlower.java @@ -2,19 +2,20 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; import cn.nukkit.level.Level; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created on 2015/11/23 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockFlower extends BlockFlowable { + public static final int TYPE_POPPY = 0; public static final int TYPE_BLUE_ORCHID = 1; public static final int TYPE_ALLIUM = 2; @@ -27,6 +28,25 @@ public class BlockFlower extends BlockFlowable { public static final int TYPE_CORNFLOWER = 9; public static final int TYPE_LILY_OF_THE_VALLEY = 10; + private static final String[] names = { + "Poppy", + "Blue Orchid", + "Allium", + "Azure Bluet", + "Red Tulip", + "Orange Tulip", + "White Tulip", + "Pink Tulip", + "Oxeye Daisy", + "Cornflower", + "Lily of the Valley", + "Unknown", + "Unknown", + "Unknown", + "Unknown", + "Unknown" + }; + public BlockFlower() { this(0); } @@ -42,24 +62,6 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ - "Poppy", - "Blue Orchid", - "Allium", - "Azure Bluet", - "Red Tulip", - "Orange Tulip", - "White Tulip", - "Pink Tulip", - "Oxeye Daisy", - "Cornflower", - "Lily of the Valley", - "Unknown", - "Unknown", - "Unknown", - "Unknown", - "Unknown" - }; return names[this.getDamage() & 0x0f]; } @@ -69,7 +71,6 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl int id = down.getId(); if (id == Block.GRASS || id == Block.DIRT || id == Block.FARMLAND || id == Block.PODZOL || id == MYCELIUM) { this.getLevel().setBlock(block, this, true); - return true; } return false; @@ -100,8 +101,8 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0f) { //Bone meal - if (player != null && (player.gamemode & 0x01) == 0) { + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + if (player != null && !player.isCreative()) { item.count--; } @@ -109,15 +110,15 @@ public boolean onActivate(Item item, Player player) { for (int i = 0; i < 8; i++) { Vector3 vec = this.add( - ThreadLocalRandom.current().nextInt(-3, 4), - ThreadLocalRandom.current().nextInt(-1, 2), - ThreadLocalRandom.current().nextInt(-3, 4)); + Utils.random.nextInt(-3, 4), + Utils.random.nextInt(-1, 2), + Utils.random.nextInt(-3, 4)); - if (level.getBlock(vec).getId() == AIR && level.getBlock(vec.down()).getId() == GRASS && vec.getY() >= 0 && vec.getY() < 256) { - if (ThreadLocalRandom.current().nextInt(10) == 0) { + if (vec.getY() >= level.getMinBlockY() && vec.getY() <= level.getMaxBlockY() && level.getBlock(vec).getId() == AIR && level.getBlock(vec.down()).getId() == GRASS) { + if ((this.getDamage() == POPPY || this.getDamage() == DANDELION) && Utils.random.nextInt(10) == 0) { this.level.setBlock(vec, this.getUncommonFlower(), true); } else { - this.level.setBlock(vec, get(this.getId()), true); + this.level.setBlock(vec, get(this.getId(), this.getDamage()), true); } } } @@ -131,4 +132,9 @@ public boolean onActivate(Item item, Player player) { protected Block getUncommonFlower() { return get(DANDELION); } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockFlowerPot.java b/src/main/java/cn/nukkit/block/BlockFlowerPot.java index 1661b090f60..47949e09625 100644 --- a/src/main/java/cn/nukkit/block/BlockFlowerPot.java +++ b/src/main/java/cn/nukkit/block/BlockFlowerPot.java @@ -4,7 +4,8 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityFlowerPot; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemFlowerPot; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; @@ -23,8 +24,8 @@ public BlockFlowerPot(int meta) { super(meta); } - protected static boolean canPlaceIntoFlowerPot(int id) { - switch (id) { + private static boolean canPlaceIntoFlowerPot(Item item) { + switch (item.getId()) { case SAPLING: case DEAD_BUSH: case DANDELION: @@ -33,6 +34,33 @@ protected static boolean canPlaceIntoFlowerPot(int id) { case BROWN_MUSHROOM: case CACTUS: return true; + case TALL_GRASS: + if (item.getDamage() == 2 || item.getDamage() == 3) { + return true; + } + default: + return false; + } + } + + private static boolean canPlaceIntoFlowerPot(Block block) { + if (block == null) { + return false; + } + switch (block.getId()) { + case SAPLING: + case DEAD_BUSH: + case DANDELION: + case ROSE: + case RED_MUSHROOM: + case BROWN_MUSHROOM: + case CACTUS: + case BAMBOO: + case CRIMSON_FUNGUS: + case WARPED_FUNGUS: + case CRIMSON_ROOTS: + case WARPED_ROOTS: + return true; default: return false; } @@ -48,19 +76,24 @@ public int getId() { return FLOWER_POT_BLOCK; } - @Override - public double getHardness() { - return 0; + private boolean isSupportValid(Block block) { + return block.isSolid() || block instanceof BlockFence || block instanceof BlockWall || block instanceof BlockHopper; } @Override - public double getResistance() { + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!isSupportValid(down())) { + level.useBreakOn(this); + return type; + } + } return 0; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (face != BlockFace.UP) return false; + if (!isSupportValid(down())) return false; CompoundTag nbt = new CompoundTag() .putString("id", BlockEntity.FLOWER_POT) .putInt("x", (int) this.x) @@ -73,10 +106,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl nbt.put(aTag.getName(), aTag); } } - BlockEntityFlowerPot flowerPot = (BlockEntityFlowerPot) BlockEntity.createBlockEntity(BlockEntity.FLOWER_POT, getLevel().getChunk((int) block.x >> 4, (int) block.z >> 4), nbt); - if (flowerPot == null) return false; + BlockEntity.createBlockEntity(BlockEntity.FLOWER_POT, this.getChunk(), nbt); - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } @@ -85,26 +117,27 @@ public boolean canBeActivated() { return true; } - @Override - public boolean onActivate(Item item) { - return this.onActivate(item, null); - } - @Override public boolean onActivate(Item item, Player player) { BlockEntity blockEntity = getLevel().getBlockEntity(this); if (!(blockEntity instanceof BlockEntityFlowerPot)) return false; - - if (blockEntity.namedTag.getShort("item") != AIR || blockEntity.namedTag.getInt("mData") != AIR) { - if (!canPlaceIntoFlowerPot(item.getId())) { + if (blockEntity.namedTag.getShort("item") != AIR) { + if (!canPlaceIntoFlowerPot(item) && !canPlaceIntoFlowerPot(item.getBlockUnsafe())) { int id = blockEntity.namedTag.getShort("item"); - if (id == AIR) id = blockEntity.namedTag.getInt("mData"); - for (Item drop : player.getInventory().addItem(Item.get(id, blockEntity.namedTag.getInt("data")))) { - player.dropItem(drop); + if (id > 255) { + for (Item drop : player.getInventory().addItem(new ItemBlock(Block.get(id)))) { + player.dropItem(drop); + } + } else { + for (Item drop : player.getInventory().addItem(Item.get(id, blockEntity.namedTag.getInt("data")))) { + player.dropItem(drop); + } } blockEntity.namedTag.putShort("item", AIR); blockEntity.namedTag.putInt("data", 0); + blockEntity.setDirty(); + this.setDamage(0); this.level.setBlock(this, this, true); ((BlockEntityFlowerPot) blockEntity).spawnToAll(); @@ -112,28 +145,25 @@ public boolean onActivate(Item item, Player player) { } return false; } - int itemID; - int itemMeta; - if (!canPlaceIntoFlowerPot(item.getId())) { + if (!canPlaceIntoFlowerPot(item)) { Block block = item.getBlockUnsafe(); - if (block == null || !canPlaceIntoFlowerPot(block.getId())) { + if (!canPlaceIntoFlowerPot(block)) { return true; } itemID = block.getId(); - itemMeta = item.getDamage(); } else { itemID = item.getId(); - itemMeta = item.getDamage(); } blockEntity.namedTag.putShort("item", itemID); - blockEntity.namedTag.putInt("data", itemMeta); + blockEntity.namedTag.putInt("data", item.getDamage()); + blockEntity.setDirty(); this.setDamage(1); this.getLevel().setBlock(this, this, true); ((BlockEntityFlowerPot) blockEntity).spawnToAll(); - if (player.isSurvival()) { + if (!player.isCreative()) { item.setCount(item.getCount() - 1); player.getInventory().setItemInHand(item.getCount() > 0 ? item : Item.get(Item.AIR)); } @@ -153,13 +183,20 @@ public Item[] getDrops(Item item) { } if (dropInside) { - return new Item[]{ - new ItemFlowerPot(), - Item.get(insideID, insideMeta, 1) - }; + if (insideID > 255) { + return new Item[]{ + Item.get(Item.FLOWER_POT), + new ItemBlock(Block.get(insideID)) + }; + } else { + return new Item[]{ + Item.get(Item.FLOWER_POT), + Item.get(insideID, insideMeta, 1) + }; + } } else { return new Item[]{ - new ItemFlowerPot() + Item.get(Item.FLOWER_POT) }; } } @@ -201,6 +238,21 @@ public boolean canPassThrough() { @Override public Item toItem() { - return new ItemFlowerPot(); + return Item.get(Item.FLOWER_POT); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation } } diff --git a/src/main/java/cn/nukkit/block/BlockFungus.java b/src/main/java/cn/nukkit/block/BlockFungus.java new file mode 100644 index 00000000000..deaa054567a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockFungus.java @@ -0,0 +1,89 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; + +import java.util.concurrent.ThreadLocalRandom; + +public abstract class BlockFungus extends BlockFlowable { + + protected BlockFungus() { + super(0); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!isValidSupport(this.down())) { + return false; + } + return super.place(item, block, target, face, fx, fy, fz, player); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL && !isValidSupport(down())) { + this.level.useBreakOn(this); + return type; + } + return 0; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.isNull()) { + return false; + } + + if (!(item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL)) { + return false; + } + + this.level.addParticle(new BoneMealParticle(this)); + if (player != null && !player.isCreative()) { + item.count--; + } + + Block down = this.down(); + if (!this.isValidSupport(down)) { + this.level.useBreakOn(this); + return true; + } + + if (!this.canGrowOn(down) || ThreadLocalRandom.current().nextFloat() >= 0.4) { + return true; + } + + this.grow(player); + return true; + } + + protected abstract boolean canGrowOn(Block support); + + protected boolean isValidSupport(Block support) { + switch (support.getId()) { + case GRASS: + case DIRT: + case PODZOL: + case FARMLAND: + case CRIMSON_NYLIUM: + case WARPED_NYLIUM: + case SOUL_SOIL: + case MYCELIUM: + return true; + default: + return false; + } + } + + @Override + public boolean canBeActivated() { + return true; + } + + public abstract boolean grow(Player cause); +} diff --git a/src/main/java/cn/nukkit/block/BlockFurnace.java b/src/main/java/cn/nukkit/block/BlockFurnace.java index 37851c63e3c..ac7ba3988c8 100644 --- a/src/main/java/cn/nukkit/block/BlockFurnace.java +++ b/src/main/java/cn/nukkit/block/BlockFurnace.java @@ -1,10 +1,13 @@ package cn.nukkit.block; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ -public class BlockFurnace extends BlockFurnaceBurning { +public class BlockFurnace extends BlockFurnaceBurning implements Faceable { public BlockFurnace() { this(0); @@ -30,7 +33,7 @@ public int getLightLevel() { } @Override - public boolean canHarvestWithHand() { - return false; + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java b/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java index 2ef0ca35b9a..048dfbfd712 100644 --- a/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java +++ b/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java @@ -12,15 +12,14 @@ import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.StringTag; import cn.nukkit.nbt.tag.Tag; -import cn.nukkit.utils.Faceable; import java.util.Map; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ -public class BlockFurnaceBurning extends BlockSolidMeta implements Faceable { +public class BlockFurnaceBurning extends BlockSolidMeta { public BlockFurnaceBurning() { this(0); @@ -67,8 +66,7 @@ public int getLightLevel() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = {2, 5, 3, 4}; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) @@ -88,8 +86,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityFurnace furnace = (BlockEntityFurnace) BlockEntity.createBlockEntity(BlockEntity.FURNACE, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - return furnace != null; + BlockEntity.createBlockEntity(BlockEntity.FURNACE, this.getChunk(), nbt); + return true; } @Override @@ -102,22 +100,11 @@ public boolean onBreak(Item item) { public boolean onActivate(Item item, Player player) { if (player != null) { BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityFurnace furnace; - if (t instanceof BlockEntityFurnace) { - furnace = (BlockEntityFurnace) t; - } else { - CompoundTag nbt = new CompoundTag() - .putList(new ListTag<>("Items")) - .putString("id", BlockEntity.FURNACE) - .putInt("x", (int) this.x) - .putInt("y", (int) this.y) - .putInt("z", (int) this.z); - furnace = (BlockEntityFurnace) BlockEntity.createBlockEntity(BlockEntity.FURNACE, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - if (furnace == null) { - return false; - } + if (!(t instanceof BlockEntityFurnace)) { + return false; } + BlockEntityFurnace furnace = (BlockEntityFurnace) t; if (furnace.namedTag.contains("Lock") && furnace.namedTag.get("Lock") instanceof StringTag) { if (!furnace.namedTag.getString("Lock").equals(item.getCustomName())) { return true; @@ -132,12 +119,12 @@ public boolean onActivate(Item item, Player player) { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.FURNACE)); + return new ItemBlock(Block.get(FURNACE)); } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ this.toItem() }; @@ -167,7 +154,7 @@ public boolean canHarvestWithHand() { } @Override - public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation } } diff --git a/src/main/java/cn/nukkit/block/BlockGlass.java b/src/main/java/cn/nukkit/block/BlockGlass.java index 219e4df9dcd..c754c9c8168 100644 --- a/src/main/java/cn/nukkit/block/BlockGlass.java +++ b/src/main/java/cn/nukkit/block/BlockGlass.java @@ -4,14 +4,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockGlass extends BlockTransparent { - public BlockGlass() { - } - @Override public int getId() { return GLASS; diff --git a/src/main/java/cn/nukkit/block/BlockGlassPane.java b/src/main/java/cn/nukkit/block/BlockGlassPane.java index 3f04d2bee2b..e418f961fa4 100644 --- a/src/main/java/cn/nukkit/block/BlockGlassPane.java +++ b/src/main/java/cn/nukkit/block/BlockGlassPane.java @@ -9,9 +9,6 @@ */ public class BlockGlassPane extends BlockThin { - public BlockGlassPane() { - } - @Override public String getName() { return "Glass Pane"; @@ -46,4 +43,9 @@ public BlockColor getColor() { public boolean canSilkTouch() { return true; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockGlassPaneStained.java b/src/main/java/cn/nukkit/block/BlockGlassPaneStained.java index 2c37ea9367b..85247cb6b85 100644 --- a/src/main/java/cn/nukkit/block/BlockGlassPaneStained.java +++ b/src/main/java/cn/nukkit/block/BlockGlassPaneStained.java @@ -20,7 +20,7 @@ public BlockGlassPaneStained(int meta) { @Override public int getFullId() { - return (getId() << 4) + getDamage(); + return (STAINED_GLASS_PANE << Block.DATA_BITS) + meta; } @Override @@ -30,7 +30,7 @@ public int getId() { @Override public String getName() { - return getDyeColor().getName() + " stained glass pane"; + return getDyeColor().getName() + " Stained Glass Pane"; } @Override @@ -39,7 +39,7 @@ public BlockColor getColor() { } public DyeColor getDyeColor() { - return DyeColor.getByWoolData(getDamage()); + return DyeColor.getByWoolData(meta); } @Override @@ -51,9 +51,4 @@ public final int getDamage() { public final void setDamage(int meta) { this.meta = meta; } - - @Override - public boolean canSilkTouch() { - return true; - } } diff --git a/src/main/java/cn/nukkit/block/BlockGlassStained.java b/src/main/java/cn/nukkit/block/BlockGlassStained.java index d27e300b820..7f746b4365a 100644 --- a/src/main/java/cn/nukkit/block/BlockGlassStained.java +++ b/src/main/java/cn/nukkit/block/BlockGlassStained.java @@ -20,7 +20,7 @@ public BlockGlassStained(int meta) { @Override public int getFullId() { - return (getId() << 4) + getDamage(); + return (this.getId() << Block.DATA_BITS) + this.getDamage(); } @Override @@ -35,11 +35,11 @@ public String getName() { @Override public BlockColor getColor() { - return DyeColor.getByWoolData(getDamage()).getColor(); + return DyeColor.getByWoolData(meta).getColor(); } public DyeColor getDyeColor() { - return DyeColor.getByWoolData(getDamage()); + return DyeColor.getByWoolData(meta); } @Override @@ -51,9 +51,4 @@ public final int getDamage() { public final void setDamage(int meta) { this.meta = meta; } - - @Override - public boolean canSilkTouch() { - return true; - } } diff --git a/src/main/java/cn/nukkit/block/BlockGlassTinted.java b/src/main/java/cn/nukkit/block/BlockGlassTinted.java new file mode 100644 index 00000000000..b4bf8595d0d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockGlassTinted.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockGlassTinted extends BlockGlass { + + public BlockGlassTinted() { + // Does nothing + } + + @Override + public String getName() { + return "Tinted Glass"; + } + + @Override + public int getId() { + return TINTED_GLASS; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[] { this.toItem() }; + } + + @Override + public boolean canSilkTouch() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockGlowLichen.java b/src/main/java/cn/nukkit/block/BlockGlowLichen.java new file mode 100644 index 00000000000..4be29eefbed --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockGlowLichen.java @@ -0,0 +1,204 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.BlockPropertiesHelper; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.customblock.properties.BooleanBlockProperty; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +import java.util.EnumSet; +import java.util.Set; + +public class BlockGlowLichen extends BlockTransparentMeta implements BlockPropertiesHelper { + + // Currently multi_face_direction_bits: 0x01 - down, 0x02 - up, 0x04 - north, 0x08 - south, 0x10 - west, 0x20 - east + private static final BooleanBlockProperty CONNECTION_DOWN = new BooleanBlockProperty("connection_down", false); + private static final BooleanBlockProperty CONNECTION_UP = new BooleanBlockProperty("connection_up", false); + private static final BooleanBlockProperty CONNECTION_NORTH = new BooleanBlockProperty("connection_north", false); + private static final BooleanBlockProperty CONNECTION_SOUTH = new BooleanBlockProperty("connection_south", false); + private static final BooleanBlockProperty CONNECTION_WEST = new BooleanBlockProperty("connection_west", false); + private static final BooleanBlockProperty CONNECTION_EAST = new BooleanBlockProperty("connection_east", false); + + private static final BlockProperties PROPERTIES = new BlockProperties(CONNECTION_DOWN, CONNECTION_UP, CONNECTION_NORTH, CONNECTION_SOUTH, CONNECTION_WEST, CONNECTION_EAST); + + public BlockGlowLichen() { + this(0); + } + + public BlockGlowLichen(int meta) { + super(meta); + } + + @Override + public int getId() { + return GLOW_LICHEN; + } + + @Override + public String getName() { + return "Glow Lichen"; + } + + @Override + public BlockProperties getBlockProperties() { + return PROPERTIES; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!this.canPlaceOn(block.down(), target) || !target.isSolid()) { + return false; + } + + if (block.getId() == GLOW_LICHEN) { + this.setDamage(block.getDamage()); + } else { + this.setDamage(0); + } + + this.setBlockFace(face.getOpposite(), true); + this.getLevel().setBlock(this, this, false, true); + return true; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isShears()) { + return new Item[] { this.toItem() }; + } + return new Item[0]; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.getLevel().useBreakOn(this, null, null, true); + } else if (type != Level.BLOCK_UPDATE_NORMAL) { + return type; + } + + boolean update = false; + boolean support = false; + + Set faces = this.getSupportedFaces(); + for (BlockFace face : faces) { + Block block = this.getLevel().getBlock(this.getSide(face)); + if (block.isSolid()) { + support = true; + } else { + update = true; + this.setBlockFace(face, false); + } + } + + if (!support) { + this.getLevel().scheduleUpdate(this, 1); + } else if (update) { + this.getLevel().setBlock(this, this, false, true); + } + return type; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public double getHardness() { + return 0.2; + } + + @Override + public int getLightLevel() { + return 7; + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return null; + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } + + public void setBlockFace(BlockFace face, boolean value) { + switch (face) { + case UP: + this.setBooleanValue(CONNECTION_UP, value); + break; + case DOWN: + this.setBooleanValue(CONNECTION_DOWN, value); + break; + case NORTH: + this.setBooleanValue(CONNECTION_NORTH, value); + break; + case SOUTH: + this.setBooleanValue(CONNECTION_SOUTH, value); + break; + case WEST: + this.setBooleanValue(CONNECTION_WEST, value); + break; + case EAST: + this.setBooleanValue(CONNECTION_EAST, value); + break; + + } + } + + public boolean hasBlockFace(BlockFace face) { + switch (face) { + case UP: + return this.getBooleanValue(CONNECTION_UP); + case DOWN: + return this.getBooleanValue(CONNECTION_DOWN); + case NORTH: + return this.getBooleanValue(CONNECTION_NORTH); + case SOUTH: + return this.getBooleanValue(CONNECTION_SOUTH); + case WEST: + return this.getBooleanValue(CONNECTION_WEST); + case EAST: + return this.getBooleanValue(CONNECTION_EAST); + + } + return false; + } + + public Set getSupportedFaces() { + EnumSet faces = EnumSet.noneOf(BlockFace.class); + for (BlockFace face : BlockFace.values()) { + if (this.hasBlockFace(face)) { + faces.add(face); + } + } + return faces; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockGlowstone.java b/src/main/java/cn/nukkit/block/BlockGlowstone.java index 77e95fdd708..99eebf56fed 100644 --- a/src/main/java/cn/nukkit/block/BlockGlowstone.java +++ b/src/main/java/cn/nukkit/block/BlockGlowstone.java @@ -1,20 +1,16 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemGlowstoneDust; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.math.MathHelper; import cn.nukkit.utils.BlockColor; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/6 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockGlowstone extends BlockTransparent { - public BlockGlowstone() { - } @Override public String getName() { @@ -43,16 +39,19 @@ public int getLightLevel() { @Override public Item[] getDrops(Item item) { - Random random = new Random(); - int count = 2 + random.nextInt(3); + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int count = 2 + Utils.random.nextInt(3); Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - count += random.nextInt(fortune.getLevel() + 1); + count += Utils.random.nextInt(fortune.getLevel() + 1); } return new Item[]{ - new ItemGlowstoneDust(0, MathHelper.clamp(count, 1, 4)) + Item.get(Item.GLOWSTONE_DUST, 0, MathHelper.clamp(count, 1, 4)) }; } diff --git a/src/main/java/cn/nukkit/block/BlockGold.java b/src/main/java/cn/nukkit/block/BlockGold.java index 35b92555d88..7124c8445f2 100644 --- a/src/main/java/cn/nukkit/block/BlockGold.java +++ b/src/main/java/cn/nukkit/block/BlockGold.java @@ -5,15 +5,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockGold extends BlockSolid { - - public BlockGold() { - } - @Override public int getId() { return GOLD_BLOCK; @@ -21,7 +17,7 @@ public int getId() { @Override public String getName() { - return "Gold Block"; + return "Block of Gold"; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockGrass.java b/src/main/java/cn/nukkit/block/BlockGrass.java index 3da4fe6faa7..be7e83fb3e0 100644 --- a/src/main/java/cn/nukkit/block/BlockGrass.java +++ b/src/main/java/cn/nukkit/block/BlockGrass.java @@ -4,14 +4,18 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockSpreadEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; import cn.nukkit.level.generator.object.ObjectTallGrass; import cn.nukkit.level.particle.BoneMealParticle; -import cn.nukkit.math.NukkitRandom; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockGrass extends BlockDirt { @@ -21,7 +25,6 @@ public BlockGrass() { } public BlockGrass(int meta) { - // Grass can't have meta. super(0); } @@ -47,8 +50,8 @@ public String getName() { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0F) { - ObjectTallGrass.growGrass(this.getLevel(), this, new NukkitRandom()); + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + ObjectTallGrass.growGrass(this.getLevel(), this); this.level.addParticle(new BoneMealParticle(this)); if (player != null) { if (!player.isCreative()) { @@ -61,6 +64,9 @@ public boolean onActivate(Item item, Player player) { if (up instanceof BlockAir || up instanceof BlockFlowable) { item.useOn(this); this.getLevel().setBlock(this, Block.get(FARMLAND)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } return true; } } else if (item.isShovel()) { @@ -68,6 +74,9 @@ public boolean onActivate(Item item, Player player) { if (up instanceof BlockAir || up instanceof BlockFlowable) { item.useOn(this); this.getLevel().setBlock(this, Block.get(GRASS_PATH)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } return true; } } @@ -88,10 +97,9 @@ public int onUpdate(int type) { return 0; } - NukkitRandom random = new NukkitRandom(); - int xx = random.nextRange((int) x - 1, (int) x + 1); - int yy = random.nextRange((int) y - 2, (int) y + 2); - int zz = random.nextRange((int) z - 1, (int) z + 1); + int xx = Utils.rand((int) x - 1, (int) x + 1); + int yy = Utils.rand((int) y - 2, (int) y + 2); + int zz = Utils.rand((int) z - 1, (int) z + 1); Block block = this.getLevel().getBlock(xx, yy, zz); if (block.getId() == Block.DIRT && block.getDamage() == 0) { up = block.up(); @@ -119,11 +127,18 @@ public boolean canSilkTouch() { @Override public int getFullId() { - return this.getId() << 4; + return this.getId() << Block.DATA_BITS; } @Override public void setDamage(int meta) { + } + @Override + public Item[] getDrops(Item item) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + return new Item[]{new ItemBlock(Block.get(BlockID.DIRT))}; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockGrassPath.java b/src/main/java/cn/nukkit/block/BlockGrassPath.java index e8bde73f525..8409c798abb 100644 --- a/src/main/java/cn/nukkit/block/BlockGrassPath.java +++ b/src/main/java/cn/nukkit/block/BlockGrassPath.java @@ -2,7 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Sound; import cn.nukkit.utils.BlockColor; /** @@ -11,9 +11,6 @@ */ public class BlockGrassPath extends BlockGrass { - public BlockGrassPath() { - } - @Override public int getId() { return GRASS_PATH; @@ -24,29 +21,11 @@ public String getName() { return "Grass Path"; } - @Override - public int getToolType() { - return ItemTool.TYPE_SHOVEL; - } - - @Override - public double getMaxY() { - return this.y + 1; - } - @Override public double getResistance() { return 3.25; } - @Override - public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } - - @Override - public boolean canSilkTouch() { - return true; - } - @Override public int onUpdate(int type) { return 0; @@ -55,11 +34,27 @@ public int onUpdate(int type) { @Override public boolean onActivate(Item item, Player player) { if (item.isHoe()) { - item.useOn(this); - this.getLevel().setBlock(this, get(FARMLAND), true); - return true; + Block up = this.up(); + if (up instanceof BlockAir || up instanceof BlockFlowable) { + item.useOn(this); + this.getLevel().setBlock(this, get(FARMLAND), true); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + return true; + } } return false; } + + @Override + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } + + @Override + public double getMaxY() { + return this.y + 0.9375; + } } diff --git a/src/main/java/cn/nukkit/block/BlockGravel.java b/src/main/java/cn/nukkit/block/BlockGravel.java index d298404c7d2..df8fab2ce86 100644 --- a/src/main/java/cn/nukkit/block/BlockGravel.java +++ b/src/main/java/cn/nukkit/block/BlockGravel.java @@ -1,21 +1,21 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemFlint; +import cn.nukkit.item.ItemDye; import cn.nukkit.item.ItemTool; - -import java.util.Random; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.generator.object.ObjectTallGrass; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockGravel extends BlockFallable { - - public BlockGravel() { - } - @Override public int getId() { return GRAVEL; @@ -43,9 +43,9 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (new Random().nextInt(9) == 0) { + if (Utils.random.nextInt(9) == 0 && !item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { return new Item[]{ - new ItemFlint() + Item.get(Item.FLINT) }; } else { return new Item[]{ @@ -53,9 +53,38 @@ public Item[] getDrops(Item item) { }; } } - + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_BLOCK_COLOR; + } + @Override public boolean canSilkTouch() { return true; } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null && item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + Block up = this.up(); + if (up instanceof BlockWater) { + if (!player.isCreative()) { + item.count--; + } + this.level.addParticle(new BoneMealParticle(this)); + if (up.getDamage() == 0 && up.up() instanceof BlockWater) { + ObjectTallGrass.growSeagrass(this.getLevel(), this); + } + return true; + } + } + + return false; + } + + @Override + public boolean canBeActivated() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockGrindstone.java b/src/main/java/cn/nukkit/block/BlockGrindstone.java new file mode 100644 index 00000000000..e96c1e03870 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockGrindstone.java @@ -0,0 +1,135 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; + +public class BlockGrindstone extends BlockTransparentMeta implements Faceable { + + public static final int TYPE_ATTACHMENT_STANDING = 0; + public static final int TYPE_ATTACHMENT_HANGING = 1; + public static final int TYPE_ATTACHMENT_SIDE = 2; + public static final int TYPE_ATTACHMENT_MULTIPLE = 3; + + public BlockGrindstone() { + this(0); + } + + public BlockGrindstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Grindstone"; + } + + @Override + public int getId() { + return GRINDSTONE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.IRON_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(getDamage() & 0b11); + } + + public void setBlockFace(BlockFace face) { + if (face.getHorizontalIndex() == -1) { + return; + } + setDamage(getDamage() & (DATA_MASK ^ 0b11) | face.getHorizontalIndex()); + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(GRINDSTONE)); + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block.getId() != AIR && block.canBeReplaced()) { + face = BlockFace.UP; + } + + switch (face) { + case UP: + this.setAttachmentType(TYPE_ATTACHMENT_STANDING); + this.setBlockFace(player.getDirection().getOpposite()); + break; + case DOWN: + this.setAttachmentType(TYPE_ATTACHMENT_HANGING); + this.setBlockFace(player.getDirection().getOpposite()); + break; + default: + this.setBlockFace(face); + this.setAttachmentType(TYPE_ATTACHMENT_SIDE); + } + + if (!this.checkSupport()) { + return false; + } + + return super.place(item, block, target, face, fx, fy, fz, player); + } + + public int getAttachmentType() { + return (getDamage() & 0b1100) >> 2 & 0b11; + } + + public void setAttachmentType(int attachmentType) { + attachmentType = attachmentType & 0b11; + setDamage(getDamage() & (DATA_MASK ^ 0b1100) | (attachmentType << 2)); + } + + private boolean checkSupport() { + switch (this.getAttachmentType()) { + case TYPE_ATTACHMENT_STANDING: + if (down().getId() != AIR) { + return true; + } + break; + case TYPE_ATTACHMENT_HANGING: + if (up().getId() != AIR) { + return true; + } + break; + case TYPE_ATTACHMENT_SIDE: + BlockFace blockFace = getBlockFace(); + if (getSide(blockFace.getOpposite()).getId() != AIR) { + return true; + } + break; + } + + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHayBale.java b/src/main/java/cn/nukkit/block/BlockHayBale.java index 7bef3ed7b47..963926520d5 100644 --- a/src/main/java/cn/nukkit/block/BlockHayBale.java +++ b/src/main/java/cn/nukkit/block/BlockHayBale.java @@ -12,6 +12,16 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockHayBale extends BlockSolidMeta implements Faceable { + + private static final short[] faces = { + 0, + 0, + 0b1000, + 0b1000, + 0b0100, + 0b0100, + }; + public BlockHayBale() { this(0); } @@ -40,11 +50,6 @@ public double getResistance() { return 2.5; } - @Override - public int getToolType() { - return ItemTool.TYPE_HOE; - } - @Override public int getBurnChance() { return 60; @@ -55,16 +60,13 @@ public int getBurnAbility() { return 20; } + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = new int[]{ - 0, - 0, - 0b1000, - 0b1000, - 0b0100, - 0b0100, - }; this.setDamage((this.getDamage() & 0x03) | faces[face.getIndex()]); this.getLevel().setBlock(block, this, true, true); @@ -78,7 +80,7 @@ public BlockColor getColor() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockHoneyBlock.java b/src/main/java/cn/nukkit/block/BlockHoneyBlock.java new file mode 100644 index 00000000000..2e6058b30c0 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHoneyBlock.java @@ -0,0 +1,56 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.math.Vector3; + +public class BlockHoneyBlock extends BlockSolid { + + @Override + public String getName() { + return "Honey Block"; + } + + @Override + public int getId() { + return HONEY_BLOCK; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 0; + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + if (!entity.onGround && entity.motionY <= 0.08 && !(entity instanceof Player)) { + double ex = Math.abs(x + 0.5D - entity.x); + double ez = Math.abs(z + 0.5D - entity.z); + double width = 0.4375D + (double)(entity.getWidth() / 2.0F); + if (ex + 1.0E-3D > width || ez + 1.0E-3D > width) { + Vector3 motion = entity.getMotion(); + motion.y = -0.05; + if (entity.motionY < -0.13) { + double m = -0.05 / entity.motionY; + motion.x *= m; + motion.z *= m; + } + + if (!entity.getMotion().equals(motion)) { + entity.setMotion(motion); + } + entity.resetFallDistance(); + } + } + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHoneycombBlock.java b/src/main/java/cn/nukkit/block/BlockHoneycombBlock.java new file mode 100644 index 00000000000..7c477911b66 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHoneycombBlock.java @@ -0,0 +1,37 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockHoneycombBlock extends BlockSolid { + + @Override + public String getName() { + return "Honeycomb Block"; + } + + @Override + public int getId() { + return HONEYCOMB_BLOCK; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 0.6; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_NONE; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHopper.java b/src/main/java/cn/nukkit/block/BlockHopper.java index dc58c00cf10..1b6c8eb13ca 100644 --- a/src/main/java/cn/nukkit/block/BlockHopper.java +++ b/src/main/java/cn/nukkit/block/BlockHopper.java @@ -5,7 +5,6 @@ import cn.nukkit.blockentity.BlockEntityHopper; import cn.nukkit.inventory.ContainerInventory; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemHopper; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -56,13 +55,13 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl this.setDamage(facing.getIndex()); - boolean powered = this.level.isBlockPowered(this.getLocation()); + boolean powered = this.level.isBlockPowered(this); if (powered == this.isEnabled()) { this.setEnabled(!powered); } - this.level.setBlock(this, this); + this.getLevel().setBlock(this, this, true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) @@ -71,8 +70,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl .putInt("y", (int) this.y) .putInt("z", (int) this.z); - BlockEntityHopper hopper = (BlockEntityHopper) BlockEntity.createBlockEntity(BlockEntity.HOPPER, this.level.getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), nbt); - return hopper != null; + BlockEntity.createBlockEntity(BlockEntity.HOPPER, this.getChunk(), nbt); + return true; } @Override @@ -123,11 +122,11 @@ public void setEnabled(boolean enabled) { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { - boolean powered = this.level.isBlockPowered(this.getLocation()); + boolean powered = this.level.isBlockPowered(this); if (powered == this.isEnabled()) { this.setEnabled(!powered); - this.level.setBlock(this, this, true, false); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client } return type; @@ -152,7 +151,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemHopper(); + return Item.get(Item.HOPPER); } @Override @@ -162,6 +161,16 @@ public boolean canHarvestWithHand() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation } } diff --git a/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java b/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java index 3ee1bb5d380..93f21cf1315 100644 --- a/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java +++ b/src/main/java/cn/nukkit/block/BlockHugeMushroomBrown.java @@ -3,8 +3,9 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 28.01.2016. @@ -39,20 +40,12 @@ public double getHardness() { return 0.2; } - @Override - public double getResistance() { - return 1; - } - @Override public Item[] getDrops(Item item) { - if (new NukkitRandom().nextRange(1, 20) == 0) { - return new Item[]{ - new ItemBlock(Block.get(BlockID.BROWN_MUSHROOM)) - }; - } else { - return new Item[0]; + if (item != null && item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; } + return new Item[]{new ItemBlock(Block.get(BROWN_MUSHROOM), 0, Utils.rand() ? Utils.rand(0, 2) : 0)}; } @Override @@ -61,5 +54,7 @@ public boolean canSilkTouch() { } @Override - public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } } diff --git a/src/main/java/cn/nukkit/block/BlockHugeMushroomRed.java b/src/main/java/cn/nukkit/block/BlockHugeMushroomRed.java index 4b24523a5ad..08d19cbff7f 100644 --- a/src/main/java/cn/nukkit/block/BlockHugeMushroomRed.java +++ b/src/main/java/cn/nukkit/block/BlockHugeMushroomRed.java @@ -3,8 +3,9 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 28.01.2016. @@ -39,20 +40,12 @@ public double getHardness() { return 0.2; } - @Override - public double getResistance() { - return 1; - } - @Override public Item[] getDrops(Item item) { - if (new NukkitRandom().nextRange(1, 20) == 0) { - return new Item[]{ - new ItemBlock(Block.get(BlockID.RED_MUSHROOM)) - }; - } else { - return new Item[0]; + if (item != null && item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; } + return new Item[]{new ItemBlock(Block.get(RED_MUSHROOM), 0, Utils.rand() ? Utils.rand(0, 2) : 0)}; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockHyphaeCrimson.java b/src/main/java/cn/nukkit/block/BlockHyphaeCrimson.java new file mode 100644 index 00000000000..bff5138dd04 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHyphaeCrimson.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockHyphaeCrimson extends BlockStem { + + public BlockHyphaeCrimson() { + this(0); + } + + public BlockHyphaeCrimson(int meta) { + super(meta); + } + + @Override + public int getId() { + return CRIMSON_HYPHAE; + } + + @Override + public int getStrippedId() { + return STRIPPED_CRIMSON_HYPHAE; + } + + @Override + public String getName() { + return "Crimson Hyphae"; + } + + @Override + public double getHardness() { + return 0.3; // 2 + } + + @Override + public double getResistance() { + return 2; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_HYPHAE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHyphaeStrippedCrimson.java b/src/main/java/cn/nukkit/block/BlockHyphaeStrippedCrimson.java new file mode 100644 index 00000000000..0bc86caba82 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHyphaeStrippedCrimson.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockHyphaeStrippedCrimson extends BlockStemStripped { + + public BlockHyphaeStrippedCrimson() { + this(0); + } + + public BlockHyphaeStrippedCrimson(int meta) { + super(meta); + } + + @Override + public int getId() { + return STRIPPED_CRIMSON_HYPHAE; + } + + @Override + public String getName() { + return "Crimson Stripped Hyphae"; + } + + @Override + public double getHardness() { + return 0.3; // 2 + } + + @Override + public BlockColor getColor() { + return BlockColor.CRIMSON_HYPHAE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHyphaeStrippedWarped.java b/src/main/java/cn/nukkit/block/BlockHyphaeStrippedWarped.java new file mode 100644 index 00000000000..83d16c54bc3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHyphaeStrippedWarped.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockHyphaeStrippedWarped extends BlockStemStripped { + + public BlockHyphaeStrippedWarped() { + this(0); + } + + public BlockHyphaeStrippedWarped(int meta) { + super(meta); + } + + @Override + public int getId() { + return STRIPPED_WARPED_HYPHAE; + } + + @Override + public String getName() { + return "Warped Stripped Hyphae"; + } + + @Override + public double getHardness() { + return 0.3; // 2 + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_HYPHAE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockHyphaeWarped.java b/src/main/java/cn/nukkit/block/BlockHyphaeWarped.java new file mode 100644 index 00000000000..e10bde7a5f6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockHyphaeWarped.java @@ -0,0 +1,43 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockHyphaeWarped extends BlockStem { + + public BlockHyphaeWarped() { + this(0); + } + + public BlockHyphaeWarped(int meta) { + super(meta); + } + + @Override + public int getId() { + return WARPED_HYPHAE; + } + + @Override + public int getStrippedId() { + return STRIPPED_WARPED_HYPHAE; + } + + @Override + public String getName() { + return "Warped Hyphae"; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_HYPHAE_BLOCK_COLOR; + } + + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 2; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockID.java b/src/main/java/cn/nukkit/block/BlockID.java index 2bc85eaa001..d071dce3389 100644 --- a/src/main/java/cn/nukkit/block/BlockID.java +++ b/src/main/java/cn/nukkit/block/BlockID.java @@ -1,6 +1,10 @@ package cn.nukkit.block; +/** + * List of block IDs + */ public interface BlockID { + int AIR = 0; int STONE = 1; int GRASS = 2; @@ -49,6 +53,7 @@ public interface BlockID { int PISTON = 33; int PISTON_HEAD = 34; int WOOL = 35; + int WHITE_WOOL = 35; int DANDELION = 37; int POPPY = 38; int ROSE = 38; @@ -102,7 +107,6 @@ public interface BlockID { int STONE_PRESSURE_PLATE = 70; int IRON_DOOR_BLOCK = 71; int WOODEN_PRESSURE_PLATE = 72; - int REDSTONE_ORE = 73; int GLOWING_REDSTONE_ORE = 74; int LIT_REDSTONE_ORE = 74; @@ -171,7 +175,6 @@ public interface BlockID { int DRAGON_EGG = 122; int REDSTONE_LAMP = 123; int LIT_REDSTONE_LAMP = 124; - //Note: dropper CAN NOT BE HARVESTED WITH HAND -- canHarvestWithHand method should be overridden FALSE. int DROPPER = 125; int ACTIVATOR_RAIL = 126; int COCOA = 127; @@ -258,6 +261,9 @@ public interface BlockID { int FENCE_GATE_ACACIA = 187; int REPEATING_COMMAND_BLOCK = 188; int CHAIN_COMMAND_BLOCK = 189; + int HARD_GLASS_PANE = 190; + int HARD_STAINED_GLASS_PANE = 191; + int CHEMICAL_HEAT = 192; int SPRUCE_DOOR_BLOCK = 193; int BIRCH_DOOR_BLOCK = 194; int JUNGLE_DOOR_BLOCK = 195; @@ -267,11 +273,11 @@ public interface BlockID { int ITEM_FRAME_BLOCK = 199; int CHORUS_FLOWER = 200; int PURPUR_BLOCK = 201; - + int COLORED_TORCH_RG = 202; int PURPUR_STAIRS = 203; + int COLORED_TORCH_BP = 204; int UNDYED_SHULKER_BOX = 205; int END_BRICKS = 206; - //Note: frosted ice CAN NOT BE HARVESTED WITH HAND -- canHarvestWithHand method should be overridden FALSE. int FROSTED_ICE = 207; int ICE_FROSTED = 207; int END_ROD = 208; @@ -283,7 +289,7 @@ public interface BlockID { int BLOCK_NETHER_WART_BLOCK = 214; int RED_NETHER_BRICK = 215; int BONE_BLOCK = 216; - + // 217 not yet in Minecraft int SHULKER_BOX = 218; int PURPLE_GLAZED_TERRACOTTA = 219; int WHITE_GLAZED_TERRACOTTA = 220; @@ -296,6 +302,7 @@ public interface BlockID { int GRAY_GLAZED_TERRACOTTA = 227; int SILVER_GLAZED_TERRACOTTA = 228; int CYAN_GLAZED_TERRACOTTA = 229; + // 230 Chalkboard in Education Edition int BLUE_GLAZED_TERRACOTTA = 231; int BROWN_GLAZED_TERRACOTTA = 232; int GREEN_GLAZED_TERRACOTTA = 233; @@ -304,19 +311,351 @@ public interface BlockID { int CONCRETE = 236; int CONCRETE_POWDER = 237; int CONCRETEPOWDER = 237; - + int CHEMISTRY_TABLE = 238; + int UNDERWATER_TORCH = 239; int CHORUS_PLANT = 240; int STAINED_GLASS = 241; + int CAMERA_BLOCK = 242; int PODZOL = 243; int BEETROOT_BLOCK = 244; int STONECUTTER = 245; int GLOWING_OBSIDIAN = 246; - int NETHER_REACTOR = 247; //Should not be removed + int NETHER_REACTOR = 247; int INFO_UPDATE = 248; int INFO_UPDATE2 = 249; - int PISTON_EXTENSION = 250; - + int MOVING_BLOCK = 250; int OBSERVER = 251; int STRUCTURE_BLOCK = 252; + int HARD_GLASS = 253; + int HARD_STAINED_GLASS = 254; + int RESERVED6 = 255; + // 256 not yet in Minecraft + int PRISMARINE_STAIRS = 257; + int DARK_PRISMARINE_STAIRS = 258; + int PRISMARINE_BRICKS_STAIRS = 259; + int STRIPPED_SPRUCE_LOG = 260; + int STRIPPED_BIRCH_LOG = 261; + int STRIPPED_JUNGLE_LOG = 262; + int STRIPPED_ACACIA_LOG = 263; + int STRIPPED_DARK_OAK_LOG = 264; + int STRIPPED_OAK_LOG = 265; + int BLUE_ICE = 266; + // + int SEAGRASS = 385; + int CORAL = 386; + int CORAL_BLOCK = 387; + int CORAL_FAN = 388; + int CORAL_FAN_DEAD = 389; + int CORAL_FAN_HANG = 390; + int CORAL_FAN_HANG2 = 391; + int CORAL_FAN_HANG3 = 392; + int BLOCK_KELP = 393; + int DRIED_KELP_BLOCK = 394; + int ACACIA_BUTTON = 395; + int BIRCH_BUTTON = 396; + int DARK_OAK_BUTTON = 397; + int JUNGLE_BUTTON = 398; + int SPRUCE_BUTTON = 399; + int ACACIA_TRAPDOOR = 400; + int BIRCH_TRAPDOOR = 401; + int DARK_OAK_TRAPDOOR = 402; + int JUNGLE_TRAPDOOR = 403; + int SPRUCE_TRAPDOOR = 404; + int ACACIA_PRESSURE_PLATE = 405; + int BIRCH_PRESSURE_PLATE = 406; + int DARK_OAK_PRESSURE_PLATE = 407; + int JUNGLE_PRESSURE_PLATE = 408; + int SPRUCE_PRESSURE_PLATE = 409; + int CARVED_PUMPKIN = 410; + int SEA_PICKLE = 411; + int CONDUIT = 412; + // 413 not yet in Minecraft + int TURTLE_EGG = 414; + int BUBBLE_COLUMN = 415; + int BARRIER = 416; + int STONE_SLAB3 = 417; + int BAMBOO = 418; + int BAMBOO_SAPLING = 419; + int SCAFFOLDING = 420; + int STONE_SLAB4 = 421; + int DOUBLE_STONE_SLAB3 = 422; + int DOUBLE_STONE_SLAB4 = 423; + int GRANITE_STAIRS = 424; + int DIORITE_STAIRS = 425; + int ANDESITE_STAIRS = 426; + int POLISHED_GRANITE_STAIRS = 427; + int POLISHED_DIORITE_STAIRS = 428; + int POLISHED_ANDESITE_STAIRS = 429; + int MOSSY_STONE_BRICK_STAIRS = 430; + int SMOOTH_RED_SANDSTONE_STAIRS = 431; + int SMOOTH_SANDSTONE_STAIRS = 432; + int END_BRICK_STAIRS = 433; + int MOSSY_COBBLESTONE_STAIRS = 434; + int NORMAL_STONE_STAIRS = 435; + int SPRUCE_STANDING_SIGN = 436; + int SPRUCE_WALL_SIGN = 437; + int SMOOTH_STONE = 438; + int RED_NETHER_BRICK_STAIRS = 439; + int SMOOTH_QUARTZ_STAIRS = 440; + int BIRCH_STANDING_SIGN = 441; + int BIRCH_WALL_SIGN = 442; + int JUNGLE_STANDING_SIGN = 443; + int JUNGLE_WALL_SIGN = 444; + int ACACIA_STANDING_SIGN = 445; + int ACACIA_WALL_SIGN = 446; + int DARK_OAK_STANDING_SIGN = 447; + int DARK_OAK_WALL_SIGN = 448; + int LECTERN = 449; + int GRINDSTONE = 450; + int BLAST_FURNACE = 451; + int STONECUTTER_BLOCK = 452; + int SMOKER = 453; + int LIT_SMOKER = 454; + int CARTOGRAPHY_TABLE = 455; + int FLETCHING_TABLE = 456; + int SMITHING_TABLE = 457; + int BARREL = 458; + int LOOM = 459; + // + int BELL = 461; + int SWEET_BERRY_BUSH = 462; + int LANTERN = 463; + int CAMPFIRE_BLOCK = 464; + int LAVA_CAULDRON = 465; + int JIGSAW = 466; + int WOOD_BARK = 467; + int COMPOSTER = 468; + int LIT_BLAST_FURNACE = 469; + int LIGHT_BLOCK = 470; + int WITHER_ROSE = 471; + int PISTON_HEAD_STICKY = 472; + int BEE_NEST = 473; + int BEEHIVE = 474; + int HONEY_BLOCK = 475; + int HONEYCOMB_BLOCK = 476; + int LODESTONE = 477; + int CRIMSON_ROOTS = 478; + int WARPED_ROOTS = 479; + int CRIMSON_STEM = 480; + int WARPED_STEM = 481; + int WARPED_WART_BLOCK = 482; + int CRIMSON_FUNGUS = 483; + int WARPED_FUNGUS = 484; + int SHROOMLIGHT = 485; + int WEEPING_VINES = 486; + int CRIMSON_NYLIUM = 487; + int WARPED_NYLIUM = 488; + int BASALT = 489; + int POLISHED_BASALT = 490; + int SOUL_SOIL = 491; + int SOUL_FIRE = 492; + int NETHER_SPROUTS_BLOCK = 493; + int TARGET = 494; + int STRIPPED_CRIMSON_STEM = 495; + int STRIPPED_WARPED_STEM = 496; + int CRIMSON_PLANKS = 497; + int WARPED_PLANKS = 498; + int CRIMSON_DOOR_BLOCK = 499; + int WARPED_DOOR_BLOCK = 500; + int CRIMSON_TRAPDOOR = 501; + int WARPED_TRAPDOOR = 502; + + int CRIMSON_STANDING_SIGN = 505; + int WARPED_STANDING_SIGN = 506; + int CRIMSON_WALL_SIGN = 507; + int WARPED_WALL_SIGN = 508; + int CRIMSON_STAIRS = 509; + int WARPED_STAIRS = 510; + int CRIMSON_FENCE = 511; + int WARPED_FENCE = 512; + int CRIMSON_FENCE_GATE = 513; + int WARPED_FENCE_GATE = 514; + int CRIMSON_BUTTON = 515; + int WARPED_BUTTON = 516; + int CRIMSON_PRESSURE_PLATE = 517; + int WARPED_PRESSURE_PLATE = 518; + int CRIMSON_SLAB = 519; + int WARPED_SLAB = 520; + int CRIMSON_DOUBLE_SLAB = 521; + int WARPED_DOUBLE_SLAB = 522; + int SOUL_TORCH = 523; + int SOUL_LANTERN = 524; + int NETHERITE_BLOCK = 525; + int ANCIENT_DEBRIS = 526; + int RESPAWN_ANCHOR = 527; + int BLACKSTONE = 528; + int POLISHED_BLACKSTONE_BRICKS = 529; + int POLISHED_BLACKSTONE_BRICK_STAIRS = 530; + int BLACKSTONE_STAIRS = 531; + int BLACKSTONE_WALL = 532; + int POLISHED_BLACKSTONE_BRICK_WALL = 533; + int CHISELED_POLISHED_BLACKSTONE = 534; + int CRACKED_POLISHED_BLACKSTONE_BRICKS = 535; + int GILDED_BLACKSTONE = 536; + int BLACKSTONE_SLAB = 537; + int BLACKSTONE_DOUBLE_SLAB = 538; + int POLISHED_BLACKSTONE_BRICK_SLAB = 539; + int POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB = 540; + int CHAIN_BLOCK = 541; + int TWISTING_VINES = 542; + int NETHER_GOLD_ORE = 543; + int CRYING_OBSIDIAN = 544; + int SOUL_CAMPFIRE_BLOCK = 545; + int POLISHED_BLACKSTONE = 546; + int POLISHED_BLACKSTONE_STAIRS = 547; + int POLISHED_BLACKSTONE_SLAB = 548; + int POLISHED_BLACKSTONE_DOUBLE_SLAB = 549; + int POLISHED_BLACKSTONE_PRESSURE_PLATE = 550; + int POLISHED_BLACKSTONE_BUTTON = 551; + int POLISHED_BLACKSTONE_WALL = 552; + int WARPED_HYPHAE = 553; + int CRIMSON_HYPHAE = 554; + int STRIPPED_CRIMSON_HYPHAE = 555; + int STRIPPED_WARPED_HYPHAE = 556; + int CHISELED_NETHER_BRICKS = 557; + int CRACKED_NETHER_BRICKS = 558; + int QUARTZ_BRICKS = 559; + + int POWDER_SNOW = 561; + int SCULK_SENSOR = 562; + int POINTED_DRIPSTONE = 563; + // 564 (unused) + // 565 (unused) + int COPPER_ORE = 566; + int LIGHTNING_ROD = 567; + // 568 (unused) + // 569 (unused) + // 570 (unused) + // 571 (unused) + int DRIPSTONE_BLOCK = 572; + int ROOTED_DIRT = 573; + int HANGING_ROOTS = 574; + int MOSS_BLOCK = 575; + int SPORE_BLOSSOM = 576; + int CAVE_VINES = 577; + int BIG_DRIPLEAF = 578; + int AZALEA_LEAVES = 579; + int AZALEA_LEAVES_FLOWERED = 580; + int CALCITE = 581; + int AMETHYST_BLOCK = 582; + int BUDDING_AMETHYST = 583; + int AMETHYST_CLUSTER = 584; + int LARGE_AMETHYST_BUD = 585; + int MEDIUM_AMETHYST_BUD = 586; + int SMALL_AMETHYST_BUD = 587; + int TUFF = 588; + int TINTED_GLASS = 589; + int MOSS_CARPET = 590; + int SMALL_DRIPLEAF = 591; + int AZALEA = 592; + int FLOWERING_AZALEA = 593; + int GLOW_FRAME = 594; + int COPPER_BLOCK = 595; + int EXPOSED_COPPER = 596; + int WEATHERED_COPPER = 597; + int OXIDIZED_COPPER = 598; + int WAXED_COPPER = 599; + int WAXED_EXPOSED_COPPER = 600; + int WAXED_WEATHERED_COPPER = 601; + int CUT_COPPER = 602; + int EXPOSED_CUT_COPPER = 603; + int WEATHERED_CUT_COPPER = 604; + int OXIDIZED_CUT_COPPER = 605; + int WAXED_CUT_COPPER = 606; + int WAXED_EXPOSED_CUT_COPPER = 607; + int WAXED_WEATHERED_CUT_COPPER = 608; + int CUT_COPPER_STAIRS = 609; + int EXPOSED_CUT_COPPER_STAIRS = 610; + int WEATHERED_CUT_COPPER_STAIRS = 611; + int OXIDIZED_CUT_COPPER_STAIRS = 612; + int WAXED_CUT_COPPER_STAIRS = 613; + int WAXED_EXPOSED_CUT_COPPER_STAIRS = 614; + int WAXED_WEATHERED_CUT_COPPER_STAIRS = 615; + int CUT_COPPER_SLAB = 616; + int EXPOSED_CUT_COPPER_SLAB = 617; + int WEATHERED_CUT_COPPER_SLAB = 618; + int OXIDIZED_CUT_COPPER_SLAB = 619; + int WAXED_CUT_COPPER_SLAB = 620; + int WAXED_EXPOSED_CUT_COPPER_SLAB = 621; + int WAXED_WEATHERED_CUT_COPPER_SLAB = 622; + int DOUBLE_CUT_COPPER_SLAB = 623; + int EXPOSED_DOUBLE_CUT_COPPER_SLAB = 624; + int WEATHERED_DOUBLE_CUT_COPPER_SLAB = 625; + int OXIDIZED_DOUBLE_CUT_COPPER_SLAB = 626; + int WAXED_DOUBLE_CUT_COPPER_SLAB = 627; + int WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB = 628; + int WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB = 629; + int CAVE_VINES_BODY_WITH_BERRIES = 630; + int CAVE_VINES_HEAD_WITH_BERRIES = 631; + int SMOOTH_BASALT = 632; + int DEEPSLATE = 633; + int COBBLED_DEEPSLATE = 634; + int COBBLED_DEEPSLATE_SLAB = 635; + int COBBLED_DEEPSLATE_STAIRS = 636; + int COBBLED_DEEPSLATE_WALL = 637; + int POLISHED_DEEPSLATE = 638; + int POLISHED_DEEPSLATE_SLAB = 639; + int POLISHED_DEEPSLATE_STAIRS = 640; + int POLISHED_DEEPSLATE_WALL = 641; + int DEEPSLATE_TILES = 642; + int DEEPSLATE_TILE_SLAB = 643; + int DEEPSLATE_TILE_STAIRS = 644; + int DEEPSLATE_TILE_WALL = 645; + int DEEPSLATE_BRICKS = 646; + int DEEPSLATE_BRICK_SLAB = 647; + int DEEPSLATE_BRICK_STAIRS = 648; + int DEEPSLATE_BRICK_WALL = 649; + int CHISELED_DEEPSLATE = 650; + int COBBLED_DEEPSLATE_DOUBLE_SLAB = 651; + int POLISHED_DEEPSLATE_DOUBLE_SLAB = 652; + int DEEPSLATE_TILE_DOUBLE_SLAB = 653; + int DEEPSLATE_BRICK_DOUBLE_SLAB = 654; + int DEEPSLATE_LAPIS_ORE = 655; + int DEEPSLATE_IRON_ORE = 656; + int DEEPSLATE_GOLD_ORE = 657; + int DEEPSLATE_REDSTONE_ORE = 658; + int LIT_DEEPSLATE_REDSTONE_ORE = 659; + int DEEPSLATE_DIAMOND_ORE = 660; + int DEEPSLATE_COAL_ORE = 661; + int DEEPSLATE_EMERALD_ORE = 662; + int DEEPSLATE_COPPER_ORE = 663; + int CRACKED_DEEPSLATE_TILES = 664; + int CRACKED_DEEPSLATE_BRICKS = 665; + int GLOW_LICHEN = 666; + int CANDLE = 667; + int WHITE_CANDLE = 668; + int ORANGE_CANDLE = 669; + int MAGENTA_CANDLE = 670; + int LIGHT_BLUE_CANDLE = 671; + int YELLOW_CANDLE = 672; + int LIME_CANDLE = 673; + int PINK_CANDLE = 674; + int GRAY_CANDLE = 675; + int LIGHT_GRAY_CANDLE = 676; + int CYAN_CANDLE = 677; + int PURPLE_CANDLE = 678; + int BLUE_CANDLE = 679; + int BROWN_CANDLE = 680; + int GREEN_CANDLE = 681; + int RED_CANDLE = 682; + int BLACK_CANDLE = 683; + // + int WAXED_OXIDIZED_COPPER = 701; + int WAXED_OXIDIZED_CUT_COPPER = 702; + int WAXED_OXIDIZED_CUT_COPPER_STAIRS = 703; + int WAXED_OXIDIZED_CUT_COPPER_SLAB = 704; + int WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB = 705; + int RAW_IRON_BLOCK = 706; + int RAW_COPPER_BLOCK = 707; + int RAW_GOLD_BLOCK = 708; + int INFESTED_DEEPSLATE = 709; + + int MUD = 728; + int MUD_BRICKS = 730; + int PACKED_MUD = 732; + int MUD_BRICK_SLAB = 733; + int MUD_BRICK_DOUBLE_SLAB = 734; + int MUD_BRICK_STAIRS = 735; + int MUD_BRICK_WALL = 736; } diff --git a/src/main/java/cn/nukkit/block/BlockIce.java b/src/main/java/cn/nukkit/block/BlockIce.java index fe039468082..3bf6a2672d2 100644 --- a/src/main/java/cn/nukkit/block/BlockIce.java +++ b/src/main/java/cn/nukkit/block/BlockIce.java @@ -8,14 +8,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockIce extends BlockTransparent { - public BlockIce() { - } - @Override public int getId() { return ICE; @@ -28,7 +25,7 @@ public String getName() { @Override public double getResistance() { - return 2.5; + return 0.5; } @Override @@ -48,11 +45,10 @@ public int getToolType() { @Override public boolean onBreak(Item item) { - if (this.getLevel().getDimension() != Level.DIMENSION_NETHER) { - return this.getLevel().setBlock(this, Block.get(BlockID.WATER), true); - } else { + if (this.getLevel().getDimension() == Level.DIMENSION_NETHER || item.hasEnchantment(Enchantment.ID_SILK_TOUCH) || down().getId() == BlockID.AIR) { return super.onBreak(item); } + return this.getLevel().setBlock(this, Block.get(BlockID.WATER), true); } @Override @@ -82,7 +78,7 @@ public Item[] getDrops(Item item) { public BlockColor getColor() { return BlockColor.ICE_BLOCK_COLOR; } - + @Override public boolean canSilkTouch() { return true; diff --git a/src/main/java/cn/nukkit/block/BlockIceFrosted.java b/src/main/java/cn/nukkit/block/BlockIceFrosted.java new file mode 100644 index 00000000000..45007fd067e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockIceFrosted.java @@ -0,0 +1,125 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; + +public class BlockIceFrosted extends BlockTransparentMeta { + + public BlockIceFrosted() { + this(0); + } + + public BlockIceFrosted(int meta) { + super(meta); + } + + @Override + public int getId() { + return ICE_FROSTED; + } + + @Override + public String getName() { + return "Frosted Ice"; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public double getHardness() { + return 0.5; + } + + @Override + public double getFrictionFactor() { + return 0.98; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + boolean success = super.place(item, block, target, face, fx, fy, fz, player); + if (success) { + level.scheduleUpdate(this, Utils.random.nextInt(20, 40)); + } + return success; + } + + @Override + public boolean onBreak(Item item) { + level.setBlock(this, get(WATER), true); + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + int time = level.getTime() % Level.TIME_FULL; + if (((time < 13184 || time > 22800) || this.getLevel().getBlockLightAt((int) this.x, (int) this.y, (int) this.z) >= 12) && (Utils.random.nextInt(3) == 0 || countNeighbors() < 4)) { + slightlyMelt(true); + } else { + level.scheduleUpdate(this, Utils.random.nextInt(20, 40)); + } + } else if (type == Level.BLOCK_UPDATE_NORMAL) { + if (countNeighbors() < 2) { + level.setBlock(this, get(WATER), true); + } + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + int time = level.getTime() % Level.TIME_FULL; + if (((time < 13184 || time > 22800) || this.getLevel().getBlockLightAt((int) this.x, (int) this.y, (int) this.z) >= 12) && (Utils.random.nextInt(3) == 0 || countNeighbors() < 4)) { + slightlyMelt(true); + } + } + return super.onUpdate(type); + } + + @Override + public Item toItem() { + return Item.get(AIR); + } + + @Override + public BlockColor getColor() { + return BlockColor.ICE_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + protected void slightlyMelt(boolean isSource) { + int age = getDamage(); + if (age < 3) { + setDamage(age + 1); + level.setBlock(this, this, true); + level.scheduleUpdate(level.getBlock(this), Utils.random.nextInt(20, 40)); + } else { + level.setBlock(this, get(WATER), true); + if (isSource) { + for (BlockFace face : BlockFace.values()) { + Block block = getSide(face); + if (block instanceof BlockIceFrosted) { + ((BlockIceFrosted) block).slightlyMelt(false); + } + } + } + } + } + + private int countNeighbors() { + int neighbors = 0; + for (BlockFace face : BlockFace.values()) { + if (getSide(face).getId() == ICE_FROSTED && ++neighbors >= 4) { + return neighbors; + } + } + return neighbors; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockIcePacked.java b/src/main/java/cn/nukkit/block/BlockIcePacked.java index ae8f9ec26c0..0cb32b1ca62 100644 --- a/src/main/java/cn/nukkit/block/BlockIcePacked.java +++ b/src/main/java/cn/nukkit/block/BlockIcePacked.java @@ -1,17 +1,14 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockIcePacked extends BlockIce { - public BlockIcePacked() { - } - @Override public int getId() { return PACKED_ICE; @@ -23,29 +20,33 @@ public String getName() { } @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + public double getHardness() { + return 0.5; } @Override - public int onUpdate(int type) { - return 0; //not being melted + public boolean onBreak(Item item) { + return this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); } @Override - public boolean canHarvestWithHand() { - return false; + public Item[] getDrops(Item item) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{ + this.toItem() + }; + } + return new Item[0]; } - + @Override - public boolean onBreak(Item item) { - this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); //no water - return true; + public int onUpdate(int type) { + return 0; } @Override - public boolean canSilkTouch() { - return true; + public boolean canHarvestWithHand() { + return false; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockInfestedDeepslate.java b/src/main/java/cn/nukkit/block/BlockInfestedDeepslate.java new file mode 100644 index 00000000000..d17518da376 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockInfestedDeepslate.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockInfestedDeepslate extends BlockDeepslate { + + public BlockInfestedDeepslate() { + this(0); + } + + public BlockInfestedDeepslate(int meta) { + super(meta); + } + + @Override + public int getId() { + return INFESTED_DEEPSLATE; + } + + @Override + public String getName() { + return "Infested Deepslate"; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 0.75; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public boolean canSilkTouch() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockInfoUpdate.java b/src/main/java/cn/nukkit/block/BlockInfoUpdate.java new file mode 100644 index 00000000000..163608548ed --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockInfoUpdate.java @@ -0,0 +1,26 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockInfoUpdate extends BlockSolid { + + @Override + public int getId() { + return INFO_UPDATE; + } + + @Override + public String getName() { + return "Update Game Block"; + } + + @Override + public double getHardness() { + return 0.1; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockInfoUpdate2.java b/src/main/java/cn/nukkit/block/BlockInfoUpdate2.java new file mode 100644 index 00000000000..ba62efccc76 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockInfoUpdate2.java @@ -0,0 +1,21 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockInfoUpdate2 extends BlockSolid { + + @Override + public int getId() { + return INFO_UPDATE2; + } + + @Override + public String getName() { + return "Update Game Block"; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockIron.java b/src/main/java/cn/nukkit/block/BlockIron.java index ff825a00591..e59d64f4c33 100644 --- a/src/main/java/cn/nukkit/block/BlockIron.java +++ b/src/main/java/cn/nukkit/block/BlockIron.java @@ -5,15 +5,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockIron extends BlockSolid { - - public BlockIron() { - } - @Override public int getId() { return IRON_BLOCK; @@ -21,7 +17,7 @@ public int getId() { @Override public String getName() { - return "Iron Block"; + return "Block of Iron"; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockIronBars.java b/src/main/java/cn/nukkit/block/BlockIronBars.java index 9f80a1d1ca5..a91c61d8216 100644 --- a/src/main/java/cn/nukkit/block/BlockIronBars.java +++ b/src/main/java/cn/nukkit/block/BlockIronBars.java @@ -11,9 +11,6 @@ */ public class BlockIronBars extends BlockThin { - public BlockIronBars() { - } - @Override public String getName() { return "Iron Bars"; @@ -41,12 +38,12 @@ public int getToolType() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ this.toItem() }; @@ -64,4 +61,9 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockItemFrame.java b/src/main/java/cn/nukkit/block/BlockItemFrame.java index 02bcabf585f..6df32dc0273 100644 --- a/src/main/java/cn/nukkit/block/BlockItemFrame.java +++ b/src/main/java/cn/nukkit/block/BlockItemFrame.java @@ -4,7 +4,6 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityItemFrame; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemItemFrame; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; @@ -18,10 +17,11 @@ * Created by Pub4Game on 03.07.2016. */ public class BlockItemFrame extends BlockTransparentMeta implements Faceable { - private final static int[] FACING = new int[]{4, 5, 3, 2, 1, 0}; // TODO when 1.13 support arrives, add UP/DOWN facings + + private final static int[] FACING = {4, 5, 3, 2, 1, 0}; private final static int FACING_BITMASK = 0b0111; - private final static int MAP_BIT = 0b1000; + //private final static int MAP_BIT = 0b1000; public BlockItemFrame() { this(0); @@ -61,15 +61,22 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { BlockEntity blockEntity = this.getLevel().getBlockEntity(this); + if (!(blockEntity instanceof BlockEntityItemFrame)) { + return false; + } + BlockEntityItemFrame itemFrame = (BlockEntityItemFrame) blockEntity; + if (itemFrame.getItem() == null) { + return true; + } if (itemFrame.getItem().getId() == Item.AIR) { - Item itemOnFrame = item.clone(); - if (player != null && player.isSurvival()) { - itemOnFrame.setCount(itemOnFrame.getCount() - 1); - player.getInventory().setItemInHand(itemOnFrame); + Item itemToFrame = item.clone(); + if (player != null && !player.isCreative()) { + item.setCount(item.getCount() - 1); + player.getInventory().setItemInHand(item); } - itemOnFrame.setCount(1); - itemFrame.setItem(itemOnFrame); + itemToFrame.setCount(1); + itemFrame.setItem(itemToFrame); this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_ITEM_ADDED); } else { itemFrame.setItemRotation((itemFrame.getItemRotation() + 1) % 8); @@ -80,9 +87,11 @@ public boolean onActivate(Item item, Player player) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (face.getIndex() > 1 && target.isSolid() && (!block.isSolid() || block.canBeReplaced())) { + if (target.isSolid() && (!block.isSolid() || block.canBeReplaced())) { this.setDamage(FACING[face.getIndex()]); - this.getLevel().setBlock(block, this, true, true); + + this.getLevel().setBlock(this, this, true, true); + CompoundTag nbt = new CompoundTag() .putString("id", BlockEntity.ITEM_FRAME) .putInt("x", (int) block.x) @@ -95,10 +104,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl nbt.put(aTag.getName(), aTag); } } - BlockEntityItemFrame frame = (BlockEntityItemFrame) BlockEntity.createBlockEntity(BlockEntity.ITEM_FRAME, this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - if (frame == null) { - return false; - } + BlockEntity.createBlockEntity(BlockEntity.ITEM_FRAME, this.getChunk(), nbt); + this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_PLACED); return true; } return false; @@ -128,7 +135,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemItemFrame(); + return Item.get(Item.ITEM_FRAME); } @Override @@ -162,6 +169,10 @@ public BlockFace getFacing() { return BlockFace.NORTH; case 3: return BlockFace.SOUTH; + case 4: + return BlockFace.UP; + case 5: + return BlockFace.DOWN; } return null; @@ -172,13 +183,28 @@ public double getHardness() { return 0.25; } + @Override + public boolean isSolid() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + @Override public BlockFace getBlockFace() { return this.getFacing().getOpposite(); } @Override - public boolean isSolid() { - return false; + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation } } diff --git a/src/main/java/cn/nukkit/block/BlockItemFrameGlow.java b/src/main/java/cn/nukkit/block/BlockItemFrameGlow.java new file mode 100644 index 00000000000..a7db61b1e3e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockItemFrameGlow.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockItemFrameGlow extends BlockItemFrame { + + public BlockItemFrameGlow() { + this(0); + } + + public BlockItemFrameGlow(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Glow Item Frame"; + } + + @Override + public int getId() { + return GLOW_FRAME; + } + + @Override + public Item toItem() { + return Item.get(Item.GLOW_ITEM_FRAME); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockJigsaw.java b/src/main/java/cn/nukkit/block/BlockJigsaw.java new file mode 100644 index 00000000000..df68ee485bb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockJigsaw.java @@ -0,0 +1,79 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + +public class BlockJigsaw extends BlockSolidMeta implements Faceable { + + public BlockJigsaw() { + this(0); + } + + public BlockJigsaw(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jigsaw"; + } + + @Override + public int getId() { + return JIGSAW; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromIndex(getDamage()); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + double y = player.y + player.getEyeHeight(); + if (y - this.y > 2) { + this.setDamage(BlockFace.UP.getIndex()); + } else if (this.y - y > 0) { + this.setDamage(BlockFace.DOWN.getIndex()); + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + } else { + this.setDamage(player.getHorizontalFacing().getOpposite().getIndex()); + } + return super.place(item, block, target, face, fx, fy, fz, player); + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockJukebox.java b/src/main/java/cn/nukkit/block/BlockJukebox.java index c0efcac6220..821fbbf1c6e 100644 --- a/src/main/java/cn/nukkit/block/BlockJukebox.java +++ b/src/main/java/cn/nukkit/block/BlockJukebox.java @@ -6,9 +6,11 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemRecord; +import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.network.protocol.TextPacket; import cn.nukkit.utils.BlockColor; /** @@ -16,9 +18,6 @@ */ public class BlockJukebox extends BlockSolid { - public BlockJukebox() { - } - @Override public String getName() { return "Jukebox"; @@ -29,6 +28,21 @@ public int getId() { return JUKEBOX; } + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + @Override public boolean canBeActivated() { return true; @@ -36,14 +50,14 @@ public boolean canBeActivated() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public boolean onActivate(Item item, Player player) { BlockEntity blockEntity = this.getLevel().getBlockEntity(this); if (!(blockEntity instanceof BlockEntityJukebox)) { - blockEntity = this.createBlockEntity(); + return false; } BlockEntityJukebox jukebox = (BlockEntityJukebox) blockEntity; @@ -52,7 +66,16 @@ public boolean onActivate(Item item, Player player) { } else if (item instanceof ItemRecord) { jukebox.setRecordItem(item); jukebox.play(); - player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + if (player != null) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + + TextPacket pk = new TextPacket(); + pk.type = TextPacket.TYPE_JUKEBOX_POPUP; + pk.message = "%record.nowPlaying"; + pk.parameters = new String[]{((ItemRecord) item).getDiscName()}; + pk.isLocalized = true; + player.dataPacket(pk); + } } return false; @@ -68,6 +91,20 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } + /*@Override // Replaced with BlockEntityJukebox#onBreak + public boolean onBreak(Item item) { + if (super.onBreak(item)) { + BlockEntity blockEntity = this.level.getBlockEntity(this); + + if (blockEntity instanceof BlockEntityJukebox) { + ((BlockEntityJukebox) blockEntity).dropItem(); + } + return true; + } + + return false; + }*/ + private BlockEntity createBlockEntity() { CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) @@ -76,11 +113,16 @@ private BlockEntity createBlockEntity() { .putInt("y", getFloorY()) .putInt("z", getFloorZ()); - return BlockEntity.createBlockEntity(BlockEntity.JUKEBOX, this.level.getChunk(getFloorX() >> 4, getFloorZ() >> 4), nbt); + return BlockEntity.createBlockEntity(BlockEntity.JUKEBOX, this.getChunk(), nbt); } @Override public BlockColor getColor() { - return BlockColor.DIRT_BLOCK_COLOR; + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public boolean canBePushed() { + return false; } } diff --git a/src/main/java/cn/nukkit/block/BlockJungleSignStanding.java b/src/main/java/cn/nukkit/block/BlockJungleSignStanding.java new file mode 100644 index 00000000000..e9d9b5d4a5e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockJungleSignStanding.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockJungleSignStanding extends BlockSignPost { + + public BlockJungleSignStanding() { + this(0); + } + + public BlockJungleSignStanding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jungle Sign Post"; + } + + @Override + public int getId() { + return JUNGLE_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.JUNGLE_SIGN); + } + + @Override + protected int getPostId() { + return JUNGLE_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return JUNGLE_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockJungleWallSign.java b/src/main/java/cn/nukkit/block/BlockJungleWallSign.java new file mode 100644 index 00000000000..9b21a0912fb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockJungleWallSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockJungleWallSign extends BlockWallSign { + + public BlockJungleWallSign() { + this(0); + } + + public BlockJungleWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jungle Wall Sign"; + } + + @Override + public int getId() { + return JUNGLE_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.JUNGLE_SIGN); + } + + @Override + protected int getPostId() { + return JUNGLE_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return JUNGLE_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockKelp.java b/src/main/java/cn/nukkit/block/BlockKelp.java new file mode 100644 index 00000000000..75556260b10 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockKelp.java @@ -0,0 +1,254 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.anvil.Anvil; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.MathHelper; +import cn.nukkit.utils.Utils; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockKelp extends BlockFlowable { + + public BlockKelp() { + this(0); + } + + public BlockKelp(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Kelp"; + } + + @Override + public int getId() { + return BLOCK_KELP; + } + + public double getHardness() { + return 0; + } + + public double getResistance() { + return 0; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return level == null || !(level.getProvider() instanceof Anvil); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (level != null && level.getProvider() instanceof Anvil) { + Block down; + if (!((block instanceof BlockWater && (block.getDamage() == 0 || block.getDamage() >= 8)) || block instanceof BlockBubbleColumn) || ((down = this.down()).getId() != this.getId() && down.isTransparent())) { + return false; + } + // 15 is the highest supported meta value + // Meta 0 does not grow to not overgrow previously generated kelp + this.setDamage(Utils.rand(1, 15)); + return this.getLevel().setBlock(this, this, true, true); + } + + + Block down = this.down(); + Block layer1Block = block.getLevelBlock(BlockLayer.WATERLOGGED); + int waterDamage; + if ((down.getId() == BLOCK_KELP || down.isSolid()) && down.getId() != MAGMA && down.getId() != ICE && down.getId() != SOUL_SAND && + (layer1Block instanceof BlockWater && ((waterDamage = (block.getDamage())) == 0 || waterDamage == 8)) + ) { + if (waterDamage == 8) { + this.getLevel().setBlock(this, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + } + + if (down.getId() == BLOCK_KELP && down.getDamage() != 24) { + down.setDamage(24); + this.getLevel().setBlock(down, down, true, true); + } + + // Placing it by hand gives it a random age value between 0 and 24 + // Meta 0 does not grow to not overgrow previously generated kelp + this.setDamage(Utils.rand(1, 24)); + this.getLevel().setBlock(this, this, true, true); + return true; + } + return false; + } + + @Override + public int onUpdate(int type) { + if (level != null && level.getProvider() instanceof Anvil) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + if (down.getId() != this.getId() && down.isTransparent()) { + this.getLevel().useBreakOn(this); + } + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + // Ignore meta value 0 to not overgrow previously generated kelp + if (this.getDamage() > 0 && ThreadLocalRandom.current().nextInt(100) < 14) { // 14% chance to grow + if (this.getDamage() < 15) { + Block up = up(); + if (up instanceof BlockWater && up.getDamage() == 0) { + Block grown = Block.get(BLOCK_KELP, this.getDamage() + 1); + BlockGrowEvent ev = new BlockGrowEvent(this, grown); + Server.getInstance().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + this.getLevel().setBlock(up, ev.getNewState(), true, true); + } + } + } + } + } + return type; + } + + + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block blockLayer1 = this.getLevelBlock(BlockLayer.WATERLOGGED); + int waterDamage = 0; + if (!(blockLayer1 instanceof BlockIceFrosted) && + (!(blockLayer1 instanceof BlockWater) || ((waterDamage = blockLayer1.getDamage()) != 0 && waterDamage != 8))) { + this.getLevel().useBreakOn(this); + return type; + } + + Block down = this.down(); + if ((!down.isSolid() && down.getId() != BLOCK_KELP) || down.getId() == MAGMA || down.getId() == ICE || down.getId() == SOUL_SAND) { + this.getLevel().useBreakOn(this); + return type; + } + + if (waterDamage == 8) { + this.getLevel().setBlock(this, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + } + return type; + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + // Ignore meta value 0 to not overgrow previously generated + if (this.getDamage() > 0 && ThreadLocalRandom.current().nextInt(100) < 14) { // 14% chance to grow + this.grow(); + } + return type; + } + return super.onUpdate(type); + } + + public boolean grow() { + int age = MathHelper.clamp(this.getDamage(), 0, 25); + if (age < 25) { + Block up = this.up(); + if (up instanceof BlockWater && (up.getDamage() == 0 || up.getDamage() == 8)) { + Block grown = Block.get(BLOCK_KELP, age + 1); + BlockGrowEvent ev = new BlockGrowEvent(this, grown); + this.level.getServer().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + this.setDamage(25); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client + + this.getLevel().setBlock(up, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + this.getLevel().setBlock(up, BlockLayer.NORMAL, ev.getNewState(), true, true); + return true; + } + } + } + return false; + } + + @Override + public boolean onBreak(Item item) { + if (level != null && level.getProvider() instanceof Anvil) { + return super.onBreak(item); + } + + + Block down = this.down(); + if (down.getId() == BLOCK_KELP) { + this.getLevel().setBlock(down, Block.get(BLOCK_KELP, ThreadLocalRandom.current().nextInt(25)), true, true); + } + this.getLevel().setBlock(this, Block.get(AIR), true, true); + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (level != null && level.getProvider() instanceof Anvil) { + if (item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + if (this.grow()) { + if (player != null && !player.isCreative()) { + item.count--; + } + level.addParticle(new BoneMealParticle(this)); + } + return true; + } + return super.onActivate(item, player); + } + + + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return false; + } + int x = (int) this.x; + int z = (int) this.z; + + for (int y = (int) this.y + 1; y < 255; y++) { + int blockIdAbove = this.getLevel().getBlockIdAt(x, y, z); + if (blockIdAbove == BLOCK_KELP) continue; + if (blockIdAbove != WATER && blockIdAbove != STILL_WATER) { + return false; + } + + int waterData = this.getLevel().getBlockDataAt(x, y, z); + if (waterData == 0 || waterData == 8) { + BlockKelp highestKelp = (BlockKelp) this.getLevel().getBlock(x, y - 1, z); + if (highestKelp.grow()) { + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && (player.gamemode & 0x01) == 0) { + item.count--; + } + } + } + return true; + } + return false; + } + + @Override + public Item toItem() { + return Item.get(Item.KELP, 0, 1); + } + + @Override + public AxisAlignedBB getBoundingBox() { + return null; + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLadder.java b/src/main/java/cn/nukkit/block/BlockLadder.java index 014ac90b437..7906b886c4d 100644 --- a/src/main/java/cn/nukkit/block/BlockLadder.java +++ b/src/main/java/cn/nukkit/block/BlockLadder.java @@ -1,10 +1,10 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; -import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -15,13 +15,23 @@ */ public class BlockLadder extends BlockTransparentMeta implements Faceable { + private static final int[] faces = { + 0, //never use + 1, //never use + 3, + 2, + 5, + 4 + }; + public BlockLadder() { this(0); } public BlockLadder(int meta) { super(meta); - calculateOffsets(); + + this.calculateOffsets(); } @Override @@ -94,19 +104,13 @@ private void calculateOffsets() { break; default: this.offMinX = 0; - this.offMinZ = 1 ; + this.offMinZ = 1; this.offMaxX = 1; this.offMaxZ = 1; break; } } - @Override - public void setDamage(int meta) { - super.setDamage(meta); - calculateOffsets(); - } - @Override public double getMinX() { return this.x + offMinX; @@ -127,17 +131,12 @@ public double getMaxZ() { return this.z + offMaxZ; } - @Override - protected AxisAlignedBB recalculateCollisionBoundingBox() { - return super.recalculateBoundingBox(); - } - @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (!target.isTransparent()) { if (face.getIndex() >= 2 && face.getIndex() <= 5) { this.setDamage(face.getIndex()); - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } } @@ -147,14 +146,6 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { - int[] faces = { - 0, //never use - 1, //never use - 3, - 2, - 5, - 4 - }; if (!this.getSide(BlockFace.fromIndex(faces[this.getDamage()])).isSolid()) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; @@ -172,16 +163,31 @@ public int getToolType() { public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } - + @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.LADDER, 0, 1) + Item.get(Item.LADDER) }; } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public void onEntityCollide(Entity entity) { + entity.resetFallDistance(); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockLantern.java b/src/main/java/cn/nukkit/block/BlockLantern.java new file mode 100644 index 00000000000..5c79ab6028f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLantern.java @@ -0,0 +1,168 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockLantern extends BlockFlowable { + + public BlockLantern() { + this(0); + } + + public BlockLantern(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Lantern"; + } + + @Override + public int getId() { + return LANTERN; + } + + @Override + public int getLightLevel() { + return 15; + } + + @Override + public double getResistance() { + return 3.5; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canPassThrough() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.IRON_BLOCK_COLOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(LANTERN)); + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return this; + } + + @Override + public double getMinX() { + return this.x + 0.3125; + } + + @Override + public double getMinZ() { + return this.z + 0.3125; + } + + @Override + public double getMaxX() { + return this.x + 0.6875; + } + + @Override + public double getMaxY() { + return this.y + 0.5; + } + + @Override + public double getMaxZ() { + return this.z + 0.6875; + } + + private boolean isBlockAboveValid() { + Block support = this.up(); + switch (support.getId()) { + case IRON_BARS: + case HOPPER_BLOCK: + case CHAIN_BLOCK: + return true; + default: + if (support instanceof BlockFence) { + return true; + } + if (support instanceof BlockSlab && (support.getDamage() & 0x08) == 0x00) { + return true; + } + if (support instanceof BlockStairs && (support.getDamage() & 0x04) == 0x00) { + return true; + } + return !support.isTransparent() && support.isSolid() && !support.isPowerSource(); + } + } + + private boolean isBlockUnderValid() { + Block down = this.down(); + if (down instanceof BlockLeaves) { + return false; + } else if (down instanceof BlockFence || down instanceof BlockWall) { + return true; + } else if (down instanceof BlockSlab) { + return (down.getDamage() & 0x08) == 0x08; + } else if (down instanceof BlockStairs) { + return (down.getDamage() & 0x04) == 0x04; + } else return down.isSolid() || down instanceof BlockChain; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + boolean isUnderValid = this.isBlockUnderValid(); + boolean hanging = face != BlockFace.UP && this.isBlockAboveValid() && (!isUnderValid || face == BlockFace.DOWN); + if (!isUnderValid && !hanging) { + return false; + } + + if (hanging) { + this.setDamage(1); + } else { + this.setDamage(0); + } + + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.getDamage() == 0) { + if (!this.isBlockUnderValid()) { + level.useBreakOn(this, null, null, true); + } + } else if (!this.isBlockAboveValid()) { + level.useBreakOn(this, null, null, true); + } + return type; + } + return 0; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLapis.java b/src/main/java/cn/nukkit/block/BlockLapis.java index a8c0166fdac..a7d24808f66 100644 --- a/src/main/java/cn/nukkit/block/BlockLapis.java +++ b/src/main/java/cn/nukkit/block/BlockLapis.java @@ -5,15 +5,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockLapis extends BlockSolid { - - public BlockLapis() { - } - @Override public int getId() { return LAPIS_BLOCK; @@ -59,5 +55,4 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } - } diff --git a/src/main/java/cn/nukkit/block/BlockLava.java b/src/main/java/cn/nukkit/block/BlockLava.java index fc90be4c510..c495bdc9cbc 100644 --- a/src/main/java/cn/nukkit/block/BlockLava.java +++ b/src/main/java/cn/nukkit/block/BlockLava.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.entity.BaseEntity; import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityPrimedTNT; import cn.nukkit.event.block.BlockIgniteEvent; @@ -15,12 +16,10 @@ import cn.nukkit.math.Vector3; import cn.nukkit.potion.Effect; import cn.nukkit.utils.BlockColor; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockLava extends BlockLiquid { @@ -52,17 +51,22 @@ public String getName() { public void onEntityCollide(Entity entity) { entity.highestPosition -= (entity.highestPosition - entity.y) * 0.5; - EntityCombustByBlockEvent ev = new EntityCombustByBlockEvent(this, entity, 8); - Server.getInstance().getPluginManager().callEvent(ev); - if (!ev.isCancelled() - // Making sure the entity is actually alive and not invulnerable. - && entity.isAlive() - && entity.noDamageTicks == 0) { - entity.setOnFire(ev.getDuration()); - } + if (!entity.fireProof || !entity.isOnFire() || !(entity instanceof BaseEntity)) { // Improve performance + if (!entity.fireProof || !entity.isOnFire()) { + + EntityCombustByBlockEvent ev = new EntityCombustByBlockEvent(this, entity, 8); + Server.getInstance().getPluginManager().callEvent(ev); + if (!ev.isCancelled() + // Making sure the entity is actually alive and not invulnerable + && entity.isAlive() + && entity.noDamageTicks == 0) { + entity.setOnFire(ev.getDuration()); + } + } - if (!entity.hasEffect(Effect.FIRE_RESISTANCE)) { - entity.attack(new EntityDamageByBlockEvent(this, entity, DamageCause.LAVA, 4)); + if (!entity.hasEffect(Effect.FIRE_RESISTANCE)) { + entity.attack(new EntityDamageByBlockEvent(this, entity, DamageCause.LAVA, 4)); + } } super.onEntityCollide(entity); @@ -81,13 +85,11 @@ public int onUpdate(int type) { int result = super.onUpdate(type); if (type == Level.BLOCK_UPDATE_RANDOM && this.level.gameRules.getBoolean(GameRule.DO_FIRE_TICK)) { - Random random = ThreadLocalRandom.current(); - - int i = random.nextInt(3); + int i = Utils.random.nextInt(3); if (i > 0) { for (int k = 0; k < i; ++k) { - Vector3 v = this.add(random.nextInt(3) - 1, 1, random.nextInt(3) - 1); + Vector3 v = this.add(Utils.random.nextInt(3) - 1, 1, Utils.random.nextInt(3) - 1); Block block = this.getLevel().getBlock(v); if (block.getId() == AIR) { @@ -110,7 +112,7 @@ public int onUpdate(int type) { } } else { for (int k = 0; k < 3; ++k) { - Vector3 v = this.add(random.nextInt(3) - 1, 0, random.nextInt(3) - 1); + Vector3 v = this.add(Utils.random.nextInt(3) - 1, 0, Utils.random.nextInt(3) - 1); Block block = this.getLevel().getBlock(v); if (block.up().getId() == AIR && block.getBurnChance() > 0) { @@ -147,9 +149,9 @@ public BlockColor getColor() { @Override public BlockLiquid getBlock(int meta) { - return (BlockLiquid) Block.get(BlockID.LAVA, meta); + return (BlockLiquid) Block.get(LAVA, meta); } - + @Override public int tickRate() { if (this.level.getDimension() == Level.DIMENSION_NETHER) { @@ -167,29 +169,35 @@ public int getFlowDecayPerBlock() { } @Override - protected void checkForHarden(){ + protected void checkForHarden() { Block colliding = null; - for(int side = 1; side < 6; ++side){ //don't check downwards side + for (int side = 1; side < 6; ++side) { //don't check downwards side Block blockSide = this.getSide(BlockFace.fromIndex(side)); - if(blockSide instanceof BlockWater){ + if (blockSide instanceof BlockWater || blockSide.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockWater) { colliding = blockSide; break; } + if (blockSide instanceof BlockBlueIce) { + if (down() instanceof BlockSoulSoil) { + liquidCollide(this, Block.get(BlockID.BASALT)); + return; + } + } } - if(colliding != null){ - if(this.getDamage() == 0){ - this.liquidCollide(colliding, Block.get(BlockID.OBSIDIAN)); - }else if(this.getDamage() <= 4){ - this.liquidCollide(colliding, Block.get(BlockID.COBBLESTONE)); + if (colliding != null) { + if (this.getDamage() == 0) { + this.liquidCollide(colliding, Block.get(OBSIDIAN)); + } else if (this.getDamage() <= 4) { + this.liquidCollide(colliding, Block.get(COBBLESTONE)); } } } @Override - protected void flowIntoBlock(Block block, int newFlowDecay){ - if(block instanceof BlockWater){ - ((BlockLiquid) block).liquidCollide(this, Block.get(BlockID.STONE)); - }else{ + protected void flowIntoBlock(Block block, int newFlowDecay) { + if (block instanceof BlockWater) { + ((BlockLiquid) block).liquidCollide(this, Block.get(STONE)); + } else { super.flowIntoBlock(block, newFlowDecay); } } @@ -200,4 +208,53 @@ public void addVelocityToEntity(Entity entity, Vector3 vector) { super.addVelocityToEntity(entity, vector); } } + + @Override + protected boolean[] getOptimalFlowDirections() { + int[] flowCost = { + 1000, + 1000, + 1000, + 1000 + }; + int maxCost = 4 / this.getFlowDecayPerBlock(); + for (int j = 0; j < 4; ++j) { + int x = (int) this.x; + int y = (int) this.y; + int z = (int) this.z; + if (j == 0) { + --x; + } else if (j == 1) { + ++x; + } else if (j == 2) { + --z; + } else { + ++z; + } + Block block = this.level.getBlock(x, y, z); + if (!this.canFlowInto(block)) { + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), BLOCKED); + } else if (this.level.getBlock(x, y - 1, z).canBeFlowedInto()) { + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), CAN_FLOW_DOWN); + flowCost[j] = maxCost = 0; + } else if (maxCost > 0) { + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), CAN_FLOW); + flowCost[j] = this.calculateFlowCost(x, y, z, 1, maxCost, j ^ 0x01, j ^ 0x01); + maxCost = Math.min(maxCost, flowCost[j]); + } + } + this.flowCostVisited.clear(); + double minCost = Double.MAX_VALUE; + for (int i = 0; i < 4; i++) { + double d = flowCost[i]; + if (d < minCost) { + minCost = d; + } + } + boolean[] isOptimalFlowDirection = new boolean[4]; + for (int i = 0; i < 4; ++i) { + isOptimalFlowDirection[i] = (flowCost[i] == minCost); + } + return isOptimalFlowDirection; + } } diff --git a/src/main/java/cn/nukkit/block/BlockLavaStill.java b/src/main/java/cn/nukkit/block/BlockLavaStill.java index c587211d137..6c29fb16c5a 100644 --- a/src/main/java/cn/nukkit/block/BlockLavaStill.java +++ b/src/main/java/cn/nukkit/block/BlockLavaStill.java @@ -1,9 +1,7 @@ package cn.nukkit.block; -import cn.nukkit.level.Level; - /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockLavaStill extends BlockLava { @@ -28,14 +26,6 @@ public String getName() { @Override public BlockLiquid getBlock(int meta) { - return (BlockLiquid) Block.get(BlockID.STILL_LAVA, meta); - } - - @Override - public int onUpdate(int type) { - if (type != Level.BLOCK_UPDATE_SCHEDULED) { - return super.onUpdate(type); - } - return 0; + return (BlockLiquid) Block.get(STILL_LAVA, meta); } } diff --git a/src/main/java/cn/nukkit/block/BlockLayer.java b/src/main/java/cn/nukkit/block/BlockLayer.java new file mode 100644 index 00000000000..8f17f52fb85 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLayer.java @@ -0,0 +1,6 @@ +package cn.nukkit.block; + +public enum BlockLayer { + NORMAL, + WATERLOGGED +} diff --git a/src/main/java/cn/nukkit/block/BlockLeaves.java b/src/main/java/cn/nukkit/block/BlockLeaves.java index 2185fc3f95a..654ea1e5d50 100644 --- a/src/main/java/cn/nukkit/block/BlockLeaves.java +++ b/src/main/java/cn/nukkit/block/BlockLeaves.java @@ -6,20 +6,21 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Hash; +import cn.nukkit.utils.Utils; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; -import java.util.concurrent.ThreadLocalRandom; - /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockLeaves extends BlockTransparentMeta { + public static final int OAK = 0; public static final int SPRUCE = 1; public static final int BIRCH = 2; @@ -50,7 +51,7 @@ public int getToolType() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak Leaves", "Spruce Leaves", "Birch Leaves", @@ -71,8 +72,8 @@ public int getBurnAbility() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setPersistent(true); - this.getLevel().setBlock(this, this, true); + setPersistent(true); + this.getLevel().setBlock(this, this, true, true); return true; } @@ -88,17 +89,20 @@ public Item[] getDrops(Item item) { toItem() }; } else { - if (this.canDropApple() && ThreadLocalRandom.current().nextInt(200) == 0) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + if (this.canDropApple() && Utils.random.nextInt(200) == 0) { return new Item[]{ Item.get(Item.APPLE) }; } - if (ThreadLocalRandom.current().nextInt(20) == 0) { - if (ThreadLocalRandom.current().nextBoolean()) { + if (Utils.random.nextInt(20) == 0) { + if (Utils.rand()) { return new Item[]{ - Item.get(Item.STICK, 0, ThreadLocalRandom.current().nextInt(1, 2)) + Item.get(Item.STICK, 0, Utils.random.nextInt(1, 2)) }; - } else if ((this.getDamage() & 0x03) != JUNGLE || ThreadLocalRandom.current().nextInt(20) == 0) { + } else if ((this.getDamage() & 0x03) != JUNGLE || Utils.random.nextInt(20) == 0) { return new Item[]{ this.getSapling() }; @@ -112,16 +116,15 @@ public Item[] getDrops(Item item) { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_RANDOM && !isPersistent() && !isCheckDecay()) { setCheckDecay(true); - getLevel().setBlock(this, this, false, false); + getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client } else if (type == Level.BLOCK_UPDATE_RANDOM && isCheckDecay() && !isPersistent()) { - setDamage(getDamage() & 0x03); - int check = 0; + this.setOnDecayDamage(); LeavesDecayEvent ev = new LeavesDecayEvent(this); Server.getInstance().getPluginManager().callEvent(ev); - if (ev.isCancelled() || findLog(this, new LongArraySet(), 0, check)) { - getLevel().setBlock(this, this, false, false); + if (ev.isCancelled() || findLog(this, new LongArraySet(), 0, 0)) { + getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, false, false); // No need to send this to client } else { getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; @@ -130,6 +133,10 @@ public int onUpdate(int type) { return 0; } + protected void setOnDecayDamage() { + setDamage(getDamage() & 0x03); + } + private Boolean findLog(Block pos, LongSet visited, Integer distance, Integer check) { return findLog(pos, visited, distance, check, null); } @@ -139,7 +146,7 @@ private Boolean findLog(Block pos, LongSet visited, Integer distance, Integer ch long index = Hash.hashBlock((int) pos.x, (int) pos.y, (int) pos.z); if (visited.contains(index)) return false; if (pos.getId() == WOOD || pos.getId() == WOOD2) return true; - if ((pos.getId() == LEAVES || pos.getId() == LEAVES2) && distance <= 4) { + if ((pos.getId() == LEAVES || pos.getId() == LEAVES2) && distance < 6) { visited.add(index); int down = pos.down().getId(); if (down == WOOD || down == WOOD2) { @@ -198,7 +205,7 @@ public void setCheckDecay(boolean checkDecay) { if (checkDecay) { this.setDamage(this.getDamage() | 0x08); } else { - this.setDamage(this.getDamage() & ~0x08); + this.setDamage(this.getDamage() & -9); } } @@ -210,7 +217,7 @@ public void setPersistent(boolean persistent) { if (persistent) { this.setDamage(this.getDamage() | 0x04); } else { - this.setDamage(this.getDamage() & ~0x04); + this.setDamage(this.getDamage() & -5); } } @@ -231,4 +238,15 @@ protected boolean canDropApple() { protected Item getSapling() { return Item.get(BlockID.SAPLING, this.getDamage() & 0x03); } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + } diff --git a/src/main/java/cn/nukkit/block/BlockLeaves2.java b/src/main/java/cn/nukkit/block/BlockLeaves2.java index 4d95cea3578..bd936a427cc 100644 --- a/src/main/java/cn/nukkit/block/BlockLeaves2.java +++ b/src/main/java/cn/nukkit/block/BlockLeaves2.java @@ -4,12 +4,18 @@ /** * Created on 2015/12/1 by xtypr. - * Package cn.nukkit.block in project Nukkit . + * Package cn.nukkit.block in project Nukkit. */ public class BlockLeaves2 extends BlockLeaves { + public static final int ACACIA = 0; public static final int DARK_OAK = 1; + private static final String[] names = { + "Acacia Leaves", + "Dark Oak Leaves" + }; + public BlockLeaves2() { this(0); } @@ -19,10 +25,6 @@ public BlockLeaves2(int meta) { } public String getName() { - String[] names = new String[]{ - "Acacia Leaves", - "Dark Oak Leaves" - }; return names[this.getDamage() & 0x01]; } @@ -33,7 +35,7 @@ public int getId() { @Override protected boolean canDropApple() { - return (this.getDamage() & 0x01) != 0; + return (this.getDamage() & 0x01) == DARK_OAK; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockLectern.java b/src/main/java/cn/nukkit/block/BlockLectern.java new file mode 100644 index 00000000000..af43966cb75 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLectern.java @@ -0,0 +1,171 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityLectern; +import cn.nukkit.inventory.InventoryType; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.ContainerOpenPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; + +public class BlockLectern extends BlockSolidMeta implements Faceable { + + public BlockLectern() { + this(0); + } + + public BlockLectern(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Lectern"; + } + + @Override + public int getId() { + return LECTERN; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + public double getHardness() { + return 2.5; + } + + public double getResistance() { + return 2.5; + } + + @Override + public int getBurnChance() { + return 30; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + int horizontalIndex = (player != null ? player.getDirection().getOpposite() : BlockFace.SOUTH).getHorizontalIndex(); + if (horizontalIndex >= 0) { + this.setDamage(getDamage() & (15 ^ 0b11) | (horizontalIndex & 0b11)); + } + CompoundTag nbt = new CompoundTag() + .putString("id", BlockEntity.LECTERN) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + BlockEntityLectern lectern = (BlockEntityLectern) BlockEntity.createBlockEntity(BlockEntity.LECTERN, this.getChunk(), nbt); + if (lectern == null) { + return false; + } + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(getDamage() & 0b11); + } + + public boolean dropBook() { + BlockEntity blockEntity = this.getLevel().getBlockEntity(this); + if (blockEntity instanceof BlockEntityLectern) { + BlockEntityLectern lectern = (BlockEntityLectern) blockEntity; + Item book = lectern.getBook(); + if (book.getId() != BlockID.AIR) { + lectern.setBook(Item.get(BlockID.AIR)); + lectern.spawnToAll(); + this.level.dropItem(lectern.add(0.5f, 1, 0.5f), book); + return true; + } + } + return false; + } + + @Override + public boolean isPowerSource() { + return true; + } + + public boolean isActivated() { + return (this.getDamage() & 0x04) == 0x04; + } + + public void setActivated(boolean activated) { + if (activated) { + setDamage(getDamage() | 0x04); + } else { + setDamage(getDamage() ^ 0x04); + } + } + + @Override + public int getWeakPower(BlockFace face) { + return isActivated() ? 15 : 0; + } + + @Override + public int getStrongPower(BlockFace side) { + return 0; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null) { + BlockEntity t = this.getLevel().getBlockEntity(this); + if (!(t instanceof BlockEntityLectern)) { + return false; + } + + BlockEntityLectern lectern = (BlockEntityLectern) t;; + Item currentBook = lectern.getBook(); + if (currentBook.getId() == BlockID.AIR) { + if (item.getId() == ItemID.WRITTEN_BOOK || item.getId() == ItemID.BOOK_AND_QUILL) { + Item newBook = item.clone(); + if (player.isSurvival()) { + newBook.setCount(newBook.getCount() - 1); + player.getInventory().setItemInHand(newBook); + } + newBook.setCount(1); + lectern.setBook(newBook); + lectern.spawnToAll(); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_BOOK_PUT); + } + } else { + ContainerOpenPacket pk = new ContainerOpenPacket(); + pk.windowId = -1; + pk.type = InventoryType.LECTERN.getNetworkType(); + pk.x = (int) x; + pk.y = (int) y; + pk.z = (int) z; + pk.entityId = player.getId(); + player.dataPacket(pk); + } + } + + return true; + } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLever.java b/src/main/java/cn/nukkit/block/BlockLever.java index 98903e236de..c43cfcdacf6 100644 --- a/src/main/java/cn/nukkit/block/BlockLever.java +++ b/src/main/java/cn/nukkit/block/BlockLever.java @@ -6,7 +6,7 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; -import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -50,7 +50,7 @@ public double getResistance() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override @@ -68,12 +68,16 @@ public boolean onActivate(Item item, Player player) { this.setDamage(this.getDamage() ^ 0x08); this.getLevel().setBlock(this, this, false, true); - this.getLevel().addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_BUTTON_CLICK, this.isPowerOn() ? 600 : 500); + if (this.isPowerOn()) { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_ON); + } else { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_OFF); + } LeverOrientation orientation = LeverOrientation.byMetadata(this.isPowerOn() ? this.getDamage() ^ 0x08 : this.getDamage()); BlockFace face = orientation.getFacing(); - //this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(this.getLocation().getSide(face.getOpposite()), isPowerOn() ? face : null); + level.updateAroundRedstone(this, null); + this.level.updateAroundRedstone(this.getSideVec(face.getOpposite()), isPowerOn() ? face : null); return true; } @@ -104,8 +108,8 @@ public boolean onBreak(Item item) { this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); if (isPowerOn()) { - BlockFace face = LeverOrientation.byMetadata(this.isPowerOn() ? this.getDamage() ^ 0x08 : this.getDamage()).getFacing(); - this.level.updateAround(this.getLocation().getSide(face.getOpposite())); + BlockFace face = LeverOrientation.byMetadata(this.getDamage() ^ 0x08).getFacing(); + this.level.updateAround(this.getSideVec(face.getOpposite())); } return true; } @@ -116,7 +120,11 @@ public int getWeakPower(BlockFace side) { } public int getStrongPower(BlockFace side) { - return !isPowerOn() ? 0 : LeverOrientation.byMetadata(this.isPowerOn() ? this.getDamage() ^ 0x08 : this.getDamage()).getFacing() == side ? 15 : 0; + if (!isPowerOn()) { + return 0; + } else { + return LeverOrientation.byMetadata(this.getDamage() ^ 0x08).getFacing() == side ? 15 : 0; + } } @Override @@ -214,18 +222,33 @@ public String getName() { static { for (LeverOrientation face : values()) { - META_LOOKUP[face.getMetadata()] = face; + META_LOOKUP[face.meta] = face; } } } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } @Override public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockLightBlock.java b/src/main/java/cn/nukkit/block/BlockLightBlock.java new file mode 100644 index 00000000000..062911bccdc --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLightBlock.java @@ -0,0 +1,75 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.math.AxisAlignedBB; + +public class BlockLightBlock extends BlockTransparentMeta { + + public BlockLightBlock() { + this(0); + } + + public BlockLightBlock(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Light Block"; + } + + @Override + public int getId() { + return LIGHT_BLOCK; + } + + @Override + public int getLightLevel() { + return getDamage() & 0xF; + } + + @Override + public AxisAlignedBB getBoundingBox() { + return null; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 3600000.8; + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public Item toItem() { + return Item.get(Item.AIR); + } + + @Override + public boolean canBePushed() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLightningRod.java b/src/main/java/cn/nukkit/block/BlockLightningRod.java new file mode 100644 index 00000000000..b7de521c04d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLightningRod.java @@ -0,0 +1,87 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; + +public class BlockLightningRod extends BlockTransparentMeta { + + private static final int[] faces = {0, 1, 2, 3, 4, 5}; + + public BlockLightningRod() { + this(0); + } + + public BlockLightningRod(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Lightning Rod"; + } + + @Override + public int getId() { + return LIGHTNING_ROD; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getMinX() { + return this.x + 0.4; + } + + @Override + public double getMinZ() { + return this.z + 0.4; + } + + @Override + public double getMaxX() { + return this.x + 0.6; + } + + @Override + public double getMaxZ() { + return this.z + 0.6; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(faces[player != null ? face.getIndex() : 0]); + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_STONE) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLiquid.java b/src/main/java/cn/nukkit/block/BlockLiquid.java index 688be7cf069..e81d7beaef5 100644 --- a/src/main/java/cn/nukkit/block/BlockLiquid.java +++ b/src/main/java/cn/nukkit/block/BlockLiquid.java @@ -6,29 +6,25 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; -import cn.nukkit.level.particle.SmokeParticle; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.Vector3; -import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; import it.unimi.dsi.fastutil.longs.Long2ByteMap; import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockLiquid extends BlockTransparentMeta { - private final byte CAN_FLOW_DOWN = 1; - private final byte CAN_FLOW = 0; - private final byte BLOCKED = -1; + protected static final byte CAN_FLOW_DOWN = 1; + protected static final byte CAN_FLOW = 0; + protected static final byte BLOCKED = -1; public int adjacentSources = 0; protected Vector3 flowVector = null; - private Long2ByteMap flowCostVisited = new Long2ByteOpenHashMap(); + protected Long2ByteMap flowCostVisited = new Long2ByteOpenHashMap(); protected BlockLiquid(int meta) { super(meta); @@ -39,10 +35,12 @@ public boolean canBeFlowedInto() { return true; } + @Override protected AxisAlignedBB recalculateBoundingBox() { return null; } + @Override public Item[] getDrops(Item item) { return new Item[0]; } @@ -77,6 +75,11 @@ public AxisAlignedBB getBoundingBox() { return null; } + @Override + public boolean breakWhenPushed() { + return true; + } + @Override public double getMaxY() { return this.y + 1 - getFluidHeightPercent(); @@ -98,14 +101,22 @@ public float getFluidHeightPercent() { protected int getFlowDecay(Block block) { if (block.getId() != this.getId()) { - return -1; + Block layer1 = block.getLevelBlock(BlockLayer.WATERLOGGED); + if (layer1.getId() != this.getId()) { + return -1; + } else { + return layer1.getDamage(); + } } return block.getDamage(); } protected int getEffectiveFlowDecay(Block block) { if (block.getId() != this.getId()) { - return -1; + block = block.getLevelBlock(BlockLayer.WATERLOGGED); + if (block.getId() != this.getId()) { + return -1; + } } int decay = block.getDamage(); if (decay >= 8) { @@ -142,13 +153,14 @@ public Vector3 getFlowVector() { default: z++; } - Block sideBlock = this.level.getBlock(x, y, z); + FullChunk chunk = this.level.getChunk(x >> 4, z >> 4); + Block sideBlock = this.level.getBlock(chunk, x, y, z, true); int blockDecay = this.getEffectiveFlowDecay(sideBlock); if (blockDecay < 0) { if (!sideBlock.canBeFlowedInto()) { continue; } - blockDecay = this.getEffectiveFlowDecay(this.level.getBlock(x, y - 1, z)); + blockDecay = this.getEffectiveFlowDecay(this.level.getBlock(chunk, x, y - 1, z, true)); if (blockDecay >= 0) { int realDecay = blockDecay - (decay - 8); vector.x += (sideBlock.x - this.x) * realDecay; @@ -163,14 +175,15 @@ public Vector3 getFlowVector() { } } if (this.getDamage() >= 8) { - if (!this.canFlowInto(this.level.getBlock((int) this.x, (int) this.y, (int) this.z - 1)) || - !this.canFlowInto(this.level.getBlock((int) this.x, (int) this.y, (int) this.z + 1)) || - !this.canFlowInto(this.level.getBlock((int) this.x - 1, (int) this.y, (int) this.z)) || - !this.canFlowInto(this.level.getBlock((int) this.x + 1, (int) this.y, (int) this.z)) || - !this.canFlowInto(this.level.getBlock((int) this.x, (int) this.y + 1, (int) this.z - 1)) || - !this.canFlowInto(this.level.getBlock((int) this.x, (int) this.y + 1, (int) this.z + 1)) || - !this.canFlowInto(this.level.getBlock((int) this.x - 1, (int) this.y + 1, (int) this.z)) || - !this.canFlowInto(this.level.getBlock((int) this.x + 1, (int) this.y + 1, (int) this.z))) { + FullChunk guessChunk = getChunk(); + if (!this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z - 1, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z + 1, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x - 1, (int) this.y, (int) this.z, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x + 1, (int) this.y, (int) this.z, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x, (int) this.y + 1, (int) this.z - 1, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x, (int) this.y + 1, (int) this.z + 1, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x - 1, (int) this.y + 1, (int) this.z, true)) || + !this.canFlowInto(this.level.getBlock(guessChunk, (int) this.x + 1, (int) this.y + 1, (int) this.z, true))) { vector = vector.normalize().add(0, -6, 0); } } @@ -195,32 +208,47 @@ public int getFlowDecayPerBlock() { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { this.checkForHarden(); + if (this.usesWaterLogging() && this.getLayer().ordinal() > LAYER_NORMAL.ordinal()) { + Block layer0 = this.level.getBlock(this, LAYER_NORMAL, true); + if (layer0.getId() == Block.AIR) { + this.level.setBlock(this, LAYER_WATERLOGGED, Block.get(Block.AIR), false, false); + this.level.setBlock(this, LAYER_NORMAL, this, false, false); + } else if (layer0.getWaterloggingType() == WaterloggingType.NO_WATERLOGGING || ((layer0.getWaterloggingType() == WaterloggingType.WHEN_PLACED_IN_WATER) && (this.getDamage() > 0))) { + this.level.setBlock(this, LAYER_WATERLOGGED, Block.get(Block.AIR), true, true); + } + } this.level.scheduleUpdate(this, this.tickRate()); return 0; } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { int decay = this.getFlowDecay(this); int multiplier = this.getFlowDecayPerBlock(); + FullChunk guessChunk = getChunk(); if (decay > 0) { int smallestFlowDecay = -100; this.adjacentSources = 0; - smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock((int) this.x, (int) this.y, (int) this.z - 1), smallestFlowDecay); - smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock((int) this.x, (int) this.y, (int) this.z + 1), smallestFlowDecay); - smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock((int) this.x - 1, (int) this.y, (int) this.z), smallestFlowDecay); - smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock((int) this.x + 1, (int) this.y, (int) this.z), smallestFlowDecay); + smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z - 1, true), smallestFlowDecay); + smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z + 1, true), smallestFlowDecay); + smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock(guessChunk, (int) this.x - 1, (int) this.y, (int) this.z, true), smallestFlowDecay); + smallestFlowDecay = this.getSmallestFlowDecay(this.level.getBlock(guessChunk, (int) this.x + 1, (int) this.y, (int) this.z, true), smallestFlowDecay); int newDecay = smallestFlowDecay + multiplier; if (newDecay >= 8 || smallestFlowDecay < 0) { newDecay = -1; } - int topFlowDecay = this.getFlowDecay(this.level.getBlock((int) this.x, (int) this.y + 1, (int) this.z)); + int topFlowDecay = this.getFlowDecay(this.level.getBlock(guessChunk, (int) this.x, (int) this.y + 1, (int) this.z, true)); if (topFlowDecay >= 0) { newDecay = topFlowDecay | 0x08; } if (this.adjacentSources >= 2 && this instanceof BlockWater) { - Block bottomBlock = this.level.getBlock((int) this.x, (int) this.y - 1, (int) this.z); + Block bottomBlock = this.level.getBlock(guessChunk, (int) this.x, (int) this.y - 1, (int) this.z, true); if (bottomBlock.isSolid()) { newDecay = 0; } else if (bottomBlock instanceof BlockWater && bottomBlock.getDamage() == 0) { newDecay = 0; + } else { + bottomBlock = bottomBlock.getLevelBlock(BlockLayer.WATERLOGGED); + if (bottomBlock instanceof BlockWater && bottomBlock.getDamage() == 0) { + newDecay = 0; + } } } if (newDecay != decay) { @@ -235,7 +263,7 @@ public int onUpdate(int type) { BlockFromToEvent event = new BlockFromToEvent(this, to); level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { - this.level.setBlock(this, event.getTo(), true, true); + this.level.setBlock(this, this.getLayer(), event.getTo(), true, true); if (!decayed) { this.level.scheduleUpdate(this, this.tickRate()); } @@ -243,9 +271,9 @@ public int onUpdate(int type) { } } if (decay >= 0) { - Block bottomBlock = this.level.getBlock((int) this.x, (int) this.y - 1, (int) this.z); + Block bottomBlock = this.level.getBlock(guessChunk, (int) this.x, (int) this.y - 1, (int) this.z, true); this.flowIntoBlock(bottomBlock, decay | 0x08); - if (decay == 0 || !bottomBlock.canBeFlowedInto()) { + if (decay == 0 || !(this.usesWaterLogging()? bottomBlock.canWaterloggingFlowInto(): bottomBlock.canBeFlowedInto())) { int adjacentDecay; if (decay >= 8) { adjacentDecay = 1; @@ -255,16 +283,16 @@ public int onUpdate(int type) { if (adjacentDecay < 8) { boolean[] flags = this.getOptimalFlowDirections(); if (flags[0]) { - this.flowIntoBlock(this.level.getBlock((int) this.x - 1, (int) this.y, (int) this.z), adjacentDecay); + this.flowIntoBlock(this.level.getBlock(guessChunk, (int) this.x - 1, (int) this.y, (int) this.z, true), adjacentDecay); } if (flags[1]) { - this.flowIntoBlock(this.level.getBlock((int) this.x + 1, (int) this.y, (int) this.z), adjacentDecay); + this.flowIntoBlock(this.level.getBlock(guessChunk, (int) this.x + 1, (int) this.y, (int) this.z, true), adjacentDecay); } if (flags[2]) { - this.flowIntoBlock(this.level.getBlock((int) this.x, (int) this.y, (int) this.z - 1), adjacentDecay); + this.flowIntoBlock(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z - 1, true), adjacentDecay); } if (flags[3]) { - this.flowIntoBlock(this.level.getBlock((int) this.x, (int) this.y, (int) this.z + 1), adjacentDecay); + this.flowIntoBlock(this.level.getBlock(guessChunk, (int) this.x, (int) this.y, (int) this.z + 1, true), adjacentDecay); } } } @@ -275,27 +303,37 @@ public int onUpdate(int type) { } protected void flowIntoBlock(Block block, int newFlowDecay) { - if (this.canFlowInto(block) && !(block instanceof BlockLiquid)) { + if (!(block instanceof BlockLiquid) && this.canFlowInto(block)) { + if (this.usesWaterLogging()) { + Block waterlogged = block.getLevelBlock(LAYER_WATERLOGGED); + if (waterlogged instanceof BlockLiquid) { + return; + } + + if (block.getWaterloggingType() == WaterloggingType.FLOW_INTO_BLOCK) { + block = waterlogged; + } + } + LiquidFlowEvent event = new LiquidFlowEvent(block, this, newFlowDecay); level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { - if (block.getId() > 0) { + if (block.getLayer() == BlockLayer.NORMAL && block.getId() != 0) { this.level.useBreakOn(block, block.getId() == COBWEB ? Item.get(Item.WOODEN_SWORD) : null); } - this.level.setBlock(block, getBlock(newFlowDecay), true, true); + this.level.setBlock(block, block.getLayer(), getBlock(newFlowDecay), true, true); this.level.scheduleUpdate(block, this.tickRate()); } } } - private int calculateFlowCost(int blockX, int blockY, int blockZ, int accumulatedCost, int maxCost, int originOpposite, int lastOpposite) { + protected int calculateFlowCost(int blockX, int blockY, int blockZ, int accumulatedCost, int maxCost, int originOpposite, int lastOpposite) { int cost = 1000; for (int j = 0; j < 4; ++j) { if (j == originOpposite || j == lastOpposite) { continue; } int x = blockX; - int y = blockY; int z = blockZ; if (j == 0) { --x; @@ -306,18 +344,26 @@ private int calculateFlowCost(int blockX, int blockY, int blockZ, int accumulate } else if (j == 3) { ++z; } - long hash = Level.blockHash(x, y, z); - if (!this.flowCostVisited.containsKey(hash)) { - Block blockSide = this.level.getBlock(x, y, z); + long hash = Level.blockHash(x, blockY, z, this.level.getDimensionData()); + byte status; + if (this.flowCostVisited.containsKey(hash)) { + status = this.flowCostVisited.get(hash); + } else { + FullChunk chunk = this.level.getChunk(x >> 4, z >> 4); + Block blockSide = this.level.getBlock(chunk, x, blockY, z, true); if (!this.canFlowInto(blockSide)) { this.flowCostVisited.put(hash, BLOCKED); - } else if (this.level.getBlock(x, y - 1, z).canBeFlowedInto()) { + status = BLOCKED; + } else if (usesWaterLogging()? + this.level.getBlock(x, blockY - 1, z).canWaterloggingFlowInto() : + this.level.getBlock(chunk, x, blockY - 1, z, true).canBeFlowedInto()) { this.flowCostVisited.put(hash, CAN_FLOW_DOWN); + status = CAN_FLOW_DOWN; } else { this.flowCostVisited.put(hash, CAN_FLOW); + status = CAN_FLOW; } } - byte status = this.flowCostVisited.get(hash); if (status == BLOCKED) { continue; } else if (status == CAN_FLOW_DOWN) { @@ -326,7 +372,7 @@ private int calculateFlowCost(int blockX, int blockY, int blockZ, int accumulate if (accumulatedCost >= maxCost) { continue; } - int realCost = this.calculateFlowCost(x, y, z, accumulatedCost + 1, maxCost, originOpposite, j ^ 0x01); + int realCost = this.calculateFlowCost(x, blockY, z, accumulatedCost + 1, maxCost, originOpposite, j ^ 0x01); if (realCost < cost) { cost = realCost; } @@ -344,8 +390,8 @@ public double getResistance() { return 500; } - private boolean[] getOptimalFlowDirections() { - int[] flowCost = new int[]{ + protected boolean[] getOptimalFlowDirections() { + int[] flowCost = { 1000, 1000, 1000, @@ -365,14 +411,17 @@ private boolean[] getOptimalFlowDirections() { } else { ++z; } - Block block = this.level.getBlock(x, y, z); + FullChunk chunk = this.level.getChunk(x >> 4, z >> 4); + Block block = this.level.getBlock(chunk, x, y, z, true); if (!this.canFlowInto(block)) { - this.flowCostVisited.put(Level.blockHash(x, y, z), BLOCKED); - } else if (this.level.getBlock(x, y - 1, z).canBeFlowedInto()) { - this.flowCostVisited.put(Level.blockHash(x, y, z), CAN_FLOW_DOWN); + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), BLOCKED); + } else if (usesWaterLogging()? + this.level.getBlock(x, y - 1, z).canWaterloggingFlowInto(): + this.level.getBlock(chunk, x, y - 1, z, true).canBeFlowedInto()) { + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), CAN_FLOW_DOWN); flowCost[j] = maxCost = 0; } else if (maxCost > 0) { - this.flowCostVisited.put(Level.blockHash(x, y, z), CAN_FLOW); + this.flowCostVisited.put(Level.blockHash(x, y, z, this.level.getDimensionData()), CAN_FLOW); flowCost[j] = this.calculateFlowCost(x, y, z, 1, maxCost, j ^ 0x01, j ^ 0x01); maxCost = Math.min(maxCost, flowCost[j]); } @@ -407,15 +456,6 @@ private int getSmallestFlowDecay(Block block, int decay) { protected void checkForHarden() { } - protected void triggerLavaMixEffects(Vector3 pos) { - Random random = ThreadLocalRandom.current(); - this.getLevel().addLevelEvent(pos.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_FIZZ, (int) ((random.nextFloat() - random.nextFloat()) * 800) + 2600); - - for (int i = 0; i < 8; ++i) { - this.getLevel().addParticle(new SmokeParticle(pos.add(Math.random(), 1.2, Math.random()))); - } - } - public abstract BlockLiquid getBlock(int meta); @Override @@ -435,11 +475,18 @@ protected boolean liquidCollide(Block cause, Block result) { return false; } this.level.setBlock(this, event.getTo(), true, true); + this.level.setBlock(this, Block.LAYER_WATERLOGGED, Block.get(Block.AIR), true, true); this.getLevel().addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_FIZZ); return true; } protected boolean canFlowInto(Block block) { + if (this.usesWaterLogging()) { + if (block.canWaterloggingFlowInto()) { + Block blockLayer1 = block.getLevelBlock(BlockLayer.WATERLOGGED); + return !(block instanceof BlockLiquid && block.getDamage() == 0) && !(blockLayer1 instanceof BlockLiquid && blockLayer1.getDamage() == 0); + } + } return block.canBeFlowedInto() && !(block instanceof BlockLiquid && block.getDamage() == 0); } @@ -447,4 +494,8 @@ protected boolean canFlowInto(Block block) { public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); } -} + + public boolean usesWaterLogging() { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockLodestone.java b/src/main/java/cn/nukkit/block/BlockLodestone.java new file mode 100644 index 00000000000..cd54861b331 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLodestone.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockLodestone extends BlockSolid { + + @Override + public String getName() { + return "Lodestone"; + } + + @Override + public int getId() { + return LODESTONE; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 3.5; + } + + @Override + public boolean canBePushed() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockLoom.java b/src/main/java/cn/nukkit/block/BlockLoom.java new file mode 100644 index 00000000000..6e186e14b9f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockLoom.java @@ -0,0 +1,76 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.inventory.LoomInventory; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockLoom extends BlockSolidMeta { + + public BlockLoom() { + this(0); + } + + public BlockLoom(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Loom"; + } + + @Override + public int getId() { + return LOOM; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public double getResistance() { + return 12.5; + } + + @Override + public double getHardness() { + return 2.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null) { + player.addWindow(new LoomInventory(player.getUIInventory(), this), Player.LOOM_WINDOW_ID); + } + return true; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + private static final short[] faces = {2, 3, 0, 1}; + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + return this.getLevel().setBlock(this, this, true, true); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMagma.java b/src/main/java/cn/nukkit/block/BlockMagma.java index 9ba436318ea..286b61f07a7 100644 --- a/src/main/java/cn/nukkit/block/BlockMagma.java +++ b/src/main/java/cn/nukkit/block/BlockMagma.java @@ -1,21 +1,22 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.entity.Entity; +import cn.nukkit.event.block.BlockFormEvent; import cn.nukkit.event.entity.EntityDamageByBlockEvent; import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.inventory.PlayerInventory; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.GameRule; +import cn.nukkit.level.Level; import cn.nukkit.potion.Effect; import cn.nukkit.utils.BlockColor; public class BlockMagma extends BlockSolid { - public BlockMagma(){ - - } - @Override public int getId() { return MAGMA; @@ -38,7 +39,7 @@ public double getHardness() { @Override public double getResistance() { - return 30; + return 0.5; } @Override @@ -48,7 +49,7 @@ public int getLightLevel() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -59,14 +60,18 @@ public Item[] getDrops(Item item) { @Override public void onEntityCollide(Entity entity) { - if (!entity.hasEffect(Effect.FIRE_RESISTANCE)) { + if (entity.y >= this.y + 1 && !entity.hasEffect(Effect.FIRE_RESISTANCE)) { if (entity instanceof Player) { Player p = (Player) entity; - if (!p.isCreative() && !p.isSpectator() && !p.isSneaking() && p.level.gameRules.getBoolean(GameRule.FIRE_DAMAGE)) { - entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.LAVA, 1)); + PlayerInventory inv = p.getInventory(); + if (inv == null || inv.getBootsFast().hasEnchantment(Enchantment.ID_FROST_WALKER) || !entity.level.gameRules.getBoolean(GameRule.FIRE_DAMAGE)) { + return; + } + if (!p.isCreative() && !p.isSpectator() && !p.isSneaking()) { + entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.MAGMA, 1)); } } else { - entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.LAVA, 1)); + entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.MAGMA, 1)); } } } @@ -80,5 +85,26 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } + + @Override + public boolean hasEntityCollision() { + return true; + } + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block up = this.up(); + if (up instanceof BlockWater && (up.getDamage() == 0 || up.getDamage() == 8)) { + BlockFormEvent event = new BlockFormEvent(up, Block.get(BUBBLE_COLUMN, BlockBubbleColumn.DIRECTION_DOWN)); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + this.getLevel().setBlock(up, event.getNewState(), false, true); + } + } + } + + return 0; + } } diff --git a/src/main/java/cn/nukkit/block/BlockMelon.java b/src/main/java/cn/nukkit/block/BlockMelon.java index 6bad17f49ae..3e99161d1e1 100644 --- a/src/main/java/cn/nukkit/block/BlockMelon.java +++ b/src/main/java/cn/nukkit/block/BlockMelon.java @@ -1,12 +1,10 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemMelon; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/11 by Pub4Game. @@ -15,9 +13,6 @@ public class BlockMelon extends BlockSolid { - public BlockMelon() { - } - @Override public int getId() { return MELON_BLOCK; @@ -38,16 +33,19 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - Random random = new Random(); - int count = 3 + random.nextInt(5); + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int count = 3 + Utils.random.nextInt(5); Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - count += random.nextInt(fortune.getLevel() + 1); + count += Utils.random.nextInt(fortune.getLevel() + 1); } return new Item[]{ - new ItemMelon(0, Math.min(9, count)) + Item.get(Item.MELON, 0, Math.min(9, count)) }; } @@ -60,9 +58,14 @@ public int getToolType() { public BlockColor getColor() { return BlockColor.LIME_BLOCK_COLOR; } - + @Override public boolean canSilkTouch() { return true; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockMeta.java b/src/main/java/cn/nukkit/block/BlockMeta.java index 6ffb2087e38..7fc627bcc51 100644 --- a/src/main/java/cn/nukkit/block/BlockMeta.java +++ b/src/main/java/cn/nukkit/block/BlockMeta.java @@ -1,6 +1,7 @@ package cn.nukkit.block; public abstract class BlockMeta extends Block { + private int meta; protected BlockMeta(int meta) { @@ -9,7 +10,7 @@ protected BlockMeta(int meta) { @Override public int getFullId() { - return (getId() << 4) + getDamage(); + return (getId() << DATA_BITS) + getDamage(); } @Override @@ -21,5 +22,4 @@ public final int getDamage() { public void setDamage(int meta) { this.meta = meta; } - -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockMobSpawner.java b/src/main/java/cn/nukkit/block/BlockMobSpawner.java index 766b662146b..dca8ea8089e 100644 --- a/src/main/java/cn/nukkit/block/BlockMobSpawner.java +++ b/src/main/java/cn/nukkit/block/BlockMobSpawner.java @@ -1,16 +1,17 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 27.12.2015. */ public class BlockMobSpawner extends BlockSolid { - public BlockMobSpawner() { - } - @Override public String getName() { return "Monster Spawner"; @@ -37,8 +38,13 @@ public double getResistance() { } @Override - public Item[] getDrops(Item item) { - return new Item[0]; + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (super.place(item, block, target, face, fx, fy, fz, player)) { + BlockEntity.createBlockEntity(BlockEntity.MOB_SPAWNER, this.getChunk(), BlockEntity.getDefaultCompound(this, BlockEntity.MOB_SPAWNER)); + + return true; + } + return false; } @Override @@ -51,4 +57,28 @@ public boolean canHarvestWithHand() { return false; } + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public int getLightLevel() { + return 3; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public int getDropExp() { + return Utils.rand(15, 43); + } } diff --git a/src/main/java/cn/nukkit/block/BlockMonsterEgg.java b/src/main/java/cn/nukkit/block/BlockMonsterEgg.java index 129c35d09c2..2b3e4238e7b 100644 --- a/src/main/java/cn/nukkit/block/BlockMonsterEgg.java +++ b/src/main/java/cn/nukkit/block/BlockMonsterEgg.java @@ -3,6 +3,7 @@ import cn.nukkit.item.Item; public class BlockMonsterEgg extends BlockSolidMeta { + public static final int STONE = 0; public static final int COBBLESTONE = 1; public static final int STONE_BRICK = 2; @@ -10,7 +11,7 @@ public class BlockMonsterEgg extends BlockSolidMeta { public static final int CRACKED_BRICK = 4; public static final int CHISELED_BRICK = 5; - private static final String[] NAMES = new String[]{ + private static final String[] NAMES = { "Stone", "Cobblestone", "Stone Brick", @@ -39,7 +40,7 @@ public double getHardness() { @Override public double getResistance() { - return 3.75; + return 0.75; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockMoss.java b/src/main/java/cn/nukkit/block/BlockMoss.java new file mode 100644 index 00000000000..12611a00028 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMoss.java @@ -0,0 +1,112 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockMoss extends BlockDirt { + + public BlockMoss() { + } + + public BlockMoss(int meta) { + super(0); + } + + @Override + public int getId() { + return MOSS_BLOCK; + } + + @Override + public String getName() { + return "Moss Block"; + } + + @Override + public double getHardness() { + return 0.1; + } + + @Override + public double getResistance() { + return 2.5; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL || this.up().getId() != AIR) { + return false; + } + + int random = ThreadLocalRandom.current().nextInt(13); + Block block; + Block block2 = null; + if (random < 5) { + block = Block.get(TALL_GRASS); + } else if (random < 8) { + block = Block.get(MOSS_CARPET); + } else if (random < 9) { + if (this.up(2).getId() != AIR) { + return false; + } + + block = Block.get(DOUBLE_PLANT, BlockDoublePlant.TALL_GRASS); + block2 = Block.get(DOUBLE_PLANT, BlockDoublePlant.TALL_GRASS ^ BlockDoublePlant.TOP_HALF_BITMASK); + } else if (random < 11) { + block = Block.get(AZALEA); + } else { + block = Block.get(FLOWERING_AZALEA); + } + + this.getLevel().setBlock(this.up(), block, false, true); + if (block2 != null) { + this.getLevel().setBlock(this.up(2), block2, false, true); + } + + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && !player.isCreative()) { + item.count--; + } + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.GREEN_BLOCK_COLOR; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public int getFullId() { + return this.getId() << Block.DATA_BITS; + } + + @Override + public void setDamage(int meta) { + // Noop + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public Item[] getDrops(Item item) { + if (this.canHarvestWithHand() || this.canHarvest(item)) { + return new Item[]{this.toItem()}; + } + return new Item[0]; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMossCarpet.java b/src/main/java/cn/nukkit/block/BlockMossCarpet.java new file mode 100644 index 00000000000..a482fc38db8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMossCarpet.java @@ -0,0 +1,90 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.DyeColor; + +public class BlockMossCarpet extends BlockTransparent { + + public BlockMossCarpet() { + } + + @Override + public int getId() { + return MOSS_CARPET; + } + + @Override + public String getName() { + return "Moss Carpet"; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } + + @Override + public double getHardness() { + return 0.1; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public boolean isSolid() { + return true; + } + + @Override + public boolean canPassThrough() { + return false; + } + + @Override + public double getMaxY() { + return this.y + 0.0625; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + Block down = this.down(); + if (down.getId() != Item.AIR) { + this.getLevel().setBlock(block, this, true, true); + return true; + } + return false; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!this.down().isSolid()) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } + return 0; + } + + @Override + public BlockColor getColor() { + return DyeColor.GREEN.getColor(); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMossStone.java b/src/main/java/cn/nukkit/block/BlockMossStone.java index 9c4a6e9cf72..cf1a65533d3 100644 --- a/src/main/java/cn/nukkit/block/BlockMossStone.java +++ b/src/main/java/cn/nukkit/block/BlockMossStone.java @@ -9,9 +9,6 @@ */ public class BlockMossStone extends BlockSolid { - public BlockMossStone() { - } - @Override public String getName() { return "Mossy Cobblestone"; @@ -39,7 +36,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockMud.java b/src/main/java/cn/nukkit/block/BlockMud.java new file mode 100644 index 00000000000..8ce0ce5b773 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMud.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockMud extends BlockSolid { + + public BlockMud() { + } + + @Override + public String getName() { + return "Mud"; + } + + @Override + public int getId() { + return MUD; + } + + @Override + public double getHardness() { + return 0.5; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_SHOVEL; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMudBrick.java b/src/main/java/cn/nukkit/block/BlockMudBrick.java new file mode 100644 index 00000000000..4ea3fb4322f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMudBrick.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockMudBrick extends BlockSolid { + + public BlockMudBrick() { + } + + @Override + public String getName() { + return "Mud Brick"; + } + + @Override + public int getId() { + return MUD_BRICKS; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMudBrickSlab.java b/src/main/java/cn/nukkit/block/BlockMudBrickSlab.java new file mode 100644 index 00000000000..450db319183 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMudBrickSlab.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockMudBrickSlab extends BlockSlab { + + public BlockMudBrickSlab() { + this(0); + } + + public BlockMudBrickSlab(int meta) { + super(meta, MUD_BRICK_DOUBLE_SLAB); + } + + @Override + public int getId() { + return MUD_BRICK_SLAB; + } + + @Override + public String getSlabName() { + return "Mud Brick Slab"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMudBrickStairs.java b/src/main/java/cn/nukkit/block/BlockMudBrickStairs.java new file mode 100644 index 00000000000..99e5bee899a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMudBrickStairs.java @@ -0,0 +1,32 @@ +package cn.nukkit.block; + +public class BlockMudBrickStairs extends BlockStairs { + + public BlockMudBrickStairs() { + this(0); + } + + public BlockMudBrickStairs(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Mud Brick Stair"; + } + + @Override + public int getId() { + return MUD_BRICK_STAIRS; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMudBrickWall.java b/src/main/java/cn/nukkit/block/BlockMudBrickWall.java new file mode 100644 index 00000000000..1924a95f277 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockMudBrickWall.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockMudBrickWall extends BlockWall { + + public BlockMudBrickWall() { + this(0); + } + + public BlockMudBrickWall(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Mud Brick Wall"; + } + + @Override + public int getId() { + return MUD_BRICK_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockMushroom.java b/src/main/java/cn/nukkit/block/BlockMushroom.java index 350229a9eef..f1f7e173c5b 100644 --- a/src/main/java/cn/nukkit/block/BlockMushroom.java +++ b/src/main/java/cn/nukkit/block/BlockMushroom.java @@ -1,10 +1,8 @@ package cn.nukkit.block; import cn.nukkit.Player; -import cn.nukkit.event.level.StructureGrowEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; -import cn.nukkit.level.ListChunkManager; import cn.nukkit.level.generator.object.mushroom.BigMushroom; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; @@ -38,6 +36,10 @@ public int onUpdate(int type) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block instanceof BlockWater) { + return false; + } + if (canStay()) { getLevel().setBlock(block, this, true, true); return true; @@ -53,7 +55,7 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { if (item.getId() == Item.DYE && item.getDamage() == DyeColor.WHITE.getDyeData()) { - if (player != null && (player.gamemode & 0x01) == 0) { + if (player != null && !player.isCreative()) { item.count--; } @@ -72,16 +74,7 @@ public boolean grow() { BigMushroom generator = new BigMushroom(getType()); - ListChunkManager chunkManager = new ListChunkManager(this.level); - if (generator.generate(chunkManager, new NukkitRandom(), this)) { - StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); - this.level.getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return false; - } - for(Block block : ev.getBlockList()) { - this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); - } + if (generator.generate(this.level, new NukkitRandom(), this)) { return true; } else { this.level.setBlock(this, this, true, false); @@ -91,7 +84,7 @@ public boolean grow() { public boolean canStay() { Block block = this.down(); - return block.getId() == MYCELIUM || block.getId() == PODZOL || (!block.isTransparent() && this.level.getFullLight(this) < 13); + return block.getId() == MYCELIUM || block.getId() == PODZOL || (!block.isTransparent() && this.level.getBlockLightAt((int) this.x, (int) this.y, (int) this.z) < 13); // TODO: sky/full light } @Override @@ -105,4 +98,9 @@ public boolean canSilkTouch() { } protected abstract int getType(); + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockMushroomBrown.java b/src/main/java/cn/nukkit/block/BlockMushroomBrown.java index 5655ea13196..8ce4f00e33a 100644 --- a/src/main/java/cn/nukkit/block/BlockMushroomBrown.java +++ b/src/main/java/cn/nukkit/block/BlockMushroomBrown.java @@ -32,4 +32,4 @@ public int getLightLevel() { protected int getType() { return 0; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockMushroomRed.java b/src/main/java/cn/nukkit/block/BlockMushroomRed.java index ce82f01a9c2..9f27cc06ca4 100644 --- a/src/main/java/cn/nukkit/block/BlockMushroomRed.java +++ b/src/main/java/cn/nukkit/block/BlockMushroomRed.java @@ -27,4 +27,4 @@ public int getId() { protected int getType() { return 1; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockMycelium.java b/src/main/java/cn/nukkit/block/BlockMycelium.java index 358d6677130..150d3546951 100644 --- a/src/main/java/cn/nukkit/block/BlockMycelium.java +++ b/src/main/java/cn/nukkit/block/BlockMycelium.java @@ -1,23 +1,21 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.event.block.BlockSpreadEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; -import cn.nukkit.math.NukkitRandom; -import cn.nukkit.math.Vector3; +import cn.nukkit.level.Sound; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 03.01.2016. */ public class BlockMycelium extends BlockSolid { - public BlockMycelium() { - } - @Override public String getName() { return "Mycelium"; @@ -53,15 +51,13 @@ public Item[] getDrops(Item item) { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_RANDOM) { - //TODO: light levels - NukkitRandom random = new NukkitRandom(); - x = random.nextRange((int) x - 1, (int) x + 1); - y = random.nextRange((int) y - 1, (int) y + 1); - z = random.nextRange((int) z - 1, (int) z + 1); - Block block = this.getLevel().getBlock(new Vector3(x, y, z)); + int xx = Utils.rand((int) x - 1, (int) x + 1); + int yy = Utils.rand((int) y - 1, (int) y + 1); + int zz = Utils.rand((int) z - 1, (int) z + 1); + Block block = this.getLevel().getBlock(xx, yy, zz); if (block.getId() == Block.DIRT && block.getDamage() == 0) { - if (block.up().isTransparent()) { - BlockSpreadEvent ev = new BlockSpreadEvent(block, this, Block.get(BlockID.MYCELIUM)); + if (block.up() instanceof BlockAir) { + BlockSpreadEvent ev = new BlockSpreadEvent(block, this, Block.get(MYCELIUM)); Server.getInstance().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { this.getLevel().setBlock(block, ev.getNewState()); @@ -76,9 +72,30 @@ public int onUpdate(int type) { public BlockColor getColor() { return BlockColor.PURPLE_BLOCK_COLOR; } - + @Override public boolean canSilkTouch() { return true; } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.isShovel()) { + Block up = this.up(); + if (up instanceof BlockAir || up instanceof BlockFlowable) { + item.useOn(this); + this.getLevel().setBlock(this, Block.get(GRASS_PATH)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + return true; + } + } + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockNetherBrick.java b/src/main/java/cn/nukkit/block/BlockNetherBrick.java index ff28430572c..06e3bb942c0 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherBrick.java +++ b/src/main/java/cn/nukkit/block/BlockNetherBrick.java @@ -10,9 +10,6 @@ */ public class BlockNetherBrick extends BlockSolid { - public BlockNetherBrick() { - } - @Override public String getName() { return "Nether Bricks"; @@ -40,7 +37,7 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockNetherPortal.java b/src/main/java/cn/nukkit/block/BlockNetherPortal.java index b48c0ee8c7b..fbdf3af76c6 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherPortal.java +++ b/src/main/java/cn/nukkit/block/BlockNetherPortal.java @@ -1,11 +1,16 @@ package cn.nukkit.block; +import cn.nukkit.Server; +import cn.nukkit.event.level.NetherPortalSpawnEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.level.Position; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockFace.Axis; +import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -34,16 +39,6 @@ public int getId() { return NETHER_PORTAL; } - @Override - public boolean canBeFlowedInto() { - return false; - } - - @Override - public boolean canPassThrough() { - return true; - } - @Override public boolean isBreakable(Item item) { return false; @@ -64,15 +59,18 @@ public Item toItem() { return new ItemBlock(Block.get(BlockID.AIR)); } + @Override + public boolean canBeFlowedInto() { + return false; + } + @Override public boolean onBreak(Item item) { boolean result = super.onBreak(item); for (BlockFace face : BlockFace.values()) { Block b = this.getSide(face); - if (b != null) { - if (b instanceof BlockNetherPortal) { - result &= b.onBreak(item); - } + if (b instanceof BlockNetherPortal) { + result &= b.onBreak(item); } } return result; @@ -98,17 +96,234 @@ public boolean canHarvestWithHand() { return false; } + public static boolean trySpawnPortal(Level level, Vector3 pos) { + return trySpawnPortal(level, pos, false); + } + @Override protected AxisAlignedBB recalculateBoundingBox() { return this; } - public static void spawnPortal(Position pos) { + public static boolean trySpawnPortal(Level level, Vector3 pos, boolean force) { + PortalBuilder builder = new PortalBuilder(level, pos, Axis.X, force); + + if (builder.isValid() && builder.portalBlockCount == 0) { + builder.placePortalBlocks(); + return true; + } else { + builder = new PortalBuilder(level, pos, Axis.Z, force); + + if (builder.isValid() && builder.portalBlockCount == 0) { + builder.placePortalBlocks(); + return true; + } else { + return false; + } + } + } + + public static class PortalBuilder { + + private final Level level; + private final Axis axis; + private final BlockFace rightDir; + private final BlockFace leftDir; + private int portalBlockCount; + private Vector3 bottomLeft; + private int height; + private int width; + + private final boolean force; + + public PortalBuilder(Level level, Vector3 pos, Axis axis, boolean force) { + this.level = level; + this.axis = axis; + this.force = force; + + if (axis == Axis.X) { + this.leftDir = BlockFace.EAST; + this.rightDir = BlockFace.WEST; + } else { + this.leftDir = BlockFace.NORTH; + this.rightDir = BlockFace.SOUTH; + } + + + for (Vector3 blockpos = pos; pos.getY() > blockpos.getY() - 21 && pos.getY() > level.getMinBlockY() && this.isEmptyBlock(getBlockId(pos.getSideVec(BlockFace.DOWN))); pos = pos.getSideVec(BlockFace.DOWN)) { + } + + int i = this.getDistanceUntilEdge(pos, this.leftDir) - 1; + + if (i >= 0) { + this.bottomLeft = pos.getSideVec(this.leftDir, i); + this.width = this.getDistanceUntilEdge(this.bottomLeft, this.rightDir); + + if (this.width < 2 || this.width > 21) { + this.bottomLeft = null; + this.width = 0; + } + } + + if (this.bottomLeft != null) { + this.height = this.calculatePortalHeight(); + } + } + + protected int getDistanceUntilEdge(Vector3 pos, BlockFace dir) { + int i; + + for (i = 0; i < 22; ++i) { + Vector3 v = pos.getSideVec(dir, i); + + if (!this.isEmptyBlock(getBlockId(v)) || getBlockId(v.getSideVec(BlockFace.DOWN)) != OBSIDIAN) { + break; + } + } + + return getBlockId(pos.getSideVec(dir, i)) == OBSIDIAN ? i : 0; + } + + public int getHeight() { + return this.height; + } + + public int getWidth() { + return this.width; + } + + protected int calculatePortalHeight() { + + loop: + for (this.height = 0; this.height < 21; ++this.height) { + for (int i = 0; i < this.width; ++i) { + Vector3 blockpos = this.bottomLeft.getSideVec(this.rightDir, i).up(this.height); + int block = getBlockId(blockpos); + + if (!this.isEmptyBlock(block)) { + break loop; + } + + if (block == NETHER_PORTAL) { + ++this.portalBlockCount; + } + + if (i == 0) { + block = getBlockId(blockpos.getSideVec(this.leftDir)); + + if (block != OBSIDIAN) { + break loop; + } + } else if (i == this.width - 1) { + block = getBlockId(blockpos.getSideVec(this.rightDir)); + + if (block != OBSIDIAN) { + break loop; + } + } + } + } + + for (int i = 0; i < this.width; ++i) { + if (getBlockId(this.bottomLeft.getSideVec(this.rightDir, i).up(this.height)) != OBSIDIAN) { + this.height = 0; + break; + } + } + + if (this.height <= 21 && this.height >= 3) { + return this.height; + } else { + this.bottomLeft = null; + this.width = 0; + this.height = 0; + return 0; + } + } + + private int getBlockId(Vector3 pos) { + return this.level.getBlockIdAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()); + } + + protected boolean isEmptyBlock(int id) { + return force || id == AIR || id == FIRE || id == NETHER_PORTAL; + } + + public boolean isValid() { + return this.bottomLeft != null && this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21; + } + + public void placePortalBlocks() { + for (int i = 0; i < this.width; ++i) { + Vector3 blockpos = this.bottomLeft.getSideVec(this.rightDir, i); + + for (int j = 0; j < this.height; ++j) { + this.level.setBlock(blockpos.up(j), Block.get(NETHER_PORTAL, this.axis == Axis.X ? 1 : this.axis == Axis.Z ? 2 : 0)); + } + } + } + } + + public static Position getSafePortal(Position portal) { + Level level = portal.getLevel(); + FullChunk chunk = portal.getChunk(); + Vector3 down = portal.getSideVec(BlockFace.DOWN); + + while (level.getBlockIdAt(chunk, down.getFloorX(), down.getFloorY(), down.getFloorZ()) == NETHER_PORTAL) { + down = down.getSideVec(BlockFace.DOWN); + } + + return Position.fromObject(down.up(), portal.getLevel()); + } + + public static Position findNearestPortal(Position pos) { + Level level = pos.getLevel(); + Position found = null; + int maxY = level.getMaxBlockY(); + + for (int xx = -16; xx <= 16; xx++) { + for (int zz = -16; zz <= 16; zz++) { + for (int y = 0; y < maxY; y++) { + int x = pos.getFloorX() + xx, z = pos.getFloorZ() + zz; + if (level.getBlockIdAt(x, y, z) == NETHER_PORTAL) { + found = new Position(x, y, z, level); + break; + } + } + } + } + + if (found == null) { + return null; + } + Vector3 up = found.up(); + int x = up.getFloorX(), y = up.getFloorY(), z = up.getFloorZ(); + int id = level.getBlockIdAt(x, y, z); + if (id != AIR && id != OBSIDIAN && id != NETHER_PORTAL) { + for (int xx = -1; xx < 4; xx++) { + for (int yy = 1; yy < 4; yy++) { + for (int zz = -1; zz < 3; zz++) { + level.setBlockAt(x + xx, y + yy, z + zz, AIR); + } + } + } + } + return found; + } + + public static void spawnPortal(Position pos) { + NetherPortalSpawnEvent ev = new NetherPortalSpawnEvent(pos); + Server.getInstance().getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + return; + } + Level lvl = pos.level; int x = pos.getFloorX(), y = pos.getFloorY(), z = pos.getFloorZ(); for (int xx = -1; xx < 4; xx++) { - for (int yy = 1; yy < 4; yy++) { + for (int yy = 1; yy < 4; yy++) { for (int zz = -1; zz < 3; zz++) { lvl.setBlockAt(x + xx, y + yy, z + zz, AIR); } @@ -156,6 +371,6 @@ public static void spawnPortal(Position pos) { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } } diff --git a/src/main/java/cn/nukkit/block/BlockNetherReactor.java b/src/main/java/cn/nukkit/block/BlockNetherReactor.java new file mode 100644 index 00000000000..30bb6702f01 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockNetherReactor.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; + +public class BlockNetherReactor extends BlockSolid { + + @Override + public int getId() { + return NETHER_REACTOR; + } + + @Override + public String getName() { + return "Nether Reactor Core"; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 15; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{Item.get(Item.DIAMOND, 0, 3), Item.get(Item.IRON_INGOT, 0, 6)}; + } else return new Item[0]; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockNetherSprouts.java b/src/main/java/cn/nukkit/block/BlockNetherSprouts.java new file mode 100644 index 00000000000..b695f1cd9db --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockNetherSprouts.java @@ -0,0 +1,54 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockNetherSprouts extends BlockRoots { + + public BlockNetherSprouts() { + this(0); + } + + public BlockNetherSprouts(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Nether Sprouts"; + } + + @Override + public int getId() { + return NETHER_SPROUTS_BLOCK; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_SHEARS; + } + + @Override + public Item toItem() { + return Item.get(Item.NETHER_SPROUTS); + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isShears()) { + return new Item[]{ toItem() }; + } + return new Item[0]; + } + + @Override + public boolean canBeReplaced() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockNetherWart.java b/src/main/java/cn/nukkit/block/BlockNetherWart.java index f286e4f96b1..2cf4fa511f6 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherWart.java +++ b/src/main/java/cn/nukkit/block/BlockNetherWart.java @@ -4,12 +4,10 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemNetherWart; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** * Created by Leonidius20 on 22.03.17. @@ -42,7 +40,7 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_NORMAL; } } else if (type == Level.BLOCK_UPDATE_RANDOM) { - if (new Random().nextInt(10) == 1) { + if (Utils.random.nextInt(10) == 1) { if (this.getDamage() < 0x03) { BlockNetherWart block = (BlockNetherWart) this.clone(); block.setDamage(block.getDamage() + 1); @@ -65,7 +63,7 @@ public int onUpdate(int type) { @Override public BlockColor getColor() { - return BlockColor.RED_BLOCK_COLOR; + return BlockColor.FOLIAGE_BLOCK_COLOR; } @Override @@ -82,19 +80,22 @@ public int getId() { public Item[] getDrops(Item item) { if (this.getDamage() == 0x03) { return new Item[]{ - new ItemNetherWart(0, 2 + (int) (Math.random() * ((4 - 2) + 1))) + Item.get(Item.NETHER_WART, 0, 2 + (int) (Math.random() * (3))) }; } else { return new Item[]{ - new ItemNetherWart() + Item.get(Item.NETHER_WART) }; } } @Override public Item toItem() { - return new ItemNetherWart(); + return Item.get(Item.NETHER_WART); } -} - + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockNetherWartBlock.java b/src/main/java/cn/nukkit/block/BlockNetherWartBlock.java index 0edaf1cb54b..d20ed238ea0 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherWartBlock.java +++ b/src/main/java/cn/nukkit/block/BlockNetherWartBlock.java @@ -1,13 +1,11 @@ package cn.nukkit.block; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; public class BlockNetherWartBlock extends BlockSolid { - public BlockNetherWartBlock() { - } - @Override public String getName() { return "Nether Wart Block"; @@ -20,7 +18,7 @@ public int getId() { @Override public double getResistance() { - return 5; + return 1; } @Override @@ -39,4 +37,9 @@ public Item[] getDrops(Item item) { public BlockColor getColor() { return BlockColor.RED_BLOCK_COLOR; } + + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } } diff --git a/src/main/java/cn/nukkit/block/BlockNetheriteBlock.java b/src/main/java/cn/nukkit/block/BlockNetheriteBlock.java new file mode 100644 index 00000000000..7c68f12c7c8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockNetheriteBlock.java @@ -0,0 +1,45 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockNetheriteBlock extends BlockSolid { + + public BlockNetheriteBlock() { + } + + @Override + public int getId() { + return NETHERITE_BLOCK; + } + + @Override + public String getName() { + return "Netherite Block"; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 35; + } + + @Override + public double getResistance() { + return 6000; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockNetherrack.java b/src/main/java/cn/nukkit/block/BlockNetherrack.java index 8417aaa474e..2043e7dd363 100644 --- a/src/main/java/cn/nukkit/block/BlockNetherrack.java +++ b/src/main/java/cn/nukkit/block/BlockNetherrack.java @@ -10,9 +10,6 @@ */ public class BlockNetherrack extends BlockSolid { - public BlockNetherrack() { - } - @Override public int getId() { return NETHERRACK; @@ -40,7 +37,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -58,5 +55,4 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } - } diff --git a/src/main/java/cn/nukkit/block/BlockNoteblock.java b/src/main/java/cn/nukkit/block/BlockNoteblock.java index 66c304992c6..e0649baeda9 100644 --- a/src/main/java/cn/nukkit/block/BlockNoteblock.java +++ b/src/main/java/cn/nukkit/block/BlockNoteblock.java @@ -18,10 +18,6 @@ */ public class BlockNoteblock extends BlockSolid { - public BlockNoteblock() { - - } - @Override public String getName() { return "Note Block"; @@ -55,7 +51,8 @@ public boolean canBeActivated() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { this.getLevel().setBlock(block, this, true); - return this.createBlockEntity() != null; + BlockEntity.createBlockEntity(BlockEntity.MUSIC, this.getChunk(), BlockEntity.getDefaultCompound(this, BlockEntity.MUSIC)); + return true; } public int getStrength() { @@ -203,6 +200,7 @@ public Instrument getInstrument() { case CONCRETE: case STONECUTTER: case OBSERVER: + case RESPAWN_ANCHOR: return Instrument.BASS_DRUM; default: return Instrument.PIANO; @@ -210,7 +208,7 @@ public Instrument getInstrument() { } public void emitSound() { - if (this.up().getId() != AIR) return; + if (!this.isBlockAboveAir()) return; Instrument instrument = this.getInstrument(); @@ -222,11 +220,12 @@ public void emitSound() { pk.z = this.getFloorZ(); pk.case1 = instrument.ordinal(); pk.case2 = this.getStrength(); - this.getLevel().addChunkPacket(this.getFloorX() >> 4, this.getFloorZ() >> 4, pk); + this.getLevel().addChunkPacket(this.getChunkX(), this.getChunkZ(), pk); } @Override public boolean onActivate(Item item, Player player) { + if (player.isSneaking()) return false; this.increaseStrength(); this.emitSound(); return true; @@ -258,11 +257,6 @@ private BlockEntityMusic getBlockEntity() { return null; } - private BlockEntityMusic createBlockEntity() { - return (BlockEntityMusic) BlockEntity.createBlockEntity(BlockEntity.MUSIC, this.getLevel().getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), - BlockEntity.getDefaultCompound(this, BlockEntity.MUSIC)); - } - public enum Instrument { PIANO(Sound.NOTE_HARP), BASS_DRUM(Sound.NOTE_BD), @@ -296,4 +290,9 @@ public Sound getSound() { public BlockColor getColor() { return BlockColor.WOOD_BLOCK_COLOR; } -} + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockNylium.java b/src/main/java/cn/nukkit/block/BlockNylium.java new file mode 100644 index 00000000000..f58cdd2bd38 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockNylium.java @@ -0,0 +1,59 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; + +public abstract class BlockNylium extends BlockSolid { + + public BlockNylium() { + // Does nothing + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_RANDOM && !up().isTransparent()) { + level.setBlock(this, Block.get(NETHERRACK), false); + return type; + } + return 0; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public double getResistance() { + return 0.4; + } + + @Override + public double getHardness() { + return 0.4; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + return new Item[]{ Item.get(NETHERRACK) }; + } + return new Item[0]; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockObserver.java b/src/main/java/cn/nukkit/block/BlockObserver.java index f2d1855b4bc..3841dbf966e 100644 --- a/src/main/java/cn/nukkit/block/BlockObserver.java +++ b/src/main/java/cn/nukkit/block/BlockObserver.java @@ -1,16 +1,22 @@ package cn.nukkit.block; import cn.nukkit.Player; +import cn.nukkit.event.redstone.RedstoneUpdateEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; import cn.nukkit.utils.Faceable; -/** - * Created by Leonidius20 on 18.08.18. - */ public class BlockObserver extends BlockSolidMeta implements Faceable { + /** + * Where the block update happens. Used to check whether this observer should detect it. + */ + private Vector3 updatePos; + public BlockObserver() { this(0); } @@ -19,21 +25,53 @@ public BlockObserver(int meta) { super(meta); } + @Override + public int getId() { + return OBSERVER; + } + @Override public String getName() { return "Observer"; } @Override - public int getId() { - return OBSERVER; + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 17.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ + Item.get(Item.OBSERVER, 0, 1) + }; + } else { + return new Item[0]; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (player != null) { - if (Math.abs(player.getFloorX() - this.x) <= 1 && Math.abs(player.getFloorZ() - this.z) <= 1) { + if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { double y = player.y + player.getEyeHeight(); + if (y - this.y > 2) { this.setDamage(BlockFace.DOWN.getIndex()); } else if (this.y - y > 0) { @@ -44,36 +82,73 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } else { this.setDamage(player.getHorizontalFacing().getIndex()); } - } else { - this.setDamage(0); } - this.getLevel().setBlock(block, this, true, true); - return true; + + return this.getLevel().setBlock(this, this, true, true); } @Override - public boolean canHarvestWithHand() { - return false; + public BlockFace getBlockFace() { + return BlockFace.fromIndex(this.getDamage() & 0x07); } @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL && this.getSideVec(this.getBlockFace()).equals(this.updatePos) && !this.isPowered()) { + RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return 0; + } + this.setPowered(true); + this.level.setBlock(this, this, false, false); + this.level.updateAroundRedstone(this, this.getBlockFace()); + level.scheduleUpdate(this, 4); + return Level.BLOCK_UPDATE_NORMAL; + } else if (type == Level.BLOCK_UPDATE_SCHEDULED && this.isPowered()) { + RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return 0; + } + this.setPowered(false); + this.level.setBlock(this, this, false, false); + this.level.updateAroundRedstone(this, this.getBlockFace()); + } + return type; } @Override - public double getHardness() { - return 3.5; + public Item toItem() { + return new ItemBlock(Block.get(Block.OBSERVER)); } @Override - public double getResistance() { - return 17.5; + public boolean isPowerSource() { + return true; } @Override - public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + public int getStrongPower(BlockFace side) { + return this.isPowered() && side == this.getBlockFace() ? 15 : 0; + } + + @Override + public int getWeakPower(BlockFace face) { + return this.getStrongPower(face); + } + + public boolean isPowered() { + return (this.getDamage() & 0x8) == 0x8; } + public void setPowered(boolean powered) { + this.setDamage((this.getDamage() & 0x7) | (powered ? 0x8 : 0x0)); + } + + @Override + public Block setUpdatePos(Vector3 pos) { + this.updatePos = pos; + return this; + } } diff --git a/src/main/java/cn/nukkit/block/BlockObsidian.java b/src/main/java/cn/nukkit/block/BlockObsidian.java index 7ac74925ae3..cca7e15cff0 100644 --- a/src/main/java/cn/nukkit/block/BlockObsidian.java +++ b/src/main/java/cn/nukkit/block/BlockObsidian.java @@ -10,9 +10,6 @@ */ public class BlockObsidian extends BlockSolid { - public BlockObsidian() { - } - @Override public String getName() { return "Obsidian"; @@ -30,7 +27,7 @@ public int getToolType() { @Override public double getHardness() { - return 35; //50 in PC + return 35; } @Override @@ -52,7 +49,7 @@ public Item[] getDrops(Item item) { @Override public boolean onBreak(Item item) { //destroy the nether portal - Block[] nearby = new Block[]{ + Block[] nearby = { this.up(), this.down(), this.north(), south(), this.west(), this.east(), diff --git a/src/main/java/cn/nukkit/block/BlockObsidianCrying.java b/src/main/java/cn/nukkit/block/BlockObsidianCrying.java new file mode 100644 index 00000000000..84fd9177d13 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockObsidianCrying.java @@ -0,0 +1,63 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockObsidianCrying extends BlockSolid { + + @Override + public int getId() { + return CRYING_OBSIDIAN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Crying Obsidian"; + } + + @Override + public double getHardness() { + return 35; + } + + @Override + public double getResistance() { + return 1200; + } + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_DIAMOND) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public int getLightLevel() { + return 10; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.OBSIDIAN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java b/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java index 8b57354b0e5..2797560fd03 100644 --- a/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java +++ b/src/main/java/cn/nukkit/block/BlockObsidianGlowing.java @@ -3,6 +3,7 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; /** * Created on 2015/11/22 by xtypr. @@ -10,47 +11,49 @@ */ public class BlockObsidianGlowing extends BlockSolid { - public BlockObsidianGlowing() { - } - @Override public int getId() { return GLOWING_OBSIDIAN; } @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + public String getName() { + return "Glowing Obsidian"; } @Override - public String getName() { - return "Glowing Obsidian"; + public int getLightLevel() { + return 12; } @Override - public double getHardness() { - return 50; + public Item toItem() { + return new ItemBlock(Block.get(GLOWING_OBSIDIAN)); } @Override - public double getResistance() { - return 6000; + public boolean onBreak(Item item) { + return this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); } @Override - public int getLightLevel() { - return 12; + public int getToolType() { + return ItemTool.TYPE_PICKAXE; } @Override - public Item toItem() { - return new ItemBlock(Block.get(BlockID.OBSIDIAN)); + public double getHardness() { + return 12; //? + } + + @Override + public double getResistance() { + return 6000; } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() > ItemTool.DIAMOND_PICKAXE) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_DIAMOND) { return new Item[]{ toItem() }; @@ -59,6 +62,11 @@ public Item[] getDrops(Item item) { } } + @Override + public BlockColor getColor() { + return BlockColor.OBSIDIAN_BLOCK_COLOR; + } + @Override public boolean canBePushed() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockOre.java b/src/main/java/cn/nukkit/block/BlockOre.java new file mode 100644 index 00000000000..ea25e4fc42e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOre.java @@ -0,0 +1,82 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.math.NukkitMath; + +import java.util.concurrent.ThreadLocalRandom; + +public abstract class BlockOre extends BlockSolid { + + public BlockOre() { + } + + @Override + public Item[] getDrops(Item item) { + if (!this.canHarvest(item) || item.getTier() < this.getToolTier()) { + return new Item[0]; + } + + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int rawMaterial = this.getRawMaterial(); + if (rawMaterial == BlockID.AIR) { + return super.getDrops(item); + } + + float multiplier = this.getDropMultiplier(); + int amount = (int) multiplier; + if (amount > 1) { + amount = 1 + ThreadLocalRandom.current().nextInt(amount); + } + int fortuneLevel = NukkitMath.clamp(item.getEnchantmentLevel(Enchantment.ID_FORTUNE_DIGGING), 0, 3); + if (fortuneLevel > 0) { + int increase = ThreadLocalRandom.current().nextInt((int)(multiplier * fortuneLevel) + 1); + amount += increase; + } + return new Item[]{ Item.get(rawMaterial, this.getRawMaterialMeta(), amount) }; + } + + protected abstract int getRawMaterial(); + + protected int getRawMaterialMeta() { + return 0; + } + + protected float getDropMultiplier() { + return 1; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreCoal.java b/src/main/java/cn/nukkit/block/BlockOreCoal.java index e7ea88dd02d..1f7216f1707 100644 --- a/src/main/java/cn/nukkit/block/BlockOreCoal.java +++ b/src/main/java/cn/nukkit/block/BlockOreCoal.java @@ -1,23 +1,16 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemCoal; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitRandom; -import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockOreCoal extends BlockSolid { - public BlockOreCoal() { - } - @Override public int getId() { return COAL_ORE; @@ -30,7 +23,7 @@ public double getHardness() { @Override public double getResistance() { - return 15; + return 3; } @Override @@ -45,11 +38,15 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + int count = 1; Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - int i = ThreadLocalRandom.current().nextInt(fortune.getLevel() + 2) - 1; + int i = Utils.random.nextInt(fortune.getLevel() + 2) - 1; if (i < 0) { i = 0; @@ -59,7 +56,7 @@ public Item[] getDrops(Item item) { } return new Item[]{ - new ItemCoal(0, count) + Item.get(Item.COAL, 0, count) }; } else { return new Item[0]; @@ -68,21 +65,16 @@ public Item[] getDrops(Item item) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(0, 2); + return Utils.rand(0, 2); } @Override public boolean canHarvestWithHand() { return false; } - + @Override public boolean canSilkTouch() { return true; } - - @Override - public BlockColor getColor() { - return BlockColor.BLACK_BLOCK_COLOR; - } } diff --git a/src/main/java/cn/nukkit/block/BlockOreCoalDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreCoalDeepslate.java new file mode 100644 index 00000000000..0d71042547a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreCoalDeepslate.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; + +public class BlockOreCoalDeepslate extends BlockOre { + + public BlockOreCoalDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.COAL; + } + + @Override + public int getId() { + return DEEPSLATE_COAL_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Coal Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } + + @Override + public int getDropExp() { + return Utils.rand(0, 2); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreCopper.java b/src/main/java/cn/nukkit/block/BlockOreCopper.java new file mode 100644 index 00000000000..c538a855257 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreCopper.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.utils.Utils; + +public class BlockOreCopper extends BlockOre { + + public BlockOreCopper() { + // Does nothing + } + + @Override + public String getName() { + return "Copper Ore"; + } + + @Override + public int getId() { + return COPPER_ORE; + } + + @Override + protected int getRawMaterial() { + return ItemID.RAW_COPPER; + } + + @Override + protected float getDropMultiplier() { + return 3; + } + + @Override + public int getDropExp() { + return Utils.rand(0, 2); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreCopperDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreCopperDeepslate.java new file mode 100644 index 00000000000..2da6a62e90a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreCopperDeepslate.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockOreCopperDeepslate extends BlockOreCopper { + + public BlockOreCopperDeepslate() { + // Does nothing + } + + @Override + public String getName() { + return "Deepslate Copper Ore"; + } + + @Override + public int getId() { + return DEEPSLATE_COPPER_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreDiamond.java b/src/main/java/cn/nukkit/block/BlockOreDiamond.java index d284ad98a6a..6d0d75d97cf 100644 --- a/src/main/java/cn/nukkit/block/BlockOreDiamond.java +++ b/src/main/java/cn/nukkit/block/BlockOreDiamond.java @@ -1,23 +1,16 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDiamond; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitRandom; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockOreDiamond extends BlockSolid { - - public BlockOreDiamond() { - } - @Override public int getId() { return DIAMOND_ORE; @@ -30,7 +23,7 @@ public double getHardness() { @Override public double getResistance() { - return 15; + return 3; } @Override @@ -46,10 +39,14 @@ public String getName() { @Override public Item[] getDrops(Item item) { if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_IRON) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + int count = 1; Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - int i = ThreadLocalRandom.current().nextInt(fortune.getLevel() + 2) - 1; + int i = Utils.random.nextInt(fortune.getLevel() + 2) - 1; if (i < 0) { i = 0; @@ -59,7 +56,7 @@ public Item[] getDrops(Item item) { } return new Item[]{ - new ItemDiamond(0, count) + Item.get(Item.DIAMOND, 0, count) }; } else { return new Item[0]; @@ -68,14 +65,14 @@ public Item[] getDrops(Item item) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(3, 7); + return Utils.rand(3, 7); } @Override public boolean canHarvestWithHand() { return false; } - + @Override public boolean canSilkTouch() { return true; diff --git a/src/main/java/cn/nukkit/block/BlockOreDiamondDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreDiamondDeepslate.java new file mode 100644 index 00000000000..c14504c25ab --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreDiamondDeepslate.java @@ -0,0 +1,47 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; + +public class BlockOreDiamondDeepslate extends BlockOre { + + public BlockOreDiamondDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.DIAMOND; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_IRON; + } + + @Override + public int getId() { + return DEEPSLATE_DIAMOND_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Diamond Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } + + @Override + public int getDropExp() { + return Utils.rand(3, 7); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreEmerald.java b/src/main/java/cn/nukkit/block/BlockOreEmerald.java index 65c8d0e34f1..c9f1d3d3426 100644 --- a/src/main/java/cn/nukkit/block/BlockOreEmerald.java +++ b/src/main/java/cn/nukkit/block/BlockOreEmerald.java @@ -1,12 +1,9 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemEmerald; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitRandom; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/1 by xtypr. @@ -14,9 +11,6 @@ */ public class BlockOreEmerald extends BlockSolid { - public BlockOreEmerald() { - } - @Override public String getName() { return "Emerald Ore"; @@ -39,16 +33,20 @@ public double getHardness() { @Override public double getResistance() { - return 15; + return 3; } @Override public Item[] getDrops(Item item) { if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_IRON) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + int count = 1; Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - int i = ThreadLocalRandom.current().nextInt(fortune.getLevel() + 2) - 1; + int i = Utils.random.nextInt(fortune.getLevel() + 2) - 1; if (i < 0) { i = 0; @@ -58,7 +56,7 @@ public Item[] getDrops(Item item) { } return new Item[]{ - new ItemEmerald(0, count) + Item.get(Item.EMERALD, 0, count) }; } else { return new Item[0]; @@ -67,14 +65,14 @@ public Item[] getDrops(Item item) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(3, 7); + return Utils.rand(3, 7); } @Override public boolean canHarvestWithHand() { return false; } - + @Override public boolean canSilkTouch() { return true; diff --git a/src/main/java/cn/nukkit/block/BlockOreEmeraldDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreEmeraldDeepslate.java new file mode 100644 index 00000000000..aa374976d5f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreEmeraldDeepslate.java @@ -0,0 +1,47 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; + +public class BlockOreEmeraldDeepslate extends BlockOre { + + public BlockOreEmeraldDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.EMERALD; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_IRON; + } + + @Override + public int getId() { + return DEEPSLATE_EMERALD_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Emerald Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } + + @Override + public int getDropExp() { + return Utils.rand(3, 7); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreGold.java b/src/main/java/cn/nukkit/block/BlockOreGold.java index fb80d03b712..e74bda7bc3c 100644 --- a/src/main/java/cn/nukkit/block/BlockOreGold.java +++ b/src/main/java/cn/nukkit/block/BlockOreGold.java @@ -1,16 +1,12 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; +import cn.nukkit.item.ItemID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class BlockOreGold extends BlockSolid { - - public BlockOreGold() { - } +public class BlockOreGold extends BlockOre { @Override public int getId() { @@ -18,38 +14,12 @@ public int getId() { } @Override - public double getHardness() { - return 3; - } - - @Override - public double getResistance() { - return 15; - } - - @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + protected int getRawMaterial() { + return ItemID.RAW_GOLD; } @Override public String getName() { return "Gold Ore"; } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_IRON) { - return new Item[]{ - Item.get(GOLD_ORE) - }; - } else { - return new Item[0]; - } - } - - @Override - public boolean canHarvestWithHand() { - return false; - } } diff --git a/src/main/java/cn/nukkit/block/BlockOreGoldDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreGoldDeepslate.java new file mode 100644 index 00000000000..ca85c573b5c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreGoldDeepslate.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockOreGoldDeepslate extends BlockOre { + + public BlockOreGoldDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.RAW_GOLD; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_IRON; + } + + @Override + public int getId() { + return DEEPSLATE_GOLD_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Gold Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreGoldNether.java b/src/main/java/cn/nukkit/block/BlockOreGoldNether.java new file mode 100644 index 00000000000..d6df72b9ed8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreGoldNether.java @@ -0,0 +1,90 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.utils.BlockColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockOreGoldNether extends BlockSolid { + + @Override + public int getId() { + return NETHER_GOLD_ORE; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Nether Gold Ore"; + } + + @Override + public Item[] getDrops(Item item) { + if (!item.isPickaxe()) { + return new Item[0]; + } + + Enchantment enchantment = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); + int fortune = 0; + if (enchantment != null) { + fortune = enchantment.getLevel(); + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + int count = random.nextInt(2, 7); + switch (fortune) { + case 0: + // Does nothing + break; + case 1: + if (random.nextInt(0, 2) == 0) { + count *= 2; + } + break; + case 2: + if (random.nextInt(0, 1) == 0) { + count *= random.nextInt(2, 3); + } + break; + default: + case 3: + if (random.nextInt(0, 4) < 3) { + count *= random.nextInt(2, 4); + } + break; + } + + return new Item[]{ Item.get(Item.GOLD_NUGGET, 0, count) }; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreIron.java b/src/main/java/cn/nukkit/block/BlockOreIron.java index 27fcc2c5284..93e783aec64 100644 --- a/src/main/java/cn/nukkit/block/BlockOreIron.java +++ b/src/main/java/cn/nukkit/block/BlockOreIron.java @@ -1,17 +1,12 @@ package cn.nukkit.block; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; +import cn.nukkit.item.ItemID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class BlockOreIron extends BlockSolid { - - - public BlockOreIron() { - } +public class BlockOreIron extends BlockOre { @Override public int getId() { @@ -19,38 +14,12 @@ public int getId() { } @Override - public double getHardness() { - return 3; - } - - @Override - public double getResistance() { - return 5; - } - - @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + protected int getRawMaterial() { + return ItemID.RAW_IRON; } @Override public String getName() { return "Iron Ore"; } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_STONE) { - return new Item[]{ - Item.get(IRON_ORE) - }; - } else { - return new Item[0]; - } - } - - @Override - public boolean canHarvestWithHand() { - return false; - } } diff --git a/src/main/java/cn/nukkit/block/BlockOreIronDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreIronDeepslate.java new file mode 100644 index 00000000000..459b66be921 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreIronDeepslate.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.utils.BlockColor; + +public class BlockOreIronDeepslate extends BlockOre { + + public BlockOreIronDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.RAW_IRON; + } + + @Override + public int getId() { + return DEEPSLATE_IRON_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Iron Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreLapis.java b/src/main/java/cn/nukkit/block/BlockOreLapis.java index 3e4207985a2..1883976ea20 100644 --- a/src/main/java/cn/nukkit/block/BlockOreLapis.java +++ b/src/main/java/cn/nukkit/block/BlockOreLapis.java @@ -1,24 +1,16 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDye; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitRandom; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockOreLapis extends BlockSolid { - - public BlockOreLapis() { - } - @Override public int getId() { return LAPIS_ORE; @@ -31,7 +23,7 @@ public double getHardness() { @Override public double getResistance() { - return 5; + return 3; } @Override @@ -47,20 +39,24 @@ public String getName() { @Override public Item[] getDrops(Item item) { if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_STONE) { - int count = 4 + ThreadLocalRandom.current().nextInt(5); + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int count = 4 + Utils.random.nextInt(6); Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - int i = ThreadLocalRandom.current().nextInt(fortune.getLevel() + 2) - 1; + int i = Utils.random.nextInt(fortune.getLevel() + 2) - 1; if (i < 0) { i = 0; } - count *= (i + 1); + count = count + i; } return new Item[]{ - new ItemDye(4, new Random().nextInt(4) + 4) + Item.get(Item.DYE, 4, count) }; } else { return new Item[0]; @@ -69,14 +65,14 @@ public Item[] getDrops(Item item) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(2, 5); + return Utils.rand(2, 5); } @Override public boolean canHarvestWithHand() { return false; } - + @Override public boolean canSilkTouch() { return true; diff --git a/src/main/java/cn/nukkit/block/BlockOreLapisDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreLapisDeepslate.java new file mode 100644 index 00000000000..8745d203e54 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreLapisDeepslate.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.utils.BlockColor; + +public class BlockOreLapisDeepslate extends BlockOre { + + public BlockOreLapisDeepslate() { + } + + @Override + protected int getRawMaterial() { + return ItemID.DYE; + } + + @Override + protected int getRawMaterialMeta() { + return ItemDye.LAPIS_LAZULI; + } + + @Override + public int getId() { + return DEEPSLATE_LAPIS_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Lapis Lazuli Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreQuartz.java b/src/main/java/cn/nukkit/block/BlockOreQuartz.java index 3b3556971ad..ba52256307c 100644 --- a/src/main/java/cn/nukkit/block/BlockOreQuartz.java +++ b/src/main/java/cn/nukkit/block/BlockOreQuartz.java @@ -1,12 +1,9 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemQuartz; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitRandom; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/26 by xtypr. @@ -14,9 +11,6 @@ */ public class BlockOreQuartz extends BlockSolid { - public BlockOreQuartz() { - } - @Override public String getName() { return "Quartz Ore"; @@ -34,7 +28,7 @@ public double getHardness() { @Override public double getResistance() { - return 5; + return 3; } @Override @@ -44,11 +38,15 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + int count = 1; Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - int i = ThreadLocalRandom.current().nextInt(fortune.getLevel() + 2) - 1; + int i = Utils.random.nextInt(fortune.getLevel() + 2) - 1; if (i < 0) { i = 0; @@ -58,7 +56,7 @@ public Item[] getDrops(Item item) { } return new Item[]{ - new ItemQuartz(0, count) + Item.get(Item.QUARTZ, 0, count) }; } else { return new Item[0]; @@ -67,7 +65,7 @@ public Item[] getDrops(Item item) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(1, 5); + return Utils.rand(1, 5); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockOreRedstone.java b/src/main/java/cn/nukkit/block/BlockOreRedstone.java index 0ce78d76c5c..a96f0cc371c 100644 --- a/src/main/java/cn/nukkit/block/BlockOreRedstone.java +++ b/src/main/java/cn/nukkit/block/BlockOreRedstone.java @@ -1,23 +1,17 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemRedstone; import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; -import cn.nukkit.math.NukkitRandom; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockOreRedstone extends BlockSolid { - public BlockOreRedstone() { - } - @Override public int getId() { return REDSTONE_ORE; @@ -30,7 +24,7 @@ public double getHardness() { @Override public double getResistance() { - return 15; + return 3; } @Override @@ -46,15 +40,19 @@ public String getName() { @Override public Item[] getDrops(Item item) { if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_IRON) { - int count = new Random().nextInt(2) + 4; + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } + + int count = Utils.random.nextInt(2) + 4; Enchantment fortune = item.getEnchantment(Enchantment.ID_FORTUNE_DIGGING); if (fortune != null && fortune.getLevel() >= 1) { - count += new Random().nextInt(fortune.getLevel() + 1); + count += Utils.random.nextInt(fortune.getLevel() + 1); } return new Item[]{ - new ItemRedstone(0, count) + Item.get(Item.REDSTONE_DUST, 0, count) }; } else { return new Item[0]; @@ -63,8 +61,9 @@ public Item[] getDrops(Item item) { @Override public int onUpdate(int type) { - if (type == Level.BLOCK_UPDATE_TOUCH) { //type == Level.BLOCK_UPDATE_NORMAL || - this.getLevel().setBlock(this, Block.get(BlockID.GLOWING_REDSTONE_ORE), false, false); + if (type == Level.BLOCK_UPDATE_TOUCH) { + this.getLevel().setBlock(this, Block.get(GLOWING_REDSTONE_ORE), false, false); + this.getLevel().scheduleUpdate(this, 600); return Level.BLOCK_UPDATE_WEAK; } @@ -74,7 +73,7 @@ public int onUpdate(int type) { @Override public int getDropExp() { - return new NukkitRandom().nextRange(1, 5); + return Utils.rand(1, 5); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslate.java b/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslate.java new file mode 100644 index 00000000000..d4b86824ed3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslate.java @@ -0,0 +1,53 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemID; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.utils.BlockColor; + +public class BlockOreRedstoneDeepslate extends BlockOre { + + public BlockOreRedstoneDeepslate() { + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_TOUCH) { + this.getLevel().setBlock(this, Block.get(LIT_DEEPSLATE_REDSTONE_ORE), false, false); + this.getLevel().scheduleUpdate(this, 600); + + return Level.BLOCK_UPDATE_WEAK; + } + return 0; + } + + @Override + protected int getRawMaterial() { + return ItemID.REDSTONE_DUST; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_IRON; + } + + @Override + public int getId() { + return DEEPSLATE_REDSTONE_ORE; + } + + @Override + public double getHardness() { + return 4.5; + } + + @Override + public String getName() { + return "Deepslate Redstone Ore"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslateGlowing.java b/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslateGlowing.java new file mode 100644 index 00000000000..2b55f735f6d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockOreRedstoneDeepslateGlowing.java @@ -0,0 +1,47 @@ +package cn.nukkit.block; + +import cn.nukkit.event.block.BlockFadeEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; + +public class BlockOreRedstoneDeepslateGlowing extends BlockOreRedstoneDeepslate { + + public BlockOreRedstoneDeepslateGlowing() { + } + + @Override + public int getId() { + return LIT_DEEPSLATE_REDSTONE_ORE; + } + + @Override + public String getName() { + return "Glowing Deepslate Redstone Ore"; + } + + @Override + public int getLightLevel() { + return 9; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(REDSTONE_ORE)); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED || type == Level.BLOCK_UPDATE_RANDOM) { + BlockFadeEvent event = new BlockFadeEvent(this, Block.get(DEEPSLATE_REDSTONE_ORE)); + level.getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + level.setBlock(this, event.getNewState(), false, false); + } + + return Level.BLOCK_UPDATE_WEAK; + } + + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockOreRedstoneGlowing.java b/src/main/java/cn/nukkit/block/BlockOreRedstoneGlowing.java index dd607c696d4..d737a298ba2 100644 --- a/src/main/java/cn/nukkit/block/BlockOreRedstoneGlowing.java +++ b/src/main/java/cn/nukkit/block/BlockOreRedstoneGlowing.java @@ -5,17 +5,12 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; -//和pm源码有点出入,这里参考了wiki - /** * Created on 2015/12/6 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockOreRedstoneGlowing extends BlockOreRedstone { - public BlockOreRedstoneGlowing() { - } - @Override public String getName() { return "Glowing Redstone Ore"; @@ -33,7 +28,7 @@ public int getLightLevel() { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.REDSTONE_ORE)); + return new ItemBlock(Block.get(REDSTONE_ORE)); } @Override @@ -50,14 +45,4 @@ public int onUpdate(int type) { return 0; } - - @Override - public boolean canHarvestWithHand() { - return false; - } - - @Override - public boolean canSilkTouch() { - return true; - } } diff --git a/src/main/java/cn/nukkit/block/BlockPackedMud.java b/src/main/java/cn/nukkit/block/BlockPackedMud.java new file mode 100644 index 00000000000..08233302453 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPackedMud.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockPackedMud extends BlockSolid { + + public BlockPackedMud() { + } + + @Override + public String getName() { + return "Packed Mud"; + } + + @Override + public int getId() { + return PACKED_MUD; + } + + @Override + public double getHardness() { + return 1; + } + + @Override + public double getResistance() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPiston.java b/src/main/java/cn/nukkit/block/BlockPiston.java index 6bf3cff2f36..5c0c7dce6f8 100644 --- a/src/main/java/cn/nukkit/block/BlockPiston.java +++ b/src/main/java/cn/nukkit/block/BlockPiston.java @@ -18,6 +18,11 @@ public int getId() { return PISTON; } + @Override + public int getPistonHeadBlockId() { + return PISTON_HEAD; + } + @Override public String getName() { return "Piston"; diff --git a/src/main/java/cn/nukkit/block/BlockPistonBase.java b/src/main/java/cn/nukkit/block/BlockPistonBase.java index 19c8a24b4ff..b9e2dd7048e 100644 --- a/src/main/java/cn/nukkit/block/BlockPistonBase.java +++ b/src/main/java/cn/nukkit/block/BlockPistonBase.java @@ -4,6 +4,8 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityPistonArm; import cn.nukkit.event.block.BlockPistonChangeEvent; +import cn.nukkit.event.block.BlockPistonEvent; +import cn.nukkit.event.redstone.RedstoneUpdateEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; @@ -31,21 +33,22 @@ public BlockPistonBase(int meta) { super(meta); } + public abstract int getPistonHeadBlockId(); + @Override public double getResistance() { - return 2.5; + return 1.5; } @Override public double getHardness() { - return 0.5; + return 1.5; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (Math.abs(player.x - this.x) < 2 && Math.abs(player.z - this.z) < 2) { + if (Math.abs(player.getFloorX() - this.x) < 2 && Math.abs(player.getFloorZ() - this.z) < 2) { double y = player.y + player.getEyeHeight(); - if (y - this.y > 2) { this.setDamage(BlockFace.UP.getIndex()); } else if (this.y - y > 0) { @@ -56,7 +59,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } else { this.setDamage(player.getHorizontalFacing().getIndex()); } - this.level.setBlock(block, this, true, false); + this.getLevel().setBlock(this, this, true, false); CompoundTag nbt = new CompoundTag("") .putString("id", BlockEntity.PISTON_ARM) @@ -65,10 +68,11 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl .putInt("z", (int) this.z) .putBoolean("Sticky", this.sticky); - BlockEntityPistonArm be = (BlockEntityPistonArm) BlockEntity.createBlockEntity(BlockEntity.PISTON_ARM, this.level.getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); + BlockEntityPistonArm be = (BlockEntityPistonArm) BlockEntity.createBlockEntity(BlockEntity.PISTON_ARM, this.getChunk(), nbt); + be.sticky = this.sticky; + be.spawnToAll(); - if (be == null) return false; - //this.checkState(); + this.checkState(); return true; } @@ -77,8 +81,7 @@ public boolean onBreak(Item item) { this.level.setBlock(this, Block.get(BlockID.AIR), true, true); Block block = this.getSide(getFacing()); - - if (block instanceof BlockPistonHead && ((BlockPistonHead) block).getFacing() == this.getFacing()) { + if (block instanceof BlockPistonHead && ((BlockPistonHead) block).getBlockFace() == this.getFacing()) { block.onBreak(item); } return true; @@ -87,7 +90,7 @@ public boolean onBreak(Item item) { public boolean isExtended() { BlockFace face = getFacing(); Block block = getSide(face); - return block instanceof BlockPistonHead && ((BlockPistonHead) block).getFacing() == face; + return block instanceof BlockPistonHead && ((BlockPistonHead) block).getBlockFace() == face; } @Override @@ -95,19 +98,14 @@ public int onUpdate(int type) { if (type != 6 && type != 1) { return 0; } else { - BlockEntity blockEntity = this.level.getBlockEntity(this); - if (blockEntity instanceof BlockEntityPistonArm) { - BlockEntityPistonArm arm = (BlockEntityPistonArm) blockEntity; - boolean powered = this.isPowered(); - if (arm.powered != powered) { - this.level.getServer().getPluginManager().callEvent(new BlockPistonChangeEvent(this, powered ? 0 : 15, powered ? 15 : 0)); - arm.powered = !arm.powered; - if (arm.chunk != null) { - arm.chunk.setChanged(); - } + if (type == Level.BLOCK_UPDATE_REDSTONE) { + RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); + getLevel().getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return 0; } } - + this.checkState(); return type; } } @@ -115,31 +113,36 @@ public int onUpdate(int type) { private void checkState() { BlockFace facing = getFacing(); boolean isPowered = this.isPowered(); + boolean extended = isExtended(); - if (isPowered && !isExtended()) { - if ((new BlocksCalculator(this.level, this, facing, true)).canMove()) { - if (!this.doMove(true)) { + if (isPowered && !extended) { + BlocksCalculator calculator = new BlocksCalculator(this, facing, true); + if (calculator.canMove()) { + if (!this.doMove(true, calculator)) { return; } - + this.updateBlockEntity(true); this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_PISTON_OUT); - } else { } - } else if (!isPowered && isExtended()) { - //this.level.setBlock() TODO: set piston extension? + return; + } + if (!isPowered && extended) { if (this.sticky) { - Vector3 pos = this.add(facing.getXOffset() * 2, facing.getYOffset() * 2, facing.getZOffset() * 2); + Vector3 pos = this.add(facing.getXOffset() << 1, facing.getYOffset() << 1, facing.getZOffset() << 1); Block block = this.level.getBlock(pos); if (block.getId() == AIR) { - this.level.setBlock(this.getLocation().getSide(facing), Block.get(BlockID.AIR), true, true); + this.level.setBlock(this.getSideVec(facing), Block.get(BlockID.AIR), true, true); } - if (canPush(block, facing.getOpposite(), false) && (!(block instanceof BlockFlowable) || block.getId() == PISTON || block.getId() == STICKY_PISTON)) { - this.doMove(false); + if (canPush(block, facing.getOpposite(), false) && (!(block instanceof BlockFlowable || block.breakWhenPushed()) || block.getId() == PISTON || block.getId() == STICKY_PISTON)) { + if (this.doMove(false, null)) { + this.updateBlockEntity(false); + } } } else { - this.level.setBlock(getLocation().getSide(facing), Block.get(BlockID.AIR), true, false); + this.updateBlockEntity(false); + this.level.setBlock(getSideVec(facing), Block.get(BlockID.AIR), true, true); } this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_PISTON_IN); @@ -147,14 +150,21 @@ private void checkState() { } public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage()).getOpposite(); + BlockFace face = BlockFace.fromIndex(this.getDamage()).getOpposite(); + if (face == BlockFace.UP) return BlockFace.DOWN; + if (face == BlockFace.DOWN) return BlockFace.UP; + return face; } private boolean isPowered() { BlockFace face = getFacing(); + // Revert to opposite + if (face == BlockFace.UP) face = BlockFace.DOWN; + if (face == BlockFace.DOWN) face = BlockFace.UP; + for (BlockFace side : BlockFace.values()) { - if (side != face && this.level.isSidePowered(this.getLocation().getSide(side), side)) { + if (side != face && this.level.isSidePowered(this.getSideVec(side), side)) { return true; } } @@ -162,10 +172,10 @@ private boolean isPowered() { if (this.level.isSidePowered(this, BlockFace.DOWN)) { return true; } else { - Vector3 pos = this.getLocation().up(); + Vector3 pos = this.getSideVec(BlockFace.UP); for (BlockFace side : BlockFace.values()) { - if (side != BlockFace.DOWN && this.level.isSidePowered(pos.getSide(side), side)) { + if (side != BlockFace.DOWN && this.level.isSidePowered(pos.getSideVec(side), side)) { return true; } } @@ -174,23 +184,45 @@ private boolean isPowered() { } } - private boolean doMove(boolean extending) { - Vector3 pos = this.getLocation(); + private void updateBlockEntity(boolean extending) { + BlockEntity blockEntity = this.level.getBlockEntity(this); + if (blockEntity instanceof BlockEntityPistonArm) { + BlockEntityPistonArm arm = (BlockEntityPistonArm) blockEntity; + if (arm.isExtended() != extending) { + this.level.getServer().getPluginManager().callEvent(new BlockPistonChangeEvent(this, extending ? 0 : 15, extending ? 15 : 0)); + arm.setExtended(extending); + arm.broadcastMove(); + if (arm.chunk != null) { + arm.chunk.setChanged(); + } + } + } + } + + private boolean doMove(boolean extending, BlocksCalculator calculator) { BlockFace direction = getFacing(); if (!extending) { - this.level.setBlock(pos.getSide(direction), Block.get(BlockID.AIR), true, false); + this.level.setBlock(this.getSideVec(direction), Block.get(BlockID.AIR), true, false); + } + if (calculator == null) { + calculator = new BlocksCalculator(this, direction, extending); } - BlocksCalculator calculator = new BlocksCalculator(this.level, this, direction, extending); + if (calculator.canMove()) { + BlockPistonEvent event = new BlockPistonEvent(this, direction, calculator.getBlocksToMove(), calculator.getBlocksToDestroy(), extending); + this.level.getServer().getPluginManager().callEvent(event); - if (!calculator.canMove()) { - return false; - } else { - List blocks = calculator.getBlocksToMove(); + if (event.isCancelled()) { + return false; + } + List blocks = calculator.getBlocksToMove(); + if (!extending && blocks.isEmpty()) { + this.level.setBlock(this.getSideVec(direction), Block.get(BlockID.AIR), false, true); + return true; + } List newBlocks = new ArrayList<>(blocks); - List destroyBlocks = calculator.getBlocksToDestroy(); BlockFace side = extending ? direction : direction.getOpposite(); @@ -201,43 +233,37 @@ private boolean doMove(boolean extending) { for (int i = blocks.size() - 1; i >= 0; --i) { Block block = blocks.get(i); - this.level.setBlock(block, Block.get(BlockID.AIR)); - Vector3 newPos = block.getLocation().getSide(side); + this.level.setBlock(block, Block.get(BlockID.AIR), true, false); + Vector3 newPos = block.getSideVec(side); - //TODO: change this to block entity - this.level.setBlock(newPos, newBlocks.get(i)); + // TODO: Change this to block entity + this.level.setBlock(newPos, newBlocks.get(i), true, false); } - Vector3 pistonHead = pos.getSide(direction); - if (extending) { - //extension block entity - - this.level.setBlock(pistonHead, Block.get(BlockID.PISTON_HEAD, this.getDamage())); + // Extension block entity + Vector3 pistonHead = this.getSideVec(direction); + this.level.setBlock(pistonHead, Block.get(this.getPistonHeadBlockId(), this.getDamage()), true, false); } - return true; + } else { + return false; } } public static boolean canPush(Block block, BlockFace face, boolean destroyBlocks) { - if (block.canBePushed() && block.getY() >= 0 && (face != BlockFace.DOWN || block.getY() != 0) && - block.getY() <= 255 && (face != BlockFace.UP || block.getY() != 255)) { + if (block.canBePushed() && block.getY() >= block.getLevel().getMinBlockY() && (face != BlockFace.DOWN || block.getY() != block.getLevel().getMinBlockY()) && block.getY() <= block.getLevel().getMaxBlockY() && (face != BlockFace.UP || block.getY() != block.getLevel().getMaxBlockY())) { if (!(block instanceof BlockPistonBase)) { - - if (block instanceof BlockFlowable) { + if ((block instanceof BlockFlowable && !(block instanceof BlockEndPortal || block instanceof BlockNetherPortal)) || block.breakWhenPushed()) { return destroyBlocks; } } else return !((BlockPistonBase) block).isExtended(); return true; } return false; - } public static class BlocksCalculator { - - private final Level level; private final Vector3 pistonPos; private final Block blockToMove; private final BlockFace moveDirection; @@ -245,8 +271,9 @@ public static class BlocksCalculator { private final List toMove = new ArrayList<>(); private final List toDestroy = new ArrayList<>(); - public BlocksCalculator(Level level, Block pos, BlockFace facing, boolean extending) { - this.level = level; + protected Boolean canMove; + + public BlocksCalculator(Block pos, BlockFace facing, boolean extending) { this.pistonPos = pos.getLocation(); if (extending) { @@ -259,13 +286,25 @@ public BlocksCalculator(Level level, Block pos, BlockFace facing, boolean extend } public boolean canMove() { + return this.canMove == null ? this.canMove = this.eval() : this.canMove; + } + + private boolean eval() { this.toMove.clear(); this.toDestroy.clear(); - Block block = this.blockToMove; - if (!canPush(block, this.moveDirection, false)) { - if (block instanceof BlockFlowable) { - this.toDestroy.add(this.blockToMove); + if (!canPush(this.blockToMove, this.moveDirection, false)) { + if ((this.blockToMove instanceof BlockFlowable && !(this.blockToMove instanceof BlockEndPortal || this.blockToMove instanceof BlockNetherPortal)) || this.blockToMove.breakWhenPushed()) { + boolean exists = false; + for (Block b : this.toDestroy) { + if (b.x == this.blockToMove.x && b.y == this.blockToMove.y && b.z == this.blockToMove.z) { + exists = true; + break; + } + } + if (!exists) { + this.toDestroy.add(this.blockToMove); + } return true; } else { return false; @@ -273,18 +312,20 @@ public boolean canMove() { } else if (!this.addBlockLine(this.blockToMove)) { return false; } else { - for (Block b : this.toMove) { - if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { - return false; + /*if (false) { //todo? + for (Block b : this.toMove) { + if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { + return false; + } } - } + }*/ return true; } } private boolean addBlockLine(Block origin) { - Block block = origin.clone(); + Block block = origin/*.clone()*/; if (block.getId() == AIR) { return true; @@ -300,7 +341,7 @@ private boolean addBlockLine(Block origin) { if (count + this.toMove.size() > 12) { return false; } else { - while (block.getId() == SLIME_BLOCK) { + while (false && block.getId() == SLIME_BLOCK) { block = origin.getSide(this.moveDirection.getOpposite(), count); if (block.getId() == AIR || !canPush(block, this.moveDirection, false) || block.equals(this.pistonPos)) { @@ -317,7 +358,11 @@ private boolean addBlockLine(Block origin) { int blockCount = 0; for (int step = count - 1; step >= 0; --step) { - this.toMove.add(block.getSide(this.moveDirection.getOpposite(), step)); + Block aBlock = block.getSide(this.moveDirection.getOpposite(), step); + if (aBlock.breakWhenPushed()) { + return true; // shouldn't be possible? + } + this.toMove.add(aBlock); ++blockCount; } @@ -333,7 +378,7 @@ private boolean addBlockLine(Block origin) { for (int l = 0; l <= index + blockCount; ++l) { Block b = this.toMove.get(l); - if (b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { + if (false && b.getId() == SLIME_BLOCK && !this.addBranchingBlocks(b)) { return false; } } @@ -349,8 +394,17 @@ private boolean addBlockLine(Block origin) { return false; } - if (nextBlock instanceof BlockFlowable) { - this.toDestroy.add(nextBlock); + if ((nextBlock instanceof BlockFlowable && !(nextBlock instanceof BlockEndPortal || nextBlock instanceof BlockNetherPortal)) || nextBlock.breakWhenPushed()) { + boolean exists = false; + for (Block b : this.toDestroy) { + if (b.x == nextBlock.x && b.y == nextBlock.y && b.z == nextBlock.z) { + exists = true; + break; + } + } + if (!exists) { + this.toDestroy.add(nextBlock); + } return true; } @@ -397,7 +451,7 @@ public List getBlocksToDestroy() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockPistonExtension.java b/src/main/java/cn/nukkit/block/BlockPistonExtension.java new file mode 100644 index 00000000000..92df04df905 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPistonExtension.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockPistonExtension extends BlockTransparent { + + @Override + public int getId() { + return PISTON_EXTENSION; + } + + @Override + public String getName() { + return "Piston Extension"; + } + + @Override + public double getHardness() { + return 0.1; + } + + @Override + public double getResistance() { + return 0.1; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPistonHead.java b/src/main/java/cn/nukkit/block/BlockPistonHead.java index fb99b423307..f58b42c3bff 100644 --- a/src/main/java/cn/nukkit/block/BlockPistonHead.java +++ b/src/main/java/cn/nukkit/block/BlockPistonHead.java @@ -3,11 +3,12 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; /** * @author CreeperFace */ -public class BlockPistonHead extends BlockTransparentMeta { +public class BlockPistonHead extends BlockTransparentMeta implements Faceable { public BlockPistonHead() { this(0); @@ -29,12 +30,12 @@ public String getName() { @Override public double getResistance() { - return 2.5; + return 1.5; } @Override public double getHardness() { - return 0.5; + return 1.5; } @Override @@ -45,16 +46,19 @@ public Item[] getDrops(Item item) { @Override public boolean onBreak(Item item) { this.level.setBlock(this, Block.get(BlockID.AIR), true, true); - Block piston = getSide(getFacing().getOpposite()); - - if (piston instanceof BlockPistonBase && ((BlockPistonBase) piston).getFacing() == this.getFacing()) { + Block piston = getSide(getBlockFace().getOpposite()); + if (piston instanceof BlockPistonBase && ((BlockPistonBase) piston).getFacing() == this.getBlockFace()) { piston.onBreak(item); } return true; } - public BlockFace getFacing() { - return BlockFace.fromIndex(this.getDamage()).getOpposite(); + @Override + public BlockFace getBlockFace() { + BlockFace face = BlockFace.fromIndex(this.getDamage()).getOpposite(); + if (face == BlockFace.UP) return BlockFace.DOWN; + if (face == BlockFace.DOWN) return BlockFace.UP; + return face; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockPistonHeadSticky.java b/src/main/java/cn/nukkit/block/BlockPistonHeadSticky.java new file mode 100644 index 00000000000..ad895ed5090 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPistonHeadSticky.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPistonHeadSticky extends BlockPistonHead { + + public BlockPistonHeadSticky() { + this(0); + } + + public BlockPistonHeadSticky(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Sticky Piston Head"; + } + + @Override + public int getId() { + return PISTON_HEAD_STICKY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPistonSticky.java b/src/main/java/cn/nukkit/block/BlockPistonSticky.java index 4962b5c6db3..de68535b58c 100644 --- a/src/main/java/cn/nukkit/block/BlockPistonSticky.java +++ b/src/main/java/cn/nukkit/block/BlockPistonSticky.java @@ -19,6 +19,11 @@ public int getId() { return STICKY_PISTON; } + @Override + public int getPistonHeadBlockId() { + return PISTON_HEAD_STICKY; + } + @Override public String getName() { return "Sticky Piston"; diff --git a/src/main/java/cn/nukkit/block/BlockPlanks.java b/src/main/java/cn/nukkit/block/BlockPlanks.java index 409ae264815..840721f41a3 100644 --- a/src/main/java/cn/nukkit/block/BlockPlanks.java +++ b/src/main/java/cn/nukkit/block/BlockPlanks.java @@ -4,10 +4,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockPlanks extends BlockSolidMeta { + public static final int OAK = 0; public static final int SPRUCE = 1; public static final int BIRCH = 2; @@ -15,7 +16,6 @@ public class BlockPlanks extends BlockSolidMeta { public static final int ACACIA = 4; public static final int DARK_OAK = 5; - public BlockPlanks() { this(0); } @@ -51,7 +51,7 @@ public int getBurnAbility() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak Wood Planks", "Spruce Wood Planks", "Birch Wood Planks", @@ -60,7 +60,7 @@ public String getName() { "Dark Oak Wood Planks", }; - return this.getDamage() < 0 ? "Unknown" : names[this.getDamage() % 6]; + return names[this.getDamage() & 0x07]; } @Override @@ -70,7 +70,7 @@ public int getToolType() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { default: case OAK: return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockPodzol.java b/src/main/java/cn/nukkit/block/BlockPodzol.java index b2cff7609f7..512caff6f5e 100644 --- a/src/main/java/cn/nukkit/block/BlockPodzol.java +++ b/src/main/java/cn/nukkit/block/BlockPodzol.java @@ -2,7 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.utils.BlockColor; +import cn.nukkit.level.Sound; /** * Created on 2015/11/22 by xtypr. @@ -15,7 +15,6 @@ public BlockPodzol() { } public BlockPodzol(int meta) { - // Podzol can't have meta. super(0); } @@ -34,28 +33,28 @@ public boolean canSilkTouch() { return true; } - @Override - public boolean canBeActivated() { - return false; - } - @Override public boolean onActivate(Item item, Player player) { + if (item.isShovel()) { + Block up = this.up(); + if (up instanceof BlockAir || up instanceof BlockFlowable) { + item.useOn(this); + this.getLevel().setBlock(this, Block.get(GRASS_PATH)); + if (player != null) { + player.getLevel().addSound(player, Sound.STEP_GRASS); + } + return true; + } + } return false; } @Override public int getFullId() { - return this.getId() << 4; + return getId() << DATA_BITS; } @Override public void setDamage(int meta) { - - } - - @Override - public BlockColor getColor() { - return BlockColor.SPRUCE_BLOCK_COLOR; } } diff --git a/src/main/java/cn/nukkit/block/BlockPointedDripstone.java b/src/main/java/cn/nukkit/block/BlockPointedDripstone.java new file mode 100644 index 00000000000..7bdf53e1b77 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPointedDripstone.java @@ -0,0 +1,288 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.BlockPropertiesHelper; +import cn.nukkit.block.properties.DripstoneThickness; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.customblock.properties.BooleanBlockProperty; +import cn.nukkit.customblock.properties.EnumBlockProperty; +import cn.nukkit.entity.item.EntityFallingBlock; +import cn.nukkit.event.block.BlockFallEvent; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; +import cn.nukkit.level.Location; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; +import it.unimi.dsi.fastutil.ints.IntObjectPair; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Consumer; + +public class BlockPointedDripstone extends BlockSolidMeta implements BlockPropertiesHelper, Faceable { + + private static final float GROWTH_PROBABILITY = 0.011377778F; + private static final int MAX_HEIGHT = 7; + + private static final EnumBlockProperty THICKNESS = new EnumBlockProperty<>("dripstone_thickness", false, DripstoneThickness.class); + private static final BooleanBlockProperty HANGING = new BooleanBlockProperty("hanging", false); + private static final BlockProperties PROPERTIES = new BlockProperties(HANGING, THICKNESS); + + public BlockPointedDripstone() { + this(0); + } + + public BlockPointedDripstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Pointed Dripstone"; + } + + @Override + public int getId() { + return POINTED_DRIPSTONE; + } + + @Override + public BlockProperties getBlockProperties() { + return PROPERTIES; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!this.canPlaceOn(block.down(), target)) { + return false; + } + + Block up = this.up(); + Block down = this.down(); + + boolean hanging = false; + if (face == BlockFace.UP || face == BlockFace.DOWN) { + if ((face == BlockFace.UP && !down.isSolid()) || (face == BlockFace.DOWN && !up.isSolid())) { + return false; + } + hanging = face == BlockFace.DOWN; + } else if (up.isSolid()) { + hanging = true; + } else if (!down.isSolid()) { + return false; + } + + + BlockPointedDripstone tip = null; + if (up instanceof BlockPointedDripstone && hanging) { + tip = (BlockPointedDripstone) up; + } else if (down instanceof BlockPointedDripstone) { + tip = (BlockPointedDripstone) down; + } + + if (tip != null) { + IntObjectPair pair = this.getDripstoneHeightFromTip(tip, hanging); + int height = pair.keyInt(); + if (height == 0 || height == MAX_HEIGHT) { + return false; + } + Location location = pair.right().getLocation(); + this.growPointedDripstone(location, hanging, height); + } else { + this.setHanging(hanging); + this.setThickness(DripstoneThickness.TIP); + this.getLevel().setBlock(this, this, true, true); + } + return true; + } + + @Override + public boolean onBreak(Item item, Player player) { + boolean hanging = this.isHanging(); + + Block newTip = hanging ? this.up() : this.down(); + if (newTip instanceof BlockPointedDripstone) { + ((BlockPointedDripstone) newTip).setThickness(DripstoneThickness.TIP); + this.getLevel().setBlock(newTip, newTip); + } + + DripstoneThickness thickness = this.getThickness(); + if (thickness == DripstoneThickness.TIP || thickness == DripstoneThickness.MERGE) { + return super.onBreak(item, player); + } + + Block block = this; + while (block instanceof BlockPointedDripstone) { + BlockPointedDripstone dripstone = (BlockPointedDripstone) block; + if (this != dripstone) { + this.getLevel().addParticle(new DestroyBlockParticle(block.add(0.5), block)); + if (hanging) { + this.spawnFallingBlock(dripstone); + } else { + this.getLevel().dropItem(block.add(0.5, 0.5, 0.5), block.toItem()); + } + } + this.getLevel().setBlock(block, Block.get(BlockID.AIR), false, true); + block = hanging ? block.down() : block.up(); + } + return true; + } + + @Override + public int onUpdate(int type) { + if (type != Level.BLOCK_UPDATE_RANDOM) { + return 0; + } + + if (ThreadLocalRandom.current().nextFloat() >= GROWTH_PROBABILITY || !this.isHanging() || this.up().getId() == POINTED_DRIPSTONE) { + return 0; + } + + int height; + if (this.canGrow() && (height = this.getDripstoneHeightFromBase(this, true)) < MAX_HEIGHT) { + BlockGrowEvent event = new BlockGrowEvent(this, Block.get(BlockID.POINTED_DRIPSTONE)); + this.getLevel().getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return 0; + } + this.growPointedDripstone(this.getLocation(), true, height); + } + + // TODO: grow from ground too + return 0; + } + + private void growPointedDripstone(Position position, boolean hanging, int height) { + this.buildBaseToTipColumn(height + 1, false, thickness -> { + BlockPointedDripstone dripstone = (BlockPointedDripstone) Block.get(POINTED_DRIPSTONE); + dripstone.setHanging(hanging); + dripstone.setThickness(thickness); + this.getLevel().setBlock(position, dripstone); + position.setY(hanging ? position.getY() - 1 : position.getY() + 1); + }); + } + + private IntObjectPair getDripstoneHeightFromTip(Block block, boolean hanging) { + int height = 0; + BlockPointedDripstone dripstone = null; + while (block instanceof BlockPointedDripstone) { + height++; + dripstone = (BlockPointedDripstone) block; + block = hanging ? block.up() : block.down(); + } + return IntObjectPair.of(height, dripstone); + } + + private int getDripstoneHeightFromBase(Block block, boolean hanging) { + int height = 0; + while (block instanceof BlockPointedDripstone) { + height++; + block = hanging ? block.down() : block.up(); + } + return height; + } + + private void buildBaseToTipColumn(int height, boolean merge, Consumer callback) { + if (height >= 3) { + callback.accept(DripstoneThickness.BASE); + for(int i = 0; i < height - 3; ++i) { + callback.accept(DripstoneThickness.MIDDLE); + } + } + + if (height >= 2) { + callback.accept(DripstoneThickness.FRUSTUM); + } + + if (height >= 1) { + callback.accept(merge ? DripstoneThickness.MERGE : DripstoneThickness.TIP); + } + } + + private boolean canGrow() { + Block up2; + // TODO: grow from ground too + return this.down().getId() == AIR && this.up().getId() == DRIPSTONE_BLOCK && ((up2 = this.up(2)).getId() == WATER || up2.getId() == STILL_WATER); + } + + private void spawnFallingBlock(BlockPointedDripstone block) { + BlockFallEvent event = new BlockFallEvent(block); + this.level.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + + CompoundTag nbt = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", this.x + 0.5)) + .add(new DoubleTag("", this.y)) + .add(new DoubleTag("", this.z + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + + .putList(new ListTag("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0))) + .putInt("TileID", this.getId()) + .putByte("Data", this.getDamage()); + + EntityFallingBlock fall = new EntityFallingBlock(this.getLevel().getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); + fall.spawnToAll(); + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId()), 0, 1); + } + + @Override + public BlockColor getColor() { + return BlockColor.BROWN_TERRACOTA_BLOCK_COLOR; + } + + @Override + public BlockFace getBlockFace() { + return this.getBooleanValue(HANGING) ? BlockFace.DOWN : BlockFace.UP; + } + + public boolean isHanging() { + return this.getBooleanValue(HANGING); + } + + public void setHanging(boolean hanging) { + this.setBooleanValue(HANGING, hanging); + } + + public DripstoneThickness getThickness() { + return this.getPropertyValue(THICKNESS); + } + + public void setThickness(DripstoneThickness value) { + this.setPropertyValue(THICKNESS, value); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPolishedBasalt.java b/src/main/java/cn/nukkit/block/BlockPolishedBasalt.java new file mode 100644 index 00000000000..ae426211685 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPolishedBasalt.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPolishedBasalt extends BlockBasalt { + + public BlockPolishedBasalt() { + this(0); + } + + public BlockPolishedBasalt(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Basalt"; + } + + @Override + public int getId() { + return BlockID.POLISHED_BASALT; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneBrickWall.java b/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneBrickWall.java new file mode 100644 index 00000000000..a5992ee797c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneBrickWall.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPolishedBlackstoneBrickWall extends BlockWall { + + public BlockPolishedBlackstoneBrickWall() { + this(0); + } + + public BlockPolishedBlackstoneBrickWall(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Blackstone Brick Wall"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BRICK_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneWall.java b/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneWall.java new file mode 100644 index 00000000000..e61677a44e9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPolishedBlackstoneWall.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPolishedBlackstoneWall extends BlockWall { + + public BlockPolishedBlackstoneWall() { + this(0); + } + + public BlockPolishedBlackstoneWall(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Blackstone Wall"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPotato.java b/src/main/java/cn/nukkit/block/BlockPotato.java index 3d93d11d59f..65527080f70 100644 --- a/src/main/java/cn/nukkit/block/BlockPotato.java +++ b/src/main/java/cn/nukkit/block/BlockPotato.java @@ -1,9 +1,7 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemPotato; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 15.01.2016. @@ -30,18 +28,25 @@ public int getId() { @Override public Item toItem() { - return new ItemPotato(); + return Item.get(Item.POTATO); } @Override public Item[] getDrops(Item item) { if (getDamage() >= 0x07) { - return new Item[]{ - new ItemPotato(0, new Random().nextInt(3) + 1) - }; + if (Utils.random.nextInt(100) < 2) { + return new Item[]{ + Item.get(Item.POTATO, 0, Utils.random.nextInt(3) + 2), + Item.get(Item.POISONOUS_POTATO, 0, 1) + }; + } else { + return new Item[]{ + Item.get(Item.POTATO, 0, Utils.random.nextInt(3) + 2) + }; + } } else { return new Item[]{ - new ItemPotato() + Item.get(Item.POTATO) }; } } diff --git a/src/main/java/cn/nukkit/block/BlockPowderSnow.java b/src/main/java/cn/nukkit/block/BlockPowderSnow.java new file mode 100644 index 00000000000..0b8c71fcbfe --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPowderSnow.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.utils.BlockColor; + +public class BlockPowderSnow extends BlockTransparent { + + public BlockPowderSnow() { + super(); + } + + @Override + public String getName() { + return "Powder Snow"; + } + + @Override + public int getId() { + return POWDER_SNOW; + } + + @Override + public double getHardness() { + return 0.25; + } + + @Override + public double getResistance() { + return 0.25; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public BlockColor getColor() { + return BlockColor.SNOW_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateAcacia.java b/src/main/java/cn/nukkit/block/BlockPressurePlateAcacia.java new file mode 100644 index 00000000000..1543cafd353 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateAcacia.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockPressurePlateAcacia extends BlockPressurePlateWood { + + public BlockPressurePlateAcacia() { + this(0); + } + + public BlockPressurePlateAcacia(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Acacia Pressure Plate"; + } + + @Override + public int getId() { + return ACACIA_PRESSURE_PLATE; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java b/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java index 5bcc098a27c..d2228247d3e 100644 --- a/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateBase.java @@ -9,7 +9,6 @@ import cn.nukkit.event.player.PlayerInteractEvent.Action; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; -import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; @@ -33,11 +32,6 @@ protected BlockPressurePlateBase(int meta) { super(meta); } - @Override - public boolean canPassThrough() { - return true; - } - @Override public boolean canHarvestWithHand() { return false; @@ -53,11 +47,6 @@ public double getMinZ() { return this.z + 0.625; } - @Override - public double getMinY() { - return this.y + 0; - } - @Override public double getMaxX() { return this.x + 0.9375; @@ -70,7 +59,7 @@ public double getMaxZ() { @Override public double getMaxY() { - return isActivated() ? this.y + 0.03125 : this.y + 0.0625; + return this.isActivated() ? this.y + 0.03125 : this.y + 0.0625; } @Override @@ -107,7 +96,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } - this.level.setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } @@ -147,7 +136,7 @@ protected void updateState(int oldStrength) { this.level.setBlock(this, this, false, false); this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(this.getLocation().down(), null); + this.level.updateAroundRedstone(this.getSideVec(BlockFace.DOWN), null); if (!isPowered && wasPowered) { this.playOffSound(); @@ -169,7 +158,7 @@ public boolean onBreak(Item item) { if (this.getRedstonePower() > 0) { this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(this.getLocation().down(), null); + this.level.updateAroundRedstone(this.getSideVec(BlockFace.DOWN), null); } return true; @@ -194,11 +183,11 @@ public void setRedstonePower(int power) { } protected void playOnSound() { - this.level.addLevelSoundEvent(this.add(0.5, 0.1, 0.5), LevelSoundEventPacket.SOUND_POWER_ON, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_ON); } protected void playOffSound() { - this.level.addLevelSoundEvent(this.add(0.5, 0.1, 0.5), LevelSoundEventPacket.SOUND_POWER_OFF, GlobalBlockPalette.getOrCreateRuntimeId(this.getId(), this.getDamage())); + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_OFF); } protected abstract int computeRedstoneStrength(); @@ -207,4 +196,9 @@ protected void playOffSound() { public Item toItem() { return new ItemBlock(this, 0, 1); } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateBirch.java b/src/main/java/cn/nukkit/block/BlockPressurePlateBirch.java new file mode 100644 index 00000000000..a17fda05033 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateBirch.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockPressurePlateBirch extends BlockPressurePlateWood { + + public BlockPressurePlateBirch() { + this(0); + } + + public BlockPressurePlateBirch(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Birch Pressure Plate"; + } + + @Override + public int getId() { + return BIRCH_PRESSURE_PLATE; + } + + @Override + public BlockColor getColor() { + return BlockColor.SAND_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateCrimson.java b/src/main/java/cn/nukkit/block/BlockPressurePlateCrimson.java new file mode 100644 index 00000000000..e8014b9d19b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateCrimson.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPressurePlateCrimson extends BlockPressurePlateWood { + + public BlockPressurePlateCrimson() { + this(0); + } + + public BlockPressurePlateCrimson(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Crimson Pressure Plate"; + } + + @Override + public int getId() { + return CRIMSON_PRESSURE_PLATE; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateDarkOak.java b/src/main/java/cn/nukkit/block/BlockPressurePlateDarkOak.java new file mode 100644 index 00000000000..0e5d6e89906 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateDarkOak.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockPressurePlateDarkOak extends BlockPressurePlateWood { + + public BlockPressurePlateDarkOak() { + this(0); + } + + public BlockPressurePlateDarkOak(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dark Oak Pressure Plate"; + } + + @Override + public int getId() { + return DARK_OAK_PRESSURE_PLATE; + } + + @Override + public BlockColor getColor() { + return BlockColor.BROWN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateJungle.java b/src/main/java/cn/nukkit/block/BlockPressurePlateJungle.java new file mode 100644 index 00000000000..e2ab97d937a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateJungle.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockPressurePlateJungle extends BlockPressurePlateWood { + + public BlockPressurePlateJungle() { + this(0); + } + + public BlockPressurePlateJungle(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jungle Pressure Plate"; + } + + @Override + public int getId() { + return JUNGLE_PRESSURE_PLATE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlatePolishedBlackstone.java b/src/main/java/cn/nukkit/block/BlockPressurePlatePolishedBlackstone.java new file mode 100644 index 00000000000..d63ff6c0e6b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlatePolishedBlackstone.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockPressurePlatePolishedBlackstone extends BlockPressurePlateStone { + + public BlockPressurePlatePolishedBlackstone() { + this(0); + } + + public BlockPressurePlatePolishedBlackstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Blackstone Pressure Plate"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_PRESSURE_PLATE; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateSpruce.java b/src/main/java/cn/nukkit/block/BlockPressurePlateSpruce.java new file mode 100644 index 00000000000..fe84bd1e102 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateSpruce.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockPressurePlateSpruce extends BlockPressurePlateWood { + + public BlockPressurePlateSpruce() { + this(0); + } + + public BlockPressurePlateSpruce(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Spruce Pressure Plate"; + } + + @Override + public int getId() { + return SPRUCE_PRESSURE_PLATE; + } + + @Override + public BlockColor getColor() { + return BlockColor.SPRUCE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateStone.java b/src/main/java/cn/nukkit/block/BlockPressurePlateStone.java index 2abf30e05d7..e18fa96f049 100644 --- a/src/main/java/cn/nukkit/block/BlockPressurePlateStone.java +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateStone.java @@ -49,7 +49,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockPressurePlateWarped.java b/src/main/java/cn/nukkit/block/BlockPressurePlateWarped.java new file mode 100644 index 00000000000..8523e4870e7 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPressurePlateWarped.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockPressurePlateWarped extends BlockPressurePlateWood { + + public BlockPressurePlateWarped() { + this(0); + } + + public BlockPressurePlateWarped(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Pressure Plate"; + } + + @Override + public int getId() { + return WARPED_PRESSURE_PLATE; + } +} + diff --git a/src/main/java/cn/nukkit/block/BlockPrismarine.java b/src/main/java/cn/nukkit/block/BlockPrismarine.java index 6073aeffa0c..73bbcb0b4d4 100644 --- a/src/main/java/cn/nukkit/block/BlockPrismarine.java +++ b/src/main/java/cn/nukkit/block/BlockPrismarine.java @@ -11,10 +11,10 @@ public class BlockPrismarine extends BlockSolidMeta { public static final int DARK = 1; public static final int BRICKS = 2; - private static final String[] NAMES = new String[]{ + private static final String[] NAMES = { "Prismarine", - "Dark prismarine", - "Prismarine bricks" + "Dark Prismarine", + "Prismarine Bricks" }; public BlockPrismarine() { @@ -52,7 +52,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -68,11 +68,11 @@ public boolean canHarvestWithHand() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { case NORMAL: return BlockColor.CYAN_BLOCK_COLOR; - case DARK: case BRICKS: + case DARK: return BlockColor.DIAMOND_BLOCK_COLOR; default: return BlockColor.STONE_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockPumpkin.java b/src/main/java/cn/nukkit/block/BlockPumpkin.java index 82aabab6b0b..478900662d4 100644 --- a/src/main/java/cn/nukkit/block/BlockPumpkin.java +++ b/src/main/java/cn/nukkit/block/BlockPumpkin.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemID; import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; @@ -13,6 +14,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockPumpkin extends BlockSolidMeta implements Faceable { + public BlockPumpkin() { this(0); } @@ -48,12 +50,12 @@ public int getToolType() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(player != null ? player.getDirection().getOpposite().getHorizontalIndex() : 0); + this.setBlockFace(player != null ? player.getDirection().getOpposite() : BlockFace.SOUTH); this.getLevel().setBlock(block, this, true, true); return true; } @@ -64,12 +66,36 @@ public BlockColor getColor() { } @Override - public boolean canBePushed() { - return false; + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } @Override - public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean canBeActivated() { + return true; + } + + + @Override + public boolean onActivate(Item item, Player player) { + if (!item.isShears()) { + return false; + } + + BlockPumpkinCarved carvedPumpkin = new BlockPumpkinCarved(); + carvedPumpkin.setBlockFace(this.getBlockFace()); + item.useOn(this); + this.level.setBlock(this, carvedPumpkin, true, true); + this.getLevel().dropItem(add(0.5, 0.5, 0.5), Item.get(ItemID.PUMPKIN_SEEDS)); + this.getLevel().dropItem(add(0.5, 0.5, 0.5), Item.get(Item.PUMPKIN_SEEDS));return true; + } + + public void setBlockFace(BlockFace blockFace) { + this.setDamage(blockFace.getHorizontalIndex()); } } diff --git a/src/main/java/cn/nukkit/block/BlockPumpkinCarved.java b/src/main/java/cn/nukkit/block/BlockPumpkinCarved.java new file mode 100644 index 00000000000..1a5786f4cac --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockPumpkinCarved.java @@ -0,0 +1,26 @@ +package cn.nukkit.block; + +public class BlockPumpkinCarved extends BlockPumpkin { + + public BlockPumpkinCarved() { + } + + public BlockPumpkinCarved(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Carved Pumpkin"; + } + + @Override + public int getId() { + return CARVED_PUMPKIN; + } + + @Override + public boolean canBeActivated() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockPumpkinLit.java b/src/main/java/cn/nukkit/block/BlockPumpkinLit.java index 0f55f876885..8cad971779f 100644 --- a/src/main/java/cn/nukkit/block/BlockPumpkinLit.java +++ b/src/main/java/cn/nukkit/block/BlockPumpkinLit.java @@ -5,6 +5,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockPumpkinLit extends BlockPumpkin { + public BlockPumpkinLit() { this(0); } @@ -28,4 +29,8 @@ public int getLightLevel() { return 15; } + @Override + public boolean canBeActivated() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockPurpur.java b/src/main/java/cn/nukkit/block/BlockPurpur.java index 3f611508142..0265a386e2e 100644 --- a/src/main/java/cn/nukkit/block/BlockPurpur.java +++ b/src/main/java/cn/nukkit/block/BlockPurpur.java @@ -12,8 +12,17 @@ public class BlockPurpur extends BlockSolidMeta { public static final int PURPUR_NORMAL = 0; public static final int PURPUR_PILLAR = 2; + private static final short[] faces = { + 0, + 0, + 0b1000, + 0b1000, + 0b0100, + 0b0100 + }; + public BlockPurpur() { - this(0); + this(PURPUR_NORMAL); } public BlockPurpur(int meta) { @@ -22,7 +31,7 @@ public BlockPurpur(int meta) { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Purpur Block", "", "Purpur Pillar", @@ -55,15 +64,6 @@ public int getToolType() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (this.getDamage() != PURPUR_NORMAL) { - short[] faces = new short[]{ - 0, - 0, - 0b1000, - 0b1000, - 0b0100, - 0b0100 - }; - this.setDamage(((this.getDamage() & 0x03) | faces[face.getIndex()])); } this.getLevel().setBlock(block, this, true, true); @@ -73,7 +73,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -84,7 +84,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.PURPUR_BLOCK), this.getDamage() & 0x03, 1); + return new ItemBlock(Block.get(Block.PURPUR_BLOCK), this.getDamage() & 0x03, 1); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockQuartz.java b/src/main/java/cn/nukkit/block/BlockQuartz.java index 2bacdbc1ff8..e30f4d4b5d9 100644 --- a/src/main/java/cn/nukkit/block/BlockQuartz.java +++ b/src/main/java/cn/nukkit/block/BlockQuartz.java @@ -8,7 +8,7 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockQuartz extends BlockSolidMeta { @@ -18,6 +18,14 @@ public class BlockQuartz extends BlockSolidMeta { public static final int QUARTZ_PILLAR = 2; public static final int QUARTZ_PILLAR2 = 3; + private static final short[] faces = { + 0, + 0, + 0b1000, + 0b1000, + 0b0100, + 0b0100 + }; public BlockQuartz() { this(0); @@ -44,7 +52,7 @@ public double getResistance() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Quartz Block", "Chiseled Quartz Block", "Quartz Pillar", @@ -57,15 +65,6 @@ public String getName() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (this.getDamage() != QUARTZ_NORMAL) { - short[] faces = new short[]{ - 0, - 0, - 0b1000, - 0b1000, - 0b0100, - 0b0100 - }; - this.setDamage(((this.getDamage() & 0x03) | faces[face.getIndex()])); } this.getLevel().setBlock(block, this, true, true); @@ -75,7 +74,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -86,7 +85,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.QUARTZ_BLOCK), this.getDamage() & 0x03, 1); + return new ItemBlock(this, this.getDamage() & 0x03, 1); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockQuartzBricks.java b/src/main/java/cn/nukkit/block/BlockQuartzBricks.java new file mode 100644 index 00000000000..93983ec9c83 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockQuartzBricks.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockQuartzBricks extends BlockQuartz { + + public BlockQuartzBricks() { + this(0); + } + + public BlockQuartzBricks(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Quartz Bricks"; + } + + @Override + public int getId() { + return QUARTZ_BRICKS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRail.java b/src/main/java/cn/nukkit/block/BlockRail.java index e7f348763ce..5f4aad016c1 100644 --- a/src/main/java/cn/nukkit/block/BlockRail.java +++ b/src/main/java/cn/nukkit/block/BlockRail.java @@ -58,11 +58,6 @@ public double getResistance() { return 3.5; } - @Override - public boolean canPassThrough() { - return true; - } - @Override public int getToolType() { return ItemTool.TYPE_PICKAXE; @@ -72,23 +67,73 @@ public int getToolType() { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { Optional ascendingDirection = this.getOrientation().ascendingDirection(); - Block down = this.down(); - if ((down.isTransparent() && down.getId() != HOPPER_BLOCK) || (ascendingDirection.isPresent() && this.getSide(ascendingDirection.get()).isTransparent())) { + if (!canStayOnFullNonSolid(this.down()) || (ascendingDirection.isPresent() && this.getSide(ascendingDirection.get()).isTransparent())) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; } + } else if (type == Level.BLOCK_UPDATE_REDSTONE) { + if (this instanceof BlockRailPowered || this instanceof BlockRailDetector || this instanceof BlockRailActivator) { + return 0; + } + boolean power = level.isBlockPowered(this); + Map railsAround = this.checkRailsAround(Arrays.asList(SOUTH, EAST, WEST, NORTH)); + int railsAmount = railsAround.size(); + if (railsAmount <= 2) { + return 0; + } + List rails = new ArrayList<>(railsAround.keySet()); + List faces = new ArrayList<>(railsAround.values()); + if (railsAmount == 4) { + if (this.isAbstract()) { + if (power) { + this.setDamage(this.connect(rails.get(faces.indexOf(NORTH)), NORTH, rails.get(faces.indexOf(WEST)), WEST).metadata()); + } else { + this.setDamage(this.connect(rails.get(faces.indexOf(SOUTH)), SOUTH, rails.get(faces.indexOf(EAST)), EAST).metadata()); + } + } else { + this.setDamage(this.connect(rails.get(faces.indexOf(EAST)), EAST, rails.get(faces.indexOf(WEST)), WEST).metadata()); + } + } else if (!railsAround.isEmpty()) { + if (this.isAbstract()) { + List cd; + if (power) { + cd = Stream.of(CURVED_NORTH_WEST, CURVED_SOUTH_WEST, CURVED_NORTH_EAST) + .filter(o -> faces.containsAll(o.connectingDirections())) + .findFirst().get().connectingDirections(); + } else { + cd = Stream.of(CURVED_SOUTH_EAST, CURVED_NORTH_EAST, CURVED_SOUTH_WEST) + .filter(o -> faces.containsAll(o.connectingDirections())) + .findFirst().get().connectingDirections(); + } + BlockFace f1 = cd.get(0); + BlockFace f2 = cd.get(1); + this.setDamage(this.connect(rails.get(faces.indexOf(f1)), f1, rails.get(faces.indexOf(f2)), f2).metadata()); + } else { + BlockFace face = faces.stream().min((f1, f2) -> (f1.getIndex() < f2.getIndex()) ? 1 : ((x == y) ? 0 : -1)).get(); + BlockFace opposite = face.getOpposite(); + if (faces.contains(opposite)) { + this.setDamage(this.connect(rails.get(faces.indexOf(face)), face, rails.get(faces.indexOf(opposite)), opposite).metadata()); + } else { + this.setDamage(this.connect(rails.get(faces.indexOf(face)), face).metadata()); + } + } + } + this.level.setBlock(this, this, true, true); + if (!isAbstract()) { + level.scheduleUpdate(this, this, 0); + } } return 0; } @Override - public double getMaxY() { - return this.y + 0.125; + public AxisAlignedBB recalculateBoundingBox() { + return this; } @Override - public AxisAlignedBB recalculateBoundingBox() { - return this; + public double getMaxY() { + return this.y + 0.125; } @Override @@ -99,8 +144,7 @@ public BlockColor getColor() { //Information from http://minecraft.gamepedia.com/Rail @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - Block down = this.down(); - if (down == null || (down.isTransparent() && down.getId() != HOPPER_BLOCK)) { + if (!canStayOnFullNonSolid(this.down())) { return false; } Map railsAround = this.checkRailsAroundAffected(); @@ -139,7 +183,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } } - this.level.setBlock(this, this, true, true); + this.getLevel().setBlock(this, this, true, true); if (!isAbstract()) { level.scheduleUpdate(this, this, 0); } @@ -259,18 +303,28 @@ public void setActive(boolean active) { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.RAIL, 0, 1) + Item.get(Item.RAIL) }; } @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; } } diff --git a/src/main/java/cn/nukkit/block/BlockRailActivator.java b/src/main/java/cn/nukkit/block/BlockRailActivator.java index c48fa6a4d4a..491b7fc6866 100644 --- a/src/main/java/cn/nukkit/block/BlockRailActivator.java +++ b/src/main/java/cn/nukkit/block/BlockRailActivator.java @@ -1,7 +1,9 @@ package cn.nukkit.block; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.utils.Rail; @@ -36,21 +38,15 @@ public int onUpdate(int type) { return 0; // Already broken } - boolean wasPowered = isActive(); - boolean isPowered = level.isBlockPowered(this.getLocation()) + boolean isPowered = level.isBlockPowered(this) || checkSurrounding(this, true, 0) || checkSurrounding(this, false, 0); - boolean hasUpdate = false; - if (wasPowered != isPowered) { + if (isActive() != isPowered) { setActive(isPowered); - hasUpdate = true; - } - - if (hasUpdate) { - level.updateAround(down()); + level.updateAround(getSideVec(BlockFace.DOWN)); if (getOrientation().isAscending()) { - level.updateAround(up()); + level.updateAround(getSideVec(BlockFace.UP)); } } return type; @@ -75,7 +71,7 @@ protected boolean checkSurrounding(Vector3 pos, boolean relative, int power) { int dz = pos.getFloorZ(); BlockRail block; - Block block2 = level.getBlock(new Vector3(dx, dy, dz)); + Block block2 = level.getBlock(dx, dy, dz); if (Rail.isRailBlock(block2)) { block = (BlockRail) block2; @@ -169,15 +165,20 @@ protected boolean canPowered(Vector3 pos, Rail.Orientation state, int power, boo && (level.isBlockPowered(pos) || checkSurrounding(pos, relative, power + 1)); } + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.ACTIVATOR_RAIL, 0, 1) + toItem() }; } @Override public double getHardness() { - return 0.5; + return 0.5; // 0.7 } } diff --git a/src/main/java/cn/nukkit/block/BlockRailDetector.java b/src/main/java/cn/nukkit/block/BlockRailDetector.java index 28f83f07b81..b028f4f1d3e 100644 --- a/src/main/java/cn/nukkit/block/BlockRailDetector.java +++ b/src/main/java/cn/nukkit/block/BlockRailDetector.java @@ -3,6 +3,7 @@ import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityMinecartAbstract; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.SimpleAxisAlignedBB; @@ -10,7 +11,7 @@ /** * Created on 2015/11/22 by CreeperFace. * Contributed by: larryTheCoder on 2017/7/8. - *

+ * * Nukkit Project, * Minecart and Riding Project, * Package cn.nukkit.block in project Nukkit. @@ -48,7 +49,7 @@ public int getWeakPower(BlockFace side) { @Override public int getStrongPower(BlockFace side) { - return isActive() ? 0 : (side == BlockFace.UP ? 15 : 0); + return !isActive() ? 0 : (side == BlockFace.UP ? 15 : 0); } @Override @@ -60,6 +61,11 @@ public int onUpdate(int type) { return super.onUpdate(type); } + @Override + public boolean hasEntityCollision() { + return true; + } + @Override public void onEntityCollide(Entity entity) { updateState(); @@ -68,13 +74,14 @@ public void onEntityCollide(Entity entity) { protected void updateState() { boolean wasPowered = isActive(); boolean isPowered = false; + boolean changed = false; - for (Entity entity : level.getNearbyEntities(new SimpleAxisAlignedBB( + for (Entity entity : level.getCollidingEntities(new SimpleAxisAlignedBB( getFloorX() + 0.125D, getFloorY(), getFloorZ() + 0.125D, getFloorX() + 0.875D, - getFloorY() + 0.525D, + getFloorY() + 0.750D, getFloorZ() + 0.875D))) { if (entity instanceof EntityMinecartAbstract) { isPowered = true; @@ -85,22 +92,31 @@ protected void updateState() { if (isPowered && !wasPowered) { setActive(true); level.scheduleUpdate(this, this, 0); - level.scheduleUpdate(this, this.down(), 0); + level.scheduleUpdate(this, this.getSideVec(BlockFace.DOWN), 0); + changed = true; } if (!isPowered && wasPowered) { setActive(false); level.scheduleUpdate(this, this, 0); - level.scheduleUpdate(this, this.down(), 0); + level.scheduleUpdate(this, this.getSideVec(BlockFace.DOWN), 0); + changed = true; } - level.updateComparatorOutputLevel(this); + if (changed) { + level.updateComparatorOutputLevel(this); + } + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.DETECTOR_RAIL, 0, 1) + toItem() }; } } diff --git a/src/main/java/cn/nukkit/block/BlockRailPowered.java b/src/main/java/cn/nukkit/block/BlockRailPowered.java index 83a88072314..152b06e3b45 100644 --- a/src/main/java/cn/nukkit/block/BlockRailPowered.java +++ b/src/main/java/cn/nukkit/block/BlockRailPowered.java @@ -1,14 +1,16 @@ package cn.nukkit.block; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.utils.Rail; /** * Created by Snake1999 on 2016/1/11. * Contributed by: larryTheCoder on 2017/7/18. - *

+ * * Nukkit Project, * Minecart and Riding Project, * Package cn.nukkit.block in project Nukkit. @@ -36,25 +38,20 @@ public String getName() { @Override public int onUpdate(int type) { - // Warning: I din't recommended this on slow networks server or slow client - // Network below 86Kb/s. This will became unresponsive to clients - // When updating the block state. Espicially on the world with many rails. - // Trust me, I tested this on my server. if (type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE || type == Level.BLOCK_UPDATE_SCHEDULED) { if (super.onUpdate(type) == Level.BLOCK_UPDATE_NORMAL) { return 0; // Already broken } - boolean wasPowered = isActive(); - boolean isPowered = level.isBlockPowered(this.getLocation()) + + boolean isPowered = level.isBlockPowered(this) || checkSurrounding(this, true, 0) || checkSurrounding(this, false, 0); - // Avoid Block minstake - if (wasPowered != isPowered) { + if (isActive() != isPowered) { setActive(isPowered); - level.updateAround(down()); + level.updateAround(this.getSideVec(BlockFace.DOWN)); if (getOrientation().isAscending()) { - level.updateAround(up()); + level.updateAround(this.getSideVec(BlockFace.UP)); } } return type; @@ -81,7 +78,7 @@ protected boolean checkSurrounding(Vector3 pos, boolean relative, int power) { int dz = pos.getFloorZ(); // First: get the base block BlockRail block; - Block block2 = level.getBlock(new Vector3(dx, dy, dz)); + Block block2 = level.getBlock(dx, dy, dz); // Second: check if the rail is Powered rail if (Rail.isRailBlock(block2)) { @@ -182,10 +179,15 @@ protected boolean canPowered(Vector3 pos, Rail.Orientation state, int power, boo && (level.isBlockPowered(pos) || checkSurrounding(pos, relative, power + 1)); } + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + @Override public Item[] getDrops(Item item) { return new Item[]{ - Item.get(Item.POWERED_RAIL, 0, 1) + toItem() }; } } diff --git a/src/main/java/cn/nukkit/block/BlockRawCopper.java b/src/main/java/cn/nukkit/block/BlockRawCopper.java new file mode 100644 index 00000000000..d2c7cd86d7c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRawCopper.java @@ -0,0 +1,17 @@ +package cn.nukkit.block; + +public class BlockRawCopper extends BlockRawOreVariant { + + public BlockRawCopper() { + } + + @Override + public String getName() { + return "Block of Raw Copper"; + } + + @Override + public int getId() { + return RAW_COPPER_BLOCK; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRawGold.java b/src/main/java/cn/nukkit/block/BlockRawGold.java new file mode 100644 index 00000000000..b907481662d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRawGold.java @@ -0,0 +1,24 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockRawGold extends BlockRawOreVariant { + + public BlockRawGold() { + } + + @Override + public String getName() { + return "Block of Raw Gold"; + } + + @Override + public int getId() { + return RAW_GOLD_BLOCK; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_IRON; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRawIron.java b/src/main/java/cn/nukkit/block/BlockRawIron.java new file mode 100644 index 00000000000..2a94a5e2fed --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRawIron.java @@ -0,0 +1,17 @@ +package cn.nukkit.block; + +public class BlockRawIron extends BlockRawOreVariant { + + public BlockRawIron() { + } + + @Override + public String getName() { + return "Block of Raw Iron"; + } + + @Override + public int getId() { + return RAW_IRON_BLOCK; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRawOreVariant.java b/src/main/java/cn/nukkit/block/BlockRawOreVariant.java new file mode 100644 index 00000000000..369d62ff264 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRawOreVariant.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; + +public abstract class BlockRawOreVariant extends BlockSolid { + + public BlockRawOreVariant() { + } + + @Override + public double getHardness() { + return 5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Item[] getDrops(Item item) { + if (item.getTier() < this.getToolTier()) { + return new Item[0]; + } + return super.getDrops(item); + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + // TODO: + /*@Override + public boolean isLavaResistant() { + return true; + }*/ + +} diff --git a/src/main/java/cn/nukkit/block/BlockRedSandstone.java b/src/main/java/cn/nukkit/block/BlockRedSandstone.java index 4439e716df8..49968097168 100644 --- a/src/main/java/cn/nukkit/block/BlockRedSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockRedSandstone.java @@ -2,7 +2,6 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; -import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** @@ -25,7 +24,7 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Red Sandstone", "Chiseled Red Sandstone", "Smooth Red Sandstone", @@ -37,7 +36,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -51,11 +50,6 @@ public Item toItem() { return new ItemBlock(this, this.getDamage() & 0x03); } - @Override - public boolean canHarvestWithHand() { - return false; - } - @Override public BlockColor getColor() { return BlockColor.ORANGE_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockRedstone.java b/src/main/java/cn/nukkit/block/BlockRedstone.java index 19f8d33e01c..e44ec885d5f 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstone.java +++ b/src/main/java/cn/nukkit/block/BlockRedstone.java @@ -1,5 +1,6 @@ package cn.nukkit.block; +import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; @@ -9,15 +10,7 @@ * Created on 2015/12/11 by Pub4Game. * Package cn.nukkit.block in project Nukkit . */ -public class BlockRedstone extends BlockSolidMeta { - - public BlockRedstone() { - this(0); - } - - public BlockRedstone(int meta) { - super(0); - } +public class BlockRedstone extends BlockSolid { @Override public int getId() { @@ -41,14 +34,30 @@ public int getToolType() { @Override public String getName() { - return "Redstone Block"; + return "Block of Redstone"; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (!super.place(item, block, target, face, fx, fy, fz, player)) { + return false; + } + this.level.updateAroundRedstone(this, null); + return true; } - //TODO: redstone + @Override + public boolean onBreak(Item item) { + if (!super.onBreak(item)) { + return false; + } + this.level.updateAroundRedstone(this, null); + return true; + } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -76,4 +85,9 @@ public int getWeakPower(BlockFace face) { public boolean canHarvestWithHand() { return false; } -} \ No newline at end of file + + @Override + public boolean canBePushed() { + return false; // TODO: remove when crash issue fixed + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java b/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java index 11d5711c0cd..05229a25ab5 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneComparator.java @@ -4,12 +4,11 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityComparator; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemRedstoneComparator; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.BlockColor; /** @@ -41,12 +40,12 @@ public Mode getMode() { @Override protected BlockRedstoneComparator getUnpowered() { - return (BlockRedstoneComparator) Block.get(BlockID.UNPOWERED_COMPARATOR, this.getDamage()); + return (BlockRedstoneComparator) Block.get(UNPOWERED_COMPARATOR, this.getDamage()); } @Override protected BlockRedstoneComparator getPowered() { - return (BlockRedstoneComparator) Block.get(BlockID.POWERED_COMPARATOR, this.getDamage()); + return (BlockRedstoneComparator) Block.get(POWERED_COMPARATOR, this.getDamage()); } @Override @@ -64,13 +63,12 @@ public void updateState() { int power = blockEntity instanceof BlockEntityComparator ? ((BlockEntityComparator) blockEntity).getOutputSignal() : 0; if (output != power || this.isPowered() != this.shouldBePowered()) { - /*if(isFacingTowardsRepeater()) { + /*if (isFacingTowardsRepeater()) { this.level.scheduleUpdate(this, this, 2, -1); } else { this.level.scheduleUpdate(this, this, 2, 0); }*/ - //System.out.println("schedule update 0"); this.level.scheduleUpdate(this, this, 2); } } @@ -119,9 +117,12 @@ public boolean onActivate(Item item, Player player) { this.setDamage(this.getDamage() + 4); } - this.level.addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_BUTTON_CLICK, this.getMode() == Mode.SUBTRACT ? 500 : 550); + if (this.getMode() == Mode.SUBTRACT) { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_ON); + } else { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POWER_OFF); + } this.level.setBlock(this, this, true, false); - //bug? this.onChange(); return true; @@ -158,7 +159,10 @@ private void onChange() { this.level.setBlock(this, getPowered(), true, false); } - this.level.updateAroundRedstone(this, null); + this.level.updateAroundRedstone(this, null); //TODO: remove + //Block side = this.getSide(getFacing().getOpposite()); + //side.onUpdate(Level.BLOCK_UPDATE_REDSTONE); + //this.level.updateAroundRedstone(side, null); } } @@ -171,10 +175,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl .putInt("x", (int) this.x) .putInt("y", (int) this.y) .putInt("z", (int) this.z); - BlockEntityComparator comparator = (BlockEntityComparator) BlockEntity.createBlockEntity(BlockEntity.COMPARATOR, this.level.getChunk((int) this.x >> 4, (int) this.z >> 4), nbt); - if (comparator == null) { - return false; - } + BlockEntity.createBlockEntity(BlockEntity.COMPARATOR, this.getChunk(), nbt); + onUpdate(Level.BLOCK_UPDATE_REDSTONE); return true; } @@ -189,7 +191,7 @@ public boolean isPowered() { @Override public Item toItem() { - return new ItemRedstoneComparator(); + return Item.get(Item.COMPARATOR); } public enum Mode { @@ -201,4 +203,9 @@ public enum Mode { public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } + + @Override + public boolean canBePushed() { + return false; // prevent item loss issue with pistons until a working implementation + } } diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java b/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java index 668d3853bfb..9dd0ed9faa8 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneDiode.java @@ -26,18 +26,18 @@ public BlockRedstoneDiode(int meta) { @Override public boolean onBreak(Item item) { - Vector3 pos = getLocation(); this.level.setBlock(this, Block.get(BlockID.AIR), true, true); for (BlockFace face : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(face), null); + this.level.updateAroundRedstone(this.getSideVec(face), null); } + return true; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (block.getSide(BlockFace.DOWN).isTransparent()) { + if (!canStayOnFullSolid(this.down())) { return false; } @@ -54,19 +54,17 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_SCHEDULED) { if (!this.isLocked()) { - Vector3 pos = getLocation(); boolean shouldBePowered = this.shouldBePowered(); if (this.isPowered && !shouldBePowered) { - this.level.setBlock(pos, this.getUnpowered(), true, true); + this.level.setBlock(this, this.getUnpowered(), true, true); - this.level.updateAroundRedstone(this.getLocation().getSide(getFacing().getOpposite()), null); + this.level.updateAroundRedstone(this.getSideVec(getFacing().getOpposite()), null); } else if (!this.isPowered) { - this.level.setBlock(pos, this.getPowered(), true, true); - this.level.updateAroundRedstone(this.getLocation().getSide(getFacing().getOpposite()), null); + this.level.setBlock(this, this.getPowered(), true, true); + this.level.updateAroundRedstone(this.getSideVec(getFacing().getOpposite()), null); if (!shouldBePowered) { -// System.out.println("schedule update 2"); level.scheduleUpdate(getPowered(), this, this.getDelay()); } } @@ -78,13 +76,12 @@ public int onUpdate(int type) { if (ev.isCancelled()) { return 0; } - if (type == Level.BLOCK_UPDATE_NORMAL && this.getSide(BlockFace.DOWN).isTransparent()) { + if (type == Level.BLOCK_UPDATE_NORMAL && !canStayOnFullSolid(this.down())) { this.level.useBreakOn(this); - return Level.BLOCK_UPDATE_NORMAL; } else { this.updateState(); - return Level.BLOCK_UPDATE_NORMAL; } + return Level.BLOCK_UPDATE_NORMAL; } return 0; } @@ -113,7 +110,7 @@ public boolean isLocked() { protected int calculateInputStrength() { BlockFace face = getFacing(); - Vector3 pos = this.getLocation().getSide(face); + Vector3 pos = this.getSideVec(face); int power = this.level.getRedstonePower(pos, face); if (power >= 15) { @@ -125,12 +122,10 @@ protected int calculateInputStrength() { } protected int getPowerOnSides() { - Vector3 pos = getLocation(); - BlockFace face = getFacing(); BlockFace face1 = face.rotateY(); BlockFace face2 = face.rotateYCCW(); - return Math.max(this.getPowerOnSide(pos.getSide(face1), face1), this.getPowerOnSide(pos.getSide(face2), face2)); + return Math.max(this.getPowerOnSide(this.getSideVec(face1), face1), this.getPowerOnSide(this.getSideVec(face2), face2)); } protected int getPowerOnSide(Vector3 pos, BlockFace side) { @@ -202,11 +197,26 @@ public boolean isFacingTowardsRepeater() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } @Override public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java b/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java index d812b27b55a..b26d471f725 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneLamp.java @@ -14,9 +14,6 @@ */ public class BlockRedstoneLamp extends BlockSolid { - public BlockRedstoneLamp() { - } - @Override public String getName() { return "Redstone Lamp"; @@ -44,8 +41,8 @@ public int getToolType() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (this.level.isBlockPowered(this.getLocation())) { - this.level.setBlock(this, Block.get(BlockID.LIT_REDSTONE_LAMP), false, true); + if (this.level.isBlockPowered(this)) { + this.level.setBlock(this, Block.get(LIT_REDSTONE_LAMP), false, true); } else { this.level.setBlock(this, this, false, true); } @@ -61,8 +58,8 @@ public int onUpdate(int type) { if (ev.isCancelled()) { return 0; } - if (this.level.isBlockPowered(this.getLocation())) { - this.level.setBlock(this, Block.get(BlockID.LIT_REDSTONE_LAMP), false, false); + if (this.level.isBlockPowered(this)) { + this.level.setBlock(this, Block.get(LIT_REDSTONE_LAMP), false, false); return 1; } } @@ -73,7 +70,7 @@ public int onUpdate(int type) { @Override public Item[] getDrops(Item item) { return new Item[]{ - new ItemBlock(Block.get(BlockID.REDSTONE_LAMP)) + new ItemBlock(Block.get(REDSTONE_LAMP)) }; } diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java b/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java index 876e847bb82..7d547700129 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneLampLit.java @@ -10,9 +10,6 @@ */ public class BlockRedstoneLampLit extends BlockRedstoneLamp { - public BlockRedstoneLampLit() { - } - @Override public String getName() { return "Lit Redstone Lamp"; @@ -30,13 +27,12 @@ public int getLightLevel() { @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.REDSTONE_LAMP)); + return new ItemBlock(Block.get(REDSTONE_LAMP)); } @Override public int onUpdate(int type) { - if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && !this.level.isBlockPowered(this.getLocation())) { - // Redstone event + if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && !this.level.isBlockPowered(this)) { RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); getLevel().getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -46,8 +42,8 @@ public int onUpdate(int type) { return 1; } - if (type == Level.BLOCK_UPDATE_SCHEDULED && !this.level.isBlockPowered(this.getLocation())) { - this.level.setBlock(this, Block.get(BlockID.REDSTONE_LAMP), false, false); + if (type == Level.BLOCK_UPDATE_SCHEDULED && !this.level.isBlockPowered(this)) { + this.level.setBlock(this, Block.get(REDSTONE_LAMP), false, false); } return 0; } diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterPowered.java b/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterPowered.java index 18bac5eccde..c02a7ecca2a 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterPowered.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterPowered.java @@ -2,7 +2,6 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemRedstoneRepeater; import cn.nukkit.math.BlockFace; /** @@ -41,12 +40,12 @@ protected boolean isAlternateInput(Block block) { @Override public Item toItem() { - return new ItemRedstoneRepeater(); + return Item.get(Item.REPEATER); } @Override protected int getDelay() { - return (1 + (getDamage() >> 2)) * 2; + return (1 + (getDamage() >> 2)) << 1; } @Override @@ -56,7 +55,7 @@ protected Block getPowered() { @Override protected Block getUnpowered() { - return Block.get(BlockID.UNPOWERED_REPEATER, this.getDamage()); + return Block.get(UNPOWERED_REPEATER, this.getDamage()); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterUnpowered.java b/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterUnpowered.java index 47e376018c1..81950f605f1 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterUnpowered.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneRepeaterUnpowered.java @@ -2,7 +2,6 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemRedstoneRepeater; import cn.nukkit.math.BlockFace; /** @@ -50,17 +49,17 @@ protected boolean isAlternateInput(Block block) { @Override public Item toItem() { - return new ItemRedstoneRepeater(); + return Item.get(Item.REPEATER); } @Override protected int getDelay() { - return (1 + (getDamage() >> 2)) * 2; + return (1 + (getDamage() >> 2)) << 1; } @Override protected Block getPowered() { - return Block.get(BlockID.POWERED_REPEATER, this.getDamage()); + return Block.get(POWERED_REPEATER, this.getDamage()); } @Override @@ -72,4 +71,4 @@ protected Block getUnpowered() { public boolean isLocked() { return this.getPowerOnSides() > 0; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java b/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java index 4619dd35f48..e832da6faa7 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneTorch.java @@ -5,14 +5,14 @@ import cn.nukkit.item.Item; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Faceable; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ -public class BlockRedstoneTorch extends BlockTorch { +public class BlockRedstoneTorch extends BlockTorch implements Faceable { public BlockRedstoneTorch() { this(0); @@ -43,19 +43,6 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } -// if (!checkState()) { -// BlockFace facing = getFacing().getOpposite(); -// Vector3 pos = getLocation(); -// -// for (BlockFace side : BlockFace.values()) { -// if (facing == side) { -// continue; -// } -// -// this.level.updateAround(pos.getSide(side)); -// } -// } - checkState(); return true; @@ -75,8 +62,6 @@ public int getStrongPower(BlockFace side) { public boolean onBreak(Item item) { super.onBreak(item); - Vector3 pos = getLocation(); - BlockFace face = getBlockFace().getOpposite(); for (BlockFace side : BlockFace.values()) { @@ -84,7 +69,7 @@ public boolean onBreak(Item item) { continue; } - this.level.updateAroundRedstone(pos.getSide(side), null); + this.level.updateAroundRedstone(this.getSideVec(side), null); } return true; } @@ -114,16 +99,15 @@ public int onUpdate(int type) { protected boolean checkState() { if (isPoweredFromSide()) { BlockFace face = getBlockFace().getOpposite(); - Vector3 pos = getLocation(); - this.level.setBlock(pos, Block.get(BlockID.UNLIT_REDSTONE_TORCH, getDamage()), false, true); + this.level.setBlock(this, Block.get(UNLIT_REDSTONE_TORCH, getDamage()), false, true); for (BlockFace side : BlockFace.values()) { if (side == face) { continue; } - this.level.updateAroundRedstone(pos.getSide(side), null); + this.level.updateAroundRedstone(this.getSideVec(side), null); } return true; @@ -134,10 +118,9 @@ protected boolean checkState() { protected boolean isPoweredFromSide() { BlockFace face = getBlockFace().getOpposite(); - return this.level.isSidePowered(this.getLocation().getSide(face), face); + return this.level.isSidePowered(this.getSideVec(face), face); } - - @Override + @Override public int tickRate() { return 2; } @@ -151,4 +134,4 @@ public boolean isPowerSource() { public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java b/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java index eb0c46d6de4..64231974802 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneTorchUnlit.java @@ -5,7 +5,6 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; /** * Created by CreeperFace on 10.4.2017. @@ -35,19 +34,9 @@ public int getLightLevel() { return 0; } - @Override - public int getWeakPower(BlockFace side) { - return 0; - } - - @Override - public int getStrongPower(BlockFace side) { - return 0; - } - @Override public Item toItem() { - return new ItemBlock(Block.get(BlockID.REDSTONE_TORCH)); + return new ItemBlock(Block.get(REDSTONE_TORCH)); } @Override @@ -73,17 +62,16 @@ public int onUpdate(int type) { protected boolean checkState() { BlockFace face = getBlockFace().getOpposite(); - Vector3 pos = getLocation(); - if (!this.level.isSidePowered(pos.getSide(face), face)) { - this.level.setBlock(pos, Block.get(BlockID.REDSTONE_TORCH, getDamage()), false, true); + if (!this.level.isSidePowered(this.getSideVec(face), face)) { + this.level.setBlock(this, Block.get(REDSTONE_TORCH, getDamage()), false, true); for (BlockFace side : BlockFace.values()) { if (side == face) { continue; } - this.level.updateAroundRedstone(pos.getSide(side), null); + this.level.updateAroundRedstone(this.getSideVec(side), null); } return true; } @@ -95,4 +83,4 @@ protected boolean checkState() { public int tickRate() { return 2; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java index 06f26de3e5f..def1f0f126f 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java @@ -4,7 +4,6 @@ import cn.nukkit.event.block.BlockRedstoneEvent; import cn.nukkit.event.redstone.RedstoneUpdateEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemRedstone; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockFace.Plane; @@ -14,7 +13,7 @@ import java.util.EnumSet; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockRedstoneWire extends BlockFlowable { @@ -41,51 +40,48 @@ public int getId() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (!canBePlacedOn(block.down())) { + if (block instanceof BlockWater) { + return false; + } + + if (!canStayOnFullSolid(block.down())) { return false; } this.getLevel().setBlock(block, this, true, false); - this.updateSurroundingRedstone(true); - Vector3 pos = getLocation(); + this.calculateCurrentChanges(true); for (BlockFace blockFace : Plane.VERTICAL) { - this.level.updateAroundRedstone(pos.getSide(blockFace), blockFace.getOpposite()); + this.level.updateAroundRedstone(this.getSideVec(blockFace), blockFace.getOpposite()); } for (BlockFace blockFace : Plane.VERTICAL) { - this.updateAround(pos.getSide(blockFace), blockFace.getOpposite()); + this.updateAround(this.getSideVec(blockFace), blockFace.getOpposite()); } for (BlockFace blockFace : Plane.HORIZONTAL) { - Vector3 v = pos.getSide(blockFace); + Vector3 v = this.getSideVec(blockFace); if (this.level.getBlock(v).isNormalBlock()) { - this.updateAround(v.up(), BlockFace.DOWN); + this.updateAround(v.getSideVec(BlockFace.UP), BlockFace.DOWN); } else { - this.updateAround(v.down(), BlockFace.UP); + this.updateAround(v.getSideVec(BlockFace.DOWN), BlockFace.UP); } } return true; } private void updateAround(Vector3 pos, BlockFace face) { - if (this.level.getBlock(pos).getId() == Block.REDSTONE_WIRE) { + if (this.level.getBlockIdAt((int) pos.x, (int) pos.y, (int) pos.z) == Block.REDSTONE_WIRE) { this.level.updateAroundRedstone(pos, face); for (BlockFace side : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(side), side.getOpposite()); + this.level.updateAroundRedstone(pos.getSideVec(side), side.getOpposite()); } } } - private void updateSurroundingRedstone(boolean force) { - this.calculateCurrentChanges(force); - } - private void calculateCurrentChanges(boolean force) { - Vector3 pos = this.getLocation(); - int meta = this.getDamage(); int maxStrength = meta; this.canProvidePower = false; @@ -100,7 +96,7 @@ private void calculateCurrentChanges(boolean force) { int strength = 0; for (BlockFace face : Plane.HORIZONTAL) { - Vector3 v = pos.getSide(face); + Vector3 v = this.getSideVec(face); if (v.getX() == this.getX() && v.getZ() == this.getZ()) { continue; @@ -111,10 +107,10 @@ private void calculateCurrentChanges(boolean force) { boolean vNormal = this.level.getBlock(v).isNormalBlock(); - if (vNormal && !this.level.getBlock(pos.up()).isNormalBlock()) { - strength = this.getMaxCurrentStrength(v.up(), strength); + if (vNormal && !this.level.getBlock(this.getSideVec(BlockFace.UP)).isNormalBlock()) { + strength = this.getMaxCurrentStrength(v.getSideVec(BlockFace.UP), strength); } else if (!vNormal) { - strength = this.getMaxCurrentStrength(v.down(), strength); + strength = this.getMaxCurrentStrength(v.getSideVec(BlockFace.DOWN), strength); } } @@ -140,17 +136,17 @@ private void calculateCurrentChanges(boolean force) { this.level.updateAroundRedstone(this, null); for (BlockFace face : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(face), face.getOpposite()); + this.level.updateAroundRedstone(this.getSideVec(face), face.getOpposite()); } } else if (force) { for (BlockFace face : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(face), face.getOpposite()); + this.level.updateAroundRedstone(this.getSideVec(face), face.getOpposite()); } } } private int getMaxCurrentStrength(Vector3 pos, int maxStrength) { - if (this.level.getBlockIdAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()) != this.getId()) { + if (this.level.getBlockIdAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()) != REDSTONE_WIRE) { return maxStrength; } else { int strength = this.level.getBlockDataAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()); @@ -162,20 +158,19 @@ private int getMaxCurrentStrength(Vector3 pos, int maxStrength) { public boolean onBreak(Item item) { this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); - Vector3 pos = getLocation(); + this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(pos, null); for (BlockFace blockFace : BlockFace.values()) { - this.level.updateAroundRedstone(pos.getSide(blockFace), null); + this.level.updateAroundRedstone(this.getSideVec(blockFace), null); } for (BlockFace blockFace : Plane.HORIZONTAL) { - Vector3 v = pos.getSide(blockFace); + Vector3 v = this.getSideVec(blockFace); if (this.level.getBlock(v).isNormalBlock()) { - this.updateAround(v.up(), BlockFace.DOWN); + this.updateAround(v.getSideVec(BlockFace.UP), BlockFace.DOWN); } else { - this.updateAround(v.down(), BlockFace.UP); + this.updateAround(v.getSideVec(BlockFace.DOWN), BlockFace.UP); } } return true; @@ -183,7 +178,7 @@ public boolean onBreak(Item item) { @Override public Item toItem() { - return new ItemRedstone(); + return Item.get(Item.REDSTONE_DUST); } @Override @@ -196,6 +191,12 @@ public int onUpdate(int type) { if (type != Level.BLOCK_UPDATE_NORMAL && type != Level.BLOCK_UPDATE_REDSTONE) { return 0; } + + if (type == Level.BLOCK_UPDATE_NORMAL && !canStayOnFullSolid(this.down())) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + // Redstone event RedstoneUpdateEvent ev = new RedstoneUpdateEvent(this); getLevel().getServer().getPluginManager().callEvent(ev); @@ -203,22 +204,18 @@ public int onUpdate(int type) { return 0; } - if (type == Level.BLOCK_UPDATE_NORMAL && !this.canBePlacedOn(this.down())) { - this.getLevel().useBreakOn(this); - return Level.BLOCK_UPDATE_NORMAL; + // Make sure the block still exists to prevent item duplication + if (this.level.getBlockIdAt((int) this.x, (int) this.y, (int) this.z) != this.getId()) { + return 0; } - this.updateSurroundingRedstone(false); + this.calculateCurrentChanges(false); - return Level.BLOCK_UPDATE_NORMAL; + return Level.BLOCK_UPDATE_REDSTONE; } public boolean canBePlacedOn(Vector3 v) { - return this.canBePlacedOn(this.level.getBlock(v)); - } - - private boolean canBePlacedOn(Block b) { - return (b.isSolid() && !b.isTransparent() && b.getId() != GLOWSTONE) || b.getId() == HOPPER_BLOCK; + return canStayOnFullSolid(this.level.getBlock(v)); } public int getStrongPower(BlockFace side) { @@ -256,16 +253,10 @@ public int getWeakPower(BlockFace side) { } private boolean isPowerSourceAt(BlockFace side) { - Vector3 pos = getLocation(); - Vector3 v = pos.getSide(side); - Block block = this.level.getBlock(v); - boolean flag = block.isNormalBlock(); - boolean flag1 = this.level.getBlock(pos.up()).isNormalBlock(); - return !flag1 && flag && canConnectUpwardsTo(this.level, v.up()) || (canConnectTo(block, side) || !flag && canConnectUpwardsTo(this.level, block.down())); - } - - protected static boolean canConnectUpwardsTo(Level level, Vector3 pos) { - return canConnectUpwardsTo(level.getBlock(pos)); + Block sideBlock = this.getSide(side); + boolean sideBlockIsNormal = sideBlock.isNormalBlock(); + return (sideBlockIsNormal && !this.up().isNormalBlock() && canConnectUpwardsTo(sideBlock.up())) || + (canConnectTo(sideBlock, side) || (!sideBlockIsNormal && canConnectUpwardsTo(sideBlock.down()))); } protected static boolean canConnectUpwardsTo(Block block) { @@ -290,10 +281,9 @@ public boolean isPowerSource() { private int getIndirectPower() { int power = 0; - Vector3 pos = getLocation(); for (BlockFace face : BlockFace.values()) { - int blockPower = this.getIndirectPower(pos.getSide(face), face); + int blockPower = this.getIndirectPower(this.getSideVec(face), face); if (blockPower >= 15) { return 15; @@ -312,7 +302,7 @@ private int getIndirectPower(Vector3 pos, BlockFace face) { if (block.getId() == Block.REDSTONE_WIRE) { return 0; } - return block.isNormalBlock() ? getStrongPower(pos.getSide(face), face) : block.getWeakPower(face); + return block.isNormalBlock() ? getStrongPower(pos.getSideVec(face), face) : block.getWeakPower(face); } private int getStrongPower(Vector3 pos, BlockFace direction) { @@ -324,4 +314,9 @@ private int getStrongPower(Vector3 pos, BlockFace direction) { return block.getStrongPower(direction); } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockRespawnAnchor.java b/src/main/java/cn/nukkit/block/BlockRespawnAnchor.java new file mode 100644 index 00000000000..0d9f3c10746 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRespawnAnchor.java @@ -0,0 +1,151 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Explosion; +import cn.nukkit.level.GameRule; +import cn.nukkit.level.Level; +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.LevelSoundEventPacket; + +public class BlockRespawnAnchor extends BlockSolidMeta { + + public BlockRespawnAnchor() { + this(0); + } + + public BlockRespawnAnchor(int meta) { + super(meta); + } + + @Override + public int getId() { + return RESPAWN_ANCHOR; + } + + @Override + public double getHardness() { + return 50; + } + + @Override + public double getResistance() { + return 1200; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_DIAMOND) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public int getLightLevel() { + switch (this.getDamage()) { + case 0: + return 0; + case 1: + return 3; + case 2: + return 7; + default: + return 15; + } + } + + @Override + public String getName() { + return "Respawn Anchor"; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + int chargeLevel = this.getDamage(); + + if (item.getId() == GLOWSTONE && chargeLevel < 4) { + if (player != null && !player.isCreative()) { + item.count--; + } + + this.setDamage(chargeLevel + 1); + this.getLevel().setBlock(this, this, true); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_RESPAWN_ANCHOR_CHARGE); + return true; + } + + if (chargeLevel > 0 && this.level.getDimension() != Level.DIMENSION_NETHER) { + if (this.level.getGameRules().getBoolean(GameRule.RESPAWN_BLOCKS_EXPLODE)) { + this.getLevel().setBlock(this, Block.get(BlockID.AIR), true, true); + + Explosion explosion = new Explosion(this.add(0.5, 0, 0.5), 5, this); + explosion.explodeA(); + explosion.explodeB(); + } + return false; + } + + if (player != null && chargeLevel > 0 && this.level.getDimension() == Level.DIMENSION_NETHER) { + if (player.distanceSquared(this) > 36) { + return false; + } + + if (!this.equals(player.getSpawnPosition())) { + player.setSpawn(this); + + player.sendMessage("§7%tile.respawn_anchor.respawnSet", true); + + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_RESPAWN_ANCHOR_SET_SPAWN); + } + } + + return true; + } + + @Override + public boolean onBreak(Item item) { + boolean r = super.onBreak(item); + if (r) { + if (level.getDimension() == Level.DIMENSION_NETHER) { + Vector3 safeSpawn = null; + for (Player player : level.getServer().getOnlinePlayers().values()) { + if (this.equals(player.getSpawnPosition())) { + player.setSpawn(safeSpawn == null ? (safeSpawn = level.getServer().getDefaultLevel().getSafeSpawn()) : safeSpawn); + } + } + } + } + return r; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockRoots.java b/src/main/java/cn/nukkit/block/BlockRoots.java new file mode 100644 index 00000000000..e929d59b999 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRoots.java @@ -0,0 +1,53 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public abstract class BlockRoots extends BlockFlowable { + + protected BlockRoots() { + super(0); + } + + protected BlockRoots(int meta) { + super(meta); + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL && !isSupportValid()) { + level.useBreakOn(this); + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + return this.isSupportValid() && super.place(item, block, target, face, fx, fy, fz, player); + } + + protected boolean isSupportValid() { + switch (this.down().getId()) { + case BlockID.GRASS: + case BlockID.DIRT: + case BlockID.PODZOL: + case BlockID.FARMLAND: + case BlockID.CRIMSON_NYLIUM: + case BlockID.WARPED_NYLIUM: + case BlockID.MYCELIUM: + case BlockID.SOUL_SOIL: + case BlockID.ROOTED_DIRT: + return true; + default: + return false; + } + } + + @Override + public int getBurnChance() { + return 5; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockRootsHanging.java b/src/main/java/cn/nukkit/block/BlockRootsHanging.java new file mode 100644 index 00000000000..2eff0d9b432 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockRootsHanging.java @@ -0,0 +1,28 @@ +package cn.nukkit.block; + +public class BlockRootsHanging extends BlockRoots { + + public BlockRootsHanging() { + this(0); + } + + public BlockRootsHanging(int meta) { + super(0); // hanging roots have no variants + } + + @Override + protected boolean isSupportValid() { + Block up = this.up(); + return up.isSolid() && !up.isTransparent(); + } + + @Override + public String getName() { + return "Hanging Roots"; + } + + @Override + public int getId() { + return HANGING_ROOTS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSand.java b/src/main/java/cn/nukkit/block/BlockSand.java index 45791d253c9..574675c2a17 100644 --- a/src/main/java/cn/nukkit/block/BlockSand.java +++ b/src/main/java/cn/nukkit/block/BlockSand.java @@ -1,40 +1,28 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.generator.object.ObjectTallGrass; +import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class BlockSand extends BlockFallable { +public class BlockSand extends BlockFallableMeta { public static final int DEFAULT = 0; public static final int RED = 1; - private int meta; - public BlockSand() { this(0); } public BlockSand(int meta) { - this.meta = meta; - } - - @Override - public int getFullId() { - return (getId() << 4) + getDamage(); - } - - @Override - public final int getDamage() { - return this.meta; - } - - @Override - public final void setDamage(int meta) { - this.meta = meta; + super(meta); } @Override @@ -74,4 +62,28 @@ public BlockColor getColor() { return BlockColor.SAND_BLOCK_COLOR; } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null && item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + Block up = this.up(); + if (up instanceof BlockWater) { + if (!player.isCreative()) { + item.count--; + } + this.level.addParticle(new BoneMealParticle(this)); + if (up.getDamage() == 0 && up.up() instanceof BlockWater) { + ObjectTallGrass.growSeagrass(this.getLevel(), this); + } + return true; + } + } + + return false; + } + + @Override + public boolean canBeActivated() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSandstone.java b/src/main/java/cn/nukkit/block/BlockSandstone.java index 2fef1655c9e..b4f0829839d 100644 --- a/src/main/java/cn/nukkit/block/BlockSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockSandstone.java @@ -6,10 +6,11 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockSandstone extends BlockSolidMeta { + public static final int NORMAL = 0; public static final int CHISELED = 1; public static final int SMOOTH = 2; @@ -39,7 +40,7 @@ public double getResistance() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Sandstone", "Chiseled Sandstone", "Smooth Sandstone", @@ -51,7 +52,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockSapling.java b/src/main/java/cn/nukkit/block/BlockSapling.java index a010867cb0e..cfc0182dbef 100644 --- a/src/main/java/cn/nukkit/block/BlockSapling.java +++ b/src/main/java/cn/nukkit/block/BlockSapling.java @@ -3,6 +3,8 @@ import cn.nukkit.Player; import cn.nukkit.event.level.StructureGrowEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.ChunkManager; import cn.nukkit.level.Level; import cn.nukkit.level.ListChunkManager; import cn.nukkit.level.generator.object.BasicGenerator; @@ -13,27 +15,23 @@ import cn.nukkit.math.Vector2; import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.ThreadLocalRandom; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockSapling extends BlockFlowable { + public static final int OAK = 0; public static final int SPRUCE = 1; public static final int BIRCH = 2; - /** - * placeholder - */ - public static final int BIRCH_TALL = 8 | BIRCH; public static final int JUNGLE = 3; public static final int ACACIA = 4; public static final int DARK_OAK = 5; + public static final int BIRCH_TALL = 10; public BlockSapling() { this(0); @@ -50,7 +48,7 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak Sapling", "Spruce Sapling", "Birch Sapling", @@ -81,8 +79,8 @@ public boolean canBeActivated() { } public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0F) { //BoneMeal - if (player != null && (player.gamemode & 0x01) == 0) { + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { + if (player != null && !player.isCreative()) { item.count--; } @@ -91,42 +89,19 @@ public boolean onActivate(Item item, Player player) { return true; } - this.grow(); - - return true; + return growTreeHere(); } return false; } - public int onUpdate(int type) { - if (type == Level.BLOCK_UPDATE_NORMAL) { - if (this.down().isTransparent()) { - this.getLevel().useBreakOn(this); - return Level.BLOCK_UPDATE_NORMAL; - } - } else if (type == Level.BLOCK_UPDATE_RANDOM) { //Growth - if (ThreadLocalRandom.current().nextInt(1, 8) == 1) { - if ((this.getDamage() & 0x08) == 0x08) { - this.grow(); - } else { - this.setDamage(this.getDamage() | 0x08); - this.getLevel().setBlock(this, this, true); - return Level.BLOCK_UPDATE_RANDOM; - } - } else { - return Level.BLOCK_UPDATE_RANDOM; - } - } - return Level.BLOCK_UPDATE_NORMAL; - } - - private void grow() { + private boolean growTreeHere() { BasicGenerator generator = null; boolean bigTree = false; - Vector3 vector3 = new Vector3(); + Vector3 vector3 = new Vector3(this.x, this.y - 1, this.z); - switch (this.getDamage() & 0x07) { + int woodType = this.getDamage() & 0x7; + switch (woodType) { case JUNGLE: Vector2 vector2; if ((vector2 = this.findSaplings(JUNGLE)) != null) { @@ -137,12 +112,12 @@ private void grow() { if (!bigTree) { generator = new NewJungleTree(4, 7); - vector3 = this.add(0,0,0); + vector3 = this.add(0, 0, 0); } break; case ACACIA: generator = new ObjectSavannaTree(); - vector3 = this.add(0,0,0); + vector3 = this.add(0, 0, 0); break; case DARK_OAK: if ((vector2 = this.findSaplings(DARK_OAK)) != null) { @@ -152,37 +127,58 @@ private void grow() { } if (!bigTree) { - return; + return false; } break; - //TODO: big spruce + case SPRUCE: + if ((vector2 = this.findSaplings(SPRUCE)) != null) { + vector3 = this.add(vector2.getFloorX(), 0, vector2.getFloorY()); + generator = new HugeTreesGenerator(0, 0, null, null) { + @Override + public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) { + ObjectBigSpruceTree object = new ObjectBigSpruceTree(0.5f, 4, true); + if (!this.ensureGrowable(level, position, object.getTreeHeight())) { + return false; + } + object.placeObject(level, position.getFloorX(), position.getFloorY(), position.getFloorZ(), rand); + return true; + } + }; + bigTree = true; + } + + if (bigTree) { + break; + } default: ListChunkManager chunkManager = new ListChunkManager(this.level); - ObjectTree.growTree(chunkManager, this.getFloorX(), this.getFloorY(), this.getFloorZ(), new NukkitRandom(), this.getDamage() & 0x07); + ObjectTree.growTree(chunkManager, this.getFloorX(), this.getFloorY(), this.getFloorZ(), new NukkitRandom(), woodType); StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); this.level.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { - return; + return false; } - for(Block block : ev.getBlockList()) { - this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); + + for (Block block : ev.getBlockList()) { + this.level.setBlock(block, block); } - return; + return true; } if (bigTree) { - this.level.setBlock(vector3, get(AIR), true, false); - this.level.setBlock(vector3.add(1, 0, 0), get(AIR), true, false); - this.level.setBlock(vector3.add(0, 0, 1), get(AIR), true, false); - this.level.setBlock(vector3.add(1, 0, 1), get(AIR), true, false); + this.level.setBlock(vector3, Block.get(AIR), true, false); + this.level.setBlock(vector3.add(1, 0, 0), Block.get(AIR), true, false); + this.level.setBlock(vector3.add(0, 0, 1), Block.get(AIR), true, false); + this.level.setBlock(vector3.add(1, 0, 1), Block.get(AIR), true, false); } else { - this.level.setBlock(this, get(AIR), true, false); + this.level.setBlock(this, Block.get(AIR), true, false); } ListChunkManager chunkManager = new ListChunkManager(this.level); boolean success = generator.generate(chunkManager, new NukkitRandom(), vector3); StructureGrowEvent ev = new StructureGrowEvent(this, chunkManager.getBlocks()); this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled() || !success) { if (bigTree) { this.level.setBlock(vector3, this, true, false); @@ -192,38 +188,35 @@ private void grow() { } else { this.level.setBlock(this, this, true, false); } - return; + return false; } - for(Block block : ev.getBlockList()) { - this.level.setBlockAt(block.getFloorX(), block.getFloorY(), block.getFloorZ(), block.getId(), block.getDamage()); + + for (Block block : ev.getBlockList()) { + this.level.setBlock(block, block); } + return true; } - private Vector2 findSaplings(int type) { - List> validVectorsList = new ArrayList<>(); - validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1))); - validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, -1), new Vector2(-1, -1))); - validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, -1), new Vector2(1, -1))); - validVectorsList.add(Arrays.asList(new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, 1), new Vector2(-1, 1))); - for(List validVectors : validVectorsList) { - boolean correct = true; - for(Vector2 vector2 : validVectors) { - if(!this.isSameType(this.add(vector2.x, 0, vector2.y), type)) - correct = false; + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.down().isTransparent()) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; } - if(correct) { - int lowestX = 0; - int lowestZ = 0; - for(Vector2 vector2 : validVectors) { - if(vector2.getFloorX() < lowestX) - lowestX = vector2.getFloorX(); - if(vector2.getFloorY() < lowestZ) - lowestZ = vector2.getFloorY(); + } else if (type == Level.BLOCK_UPDATE_RANDOM) { //Growth + if (Utils.rand(1, 7) == 1) { + if ((this.getDamage() & 0x08) == 0x08) { + growTreeHere(); + } else { + this.setDamage(this.getDamage() | 0x08); + this.getLevel().setBlock(this, this, true); + return Level.BLOCK_UPDATE_RANDOM; } - return new Vector2(lowestX, lowestZ); + } else { + return Level.BLOCK_UPDATE_RANDOM; } } - return null; + return 1; } public boolean isSameType(Vector3 pos, int type) { @@ -240,4 +233,45 @@ public Item toItem() { public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } + + @Override + public boolean breakWhenPushed() { + return true; + } + + private static final Vector2[][] VALID_SAPLINGS = new Vector2[4][4]; + static { + VALID_SAPLINGS[0] = new Vector2[]{new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1)}; + VALID_SAPLINGS[1] = new Vector2[]{new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, -1), new Vector2(-1, -1)}; + VALID_SAPLINGS[2] = new Vector2[]{new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, -1), new Vector2(1, -1)}; + VALID_SAPLINGS[3] = new Vector2[]{new Vector2(0, 0), new Vector2(-1, 0), new Vector2(0, 1), new Vector2(-1, 1)}; + } + + private Vector2 findSaplings(int type) { + for (Vector2[] validVectors : VALID_SAPLINGS) { + boolean found = true; + + for (Vector2 vector2 : validVectors) { + if (!this.isSameType(this.add(vector2.x, 0, vector2.y), type)) { + found = false; + } + } + + if (found) { + int lowestX = 0; + int lowestZ = 0; + for (Vector2 vector2 : validVectors) { + if (vector2.getFloorX() < lowestX) { + lowestX = vector2.getFloorX(); + } + if (vector2.getFloorY() < lowestZ) { + lowestZ = vector2.getFloorY(); + } + } + return new Vector2(lowestX, lowestZ); + } + } + + return null; + } } diff --git a/src/main/java/cn/nukkit/block/BlockScaffolding.java b/src/main/java/cn/nukkit/block/BlockScaffolding.java new file mode 100644 index 00000000000..b105894b73e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockScaffolding.java @@ -0,0 +1,256 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockScaffolding extends BlockFallableMeta { + + public BlockScaffolding() { + this(0); + } + + public BlockScaffolding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Scaffolding"; + } + + @Override + public int getId() { + return SCAFFOLDING; + } + + public int getStability() { + return this.getDamage() & 0x7; + } + + public void setStability(int stability) { + this.setDamage(stability & 0x7 | (this.getDamage() & 0x8)); + } + + public boolean getStabilityCheck() { + return (this.getDamage() & 0x8) > 0; + } + + public void setStabilityCheck(boolean check) { + if (check) { + this.setDamage(getDamage() | 0x8); + } else { + this.setDamage(getDamage() & 0x7); + } + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(SCAFFOLDING)); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block instanceof BlockLava) { + return false; + } + + Block down = this.down(); + if (target.getId() != SCAFFOLDING && down.getId() != SCAFFOLDING && down.getId() != AIR && !down.isSolid()) { + boolean scaffoldOnSide = false; + for (int i = 0; i < 4; i++) { + BlockFace sideFace = BlockFace.fromHorizontalIndex(i); + if (sideFace != face) { + Block side = this.getSide(sideFace); + if (side.getId() == SCAFFOLDING) { + scaffoldOnSide = true; + break; + } + } + } + + if (!scaffoldOnSide) { + return false; + } + } + + this.setDamage(0x8); + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + if (down.isSolid()) { + if (this.getDamage() != 0) { + this.setDamage(0); + this.getLevel().setBlock(this, this, true, true); + } + return type; + } + + int stability = 7; + for (BlockFace face : BlockFace.values()) { + if (face == BlockFace.UP) { + continue; + } + + Block otherBlock = this.getSide(face); + if (otherBlock.getId() == SCAFFOLDING) { + BlockScaffolding other = (BlockScaffolding) otherBlock; + int otherStability = other.getStability(); + if (otherStability < stability) { + if (face == BlockFace.DOWN) { + stability = otherStability; + } else { + stability = otherStability + 1; + } + } + } + } + + if (stability >= 7) { + if (this.getStabilityCheck()) { + super.onUpdate(type); + } else { + this.getLevel().scheduleUpdate(this, 0); + } + return type; + } + + this.setStabilityCheck(false); + this.setStability(stability); + this.getLevel().setBlock(this, this, true, true); + return type; + } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.getLevel().useBreakOn(this); + return type; + } + + return 0; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 0; + } + + @Override + public int getBurnChance() { + return 60; + } + + @Override + public int getBurnAbility() { + return 60; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean canBeClimbed() { + return true; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public void onEntityCollide(Entity entity) { + entity.resetFallDistance(); + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public double getMinY() { + return this.y + 0.875; + } + + @Override + public boolean canPassThrough() { + return false; + } + + @Override + public boolean isTransparent() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.TRANSPARENT_BLOCK_COLOR; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getBlockUnsafe() instanceof BlockScaffolding) { + int top = (int) y; + + for (int i = 1; i <= 16; i++) { + int id = this.level.getBlockIdAt(this.getFloorX(), this.getFloorY() - i, this.getFloorZ()); + if (id != SCAFFOLDING) { + break; + } + } + + for (int i = 1; i <= 16; i++) { + int id = this.level.getBlockIdAt(this.getFloorX(), this.getFloorY() + i, this.getFloorZ()); + if (id == SCAFFOLDING) { + top++; + } else { + break; + } + } + + boolean success = false; + + Block block = this.up(top - (int) y + 1); + if (block.getId() == BlockID.AIR) { + success = this.level.setBlock(block, Block.get(SCAFFOLDING)); + } + + if (success) { + if (player != null && !player.isCreative()) { + item.count--; + } + } + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSeaLantern.java b/src/main/java/cn/nukkit/block/BlockSeaLantern.java index b1854ccac17..463f7ad8141 100644 --- a/src/main/java/cn/nukkit/block/BlockSeaLantern.java +++ b/src/main/java/cn/nukkit/block/BlockSeaLantern.java @@ -1,15 +1,11 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemPrismarineCrystals; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; - +import cn.nukkit.utils.Utils; public class BlockSeaLantern extends BlockTransparent { - public BlockSeaLantern() { - } @Override public String getName() { @@ -38,8 +34,11 @@ public int getLightLevel() { @Override public Item[] getDrops(Item item) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[]{ - new ItemPrismarineCrystals(0, ThreadLocalRandom.current().nextInt(2, 4)) + Item.get(Item.PRISMARINE_CRYSTALS, 0, Utils.random.nextInt(2, 4)) }; } @@ -47,7 +46,7 @@ public Item[] getDrops(Item item) { public BlockColor getColor() { return BlockColor.QUARTZ_BLOCK_COLOR; } - + @Override public boolean canSilkTouch() { return true; diff --git a/src/main/java/cn/nukkit/block/BlockSeaPickle.java b/src/main/java/cn/nukkit/block/BlockSeaPickle.java new file mode 100644 index 00000000000..47630a2e9eb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSeaPickle.java @@ -0,0 +1,186 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.event.block.BlockFadeEvent; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.event.block.BlockSpreadEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemDye; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockSeaPickle extends BlockTransparentMeta { + + public BlockSeaPickle() { + this(0); + } + + public BlockSeaPickle(int meta) { + super(meta); + } + + @Override + public int getId() { + return SEA_PICKLE; + } + + public double getHardness() { + return 0; + } + + public double getResistance() { + return 0; + } + + @Override + public String getName() { + return "Sea Pickle"; + } + + public boolean isDead() { + return (getDamage() & 0x4) == 0x4; + } + + public void setDead(boolean dead) { + if (dead) { + this.setDamage(getDamage() | 0x4); + } else { + this.setDamage(getDamage() ^ 0x4); + } + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + if (!down.isSolid() || down.getId() == MAGMA) { + this.getLevel().useBreakOn(this); + return type; + } + + Block layer1 = getLevelBlock(Block.LAYER_WATERLOGGED); + if (layer1 instanceof BlockWater) { + if (this.isDead() || layer1.getDamage() == 0 && layer1.getDamage() == 8) { + this.getLevel().useBreakOn(this); + return type; + } + } else if (!this.isDead()) { + BlockFadeEvent event = new BlockFadeEvent(this, Block.get(SEA_PICKLE, this.getDamage() ^ 0x4)); + if (!event.isCancelled()) { + this.getLevel().setBlock(this, event.getNewState(), true, true); + } + } + + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (target.getId() == SEA_PICKLE && (target.getDamage() & 0b11) < 3) { + target.setDamage(target.getDamage() + 1); + this.getLevel().setBlock(target, target, true, true); + return true; + } + if (!this.down().isTransparent()) { + Block layer1 = block.getLevelBlock(BlockLayer.WATERLOGGED); + if (layer1 instanceof BlockWater) { + if (layer1.getDamage() != 0 && layer1.getDamage() != 8) { + return false; + } + + if (layer1.getDamage() == 8) { + this.getLevel().setBlock(block, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + } + } else { + this.setDead(true); + } + this.getLevel().setBlock(block, this, true, true); + return true; + } + return false; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() != Item.DYE || item.getDamage() != ItemDye.BONE_MEAL) { + return super.onActivate(item, player); + } + + BlockSeaPickle block = (BlockSeaPickle) this.clone(); + if (!block.isDead()) { + block.setDamage(3); + } + + BlockGrowEvent blockGrowEvent = new BlockGrowEvent(this, block); + this.level.getServer().getPluginManager().callEvent(blockGrowEvent); + + if (blockGrowEvent.isCancelled()) { + return false; + } + + this.getLevel().setBlock(this, blockGrowEvent.getNewState(), false, true); + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && !player.isCreative()) { + item.count--; + } + + ThreadLocalRandom random = ThreadLocalRandom.current(); + Block[] blocksAround = this.getLevel().getCollisionBlocks(new SimpleAxisAlignedBB(x - 2, y - 2, z - 2, x + 3, y, z + 3)); + for (Block blockNearby : blocksAround) { + if (blockNearby.getId() == CORAL_BLOCK) { + Block up = blockNearby.up(); + if (up instanceof BlockWater && (up.getDamage() == 0 || up.getDamage() == 8) && random.nextInt(6) == 0) { + BlockSpreadEvent blockSpreadEvent = new BlockSpreadEvent(up, this, Block.get(SEA_PICKLE, random.nextInt(3))); + if (!blockSpreadEvent.isCancelled()) { + this.getLevel().setBlock(up, BlockLayer.WATERLOGGED, Block.get(WATER), true, false); + this.getLevel().setBlock(up, blockSpreadEvent.getNewState(), true, true); + } + } + } + } + return true; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + + @Override + public Item toItem() { + return new ItemBlock(Block.get(SEA_PICKLE), 0); + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{new ItemBlock(Block.get(SEA_PICKLE), 0, (this.getDamage() & 0x3) + 1)}; + } + + @Override + public int getLightLevel() { + if (this.isDead()) { + return 0; + } else { + return (this.getDamage() + 1) * 3; + } + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSeagrass.java b/src/main/java/cn/nukkit/block/BlockSeagrass.java new file mode 100644 index 00000000000..ed7781fc642 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSeagrass.java @@ -0,0 +1,178 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.*; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.anvil.Anvil; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; +import cn.nukkit.utils.BlockColor; + +public class BlockSeagrass extends BlockFlowable { + + public BlockSeagrass() { + this(0); + } + + public BlockSeagrass(int meta) { + super(meta % 3); + } + + @Override + public String getName() { + return "Seagrass"; + } + + @Override + public int getId() { + return SEAGRASS; + } + + public int getToolType() { + return ItemTool.SHEARS; + } + + public double getHardness() { + return 0; + } + + public double getResistance() { + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (level != null && level.getProvider() instanceof Anvil) { + if (!(block instanceof BlockWater && block.getDamage() == 0) || this.down().isTransparent()) { + return false; + } + return this.getLevel().setBlock(this, this, true, true); + } + + + Block down = this.down(); + Block layer1Block = block.getLevelBlock(BlockLayer.WATERLOGGED); + int waterDamage; + if (down.isSolid() && down.getId() != MAGMA && down.getId() != SOUL_SAND && + (layer1Block instanceof BlockWater && ((waterDamage = (block.getDamage())) == 0 || waterDamage == 8)) + ) { + if (waterDamage == 8) { + this.getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, Block.LAYER_WATERLOGGED, Block.get(Block.STILL_WATER), true, true); + } + this.getLevel().setBlock(this, BlockLayer.NORMAL, this, true, true); + return true; + } + return false; + } + + @Override + public int onUpdate(int type) { + if (level != null && level.getProvider() instanceof Anvil) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block down = this.down(); + if (down.isTransparent() && down.getId() != SEAGRASS) { + this.getLevel().useBreakOn(this); + } + } + return type; + } + + + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block blockLayer1 = this.getLevelBlock(BlockLayer.WATERLOGGED); + int damage; + if (!(blockLayer1 instanceof BlockIceFrosted) + && (!(blockLayer1 instanceof BlockWater) || ((damage = blockLayer1.getDamage()) != 0 && damage != 8))) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + + Block down = this.down(); + damage = this.getDamage(); + if (damage == 0 || damage == 2) { + if (!down.isSolid() || down.getId() == MAGMA || down.getId() == SOUL_SAND) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + + if (damage == 2) { + Block up = up(); + if (up.getId() != getId() || up.getDamage() != 1) { + this.getLevel().useBreakOn(this); + } + } + } else if (down.getId() != getId() || down.getDamage() != 2) { + this.getLevel().useBreakOn(this); + } + + return Level.BLOCK_UPDATE_NORMAL; + } + + return 0; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isShears()) { + return new Item[] { toItem() }; + } else { + return new Item[0]; + } + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(SEAGRASS), 0, 1); + } + + @Override + public void setDamage(int meta) { + super.setDamage(meta % 3); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return level == null || !(level.getProvider() instanceof Anvil); + } + + @Override + public BlockColor getColor() { + return BlockColor.WATER_BLOCK_COLOR; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + + @Override + public boolean canBeActivated() { + return this.getDamage() == 0; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (this.getDamage() == 0 && item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL && up() instanceof BlockWater) { + Vector3 up = this.getSideVec(BlockFace.UP); + if (this.level.setBlock(up, Block.get(SEAGRASS, 1), true, true)) { + this.level.setBlock(this, Block.get(SEAGRASS, 2), true, true); + } + + if (player != null && !player.isCreative()) { + item.count--; + } + + this.level.addParticle(new BoneMealParticle(this)); + + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockShroomlight.java b/src/main/java/cn/nukkit/block/BlockShroomlight.java new file mode 100644 index 00000000000..6934a70a81a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockShroomlight.java @@ -0,0 +1,46 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockShroomlight extends BlockSolid { + + public BlockShroomlight() { + // Does nothing + } + + @Override + public int getId() { + return SHROOMLIGHT; + } + + @Override + public String getName() { + return "Shroomlight"; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + + @Override + public double getResistance() { + return 1; + } + + @Override + public double getHardness() { + return 1; + } + + @Override + public int getLightLevel() { + return 15; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockShulkerBox.java b/src/main/java/cn/nukkit/block/BlockShulkerBox.java index 046cda38896..09d76f8c362 100644 --- a/src/main/java/cn/nukkit/block/BlockShulkerBox.java +++ b/src/main/java/cn/nukkit/block/BlockShulkerBox.java @@ -1,22 +1,32 @@ package cn.nukkit.block; +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityShulkerBox; +import cn.nukkit.inventory.ShulkerBoxInventory; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.DyeColor; -/** - * Created by PetteriM1 - */ -public class BlockShulkerBox extends BlockUndyedShulkerBox { - - private int meta; +public class BlockShulkerBox extends BlockTransparentMeta { public BlockShulkerBox() { this(0); } public BlockShulkerBox(int meta) { - super(); - this.meta = meta; + super(meta); + } + + @Override + public boolean canBeActivated() { + return true; } @Override @@ -29,6 +39,108 @@ public String getName() { return this.getDyeColor().getName() + " Shulker Box"; } + @Override + public double getHardness() { + return 0.6; + } + + @Override + public double getResistance() { + return 2; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public Item toItem() { + ItemBlock item = new ItemBlock(this, this.getDamage(), 1); + + BlockEntity be = this.getLevel().getBlockEntity(this); + + if (be instanceof BlockEntityShulkerBox) { + BlockEntityShulkerBox t = (BlockEntityShulkerBox) be; + + ShulkerBoxInventory i = t.getRealInventory(); + + if (!i.slots.isEmpty()) { + + CompoundTag nbt = item.getNamedTag(); + if (nbt == null) + nbt = new CompoundTag(""); + + ListTag items = new ListTag<>(); + + for (int it = 0; it < i.getSize(); it++) { + if (i.getItem(it).getId() != Item.AIR) { + CompoundTag d = NBTIO.putItemHelper(i.getItem(it), it); + items.add(d); + } + } + + nbt.put("Items", items); + + item.setCompoundTag(nbt); + } + + if (t.hasName()) { + item.setCustomName(t.getName()); + } + } + + return item; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.getLevel().setBlock(this, this, true, true); + + CompoundTag nbt = BlockEntity.getDefaultCompound(this, BlockEntity.SHULKER_BOX) + .putByte("facing", face.getIndex()); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + CompoundTag t = item.getNamedTag(); + + if (t != null) { + if (t.contains("Items")) { + nbt.putList(t.getList("Items")); + } + } + + BlockEntity.createBlockEntity(BlockEntity.SHULKER_BOX, this.getChunk(), nbt); + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null) { + BlockEntity t = this.getLevel().getBlockEntity(this); + if (!(t instanceof BlockEntityShulkerBox)) { + return false; + } + + BlockEntityShulkerBox box = (BlockEntityShulkerBox) t; + Block block = this.getSide(BlockFace.fromIndex(box.namedTag.getByte("facing"))); + if (!(block instanceof BlockAir) && !(block instanceof BlockLiquid) && !(block instanceof BlockFlowable)) { + return true; + } + + player.addWindow(box.getInventory()); + } + + return true; + } + @Override public BlockColor getColor() { return this.getDyeColor().getColor(); @@ -39,17 +151,17 @@ public DyeColor getDyeColor() { } @Override - public int getFullId() { - return (this.getId() << 4) + this.getDamage(); + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; } @Override - public final int getDamage() { - return this.meta; + public boolean breakWhenPushed() { + return true; } @Override - public void setDamage(int meta) { - this.meta = meta; + public boolean alwaysDropsOnExplosion() { + return true; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockSignPost.java b/src/main/java/cn/nukkit/block/BlockSignPost.java index 496cb06b285..e0641e011e9 100644 --- a/src/main/java/cn/nukkit/block/BlockSignPost.java +++ b/src/main/java/cn/nukkit/block/BlockSignPost.java @@ -7,7 +7,6 @@ import cn.nukkit.event.block.SignGlowEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemDye; -import cn.nukkit.item.ItemSign; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; @@ -63,6 +62,14 @@ public AxisAlignedBB getBoundingBox() { return null; } + protected int getPostId() { + return SIGN_POST; + } + + protected int getWallId() { + return WALL_SIGN; + } + @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (face != BlockFace.DOWN) { @@ -78,10 +85,13 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl if (face == BlockFace.UP) { setDamage((int) Math.floor(((player.yaw + 180) * 16 / 360) + 0.5) & 0x0f); - getLevel().setBlock(block, Block.get(BlockID.SIGN_POST, getDamage()), true); + getLevel().setBlock(block, Block.get(getPostId(), getDamage()), true); + } else if (target.canBeReplaced()) { + setDamage((int) Math.floor(((player.yaw + 180) * 16 / 360) + 0.5) & 0x0f); + getLevel().setBlock(target, Block.get(getPostId(), getDamage()), true); } else { setDamage(face.getIndex()); - getLevel().setBlock(block, Block.get(BlockID.WALL_SIGN, getDamage()), true); + getLevel().setBlock(block, Block.get(getWallId(), getDamage()), true); } if (player != null) { @@ -94,7 +104,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntitySign sign = (BlockEntitySign) BlockEntity.createBlockEntity(BlockEntity.SIGN, getLevel().getChunk((int) block.x >> 4, (int) block.z >> 4), nbt); + BlockEntity.createBlockEntity(BlockEntity.SIGN, this.getChunk(), nbt); if (player != null) { OpenSignPacket pk = new OpenSignPacket(); @@ -102,8 +112,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl pk.frontSide = true; player.dataPacket(pk); } - - return sign != null; + return true; } return false; @@ -124,7 +133,7 @@ public int onUpdate(int type) { @Override public Item toItem() { - return new ItemSign(); + return Item.get(Item.SIGN); } @Override @@ -150,7 +159,7 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { if (item.getId() == Item.DYE) { - BlockEntity blockEntity = this.level.getBlockEntity(this); + BlockEntity blockEntity = this.level.getBlockEntityIfLoaded(player == null ? null : player.chunk, this); if (!(blockEntity instanceof BlockEntitySign)) { return false; } @@ -169,9 +178,9 @@ public boolean onActivate(Item item, Player player) { SignGlowEvent event = new SignGlowEvent(this, player, glow); this.level.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { - if (player != null) { + /*if (player != null) { sign.spawnTo(player); - } + }*/ return false; } @@ -189,9 +198,9 @@ public boolean onActivate(Item item, Player player) { BlockColor color = DyeColor.getByDyeData(meta).getSignColor(); if (color.equals(sign.getColor())) { - if (player != null) { + /*if (player != null) { sign.spawnTo(player); - } + }*/ return false; } @@ -217,4 +226,9 @@ public boolean onActivate(Item item, Player player) { } return false; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSkull.java b/src/main/java/cn/nukkit/block/BlockSkull.java index c11158ee98b..9672c1bab7e 100644 --- a/src/main/java/cn/nukkit/block/BlockSkull.java +++ b/src/main/java/cn/nukkit/block/BlockSkull.java @@ -15,7 +15,7 @@ import cn.nukkit.utils.Faceable; /** - * author: Justin + * @author Justin */ public class BlockSkull extends BlockTransparentMeta implements Faceable { @@ -73,7 +73,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl default: return false; } - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); CompoundTag nbt = new CompoundTag() .putString("id", BlockEntity.SKULL) @@ -90,9 +90,6 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl BlockEntitySkull blockEntity = (BlockEntitySkull) BlockEntity.createBlockEntity(BlockEntity.SKULL, this.getChunk(), nbt); blockEntity.spawnToAll(); - - // TODO: 2016/2/3 SPAWN WITHER - return true; } @@ -123,7 +120,6 @@ public int getToolType() { public BlockColor getColor() { return BlockColor.AIR_BLOCK_COLOR; } - @Override public BlockFace getBlockFace() { return BlockFace.fromIndex(this.getDamage() & 0x7); @@ -144,4 +140,24 @@ protected AxisAlignedBB recalculateBoundingBox() { } return bb; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean alwaysDropsOnExplosion() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSlab.java b/src/main/java/cn/nukkit/block/BlockSlab.java index 64cfe2f1a9d..493eb093a07 100644 --- a/src/main/java/cn/nukkit/block/BlockSlab.java +++ b/src/main/java/cn/nukkit/block/BlockSlab.java @@ -2,11 +2,14 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; +import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockSlab extends BlockTransparentMeta { @@ -19,13 +22,35 @@ public BlockSlab(int meta, int doubleSlab) { } @Override - public double getMinY() { - return ((this.getDamage() & 0x08) > 0) ? this.y + 0.5 : this.y; + protected AxisAlignedBB recalculateBoundingBox() { + if (this.hasTopBit()) { + return new SimpleAxisAlignedBB( + this.x, + this.y + 0.5, + this.z, + this.x + 1, + this.y + 1, + this.z + 1 + ); + } else { + return new SimpleAxisAlignedBB( + this.x, + this.y, + this.z, + this.x + 1, + this.y + 0.5, + this.z + 1 + ); + } + } + + public String getSlabName() { + return ""; } @Override - public double getMaxY() { - return ((this.getDamage() & 0x08) > 0) ? this.y + 1 : this.y + 0.5; + public String getName() { + return (this.hasTopBit()? "Upper " : "") + this.getSlabName() + " Slab"; } @Override @@ -42,30 +67,30 @@ public double getResistance() { public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { this.setDamage(this.getDamage() & 0x07); if (face == BlockFace.DOWN) { - if (target instanceof BlockSlab && (target.getDamage() & 0x08) == 0x08 && (target.getDamage() & 0x07) == (this.getDamage() & 0x07)) { + if (target instanceof BlockSlab && ((BlockSlab) target).doubleSlab == this.doubleSlab && ((BlockSlab) target).hasTopBit() && (target.getDamage() & 0x07) == (this.getDamage() & 0x07)) { this.getLevel().setBlock(target, Block.get(doubleSlab, this.getDamage()), true); return true; - } else if (block instanceof BlockSlab && (block.getDamage() & 0x07) == (this.getDamage() & 0x07)) { + } else if (block instanceof BlockSlab && ((BlockSlab) block).doubleSlab == this.doubleSlab && (block.getDamage() & 0x07) == (this.getDamage() & 0x07)) { this.getLevel().setBlock(block, Block.get(doubleSlab, this.getDamage()), true); return true; } else { - this.setDamage(this.getDamage() | 0x08); + this.setTopBit(true); } } else if (face == BlockFace.UP) { - if (target instanceof BlockSlab && (target.getDamage() & 0x08) == 0 && (target.getDamage() & 0x07) == (this.getDamage() & 0x07)) { + if (target instanceof BlockSlab && ((BlockSlab) target).doubleSlab == this.doubleSlab && !((BlockSlab) target).hasTopBit() && (target.getDamage() & 0x07) == (this.getDamage() & 0x07)) { this.getLevel().setBlock(target, Block.get(doubleSlab, this.getDamage()), true); return true; - } else if (block instanceof BlockSlab && (block.getDamage() & 0x07) == (this.getDamage() & 0x07)) { + } else if (block instanceof BlockSlab && ((BlockSlab) block).doubleSlab == this.doubleSlab && (block.getDamage() & 0x07) == (this.getDamage() & 0x07)) { this.getLevel().setBlock(block, Block.get(doubleSlab, this.getDamage()), true); return true; } //TODO: check for collision } else { - if (block instanceof BlockSlab) { + if (block instanceof BlockSlab && ((BlockSlab) block).doubleSlab == this.doubleSlab) { if ((block.getDamage() & 0x07) == (this.getDamage() & 0x07)) { this.getLevel().setBlock(block, Block.get(doubleSlab, this.getDamage()), true); @@ -75,16 +100,45 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } else { if (fy > 0.5) { - this.setDamage(this.getDamage() | 0x08); + this.setTopBit(true); } } } - if (block instanceof BlockSlab && (target.getDamage() & 0x07) != (this.getDamage() & 0x07)) { + if (block instanceof BlockSlab && ((BlockSlab) block).doubleSlab == this.doubleSlab && (target.getDamage() & 0x07) != (this.getDamage() & 0x07)) { return false; } - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } -} \ No newline at end of file + + @Override + public boolean isTransparent() { + //HACK: Fix unable to place many blocks on slabs + return !this.hasTopBit(); + } + + public boolean hasTopBit() { + return (this.getDamage() & 0x08) > 0; + } + + public void setTopBit(boolean topBit) { + if (topBit) { + this.setDamage(this.getDamage() | 0x08); + } else { + this.setDamage(this.getDamage() & 0x07); + } + } + + @Override + public Item toItem() { + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(), damage), damage); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabBlackstone.java b/src/main/java/cn/nukkit/block/BlockSlabBlackstone.java new file mode 100644 index 00000000000..ab512d630b6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabBlackstone.java @@ -0,0 +1,70 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabBlackstone extends BlockSlab { + + public BlockSlabBlackstone() { + this(0); + } + + public BlockSlabBlackstone(int meta) { + super(meta, BLACKSTONE_DOUBLE_SLAB); + } + + @Override + public int getId() { + return BLACKSTONE_SLAB; + } + + @Override + public boolean hasTopBit() { + return (this.getDamage() & 0x01) == 1; + } + + @Override + public void setTopBit(boolean topBit) { + this.setDamage(topBit ? 1 : 0); + } + + @Override + public String getName() { + return "Blackstone Slab"; + } + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{this.toItem()}; + } else { + return new Item[0]; + } + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockSlabBlackstonePolished.java new file mode 100644 index 00000000000..260b884ecdb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabBlackstonePolished.java @@ -0,0 +1,74 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabBlackstonePolished extends BlockSlab { + + public BlockSlabBlackstonePolished() { + this(0); + } + + public BlockSlabBlackstonePolished(int meta) { + super(meta, POLISHED_BLACKSTONE_DOUBLE_SLAB); + } + + protected BlockSlabBlackstonePolished(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_SLAB; + } + + @Override + public boolean hasTopBit() { + return (this.getDamage() & 0x01) == 1; + } + + @Override + public void setTopBit(boolean topBit) { + this.setDamage(topBit ? 1 : 0); + } + + @Override + public String getSlabName() { + return "Polished Blackstone"; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ this.toItem() }; + } + return new Item[0]; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public double getResistance() { + return 6.0; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabBrickBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockSlabBrickBlackstonePolished.java new file mode 100644 index 00000000000..8cd18484070 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabBrickBlackstonePolished.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockSlabBrickBlackstonePolished extends BlockSlabBlackstonePolished { + + public BlockSlabBrickBlackstonePolished() { + this(0); + } + + public BlockSlabBrickBlackstonePolished(int meta) { + super(meta, POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB); + } + + protected BlockSlabBrickBlackstonePolished(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BRICK_SLAB; + } + + @Override + public String getSlabName() { + return "Polished Blackstone Brick"; + } + +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabBrickDeepslate.java b/src/main/java/cn/nukkit/block/BlockSlabBrickDeepslate.java new file mode 100644 index 00000000000..c7283cad770 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabBrickDeepslate.java @@ -0,0 +1,56 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabBrickDeepslate extends BlockSlab { + + public BlockSlabBrickDeepslate() { + this(0); + } + + public BlockSlabBrickDeepslate(int meta) { + super(meta, DEEPSLATE_BRICK_SLAB); + } + + @Override + public int getId() { + return DEEPSLATE_BRICK_SLAB; + } + + @Override + public String getSlabName() { + return "Deepslate Brick"; + } + + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperBase.java b/src/main/java/cn/nukkit/block/BlockSlabCopperBase.java new file mode 100644 index 00000000000..d466b8c7ad6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperBase.java @@ -0,0 +1,91 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class BlockSlabCopperBase extends BlockSlab implements Waxable, Oxidizable { + + public BlockSlabCopperBase(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public boolean onActivate(@Nonnull Item item, @Nullable Player player) { + return Waxable.super.onActivate(item, player) + || Oxidizable.super.onActivate(item, player); + } + + @Override + public int onUpdate(int type) { + return Oxidizable.super.onUpdate(type); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Block getStateWithOxidizationLevel(OxidizationLevel oxidizationLevel) { + return Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel), this.getDamage()); + } + + @Override + public boolean setOxidizationLevel(OxidizationLevel oxidizationLevel) { + if (this.getOxidizationLevel().equals(oxidizationLevel)) { + return true; + } + return this.level.setBlock(this, Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel))); + } + + @Override + public boolean setWaxed(boolean waxed) { + if (this.isWaxed() == waxed) { + return true; + } + return this.level.setBlock(this, Block.get(getCopperId(waxed, getOxidizationLevel()))); + } + + @Override + public boolean isWaxed() { + return false; + } + + protected abstract int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel); +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCut.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCut.java new file mode 100644 index 00000000000..55373beb66c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCut.java @@ -0,0 +1,62 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; + +public class BlockSlabCopperCut extends BlockSlabCopperBase { + + public BlockSlabCopperCut() { + this(0); + } + + public BlockSlabCopperCut(int meta) { + super(meta, DOUBLE_CUT_COPPER_SLAB); + } + + protected BlockSlabCopperCut(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return CUT_COPPER_SLAB; + } + + @Override + public String getSlabName() { + String name = ""; + if (this.isWaxed()) { + name += "Waxed "; + } + + OxidizationLevel oxidizationLevel = this.getOxidizationLevel(); + if (oxidizationLevel != OxidizationLevel.UNAFFECTED) { + String oxidationName = oxidizationLevel.name(); + name += oxidationName.charAt(0) + oxidationName.substring(1).toLowerCase(); + } + return name + " Cut Copper"; + } + + @Override + protected int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel) { + if (oxidizationLevel == null) { + return getId(); + } + switch (oxidizationLevel) { + case UNAFFECTED: + return waxed ? WAXED_CUT_COPPER_SLAB : CUT_COPPER_SLAB; + case EXPOSED: + return waxed ? WAXED_EXPOSED_CUT_COPPER_SLAB : EXPOSED_CUT_COPPER_SLAB; + case WEATHERED: + return waxed ? WAXED_WEATHERED_CUT_COPPER_SLAB : WEATHERED_CUT_COPPER_SLAB; + case OXIDIZED: + return waxed ? WAXED_OXIDIZED_CUT_COPPER_SLAB : OXIDIZED_CUT_COPPER_SLAB; + default: + return getId(); + } + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.UNAFFECTED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposed.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposed.java new file mode 100644 index 00000000000..8f2580568fa --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposed.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabCopperCutExposed extends BlockSlabCopperCut { + + public BlockSlabCopperCutExposed() { + this(0); + } + + public BlockSlabCopperCutExposed(int meta) { + super(meta, EXPOSED_DOUBLE_CUT_COPPER_SLAB); + } + + protected BlockSlabCopperCutExposed(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return EXPOSED_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.EXPOSED; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_TERRACOTA_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposedWaxed.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposedWaxed.java new file mode 100644 index 00000000000..1cbc80b757d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutExposedWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockSlabCopperCutExposedWaxed extends BlockSlabCopperCutExposed { + + public BlockSlabCopperCutExposedWaxed() { + this(0); + } + + public BlockSlabCopperCutExposedWaxed(int meta) { + super(meta, WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB); + } + + @Override + public int getId() { + return WAXED_EXPOSED_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidized.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidized.java new file mode 100644 index 00000000000..5cc2a84d04c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidized.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabCopperCutOxidized extends BlockSlabCopperCut { + + public BlockSlabCopperCutOxidized() { + this(0); + } + + public BlockSlabCopperCutOxidized(int meta) { + super(meta, OXIDIZED_DOUBLE_CUT_COPPER_SLAB); + } + + protected BlockSlabCopperCutOxidized(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return OXIDIZED_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.OXIDIZED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidizedWaxed.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidizedWaxed.java new file mode 100644 index 00000000000..d5c425c7247 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutOxidizedWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockSlabCopperCutOxidizedWaxed extends BlockSlabCopperCutOxidized { + + public BlockSlabCopperCutOxidizedWaxed() { + this(0); + } + + public BlockSlabCopperCutOxidizedWaxed(int meta) { + super(meta, WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB); + } + + @Override + public int getId() { + return WAXED_OXIDIZED_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutWaxed.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWaxed.java new file mode 100644 index 00000000000..2d8a8ac1495 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockSlabCopperCutWaxed extends BlockSlabCopperCut { + + public BlockSlabCopperCutWaxed() { + this(0); + } + + public BlockSlabCopperCutWaxed(int meta) { + super(meta, WAXED_DOUBLE_CUT_COPPER_SLAB); + } + + @Override + public int getId() { + return WAXED_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeathered.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeathered.java new file mode 100644 index 00000000000..7edf336bd05 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeathered.java @@ -0,0 +1,34 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabCopperCutWeathered extends BlockSlabCopperCut { + + public BlockSlabCopperCutWeathered() { + this(0); + } + + public BlockSlabCopperCutWeathered(int meta) { + super(meta, WEATHERED_DOUBLE_CUT_COPPER_SLAB); + } + + protected BlockSlabCopperCutWeathered(int meta, int doubleSlab) { + super(meta, doubleSlab); + } + + @Override + public int getId() { + return WEATHERED_CUT_COPPER_SLAB; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.WEATHERED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeatheredWaxed.java b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeatheredWaxed.java new file mode 100644 index 00000000000..17eded2a46d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCopperCutWeatheredWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockSlabCopperCutWeatheredWaxed extends BlockSlabCopperCutWeathered { + + public BlockSlabCopperCutWeatheredWaxed() { + this(0); + } + + public BlockSlabCopperCutWeatheredWaxed(int meta) { + super(meta, WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB); + } + + @Override + public int getId() { + return WAXED_WEATHERED_CUT_COPPER_SLAB; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabCrimson.java b/src/main/java/cn/nukkit/block/BlockSlabCrimson.java new file mode 100644 index 00000000000..2d8720eaa97 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabCrimson.java @@ -0,0 +1,64 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabCrimson extends BlockSlab { + + public BlockSlabCrimson() { + this(0); + } + + public BlockSlabCrimson(int meta) { + super(meta, CRIMSON_DOUBLE_SLAB); + } + + @Override + public String getSlabName() { + return "Crimson"; + } + + @Override + public int getId() { + return CRIMSON_SLAB; + } + + @Override + public boolean hasTopBit() { + return (this.getDamage() & 0x01) == 1; + } + + @Override + public void setTopBit(boolean topBit) { + this.setDamage(topBit ? 1 : 0); + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{ + this.toItem() + }; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabDeepslateCobbled.java b/src/main/java/cn/nukkit/block/BlockSlabDeepslateCobbled.java new file mode 100644 index 00000000000..ddd0ca85453 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabDeepslateCobbled.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockSlabDeepslateCobbled extends BlockSlab { + + public BlockSlabDeepslateCobbled() { + this(0); + } + + public BlockSlabDeepslateCobbled(int meta) { + super(meta, COBBLED_DEEPSLATE_DOUBLE_SLAB); + } + + @Override + public int getId() { + return COBBLED_DEEPSLATE_SLAB; + } + + @Override + public String getSlabName() { + return "Cobbled Deepslate"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabDeepslatePolished.java b/src/main/java/cn/nukkit/block/BlockSlabDeepslatePolished.java new file mode 100644 index 00000000000..ea218f10b87 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabDeepslatePolished.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabDeepslatePolished extends BlockSlab { + + public BlockSlabDeepslatePolished() { + this(0); + } + + public BlockSlabDeepslatePolished(int meta) { + super(meta, POLISHED_DEEPSLATE_SLAB); + } + + @Override + public int getId() { + return POLISHED_DEEPSLATE_SLAB; + } + + @Override + public String getSlabName() { + return "Polished Deepslate"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabRedSandstone.java b/src/main/java/cn/nukkit/block/BlockSlabRedSandstone.java index 3eb447dfdd7..2231e482498 100644 --- a/src/main/java/cn/nukkit/block/BlockSlabRedSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockSlabRedSandstone.java @@ -2,7 +2,6 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; -import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; /** @@ -11,10 +10,10 @@ public class BlockSlabRedSandstone extends BlockSlab { public static final int RED_SANDSTONE = 0; - public static final int PURPUR = 1; //WHY THIS + public static final int PURPUR = 1; public BlockSlabRedSandstone() { - this(0); + this(RED_SANDSTONE); } public BlockSlabRedSandstone(int meta) { @@ -28,7 +27,7 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Red Sandstone", "Purpur", "", @@ -44,7 +43,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -55,7 +54,8 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, this.getDamage() & 0x07); + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(),damage ), damage); } @Override @@ -65,6 +65,25 @@ public boolean canHarvestWithHand() { @Override public BlockColor getColor() { - return BlockColor.ORANGE_BLOCK_COLOR; + int damage = this.getDamage() & 0x07; + switch (damage) { + case 0: + return BlockColor.ORANGE_BLOCK_COLOR; + case 1: + return BlockColor.PURPLE_BLOCK_COLOR; + case 2: + return BlockColor.CYAN_BLOCK_COLOR; + case 3: + return BlockColor.DIAMOND_BLOCK_COLOR; + case 4: + return BlockColor.CYAN_BLOCK_COLOR; + case 5: + return BlockColor.STONE_BLOCK_COLOR; + case 6: + return BlockColor.SAND_BLOCK_COLOR; + case 7: + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + return BlockColor.STONE_BLOCK_COLOR; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabStone.java b/src/main/java/cn/nukkit/block/BlockSlabStone.java index 82cde51a5e2..5621b1190dd 100644 --- a/src/main/java/cn/nukkit/block/BlockSlabStone.java +++ b/src/main/java/cn/nukkit/block/BlockSlabStone.java @@ -9,6 +9,7 @@ * Created by CreeperFace on 26. 11. 2016. */ public class BlockSlabStone extends BlockSlab { + public static final int STONE = 0; public static final int SANDSTONE = 1; public static final int WOODEN = 2; @@ -23,7 +24,11 @@ public BlockSlabStone() { } public BlockSlabStone(int meta) { - super(meta, DOUBLE_STONE_SLAB); + this(meta, DOUBLE_STONE_SLAB); + } + + public BlockSlabStone(int meta, int doubleSlab) { + super(meta, doubleSlab); } @Override @@ -33,7 +38,7 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Stone", "Sandstone", "Wooden", @@ -49,7 +54,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -60,7 +65,8 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, this.getDamage() & 0x07); + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(),damage ), damage); } @Override @@ -92,4 +98,4 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabStone3.java b/src/main/java/cn/nukkit/block/BlockSlabStone3.java new file mode 100644 index 00000000000..193d6a50b2f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabStone3.java @@ -0,0 +1,72 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabStone3 extends BlockSlabStone { + + public static final int END_STONE_BRICKS = 0; + public static final int SMOOTH_RED_SANDSTONE = 1; + public static final int POLISHED_ANDESITE = 2; + public static final int ANDESITE = 3; + public static final int DIORITE = 4; + public static final int POLISHED_DIORITE = 5; + public static final int GRANITE = 6; + public static final int POLISHED_GRANITE = 7; + + private static final String[] names = new String[]{ + "End Stone Brick", + "Smooth Red Sandstone", + "Polished Andesite", + "Andesite", + "Diorite", + "Polished Diorite", + "Granite", + "Polisehd Granite" + }; + + public BlockSlabStone3() { + this(0); + } + + public BlockSlabStone3(int meta) { + super(meta, DOUBLE_STONE_SLAB3); + } + + @Override + public String getName() { + return ((this.getDamage() & 0x08) > 0 ? "Upper " : "") + names[this.getDamage() & 0x07] + " Slab"; + } + + @Override + public int getId() { + return STONE_SLAB3; + } + + @Override + public BlockColor getColor() { + switch (this.getDamage() & 0x07) { + case END_STONE_BRICKS: + return BlockColor.SAND_BLOCK_COLOR; + case SMOOTH_RED_SANDSTONE: + return BlockColor.ORANGE_BLOCK_COLOR; + default: + case POLISHED_ANDESITE: + case ANDESITE: + return BlockColor.STONE_BLOCK_COLOR; + case DIORITE: + case POLISHED_DIORITE: + return BlockColor.QUARTZ_BLOCK_COLOR; + case GRANITE: + case POLISHED_GRANITE: + return BlockColor.DIRT_BLOCK_COLOR; + } + } + + @Override + public Item toItem() { + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(),damage ), damage); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabStone4.java b/src/main/java/cn/nukkit/block/BlockSlabStone4.java new file mode 100644 index 00000000000..69f5ff3b258 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabStone4.java @@ -0,0 +1,64 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabStone4 extends BlockSlabStone { + + public static final int MOSSY_STONE_BRICKS = 0; + public static final int SMOOTH_QUARTZ = 1; + public static final int STONE = 2; + public static final int CUT_SANDSTONE = 3; + public static final int CUT_RED_SANDSTONE = 4; + + private static final String[] names = new String[]{ + "Mossy Stone Brick", + "Smooth Quartz", + "Stone", + "Cut Sandstone", + "Cut Red Sandstone" + }; + + public BlockSlabStone4() { + this(0); + } + + public BlockSlabStone4(int meta) { + super(meta, DOUBLE_STONE_SLAB4); + } + + @Override + public String getName() { + int variant = this.getDamage() & 0x07; + String name = variant >= names.length ? names[0] : names[variant]; + return ((this.getDamage() & 0x08) > 0 ? "Upper " : "") + name + " Slab"; + } + + @Override + public BlockColor getColor() { + switch (this.getDamage() & 0x07) { + default: + case MOSSY_STONE_BRICKS: + case STONE: + return BlockColor.STONE_BLOCK_COLOR; + case SMOOTH_QUARTZ: + return BlockColor.QUARTZ_BLOCK_COLOR; + case CUT_SANDSTONE: + return BlockColor.SAND_BLOCK_COLOR; + case CUT_RED_SANDSTONE: + return BlockColor.ORANGE_BLOCK_COLOR; + } + } + + @Override + public int getId() { + return STONE_SLAB4; + } + + @Override + public Item toItem() { + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(),damage ), damage); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabTileDeepslate.java b/src/main/java/cn/nukkit/block/BlockSlabTileDeepslate.java new file mode 100644 index 00000000000..90a8d911626 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabTileDeepslate.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabTileDeepslate extends BlockSlab { + + public BlockSlabTileDeepslate() { + this(0); + } + + public BlockSlabTileDeepslate(int meta) { + super(meta, DEEPSLATE_TILE_SLAB); + } + + @Override + public int getId() { + return DEEPSLATE_TILE_SLAB; + } + + @Override + public String getSlabName() { + return "Deepslate Tile"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabWarped.java b/src/main/java/cn/nukkit/block/BlockSlabWarped.java new file mode 100644 index 00000000000..5b2f07ab7af --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSlabWarped.java @@ -0,0 +1,62 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSlabWarped extends BlockSlab { + + public BlockSlabWarped() { + this(0); + } + + public BlockSlabWarped(int meta) { + super(meta, WARPED_DOUBLE_SLAB); + } + + @Override + public String getSlabName() { + return "Warped"; + } + + @Override + public int getId() { + return WARPED_SLAB; + } + + @Override + public boolean hasTopBit() { + return (this.getDamage() & 0x01) == 1; + } + + @Override + public void setTopBit(boolean topBit) { + this.setDamage(topBit ? 1 : 0); + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{ this.toItem() }; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSlabWood.java b/src/main/java/cn/nukkit/block/BlockSlabWood.java index 6f9b968e7be..b9310e8077e 100644 --- a/src/main/java/cn/nukkit/block/BlockSlabWood.java +++ b/src/main/java/cn/nukkit/block/BlockSlabWood.java @@ -21,7 +21,7 @@ public BlockSlabWood(int meta) { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak", "Spruce", "Birch", @@ -63,12 +63,13 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, this.getDamage() & 0x07); + int damage = this.getDamage() & 0x07; + return new ItemBlock(Block.get(this.getId(),damage ), damage); } @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { default: case 0: //OAK return BlockColor.WOOD_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockSlime.java b/src/main/java/cn/nukkit/block/BlockSlime.java index 7b54d72c551..19ea184995c 100644 --- a/src/main/java/cn/nukkit/block/BlockSlime.java +++ b/src/main/java/cn/nukkit/block/BlockSlime.java @@ -7,9 +7,6 @@ */ public class BlockSlime extends BlockSolid { - public BlockSlime() { - } - @Override public double getHardness() { return 0; @@ -32,6 +29,6 @@ public double getResistance() { @Override public BlockColor getColor() { - return BlockColor.GRASS_BLOCK_COLOR; + return BlockColor.GREEN_BLOCK_COLOR; } } diff --git a/src/main/java/cn/nukkit/block/BlockSmithingTable.java b/src/main/java/cn/nukkit/block/BlockSmithingTable.java new file mode 100644 index 00000000000..582f0fc052f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSmithingTable.java @@ -0,0 +1,60 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.inventory.SmithingInventory; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSmithingTable extends BlockSolid { + + @Override + public String getName() { + return "Smithing Table"; + } + + @Override + public int getId() { + return SMITHING_TABLE; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public double getResistance() { + return 12.5; + } + + @Override + public double getHardness() { + return 2.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 5; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player == null) { + return false; + } + + player.addWindow(new SmithingInventory(player.getUIInventory(), this), Player.SMITHING_WINDOW_ID); + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSmoker.java b/src/main/java/cn/nukkit/block/BlockSmoker.java new file mode 100644 index 00000000000..d99c5f94e60 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSmoker.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockSmoker extends BlockSmokerLit { + + public BlockSmoker() { + this(0); + } + + public BlockSmoker(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Smoker"; + } + + @Override + public int getId() { + return SMOKER; + } + + @Override + public int getLightLevel() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSmokerLit.java b/src/main/java/cn/nukkit/block/BlockSmokerLit.java new file mode 100644 index 00000000000..332162d2b16 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSmokerLit.java @@ -0,0 +1,87 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntitySmoker; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.nbt.tag.StringTag; +import cn.nukkit.nbt.tag.Tag; + +import java.util.Map; + +public class BlockSmokerLit extends BlockFurnaceBurning { + + public BlockSmokerLit() { + this(0); + } + + public BlockSmokerLit(int meta) { + super(meta); + } + + @Override + public int getId() { + return LIT_SMOKER; + } + + @Override + public String getName() { + return "Lit Smoker"; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(SMOKER)); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.getLevel().setBlock(block, this, true, true); + CompoundTag nbt = new CompoundTag() + .putList(new ListTag<>("Items")) + .putString("id", BlockEntity.SMOKER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + if (item.hasCustomBlockData()) { + Map customData = item.getCustomBlockData().getTags(); + for (Map.Entry tag : customData.entrySet()) { + nbt.put(tag.getKey(), tag.getValue()); + } + } + + BlockEntitySmoker smoker = (BlockEntitySmoker) BlockEntity.createBlockEntity(BlockEntity.SMOKER, this.getChunk(), nbt); + return smoker != null; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (player != null) { + BlockEntity t = this.getLevel().getBlockEntity(this); + if (!(t instanceof BlockEntitySmoker)) { + return false; + } + + BlockEntitySmoker smoker = (BlockEntitySmoker) t; + if (smoker.namedTag.contains("Lock") && smoker.namedTag.get("Lock") instanceof StringTag) { + if (!smoker.namedTag.getString("Lock").equals(item.getCustomName())) { + return true; + } + } + + player.addWindow(smoker.getInventory()); + } + + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSmoothStone.java b/src/main/java/cn/nukkit/block/BlockSmoothStone.java new file mode 100644 index 00000000000..b5d2db45a6b --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSmoothStone.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockSmoothStone extends BlockSolid { + + @Override + public String getName() { + return "Smooth Stone"; + } + + @Override + public int getId() { + return SMOOTH_STONE; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 10; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSnow.java b/src/main/java/cn/nukkit/block/BlockSnow.java index 2ca14c92264..d452aee3dbc 100644 --- a/src/main/java/cn/nukkit/block/BlockSnow.java +++ b/src/main/java/cn/nukkit/block/BlockSnow.java @@ -2,15 +2,12 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSnowball; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.utils.BlockColor; public class BlockSnow extends BlockSolid { - public BlockSnow() { - } - @Override public String getName() { return "Snow"; @@ -26,11 +23,6 @@ public double getHardness() { return 0.2; } - @Override - public double getResistance() { - return 1; - } - @Override public int getToolType() { return ItemTool.TYPE_SHOVEL; @@ -39,8 +31,11 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { if (item.isShovel() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[]{ - new ItemSnowball(0, 4) + Item.get(Item.SNOWBALL, 0, 4) }; } else { return new Item[0]; @@ -57,7 +52,7 @@ public BlockColor getColor() { public boolean canHarvestWithHand() { return false; } - + @Override public boolean canSilkTouch() { return true; @@ -70,11 +65,16 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.isShovel()) { + if (item.isShovel() && (player == null || (player.gamemode & 0x2) == 0)) { item.useOn(this); this.level.useBreakOn(this, item.clone().clearNamedTag(), null, true); return true; } return false; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSnowLayer.java b/src/main/java/cn/nukkit/block/BlockSnowLayer.java index 6c9b959a492..d773e6d3547 100644 --- a/src/main/java/cn/nukkit/block/BlockSnowLayer.java +++ b/src/main/java/cn/nukkit/block/BlockSnowLayer.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.event.block.BlockFadeEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSnowball; import cn.nukkit.item.ItemTool; import cn.nukkit.level.GameRule; import cn.nukkit.level.Level; @@ -14,31 +13,14 @@ * Created on 2015/12/6 by xtypr. * Package cn.nukkit.block in project Nukkit . */ -public class BlockSnowLayer extends BlockFallable { - - private int meta; +public class BlockSnowLayer extends BlockFallableMeta { public BlockSnowLayer() { this(0); } public BlockSnowLayer(int meta) { - this.meta = meta; - } - - @Override - public final int getFullId() { - return (this.getId() << 4) + this.getDamage(); - } - - @Override - public final int getDamage() { - return this.meta; - } - - @Override - public final void setDamage(int meta) { - this.meta = meta; + super(meta); } @Override @@ -71,6 +53,11 @@ public boolean canBeReplaced() { return (this.getDamage() & 0x7) != 0x7; } + @Override + public boolean canPassThrough() { + return (this.getDamage() & 0x7) < 3; + } + @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (this.canSurvive()) { @@ -114,12 +101,12 @@ public int onUpdate(int type) { @Override public Item toItem() { - return new ItemSnowball(); + return Item.get(Item.SNOWBALL); } @Override public Item[] getDrops(Item item) { - if (item.isShovel() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isShovel()) { Item drop = this.toItem(); int height = this.getDamage() & 0x7; drop.setCount(height < 3 ? 1 : height < 5 ? 2 : height == 7 ? 4 : 3); @@ -134,11 +121,6 @@ public BlockColor getColor() { return BlockColor.SNOW_BLOCK_COLOR; } - @Override - public boolean canHarvestWithHand() { - return false; - } - @Override public boolean isTransparent() { return (this.getDamage() & 0x7) != 0x7; @@ -167,11 +149,11 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.isShovel() && (player.gamemode & 0x2) == 0) { + if (item.isShovel() && (player == null || (player.gamemode & 0x2) == 0)) { item.useOn(this); this.level.useBreakOn(this, item.clone().clearNamedTag(), null, true); return true; - } else if (item.getId() == SNOW_LAYER && (player.gamemode & 0x2) == 0) { + } else if (item.getId() == SNOW_LAYER && (player == null || (player.gamemode & 0x2) == 0)) { if ((this.getDamage() & 0x7) != 0x7) { this.setDamage(this.getDamage() + 1); this.level.setBlock(this ,this, true); @@ -188,7 +170,7 @@ public boolean onActivate(Item item, Player player) { } @Override - public boolean canPassThrough() { - return (this.getDamage() & 0x7) < 3; + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockSolid.java b/src/main/java/cn/nukkit/block/BlockSolid.java index d472699f769..889be6f58cb 100644 --- a/src/main/java/cn/nukkit/block/BlockSolid.java +++ b/src/main/java/cn/nukkit/block/BlockSolid.java @@ -3,7 +3,7 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockSolid extends Block { @@ -11,11 +11,6 @@ public abstract class BlockSolid extends Block { protected BlockSolid() { } - @Override - public boolean isSolid() { - return true; - } - @Override public BlockColor getColor() { return BlockColor.STONE_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockSolidMeta.java b/src/main/java/cn/nukkit/block/BlockSolidMeta.java index 463f797a9cd..f650f403bd3 100644 --- a/src/main/java/cn/nukkit/block/BlockSolidMeta.java +++ b/src/main/java/cn/nukkit/block/BlockSolidMeta.java @@ -3,15 +3,11 @@ import cn.nukkit.utils.BlockColor; public abstract class BlockSolidMeta extends BlockMeta { + protected BlockSolidMeta(int meta) { super(meta); } - @Override - public boolean isSolid() { - return true; - } - @Override public BlockColor getColor() { return BlockColor.STONE_BLOCK_COLOR; diff --git a/src/main/java/cn/nukkit/block/BlockSoulFire.java b/src/main/java/cn/nukkit/block/BlockSoulFire.java new file mode 100644 index 00000000000..bf70df73c0f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSoulFire.java @@ -0,0 +1,42 @@ +package cn.nukkit.block; + +import cn.nukkit.level.Level; +import cn.nukkit.utils.BlockColor; + +public class BlockSoulFire extends BlockFire { + + public BlockSoulFire() { + this(0); + } + + public BlockSoulFire(int meta) { + super(meta); + } + + @Override + public int getId() { + return SOUL_FIRE; + } + + @Override + public String getName() { + return "Soul Fire"; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + int downId = down().getId(); + if (downId != Block.SOUL_SAND && downId != Block.SOUL_SOIL) { + this.getLevel().setBlock(this, Block.get(Block.FIRE, this.getDamage())); + } + return type; + } + return 0; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_BLUE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSoulLantern.java b/src/main/java/cn/nukkit/block/BlockSoulLantern.java new file mode 100644 index 00000000000..6fe086d8494 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSoulLantern.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockSoulLantern extends BlockLantern { + + public BlockSoulLantern() { + this(0); + } + + public BlockSoulLantern(int meta) { + super(meta); + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(SOUL_LANTERN)); + } + + @Override + public int getId() { + return SOUL_LANTERN; + } + + @Override + public String getName() { + return "Soul Lantern"; + } + + @Override + public int getLightLevel() { + return 10; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSoulSand.java b/src/main/java/cn/nukkit/block/BlockSoulSand.java index e4dbd455cb0..e9407c86099 100644 --- a/src/main/java/cn/nukkit/block/BlockSoulSand.java +++ b/src/main/java/cn/nukkit/block/BlockSoulSand.java @@ -1,7 +1,10 @@ package cn.nukkit.block; +import cn.nukkit.Server; import cn.nukkit.entity.Entity; +import cn.nukkit.event.block.BlockFormEvent; import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; import cn.nukkit.utils.BlockColor; /** @@ -9,9 +12,6 @@ */ public class BlockSoulSand extends BlockSolid { - public BlockSoulSand() { - } - @Override public String getName() { return "Soul Sand"; @@ -29,7 +29,7 @@ public double getHardness() { @Override public double getResistance() { - return 2.5; + return 0.5; } @Override @@ -39,7 +39,7 @@ public int getToolType() { @Override public double getMaxY() { - return this.y + 1 - 0.125; + return this.y + 0.875; } @Override @@ -58,4 +58,20 @@ public BlockColor getColor() { return BlockColor.BROWN_BLOCK_COLOR; } + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + Block up = this.up(); + if (up instanceof BlockWater && (up.getDamage() == 0 || up.getDamage() == 8)) { + BlockFormEvent event = new BlockFormEvent(up, Block.get(BUBBLE_COLUMN, BlockBubbleColumn.DIRECTION_UP)); + Server.getInstance().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + this.getLevel().setBlock(up, event.getNewState(), false, true); + } + } + } + + return 0; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSoulSoil.java b/src/main/java/cn/nukkit/block/BlockSoulSoil.java new file mode 100644 index 00000000000..3ef745a54f1 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSoulSoil.java @@ -0,0 +1,53 @@ +package cn.nukkit.block; + +import cn.nukkit.entity.Entity; +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockSoulSoil extends BlockSolid { + + public BlockSoulSoil() { + super(); + } + + @Override + public int getId() { + return SOUL_SOIL; + } + + @Override + public String getName() { + return "Soul Soil"; + } + + @Override + public double getHardness() { + return 0.5; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_SHOVEL; + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + entity.motionX *= 0.4d; + entity.motionZ *= 0.4d; + } + + @Override + public BlockColor getColor() { + return BlockColor.BROWN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSoulTorch.java b/src/main/java/cn/nukkit/block/BlockSoulTorch.java new file mode 100644 index 00000000000..fdeb4243312 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSoulTorch.java @@ -0,0 +1,27 @@ +package cn.nukkit.block; + +public class BlockSoulTorch extends BlockTorch { + + public BlockSoulTorch() { + this(0); + } + + public BlockSoulTorch(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Soul Torch"; + } + + @Override + public int getId() { + return SOUL_TORCH; + } + + @Override + public int getLightLevel() { + return 10; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSponge.java b/src/main/java/cn/nukkit/block/BlockSponge.java index db77752a515..51523ebcaf1 100644 --- a/src/main/java/cn/nukkit/block/BlockSponge.java +++ b/src/main/java/cn/nukkit/block/BlockSponge.java @@ -5,24 +5,25 @@ import cn.nukkit.item.ItemTool; import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.Level; -import cn.nukkit.level.particle.SmokeParticle; +import cn.nukkit.level.particle.ExplodeParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.BlockColor; import java.util.ArrayDeque; import java.util.Queue; -import java.util.concurrent.ThreadLocalRandom; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockSponge extends BlockSolidMeta { public static final int DRY = 0; public static final int WET = 1; - private static final String[] NAMES = new String[]{ + + private static final String[] NAMES = { "Sponge", "Wet sponge" }; @@ -50,11 +51,6 @@ public double getResistance() { return 3; } - @Override - public int getToolType() { - return ItemTool.TYPE_HOE; - } - @Override public String getName() { return NAMES[this.getDamage() & 0b1]; @@ -62,21 +58,22 @@ public String getName() { @Override public BlockColor getColor() { - return BlockColor.YELLOW_BLOCK_COLOR; + return BlockColor.CLOTH_BLOCK_COLOR; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; } @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (this.getDamage() == WET && level.getDimension() == Level.DIMENSION_NETHER) { level.setBlock(block, Block.get(BlockID.SPONGE, DRY), true, true); - this.getLevel().addLevelEvent(block.add(0.5, 0.875, 0.5), LevelEventPacket.EVENT_SOUND_EXPLODE); - - for (int i = 0; i < 8; ++i) { - level.addParticle(new SmokeParticle(block.getLocation().add(ThreadLocalRandom.current().nextDouble(), 1, ThreadLocalRandom.current().nextDouble()))); - } - + level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_FIZZ); + level.addParticle(new ExplodeParticle(block.add(0.5, 1, 0.5))); return true; - } else if (this.getDamage() == DRY && block instanceof BlockWater && performWaterAbsorb(block)) { + } else if (this.getDamage() == DRY && performWaterAbsorb(block)) { level.setBlock(block, Block.get(BlockID.SPONGE, WET), true, true); for (int i = 0; i < 4; i++) { @@ -96,6 +93,17 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } private boolean performWaterAbsorb(Block block) { + boolean waterFound = false; + for (BlockFace side : BlockFace.values()) { + if (getSide(side) instanceof BlockWater) { + waterFound = true; + break; + } + } + if (!waterFound) { + return false; + } + Queue entries = new ArrayDeque<>(); entries.add(new Entry(block, 0)); @@ -106,7 +114,7 @@ private boolean performWaterAbsorb(Block block) { for (BlockFace face : BlockFace.values()) { Block faceBlock = entry.block.getSide(face); - if (faceBlock.getId() == BlockID.WATER || faceBlock.getId() == BlockID.STILL_WATER) { + if (Block.hasWater(faceBlock.getId())) { this.level.setBlock(faceBlock, Block.get(BlockID.AIR)); ++waterRemoved; if (entry.distance < 6) { diff --git a/src/main/java/cn/nukkit/block/BlockSporeBlossom.java b/src/main/java/cn/nukkit/block/BlockSporeBlossom.java new file mode 100644 index 00000000000..f94a226c659 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSporeBlossom.java @@ -0,0 +1,75 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public class BlockSporeBlossom extends BlockTransparent { + + public BlockSporeBlossom() { + } + + @Override + public int getId() { + return SPORE_BLOSSOM; + } + + @Override + public String getName() { + return "Spore Blossom"; + } + + @Override + public double getResistance() { + return 0; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block.up().isSolid()) { + return super.place(item, block, target, face, fx, fy, fz, player); + } + return false; + } + + @Override + public double getMinX() { + return this.x + 2D / 16D; + } + + @Override + public double getMinY() { + return this.y + 13D / 16D; + } + + @Override + public double getMinZ() { + return this.z + 2D / 16D; + } + + @Override + public double getMaxX() { + return this.x + 14D / 16D; + } + + @Override + public double getMaxZ() { + return this.z + 14D / 16D; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.getLevel().useBreakOn(this, null, null, true); + } else if (type == Level.BLOCK_UPDATE_NORMAL && !this.up().isSolid()) { + this.getLevel().scheduleUpdate(this, 1); + } + return type; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSpruceSignStanding.java b/src/main/java/cn/nukkit/block/BlockSpruceSignStanding.java new file mode 100644 index 00000000000..df26ce756ca --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSpruceSignStanding.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockSpruceSignStanding extends BlockSignPost { + + public BlockSpruceSignStanding() { + this(0); + } + + public BlockSpruceSignStanding(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Spruce Sign Post"; + } + + @Override + public int getId() { + return SPRUCE_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.SPRUCE_SIGN); + } + + @Override + protected int getPostId() { + return SPRUCE_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return SPRUCE_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSpruceWallSign.java b/src/main/java/cn/nukkit/block/BlockSpruceWallSign.java new file mode 100644 index 00000000000..c6f2674b3e8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSpruceWallSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockSpruceWallSign extends BlockWallSign { + + public BlockSpruceWallSign() { + this(0); + } + + public BlockSpruceWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Spruce Wall Sign"; + } + + @Override + public int getId() { + return SPRUCE_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.SPRUCE_SIGN); + } + + @Override + protected int getPostId() { + return SPRUCE_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return SPRUCE_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairs.java b/src/main/java/cn/nukkit/block/BlockStairs.java index 135cc49704c..9a096684ca9 100644 --- a/src/main/java/cn/nukkit/block/BlockStairs.java +++ b/src/main/java/cn/nukkit/block/BlockStairs.java @@ -2,17 +2,18 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.utils.Faceable; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public abstract class BlockStairs extends BlockTransparentMeta implements Faceable { +public abstract class BlockStairs extends BlockSolidMeta implements Faceable { + + private static final short[] faces = {2, 1, 3, 0}; protected BlockStairs(int meta) { super(meta); @@ -21,32 +22,31 @@ protected BlockStairs(int meta) { @Override public double getMinY() { // TODO: this seems wrong - return this.y + ((getDamage() & 0x04) > 0 ? 0.5 : 0); + return this.y + (this.getDamage() & 0x04) > 0 ? 0.5 : 0; } @Override public double getMaxY() { // TODO: this seems wrong - return this.y + ((getDamage() & 0x04) > 0 ? 1 : 0.5); + return this.y + (this.getDamage() & 0x04) > 0 ? 1 : 0.5; } + @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = new int[]{2, 1, 3, 0}; this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); if ((fy > 0.5 && face != BlockFace.UP) || face == BlockFace.DOWN) { this.setDamage(this.getDamage() | 0x04); //Upside-down stairs } - this.getLevel().setBlock(block, this, true, true); - + this.getLevel().setBlock(this, this, true, true); return true; } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ - toItem() + toItem() }; } else { return new Item[0]; @@ -88,49 +88,41 @@ public boolean collidesWithBB(AxisAlignedBB bb) { if (side == 0) { - if (bb.intersectsWith(new SimpleAxisAlignedBB( + return bb.intersectsWith(new SimpleAxisAlignedBB( this.x + 0.5, this.y + f2, this.z, this.x + 1, this.y + f3, this.z + 1 - ))) { - return true; - } + )); } else if (side == 1) { - if (bb.intersectsWith(new SimpleAxisAlignedBB( + return bb.intersectsWith(new SimpleAxisAlignedBB( this.x, this.y + f2, this.z, this.x + 0.5, this.y + f3, this.z + 1 - ))) { - return true; - } + )); } else if (side == 2) { - if (bb.intersectsWith(new SimpleAxisAlignedBB( + return bb.intersectsWith(new SimpleAxisAlignedBB( this.x, this.y + f2, this.z + 0.5, this.x + 1, this.y + f3, this.z + 1 - ))) { - return true; - } + )); } else if (side == 3) { - if (bb.intersectsWith(new SimpleAxisAlignedBB( + return bb.intersectsWith(new SimpleAxisAlignedBB( this.x, this.y + f2, this.z, this.x + 1, this.y + f3, this.z + 0.5 - ))) { - return true; - } + )); } return false; @@ -140,4 +132,9 @@ public boolean collidesWithBB(AxisAlignedBB bb) { public BlockFace getBlockFace() { return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockStairsAcacia.java b/src/main/java/cn/nukkit/block/BlockStairsAcacia.java index cd9481407d7..78f31ba5c12 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsAcacia.java +++ b/src/main/java/cn/nukkit/block/BlockStairsAcacia.java @@ -3,7 +3,7 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockStairsAcacia extends BlockStairsWood { @@ -30,5 +30,4 @@ public String getName() { public BlockColor getColor() { return BlockColor.ORANGE_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockStairsAndesite.java b/src/main/java/cn/nukkit/block/BlockStairsAndesite.java new file mode 100644 index 00000000000..cd52f6a49fe --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsAndesite.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockStairsAndesite extends BlockStairs { + + public BlockStairsAndesite() { + this(0); + } + + public BlockStairsAndesite(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Andesite Stairs"; + } + + @Override + public int getId() { + return ANDESITE_STAIRS; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 30; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsAndesitePolished.java b/src/main/java/cn/nukkit/block/BlockStairsAndesitePolished.java new file mode 100644 index 00000000000..a43c5f32b82 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsAndesitePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsAndesitePolished extends BlockStairsAndesite { + + public BlockStairsAndesitePolished() { + this(0); + } + + public BlockStairsAndesitePolished(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Andesite Stairs"; + } + + @Override + public int getId() { + return POLISHED_ANDESITE_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsBirch.java b/src/main/java/cn/nukkit/block/BlockStairsBirch.java index fd3b0cb7f4a..d24eff46b1f 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsBirch.java +++ b/src/main/java/cn/nukkit/block/BlockStairsBirch.java @@ -30,5 +30,4 @@ public String getName() { public BlockColor getColor() { return BlockColor.SAND_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockStairsBlackstone.java b/src/main/java/cn/nukkit/block/BlockStairsBlackstone.java new file mode 100644 index 00000000000..dbde24a74d3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsBlackstone.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsBlackstone extends BlockStairs { + + public BlockStairsBlackstone() { + this(0); + } + + public BlockStairsBlackstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Blackstone Stairs"; + } + + @Override + public int getId() { + return BLACKSTONE_STAIRS; + } + + @Override + public BlockColor getColor() { + return BlockColor.BLACK_BLOCK_COLOR; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockStairsBlackstonePolished.java new file mode 100644 index 00000000000..52e703f6cdf --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsBlackstonePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsBlackstonePolished extends BlockStairsBlackstone { + + public BlockStairsBlackstonePolished() { + this(0); + } + + public BlockStairsBlackstonePolished(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Blackstone Stairs"; + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsBrick.java b/src/main/java/cn/nukkit/block/BlockStairsBrick.java index a6ba171bc8e..d73f6de546c 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsBrick.java +++ b/src/main/java/cn/nukkit/block/BlockStairsBrick.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsBrick extends BlockStairs { + public BlockStairsBrick() { this(0); } @@ -42,12 +43,12 @@ public String getName() { } @Override - public BlockColor getColor() { - return BlockColor.RED_BLOCK_COLOR; + public boolean canHarvestWithHand() { + return false; } @Override - public boolean canHarvestWithHand() { - return false; + public BlockColor getColor() { + return BlockColor.RED_BLOCK_COLOR; } } diff --git a/src/main/java/cn/nukkit/block/BlockStairsBrickBlackstonePolished.java b/src/main/java/cn/nukkit/block/BlockStairsBrickBlackstonePolished.java new file mode 100644 index 00000000000..b55bbee1656 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsBrickBlackstonePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsBrickBlackstonePolished extends BlockStairsBlackstonePolished { + + public BlockStairsBrickBlackstonePolished() { + this(0); + } + + public BlockStairsBrickBlackstonePolished(int meta) { + super(meta); + } + + @Override + public int getId() { + return POLISHED_BLACKSTONE_BRICK_STAIRS; + } + + @Override + public String getName() { + return "Polished Blackstone Brick Stairs"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsBrickDeepslate.java b/src/main/java/cn/nukkit/block/BlockStairsBrickDeepslate.java new file mode 100644 index 00000000000..80409525579 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsBrickDeepslate.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsBrickDeepslate extends BlockStairs { + + public BlockStairsBrickDeepslate() { + this(0); + } + + public BlockStairsBrickDeepslate(int meta) { + super(meta); + } + + @Override + public int getId() { + return DEEPSLATE_BRICK_STAIRS; + } + + @Override + public String getName() { + return "Deepslate Brick Stairs"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCobblestone.java b/src/main/java/cn/nukkit/block/BlockStairsCobblestone.java index 6abff3dd072..1497c3e4682 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsCobblestone.java +++ b/src/main/java/cn/nukkit/block/BlockStairsCobblestone.java @@ -1,13 +1,13 @@ package cn.nukkit.block; import cn.nukkit.item.ItemTool; -import cn.nukkit.utils.BlockColor; /** * Created on 2015/11/25 by xtypr. * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsCobblestone extends BlockStairs { + public BlockStairsCobblestone() { this(0); } @@ -41,11 +41,6 @@ public String getName() { return "Cobblestone Stairs"; } - @Override - public BlockColor getColor() { - return BlockColor.STONE_BLOCK_COLOR; - } - @Override public boolean canHarvestWithHand() { return false; diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperBase.java b/src/main/java/cn/nukkit/block/BlockStairsCopperBase.java new file mode 100644 index 00000000000..536fe95045d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperBase.java @@ -0,0 +1,86 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; + +public abstract class BlockStairsCopperBase extends BlockStairs implements Waxable, Oxidizable { + + public BlockStairsCopperBase() { + this(0); + } + + public BlockStairsCopperBase(int meta) { + super(meta); + } + + @Override + public double getHardness() { + return 3; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_STONE; + } + + @Override + public boolean onActivate(Item item, Player player) { + return Waxable.super.onActivate(item, player) + || Oxidizable.super.onActivate(item, player); + } + + @Override + public int onUpdate(int type) { + return Oxidizable.super.onUpdate(type); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public Block getStateWithOxidizationLevel(OxidizationLevel oxidizationLevel) { + return Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel), this.getDamage()); + } + + @Override + public boolean setOxidizationLevel(OxidizationLevel oxidizationLevel) { + if (this.getOxidizationLevel().equals(oxidizationLevel)) { + return true; + } + return this.level.setBlock(this, Block.get(this.getCopperId(this.isWaxed(), oxidizationLevel))); + } + + @Override + public boolean setWaxed(boolean waxed) { + if (this.isWaxed() == waxed) { + return true; + } + return this.level.setBlock(this, Block.get(getCopperId(waxed, getOxidizationLevel()))); + } + + @Override + public boolean isWaxed() { + return false; + } + + protected abstract int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel); +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCut.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCut.java new file mode 100644 index 00000000000..ccb8812101c --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCut.java @@ -0,0 +1,59 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; + +public class BlockStairsCopperCut extends BlockStairsCopperBase { + + public BlockStairsCopperCut() { + this(0); + } + + public BlockStairsCopperCut(int meta) { + super(meta); + } + + @Override + public String getName() { + String name = ""; + if (this.isWaxed()) { + name += "Waxed "; + } + + OxidizationLevel oxidizationLevel = this.getOxidizationLevel(); + if (oxidizationLevel != OxidizationLevel.UNAFFECTED) { + String oxidationName = oxidizationLevel.name(); + name += oxidationName.charAt(0) + oxidationName.substring(1).toLowerCase(); + } + return name + " Cut Copper Stairs"; + } + + @Override + public int getId() { + return CUT_COPPER_STAIRS; + } + + + @Override + protected int getCopperId(boolean waxed, OxidizationLevel oxidizationLevel) { + if (oxidizationLevel == null) { + return this.getId(); + } + switch (oxidizationLevel) { + case UNAFFECTED: + return waxed ? WAXED_CUT_COPPER_STAIRS : CUT_COPPER_STAIRS; + case EXPOSED: + return waxed ? WAXED_EXPOSED_CUT_COPPER_STAIRS : EXPOSED_CUT_COPPER_STAIRS; + case WEATHERED: + return waxed ? WAXED_WEATHERED_CUT_COPPER_STAIRS : WEATHERED_CUT_COPPER_STAIRS; + case OXIDIZED: + return waxed ? WAXED_OXIDIZED_CUT_COPPER_STAIRS : OXIDIZED_CUT_COPPER_STAIRS; + default: + return this.getId(); + } + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.UNAFFECTED; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposed.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposed.java new file mode 100644 index 00000000000..7cd15258d06 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposed.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsCopperCutExposed extends BlockStairsCopperCut { + + public BlockStairsCopperCutExposed() { + this(0); + } + + public BlockStairsCopperCutExposed(int meta) { + super(meta); + } + + @Override + public int getId() { + return EXPOSED_CUT_COPPER_STAIRS; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.EXPOSED; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_TERRACOTA_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposedWaxed.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposedWaxed.java new file mode 100644 index 00000000000..bce28120047 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutExposedWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsCopperCutExposedWaxed extends BlockStairsCopperCutExposed { + + public BlockStairsCopperCutExposedWaxed() { + this(0); + } + + public BlockStairsCopperCutExposedWaxed(int meta) { + super(meta); + } + + @Override + public int getId() { + return WAXED_EXPOSED_CUT_COPPER_STAIRS; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidized.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidized.java new file mode 100644 index 00000000000..cd6ff03f596 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidized.java @@ -0,0 +1,31 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsCopperCutOxidized extends BlockStairsCopperCut { + + public BlockStairsCopperCutOxidized() { + this(0); + } + + public BlockStairsCopperCutOxidized(int meta) { + super(meta); + } + + @Override + public int getId() { + return OXIDIZED_CUT_COPPER_STAIRS; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.OXIDIZED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } + +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidizedWaxed.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidizedWaxed.java new file mode 100644 index 00000000000..998288aecc9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutOxidizedWaxed.java @@ -0,0 +1,24 @@ +package cn.nukkit.block; + +public class BlockStairsCopperCutOxidizedWaxed extends BlockStairsCopperCutOxidized { + + public BlockStairsCopperCutOxidizedWaxed() { + this(0); + } + + + public BlockStairsCopperCutOxidizedWaxed(int meta) { + super(meta); + } + + @Override + public int getId() { + return WAXED_OXIDIZED_CUT_COPPER_STAIRS; + } + + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutWaxed.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWaxed.java new file mode 100644 index 00000000000..78715d789a0 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWaxed.java @@ -0,0 +1,23 @@ +package cn.nukkit.block; + +public class BlockStairsCopperCutWaxed extends BlockStairsCopperCut { + + public BlockStairsCopperCutWaxed() { + this(0); + } + + public BlockStairsCopperCutWaxed(int meta) { + super(meta); + } + + @Override + public int getId() { + return WAXED_CUT_COPPER_STAIRS; + } + + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeathered.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeathered.java new file mode 100644 index 00000000000..734730109f3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeathered.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsCopperCutWeathered extends BlockStairsCopperCut { + + public BlockStairsCopperCutWeathered() { + this(0); + } + + public BlockStairsCopperCutWeathered(int meta) { + super(meta); + } + + @Override + public int getId() { + return WEATHERED_CUT_COPPER_STAIRS; + } + + @Override + public OxidizationLevel getOxidizationLevel() { + return OxidizationLevel.WEATHERED; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeatheredWaxed.java b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeatheredWaxed.java new file mode 100644 index 00000000000..8077378c085 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsCopperCutWeatheredWaxed.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsCopperCutWeatheredWaxed extends BlockStairsCopperCutWeathered { + + public BlockStairsCopperCutWeatheredWaxed() { + this(0); + } + + public BlockStairsCopperCutWeatheredWaxed(int meta) { + super(meta); + } + + @Override + public int getId() { + return WAXED_WEATHERED_CUT_COPPER_STAIRS; + } + + @Override + public boolean isWaxed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsDarkOak.java b/src/main/java/cn/nukkit/block/BlockStairsDarkOak.java index 9df9077d6cf..19d5b74ed98 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsDarkOak.java +++ b/src/main/java/cn/nukkit/block/BlockStairsDarkOak.java @@ -30,5 +30,4 @@ public String getName() { public BlockColor getColor() { return BlockColor.BROWN_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockStairsDarkPrismarine.java b/src/main/java/cn/nukkit/block/BlockStairsDarkPrismarine.java new file mode 100644 index 00000000000..ab5a7b36550 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsDarkPrismarine.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsDarkPrismarine extends BlockStairs { + + public BlockStairsDarkPrismarine() { + this(0); + } + + public BlockStairsDarkPrismarine(int meta) { + super(meta); + } + + @Override + public int getId() { + return DARK_PRISMARINE_STAIRS; + } + + @Override + public double getHardness() { + return 0.8; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Dark Prismarine Stairs"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DIAMOND_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsDeepslateCobbled.java b/src/main/java/cn/nukkit/block/BlockStairsDeepslateCobbled.java new file mode 100644 index 00000000000..794129dee53 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsDeepslateCobbled.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsDeepslateCobbled extends BlockStairs { + + public BlockStairsDeepslateCobbled() { + this(0); + } + + public BlockStairsDeepslateCobbled(int meta) { + super(meta); + } + + @Override + public int getId() { + return COBBLED_DEEPSLATE_STAIRS; + } + + @Override + public String getName() { + return "Cobbled Deepslate Stairs"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsDeepslatePolished.java b/src/main/java/cn/nukkit/block/BlockStairsDeepslatePolished.java new file mode 100644 index 00000000000..f1b8dd94989 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsDeepslatePolished.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsDeepslatePolished extends BlockStairs { + + public BlockStairsDeepslatePolished() { + this(0); + } + + public BlockStairsDeepslatePolished(int meta) { + super(meta); + } + + @Override + public int getId() { + return POLISHED_DEEPSLATE_STAIRS; + } + + @Override + public String getName() { + return "Polished Deepslate Stairs"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsDiorite.java b/src/main/java/cn/nukkit/block/BlockStairsDiorite.java new file mode 100644 index 00000000000..647d9186b61 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsDiorite.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsDiorite extends BlockStairs { + + public BlockStairsDiorite() { + this(0); + } + + public BlockStairsDiorite(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Diorite Stairs"; + } + + @Override + public int getId() { + return DIORITE_STAIRS; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 30; + } + + @Override + public BlockColor getColor() { + return BlockColor.QUARTZ_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsDioritePolished.java b/src/main/java/cn/nukkit/block/BlockStairsDioritePolished.java new file mode 100644 index 00000000000..a440fd0429e --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsDioritePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsDioritePolished extends BlockStairsDiorite { + + public BlockStairsDioritePolished() { + this(0); + } + + public BlockStairsDioritePolished(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Diorite Stairs"; + } + + @Override + public int getId() { + return POLISHED_DIORITE_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsEndBrick.java b/src/main/java/cn/nukkit/block/BlockStairsEndBrick.java new file mode 100644 index 00000000000..81f71de42f2 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsEndBrick.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockStairsEndBrick extends BlockStairs { + + public BlockStairsEndBrick() { + this(0); + } + + public BlockStairsEndBrick(int meta) { + super(meta); + } + + @Override + public String getName() { + return "End Brick Stairs"; + } + + @Override + public int getId() { + return END_BRICK_STAIRS; + } + + @Override + public double getHardness() { + return 2; //3 + } + + @Override + public double getResistance() { + return 9; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsGranite.java b/src/main/java/cn/nukkit/block/BlockStairsGranite.java new file mode 100644 index 00000000000..33cdb328ffc --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsGranite.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsGranite extends BlockStairs { + + public BlockStairsGranite() { + this(0); + } + + public BlockStairsGranite(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Granite Stairs"; + } + + @Override + public int getId() { + return GRANITE_STAIRS; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 30; + } + + @Override + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsGranitePolished.java b/src/main/java/cn/nukkit/block/BlockStairsGranitePolished.java new file mode 100644 index 00000000000..64d7f6bc12f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsGranitePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsGranitePolished extends BlockStairsGranite { + + public BlockStairsGranitePolished() { + this(0); + } + + public BlockStairsGranitePolished(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Granite Stairs"; + } + + @Override + public int getId() { + return POLISHED_GRANITE_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsJungle.java b/src/main/java/cn/nukkit/block/BlockStairsJungle.java index 7af5f2b3d9a..390f7915a62 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsJungle.java +++ b/src/main/java/cn/nukkit/block/BlockStairsJungle.java @@ -30,5 +30,4 @@ public String getName() { public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockStairsMossyCobblestone.java b/src/main/java/cn/nukkit/block/BlockStairsMossyCobblestone.java new file mode 100644 index 00000000000..37515a34892 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsMossyCobblestone.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsMossyCobblestone extends BlockStairsCobblestone { + + public BlockStairsMossyCobblestone() { + this(0); + } + + public BlockStairsMossyCobblestone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Mossy Cobblestone Stairs"; + } + + @Override + public int getId() { + return MOSSY_COBBLESTONE_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsMossyStoneBrick.java b/src/main/java/cn/nukkit/block/BlockStairsMossyStoneBrick.java new file mode 100644 index 00000000000..b35398ff8a6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsMossyStoneBrick.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsMossyStoneBrick extends BlockStairsStoneBrick { + + public BlockStairsMossyStoneBrick() { + this(0); + } + + public BlockStairsMossyStoneBrick(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Mossy Stone Brick Stairs"; + } + + @Override + public int getId() { + return MOSSY_STONE_BRICK_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsNetherBrick.java b/src/main/java/cn/nukkit/block/BlockStairsNetherBrick.java index a45d63d204f..f64eae90c38 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsNetherBrick.java +++ b/src/main/java/cn/nukkit/block/BlockStairsNetherBrick.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsNetherBrick extends BlockStairs { + public BlockStairsNetherBrick() { this(0); } diff --git a/src/main/java/cn/nukkit/block/BlockStairsPrismarine.java b/src/main/java/cn/nukkit/block/BlockStairsPrismarine.java new file mode 100644 index 00000000000..276ea09ac99 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsPrismarine.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsPrismarine extends BlockStairs { + + public BlockStairsPrismarine() { + this(0); + } + + public BlockStairsPrismarine(int meta) { + super(meta); + } + + @Override + public int getId() { + return PRISMARINE_STAIRS; + } + + @Override + public double getHardness() { + return 0.8; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Prismarine Stairs"; + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsPrismarineBrick.java b/src/main/java/cn/nukkit/block/BlockStairsPrismarineBrick.java new file mode 100644 index 00000000000..428937e611a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsPrismarineBrick.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockStairsPrismarineBrick extends BlockStairsPrismarine { + + public BlockStairsPrismarineBrick() { + this(0); + } + + public BlockStairsPrismarineBrick(int meta) { + super(meta); + } + + @Override + public int getId() { + return PRISMARINE_BRICKS_STAIRS; + } + + @Override + public String getName() { + return "Prismarine Brick Stairs"; + } + + @Override + public BlockColor getColor() { + return BlockColor.DIAMOND_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsPurpur.java b/src/main/java/cn/nukkit/block/BlockStairsPurpur.java index 46d4dd53533..d374d5f42b9 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsPurpur.java +++ b/src/main/java/cn/nukkit/block/BlockStairsPurpur.java @@ -42,4 +42,9 @@ public String getName() { public BlockColor getColor() { return BlockColor.MAGENTA_BLOCK_COLOR; } + + @Override + public boolean canHarvestWithHand() { + return false; + } } diff --git a/src/main/java/cn/nukkit/block/BlockStairsQuartz.java b/src/main/java/cn/nukkit/block/BlockStairsQuartz.java index 9482765d3c3..d9aac17c7ef 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsQuartz.java +++ b/src/main/java/cn/nukkit/block/BlockStairsQuartz.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsQuartz extends BlockStairs { + public BlockStairsQuartz() { this(0); } @@ -23,12 +24,12 @@ public int getId() { @Override public double getHardness() { - return 0.8; + return 2; } @Override public double getResistance() { - return 4; + return 6; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockStairsRedNetherBrick.java b/src/main/java/cn/nukkit/block/BlockStairsRedNetherBrick.java new file mode 100644 index 00000000000..a7b4f06aed3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsRedNetherBrick.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsRedNetherBrick extends BlockStairsNetherBrick { + + public BlockStairsRedNetherBrick() { + this(0); + } + + public BlockStairsRedNetherBrick(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Red Nether Brick Stairs"; + } + + @Override + public int getId() { + return RED_NETHER_BRICK_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsRedSandstone.java b/src/main/java/cn/nukkit/block/BlockStairsRedSandstone.java index 9aa25e29e27..0de03be8c2c 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsRedSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockStairsRedSandstone.java @@ -45,7 +45,7 @@ public String getName() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -68,4 +68,4 @@ public boolean canHarvestWithHand() { public BlockColor getColor() { return BlockColor.ORANGE_BLOCK_COLOR; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsSandstone.java b/src/main/java/cn/nukkit/block/BlockStairsSandstone.java index 18557d87a85..a3597e02dd2 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsSandstone.java +++ b/src/main/java/cn/nukkit/block/BlockStairsSandstone.java @@ -8,6 +8,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsSandstone extends BlockStairs { + public BlockStairsSandstone() { this(0); } diff --git a/src/main/java/cn/nukkit/block/BlockStairsSmoothQuartz.java b/src/main/java/cn/nukkit/block/BlockStairsSmoothQuartz.java new file mode 100644 index 00000000000..66c0564d520 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsSmoothQuartz.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockStairsSmoothQuartz extends BlockStairsQuartz { + + public BlockStairsSmoothQuartz() { + this(0); + } + + public BlockStairsSmoothQuartz(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Smooth Quartz Stairs"; + } + + @Override + public int getId() { + return SMOOTH_QUARTZ_STAIRS; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsSmoothRedSandstone.java b/src/main/java/cn/nukkit/block/BlockStairsSmoothRedSandstone.java new file mode 100644 index 00000000000..9633b01fcdb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsSmoothRedSandstone.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockStairsSmoothRedSandstone extends BlockStairsRedSandstone { + + public BlockStairsSmoothRedSandstone() { + this(0); + } + + public BlockStairsSmoothRedSandstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Smooth RedSand stone Stairs"; + } + + @Override + public int getId() { + return SMOOTH_RED_SANDSTONE_STAIRS; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsSmoothSandstone.java b/src/main/java/cn/nukkit/block/BlockStairsSmoothSandstone.java new file mode 100644 index 00000000000..58b743c4fbc --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsSmoothSandstone.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockStairsSmoothSandstone extends BlockStairsSandstone { + + public BlockStairsSmoothSandstone() { + this(0); + } + + public BlockStairsSmoothSandstone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Smooth Sandstone Stairs"; + } + + @Override + public int getId() { + return SMOOTH_SANDSTONE_STAIRS; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsSpruce.java b/src/main/java/cn/nukkit/block/BlockStairsSpruce.java index 09a54a098fe..cb9b3cfb789 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsSpruce.java +++ b/src/main/java/cn/nukkit/block/BlockStairsSpruce.java @@ -30,5 +30,4 @@ public String getName() { public BlockColor getColor() { return BlockColor.SPRUCE_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockStairsStone.java b/src/main/java/cn/nukkit/block/BlockStairsStone.java new file mode 100644 index 00000000000..9f357417130 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsStone.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; + +public class BlockStairsStone extends BlockStairs { + + public BlockStairsStone() { + this(0); + } + + public BlockStairsStone(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stone Stairs"; + } + + @Override + public int getId() { + return NORMAL_STONE_STAIRS; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 30; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsStoneBrick.java b/src/main/java/cn/nukkit/block/BlockStairsStoneBrick.java index d84255e8295..a7145c21dfa 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsStoneBrick.java +++ b/src/main/java/cn/nukkit/block/BlockStairsStoneBrick.java @@ -7,6 +7,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsStoneBrick extends BlockStairs { + public BlockStairsStoneBrick() { this(0); } diff --git a/src/main/java/cn/nukkit/block/BlockStairsTileDeepslate.java b/src/main/java/cn/nukkit/block/BlockStairsTileDeepslate.java new file mode 100644 index 00000000000..0984a0559a6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStairsTileDeepslate.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockStairsTileDeepslate extends BlockStairs { + + public BlockStairsTileDeepslate() { + this(0); + } + + public BlockStairsTileDeepslate(int meta) { + super(meta); + } + + @Override + public int getId() { + return DEEPSLATE_TILE_STAIRS; + } + + @Override + public String getName() { + return "Deepslate Tile Stairs"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStairsWood.java b/src/main/java/cn/nukkit/block/BlockStairsWood.java index 11ae3209cf9..1add1fc352c 100644 --- a/src/main/java/cn/nukkit/block/BlockStairsWood.java +++ b/src/main/java/cn/nukkit/block/BlockStairsWood.java @@ -10,6 +10,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockStairsWood extends BlockStairs { + public BlockStairsWood() { this(0); } @@ -35,7 +36,7 @@ public int getToolType() { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override @@ -62,6 +63,7 @@ public int getBurnAbility() { public BlockColor getColor() { return BlockColor.WOOD_BLOCK_COLOR; } + @Override public Item[] getDrops(Item item) { return new Item[]{ diff --git a/src/main/java/cn/nukkit/block/BlockStem.java b/src/main/java/cn/nukkit/block/BlockStem.java new file mode 100644 index 00000000000..daf07887f48 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStem.java @@ -0,0 +1,73 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; + +public abstract class BlockStem extends BlockSolidMeta { + + private static final short[] faces = new short[]{ + 0, + 0, + 2, + 2, + 1, + 1 + }; + + public BlockStem() { + this(0); + } + + protected BlockStem(int meta) { + super(meta); + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(faces[face.getIndex()]); + this.getLevel().setBlock(block, this, true, true); + return true; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.isAxe()) { + Block strippedBlock = Block.get(this.getStrippedId()); + strippedBlock.setDamage(this.getDamage()); + item.useOn(this); + this.level.setBlock(this, strippedBlock, true, true); + return true; + } + return false; + } + + public abstract int getStrippedId(); + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public Item toItem() { + return new ItemBlock(this, 0); + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 2; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockStemMelon.java b/src/main/java/cn/nukkit/block/BlockStemMelon.java index 90b32f823cc..abeaed8b0f7 100644 --- a/src/main/java/cn/nukkit/block/BlockStemMelon.java +++ b/src/main/java/cn/nukkit/block/BlockStemMelon.java @@ -3,11 +3,10 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsMelon; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockFace.Plane; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 15.01.2016. @@ -40,8 +39,7 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_NORMAL; } } else if (type == Level.BLOCK_UPDATE_RANDOM) { - NukkitRandom random = new NukkitRandom(); - if (random.nextRange(1, 2) == 1) { + if (Utils.rand()) { if (this.getDamage() < 0x07) { Block block = this.clone(); block.setDamage(block.getDamage() + 1); @@ -58,10 +56,10 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_RANDOM; } } - Block side = this.getSide(Plane.HORIZONTAL.random(random)); - Block d = side.down(); - if (side.getId() == AIR && (d.getId() == FARMLAND || d.getId() == GRASS || d.getId() == DIRT)) { - BlockGrowEvent ev = new BlockGrowEvent(side, Block.get(BlockID.MELON_BLOCK)); + Block side = this.getSide(Plane.HORIZONTAL.random()); + Block d; + if (side.getId() == AIR && ((d = side.down()).getId() == FARMLAND || d.getId() == GRASS || d.getId() == DIRT)) { + BlockGrowEvent ev = new BlockGrowEvent(side, Block.get(MELON_BLOCK)); Server.getInstance().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { this.getLevel().setBlock(side, ev.getNewState(), true); @@ -76,14 +74,19 @@ public int onUpdate(int type) { @Override public Item toItem() { - return new ItemSeedsMelon(); + return Item.get(Item.MELON_SEEDS); } @Override public Item[] getDrops(Item item) { - NukkitRandom random = new NukkitRandom(); + if (this.getDamage() < 4) return new Item[0]; return new Item[]{ - new ItemSeedsMelon(0, random.nextRange(0, 3)) + Item.get(Item.MELON_SEEDS, 0, Utils.rand(0, 48) >> 4) }; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockStemPumpkin.java b/src/main/java/cn/nukkit/block/BlockStemPumpkin.java index e5982255dca..308ded3d6ab 100644 --- a/src/main/java/cn/nukkit/block/BlockStemPumpkin.java +++ b/src/main/java/cn/nukkit/block/BlockStemPumpkin.java @@ -3,11 +3,10 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsPumpkin; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockFace.Plane; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; /** * Created by Pub4Game on 15.01.2016. @@ -40,8 +39,7 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_NORMAL; } } else if (type == Level.BLOCK_UPDATE_RANDOM) { - NukkitRandom random = new NukkitRandom(); - if (random.nextRange(1, 2) == 1) { + if (Utils.rand()) { if (this.getDamage() < 0x07) { Block block = this.clone(); block.setDamage(block.getDamage() + 1); @@ -58,10 +56,10 @@ public int onUpdate(int type) { return Level.BLOCK_UPDATE_RANDOM; } } - Block side = this.getSide(Plane.HORIZONTAL.random(random)); - Block d = side.down(); - if (side.getId() == AIR && (d.getId() == FARMLAND || d.getId() == GRASS || d.getId() == DIRT)) { - BlockGrowEvent ev = new BlockGrowEvent(side, Block.get(BlockID.PUMPKIN)); + Block side = this.getSide(Plane.HORIZONTAL.random()); + Block d; + if (side.getId() == AIR && ((d = side.down()).getId() == FARMLAND || d.getId() == GRASS || d.getId() == DIRT)) { + BlockGrowEvent ev = new BlockGrowEvent(side, Block.get(PUMPKIN)); Server.getInstance().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { this.getLevel().setBlock(side, ev.getNewState(), true); @@ -76,14 +74,19 @@ public int onUpdate(int type) { @Override public Item toItem() { - return new ItemSeedsPumpkin(); + return Item.get(Item.PUMPKIN_SEEDS); } @Override public Item[] getDrops(Item item) { - NukkitRandom random = new NukkitRandom(); + if (this.getDamage() < 4) return new Item[0]; return new Item[]{ - new ItemSeedsPumpkin(0, random.nextRange(0, 3)) + Item.get(Item.PUMPKIN_SEEDS, 0, Utils.rand(0, 48) >> 4) }; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockStemStripped.java b/src/main/java/cn/nukkit/block/BlockStemStripped.java new file mode 100644 index 00000000000..affcf5cb935 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStemStripped.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; + +public abstract class BlockStemStripped extends BlockStem { + + public BlockStemStripped() { + this(0); + } + + public BlockStemStripped(int meta) { + super(meta); + } + + @Override + public boolean canBeActivated() { + return false; + } + + @Override + public boolean onActivate(Item item, Player player) { + return false; + } + + @Override + public int getStrippedId() { + return this.getId(); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockStone.java b/src/main/java/cn/nukkit/block/BlockStone.java index 15f01b008ab..0caf20472bb 100644 --- a/src/main/java/cn/nukkit/block/BlockStone.java +++ b/src/main/java/cn/nukkit/block/BlockStone.java @@ -2,12 +2,15 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockStone extends BlockSolidMeta { + public static final int NORMAL = 0; public static final int GRANITE = 1; public static final int POLISHED_GRANITE = 2; @@ -16,6 +19,16 @@ public class BlockStone extends BlockSolidMeta { public static final int ANDESITE = 5; public static final int POLISHED_ANDESITE = 6; + private static final String[] names = { + "Stone", + "Granite", + "Polished Granite", + "Diorite", + "Polished Diorite", + "Andesite", + "Polished Andesite", + "Unknown Stone" + }; public BlockStone() { this(0); @@ -37,7 +50,7 @@ public double getHardness() { @Override public double getResistance() { - return 10; + return 30; } @Override @@ -47,22 +60,15 @@ public int getToolType() { @Override public String getName() { - String[] names = new String[]{ - "Stone", - "Granite", - "Polished Granite", - "Diorite", - "Polished Diorite", - "Andesite", - "Polished Andesite", - "Unknown Stone" - }; return names[this.getDamage() & 0x07]; } @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { + if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { + return new Item[]{this.toItem()}; + } return new Item[]{ Item.get(this.getDamage() == 0 ? Item.COBBLESTONE : Item.STONE, this.getDamage(), 1) }; @@ -80,4 +86,15 @@ public boolean canHarvestWithHand() { public boolean canSilkTouch() { return true; } + + @Override + public BlockColor getColor() { + int damage = this.getDamage() & 0x07; + if (damage == GRANITE || damage == POLISHED_GRANITE) { + return BlockColor.DIRT_BLOCK_COLOR; + } else if (damage == DIORITE || damage == POLISHED_DIORITE) { + return BlockColor.QUARTZ_BLOCK_COLOR; + } + return BlockColor.STONE_BLOCK_COLOR; + } } diff --git a/src/main/java/cn/nukkit/block/BlockStonecutter.java b/src/main/java/cn/nukkit/block/BlockStonecutter.java index f077ac20d58..14cd4bdc7aa 100644 --- a/src/main/java/cn/nukkit/block/BlockStonecutter.java +++ b/src/main/java/cn/nukkit/block/BlockStonecutter.java @@ -1,52 +1,53 @@ -package cn.nukkit.block; - -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemTool; - -public class BlockStonecutter extends BlockSolid { - - public BlockStonecutter() { - - } - - @Override - public int getId() { - return STONECUTTER; - } - - @Override - public String getName() { - return "Stonecutter"; - } - - @Override - public double getHardness() { - return 3.5; - } - - @Override - public double getResistance() { - return 17.5; - } - - @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; - } - - @Override - public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { - return new Item[]{ - this.toItem() - }; - } else { - return new Item[0]; - } - } - - @Override - public boolean canHarvestWithHand() { - return false; - } -} +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; + +public class BlockStonecutter extends BlockSolid { + + @Override + public int getId() { + return STONECUTTER; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 17.5; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public String getName() { + return "Stonecutter"; + } + + @Override + public Item[] getDrops(Item item) { + if (item.isPickaxe()) { + return new Item[]{ + toItem() + }; + } else { + return new Item[0]; + } + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java b/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java new file mode 100644 index 00000000000..c36868440ce --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java @@ -0,0 +1,64 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Faceable; + +public class BlockStonecutterBlock extends BlockSolidMeta implements Faceable { + + public BlockStonecutterBlock() { + this(0); + } + + public BlockStonecutterBlock(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stonecutter Block"; + } + + @Override + public int getId() { + return STONECUTTER_BLOCK; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 17.5; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public double getMaxY() { + return y + 0.5625; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + return super.place(item, block, target, face, fx, fy, fz, player); + } + + @Override + public BlockFace getBlockFace() { + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStrippedCrimsonStem.java b/src/main/java/cn/nukkit/block/BlockStrippedCrimsonStem.java new file mode 100644 index 00000000000..4c75f67c1a0 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStrippedCrimsonStem.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockStrippedCrimsonStem extends BlockStemStripped { + + public BlockStrippedCrimsonStem() { + this(0); + } + + public BlockStrippedCrimsonStem(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Crimson Stem"; + } + + @Override + public int getId() { + return STRIPPED_CRIMSON_STEM; + } + + @Override + public BlockColor getColor() { + return BlockColor.CRIMSON_STEM_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockStrippedWarpedStem.java b/src/main/java/cn/nukkit/block/BlockStrippedWarpedStem.java new file mode 100644 index 00000000000..25f559f2b7a --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStrippedWarpedStem.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockStrippedWarpedStem extends BlockStemStripped { + + public BlockStrippedWarpedStem() { + this(0); + } + + public BlockStrippedWarpedStem(int meta) { + super(meta); + } + + @Override + public int getId() { + return STRIPPED_WARPED_STEM; + } + + @Override + public String getName() { + return "Stripped Warped Stem"; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockStructureBlock.java b/src/main/java/cn/nukkit/block/BlockStructureBlock.java new file mode 100644 index 00000000000..7bda561e5f9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockStructureBlock.java @@ -0,0 +1,47 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.utils.BlockColor; + +public class BlockStructureBlock extends BlockSolid { + + @Override + public int getId() { + return STRUCTURE_BLOCK; + } + + @Override + public double getHardness() { + return -1; + } + + @Override + public double getResistance() { + return 18000000; + } + + @Override + public String getName() { + return "Structure Block"; + } + + @Override + public boolean isBreakable(Item item) { + return false; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.LIGHT_GRAY_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockSugarcane.java b/src/main/java/cn/nukkit/block/BlockSugarcane.java index a5c0eea8abb..ef2cef0f56a 100644 --- a/src/main/java/cn/nukkit/block/BlockSugarcane.java +++ b/src/main/java/cn/nukkit/block/BlockSugarcane.java @@ -4,11 +4,10 @@ import cn.nukkit.Server; import cn.nukkit.event.block.BlockGrowEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSugarcane; +import cn.nukkit.item.ItemDye; import cn.nukkit.level.Level; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockColor; /** @@ -26,7 +25,7 @@ public BlockSugarcane(int meta) { @Override public String getName() { - return "Sugarcane"; + return "Sugar Cane"; } @Override @@ -36,7 +35,7 @@ public int getId() { @Override public Item toItem() { - return new ItemSugarcane(); + return Item.get(Item.SUGARCANE); } @Override @@ -46,7 +45,7 @@ public boolean canBeActivated() { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0F) { //Bonemeal + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { int count = 1; for (int i = 1; i <= 2; i++) { @@ -64,7 +63,7 @@ public boolean onActivate(Item item, Player player) { for (int i = 1; i <= toGrow; i++) { Block block = this.up(i); if (block.getId() == 0) { - BlockGrowEvent ev = new BlockGrowEvent(block, Block.get(BlockID.SUGARCANE_BLOCK)); + BlockGrowEvent ev = new BlockGrowEvent(block, Block.get(SUGARCANE_BLOCK)); Server.getInstance().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { @@ -77,14 +76,13 @@ public boolean onActivate(Item item, Player player) { } if (success) { - if (player != null && (player.gamemode & 0x01) == 0) { + if (player != null && !player.isCreative()) { item.count--; } this.level.addParticle(new BoneMealParticle(this)); } } - return true; } return false; @@ -102,23 +100,22 @@ public int onUpdate(int type) { if (this.down().getId() != SUGARCANE_BLOCK) { if (this.getDamage() == 0x0F) { for (int y = 1; y < 3; ++y) { - Block b = this.getLevel().getBlock(new Vector3(this.x, this.y + y, this.z)); + Block b = this.getLevel().getBlock((int) this.x, (int) this.y + y, (int) this.z); if (b.getId() == AIR) { BlockGrowEvent ev = new BlockGrowEvent(b, Block.get(BlockID.SUGARCANE_BLOCK)); Server.getInstance().getPluginManager().callEvent(ev); - + if (!ev.isCancelled()) { - this.getLevel().setBlock(b, Block.get(BlockID.SUGARCANE_BLOCK), false); + this.getLevel().setBlock(b, ev.getNewState(), false); } break; } } this.setDamage(0); - this.getLevel().setBlock(this, this, false); } else { this.setDamage(this.getDamage() + 1); - this.getLevel().setBlock(this, this, false); } + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, true, false); // No need to send this to client return Level.BLOCK_UPDATE_RANDOM; } } @@ -133,15 +130,15 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl Block down = this.down(); int id = down.getId(); if (id == SUGARCANE_BLOCK) { - this.getLevel().setBlock(block, Block.get(BlockID.SUGARCANE_BLOCK), true); + this.getLevel().setBlock(block, Block.get(SUGARCANE_BLOCK), true); return true; } else if (id == GRASS || id == DIRT || id == SAND || id == PODZOL || id == MYCELIUM) { Block block0 = down.north(); Block block1 = down.south(); Block block2 = down.west(); Block block3 = down.east(); - if ((block0 instanceof BlockWater) || (block1 instanceof BlockWater) || (block2 instanceof BlockWater) || (block3 instanceof BlockWater)) { - this.getLevel().setBlock(block, Block.get(BlockID.SUGARCANE_BLOCK), true); + if (block0 instanceof BlockWater || block1 instanceof BlockWater || block2 instanceof BlockWater || block3 instanceof BlockWater || block0 instanceof BlockIceFrosted || block1 instanceof BlockIceFrosted || block2 instanceof BlockIceFrosted || block3 instanceof BlockIceFrosted) { + this.getLevel().setBlock(block, Block.get(SUGARCANE_BLOCK), true); return true; } } @@ -152,4 +149,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockSweetBerryBush.java b/src/main/java/cn/nukkit/block/BlockSweetBerryBush.java new file mode 100644 index 00000000000..25b8cecd20d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockSweetBerryBush.java @@ -0,0 +1,206 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.event.block.BlockHarvestEvent; +import cn.nukkit.event.entity.EntityDamageByBlockEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.MathHelper; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.DyeColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockSweetBerryBush extends BlockFlowable { + + public BlockSweetBerryBush() { + this(0); + } + + public BlockSweetBerryBush(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Sweet Berry Bush"; + } + + @Override + public int getId() { + return SWEET_BERRY_BUSH; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public int getBurnChance() { + return 30; + } + + @Override + public int getBurnAbility() { + return 60; + } + + @Override + public Item toItem() { + return Item.get(ItemID.SWEET_BERRIES); + } + + @Override + public void onEntityCollide(Entity entity) { + if (this.getDamage() > 0 && !(entity instanceof EntityItem)) { + entity.resetFallDistance(); + if (!entity.isSneaking() && ThreadLocalRandom.current().nextInt(20) == 0) { + if (entity.attack(new EntityDamageByBlockEvent(this, entity, EntityDamageEvent.DamageCause.CONTACT, 1))) { + this.level.addLevelSoundEvent(entity, LevelSoundEventPacket.SOUND_BLOCK_SWEET_BERRY_BUSH_HURT); + } + } + } + } + + @Override + public boolean hasEntityCollision() { + return this.getDamage() > 0; + } + + protected AxisAlignedBB recalculateBoundingBox() { + if (this.getDamage() > 0) { + return this; + } + return null; + } + + @Override + public Item[] getDrops(Item item) { + int age = MathHelper.clamp(getDamage(), 0, 3); + + int amount = 1; + if (age > 1) { + amount = 1 + ThreadLocalRandom.current().nextInt(2); + if (age == 3) { + amount++; + } + } + + return new Item[]{Item.get(ItemID.SWEET_BERRIES, 0, amount)}; + } + + @Override + public boolean onActivate(Item item, Player player) { + int age = MathHelper.clamp(this.getDamage(), 0, 3); + + if (age < 3 && item.getId() == ItemID.DYE && item.getDamage() == DyeColor.WHITE.getDyeData()) { + BlockSweetBerryBush block = (BlockSweetBerryBush) this.clone(); + block.setDamage(block.getDamage() + 1); + if (block.getDamage() > 3) { + block.setDamage(3); + } + + BlockGrowEvent ev = new BlockGrowEvent(this, block); + this.getLevel().getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + + this.getLevel().setBlock(this, ev.getNewState(), false, true); + this.level.addParticle(new BoneMealParticle(this)); + + if (player != null && (player.gamemode & 0x01) == 0) { + item.count--; + } + return true; + } + + if (age < 2) { + return true; + } + + int amount = 1 + ThreadLocalRandom.current().nextInt(2); + if (age == 3) { + amount++; + } + + BlockHarvestEvent event = new BlockHarvestEvent(this, Block.get(SWEET_BERRY_BUSH, 1), new Item[]{Item.get(ItemID.SWEET_BERRIES, 0, amount)}); + this.getLevel().getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + this.getLevel().setBlock(this, event.getNewState(), true, true); + Item[] drops = event.getDrops(); + if (drops != null) { + Position dropPos = add(0.5, 0.5, 0.5); + for (Item drop : drops) { + if (drop != null) { + this.getLevel().dropItem(dropPos, drop); + } + } + } + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_SWEET_BERRY_BUSH_PICK); + } + return true; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!isSupportValid(this.down())) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } else if (type == Level.BLOCK_UPDATE_RANDOM) { + if (this.getDamage() < 3 && ThreadLocalRandom.current().nextInt(5) == 0) { + BlockGrowEvent event = new BlockGrowEvent(this, Block.get(this.getId(), this.getDamage() + 1)); + if (!event.isCancelled()) { + this.getLevel().setBlock(this, event.getNewState(), true, true); + } + } + return type; + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (target.getId() == SWEET_BERRY_BUSH || block.getId() != AIR) { + return false; + } + if (isSupportValid(this.down())) { + this.getLevel().setBlock(block, this, true); + return true; + } + return false; + } + + + public static boolean isSupportValid(Block block) { + switch (block.getId()) { + case GRASS: + case DIRT: + case PODZOL: + case MYCELIUM: + case FARMLAND: + return true; + default: + return false; + } + } + + @Override + public BlockColor getColor() { + return BlockColor.FOLIAGE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTNT.java b/src/main/java/cn/nukkit/block/BlockTNT.java index 462093738dc..f00f69d6952 100644 --- a/src/main/java/cn/nukkit/block/BlockTNT.java +++ b/src/main/java/cn/nukkit/block/BlockTNT.java @@ -2,16 +2,19 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityPrimedTNT; +import cn.nukkit.entity.projectile.EntityArrow; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.level.Sound; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.DoubleTag; import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/8 by xtypr. @@ -19,9 +22,6 @@ */ public class BlockTNT extends BlockSolid { - public BlockTNT() { - } - @Override public String getName() { return "TNT"; @@ -67,7 +67,7 @@ public void prime(int fuse) { public void prime(int fuse, Entity source) { this.getLevel().setBlock(this, Block.get(BlockID.AIR), true); - double mot = (new NukkitRandom()).nextSignedFloat() * Math.PI * 2; + double mot = Utils.nukkitRandom.nextSignedFloat() * 6.283185307179586; CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") .add(new DoubleTag("", this.x + 0.5)) @@ -80,21 +80,17 @@ public void prime(int fuse, Entity source) { .putList(new ListTag("Rotation") .add(new FloatTag("", 0)) .add(new FloatTag("", 0))) - .putShort("Fuse", fuse); - Entity tnt = Entity.createEntity("PrimedTnt", - this.getLevel().getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), + .putByte("Fuse", fuse); + Entity tnt = new EntityPrimedTNT( + this.getLevel().getChunk(this.getChunkX(), this.getChunkZ()), nbt, source ); - if(tnt == null) { - return; - } tnt.spawnToAll(); - this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_TNT); } @Override public int onUpdate(int type) { - if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && this.level.isBlockPowered(this.getLocation())) { + if ((type == Level.BLOCK_UPDATE_NORMAL || type == Level.BLOCK_UPDATE_REDSTONE) && this.level.isBlockPowered(this)) { this.prime(); } @@ -109,13 +105,15 @@ public boolean onActivate(Item item, Player player) { return true; } else if (item.getId() == Item.FIRE_CHARGE) { if (!player.isCreative()) item.count--; + this.level.addSound(this, Sound.MOB_GHAST_FIREBALL); this.prime(80, player); return true; - } else if (item.hasEnchantment(Enchantment.ID_FIRE_ASPECT)) { + } else if (item instanceof ItemTool && item.hasEnchantment(Enchantment.ID_FIRE_ASPECT)) { item.useOn(this); this.prime(80, player); return true; } + return false; } @@ -123,4 +121,17 @@ public boolean onActivate(Item item, Player player) { public BlockColor getColor() { return BlockColor.TNT_BLOCK_COLOR; } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + if (entity instanceof EntityArrow && entity.isOnFire()) { + entity.close(); + this.prime(); + } + } } diff --git a/src/main/java/cn/nukkit/block/BlockTallGrass.java b/src/main/java/cn/nukkit/block/BlockTallGrass.java index bdeebd2f16a..76cf82b3077 100644 --- a/src/main/java/cn/nukkit/block/BlockTallGrass.java +++ b/src/main/java/cn/nukkit/block/BlockTallGrass.java @@ -2,17 +2,16 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsWheat; +import cn.nukkit.item.ItemDye; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; import cn.nukkit.level.particle.BoneMealParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockTallGrass extends BlockFlowable { @@ -32,7 +31,7 @@ public int getId() { @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Grass", "Grass", "Fern", @@ -85,7 +84,7 @@ public int onUpdate(int type) { @Override public boolean onActivate(Item item, Player player) { - if (item.getId() == Item.DYE && item.getDamage() == 0x0f) { + if (item.getId() == Item.DYE && item.getDamage() == ItemDye.BONE_MEAL) { Block up = this.up(); if (up.getId() == AIR) { @@ -105,7 +104,7 @@ public boolean onActivate(Item item, Player player) { } if (meta != -1) { - if (player != null && (player.gamemode & 0x01) == 0) { + if (player != null && !player.isCreative()) { item.count--; } @@ -121,26 +120,18 @@ public boolean onActivate(Item item, Player player) { return false; } + @Override public Item[] getDrops(Item item) { - boolean dropSeeds = ThreadLocalRandom.current().nextInt(10) == 0; if (item.isShears()) { - //todo enchantment - if (dropSeeds) { - return new Item[]{ - new ItemSeedsWheat(), - Item.get(Item.TALL_GRASS, this.getDamage(), 1) - }; - } else { - return new Item[]{ - Item.get(Item.TALL_GRASS, this.getDamage(), 1) - }; - } + return new Item[]{ + Item.get(Item.TALL_GRASS, this.getDamage(), 1) + }; } - if (dropSeeds) { + if (Utils.random.nextInt(10) == 0) { return new Item[]{ - new ItemSeedsWheat() + Item.get(Item.WHEAT_SEEDS) }; } else { return new Item[0]; @@ -156,4 +147,9 @@ public int getToolType() { public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockTarget.java b/src/main/java/cn/nukkit/block/BlockTarget.java new file mode 100644 index 00000000000..287c2144bca --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTarget.java @@ -0,0 +1,109 @@ +package cn.nukkit.block; + +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.entity.projectile.EntityProjectile; +import cn.nukkit.entity.projectile.EntityThrownTrident; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockTarget extends BlockSolidMeta { + + public BlockTarget() { + this(0); + } + + public BlockTarget(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Target"; + } + + @Override + public int getId() { + return TARGET; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_HOE; + } + + @Override + public int getBurnChance() { + return 5; + } + + @Override + public int getBurnAbility() { + return 15; + } + + @Override + public double getHardness() { + return 0.5; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public BlockColor getColor() { + return BlockColor.WHITE_BLOCK_COLOR; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[]{new ItemBlock(Block.get(TARGET))}; + } + + @Override + public boolean isPowerSource() { + return true; + } + + @Override + public int getWeakPower(BlockFace face) { + return this.getDamage() > 0 ? 10 : 0; + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public void onEntityCollide(Entity entity) { + if (entity instanceof EntityProjectile) { + this.setDamage(1); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, true, false); // No need to send this to client + this.level.updateAroundRedstone(this, null); + + if (entity instanceof EntityArrow || entity instanceof EntityThrownTrident) { + this.level.scheduleUpdate(this, 20); + } else { + this.level.scheduleUpdate(this, 8); + } + } + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_SCHEDULED) { + this.setDamage(0); + this.level.setBlock((int) this.x, (int) this.y, (int) this.z, BlockLayer.NORMAL, this, false, true, false); // No need to send this to client + this.level.updateAroundRedstone(this, null); + return Level.BLOCK_UPDATE_SCHEDULED; + } + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTerracotta.java b/src/main/java/cn/nukkit/block/BlockTerracotta.java index 58f163a0379..d7aa085192d 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracotta.java +++ b/src/main/java/cn/nukkit/block/BlockTerracotta.java @@ -3,6 +3,7 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemTool; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.DyeColor; import cn.nukkit.utils.TerracottaColor; /** @@ -10,6 +11,7 @@ * Package cn.nukkit.block in project Nukkit . */ public class BlockTerracotta extends BlockSolidMeta { + public BlockTerracotta() { this(0); } @@ -18,6 +20,10 @@ public BlockTerracotta(int meta) { super(0); } + public BlockTerracotta(DyeColor dyeColor) { + this(dyeColor.getWoolData()); + } + public BlockTerracotta(TerracottaColor dyeColor) { this(dyeColor.getTerracottaData()); } @@ -49,7 +55,7 @@ public double getResistance() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; diff --git a/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java b/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java index 5edfde12f6d..65e22f1f337 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java +++ b/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java @@ -2,14 +2,16 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.math.BlockFace; -import cn.nukkit.utils.Faceable; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.DyeColor; /** * Created by CreeperFace on 2.6.2017. */ -public abstract class BlockTerracottaGlazed extends BlockSolidMeta implements Faceable { +public abstract class BlockTerracottaGlazed extends BlockSolidMeta { public BlockTerracottaGlazed() { this(0); @@ -41,8 +43,7 @@ public Item[] getDrops(Item item) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = {2, 5, 3, 4}; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); return this.getLevel().setBlock(block, this, true, true); } @@ -52,7 +53,16 @@ public boolean canHarvestWithHand() { } @Override - public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + public BlockColor getColor() { + return DyeColor.getByDyeData(getDyeColor().getDyeData()).getColor(); + } + + public DyeColor getDyeColor() { + return DyeColor.BLACK; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); } } diff --git a/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java b/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java index 3e7f4ec62b1..2c7f72b0bad 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java +++ b/src/main/java/cn/nukkit/block/BlockTerracottaGlazedLime.java @@ -25,5 +25,7 @@ public String getName() { return "Lime Glazed Terracotta"; } - public DyeColor getDyeColor() { return DyeColor.LIME; } + public DyeColor getDyeColor() { + return DyeColor.LIME; + } } diff --git a/src/main/java/cn/nukkit/block/BlockTerracottaStained.java b/src/main/java/cn/nukkit/block/BlockTerracottaStained.java index fa538493acb..96df1ce9ef8 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracottaStained.java +++ b/src/main/java/cn/nukkit/block/BlockTerracottaStained.java @@ -51,7 +51,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{toItem()}; } else { return new Item[0]; @@ -66,5 +66,4 @@ public BlockColor getColor() { public DyeColor getDyeColor() { return DyeColor.getByWoolData(getDamage()); } - } diff --git a/src/main/java/cn/nukkit/block/BlockThin.java b/src/main/java/cn/nukkit/block/BlockThin.java index 9b309160dcf..2997f42dbc5 100644 --- a/src/main/java/cn/nukkit/block/BlockThin.java +++ b/src/main/java/cn/nukkit/block/BlockThin.java @@ -19,38 +19,47 @@ public boolean isSolid() { } protected AxisAlignedBB recalculateBoundingBox() { - final double offNW = 7.0 / 16.0; - final double offSE = 9.0 / 16.0; - final double onNW = 0.0; - final double onSE = 1.0; - double w = offNW; - double e = offSE; - double n = offNW; - double s = offSE; + double f = 0.4375; + double f1 = 0.5625; + double f2 = 0.4375; + double f3 = 0.5625; try { - boolean north = this.canConnect(this.north()); - boolean south = this.canConnect(this.south()); - boolean west = this.canConnect(this.west()); - boolean east = this.canConnect(this.east()); - w = west ? onNW : offNW; - e = east ? onSE : offSE; - n = north ? onNW : offNW; - s = south ? onSE : offSE; - } catch (LevelException ignore) { - //null sucks - } + boolean flag = this.canConnect(this.north()); + boolean flag1 = this.canConnect(this.south()); + boolean flag2 = this.canConnect(this.west()); + boolean flag3 = this.canConnect(this.east()); + if ((!flag2 || !flag3) && (flag2 || flag3 || flag || flag1)) { + if (flag2) { + f = 0; + } else if (flag3) { + f1 = 1; + } + } else { + f = 0; + f1 = 1; + } + if ((!flag || !flag1) && (flag2 || flag3 || flag || flag1)) { + if (flag) { + f2 = 0; + } else if (flag1) { + f3 = 1; + } + } else { + f2 = 0; + f3 = 1; + } + } catch (LevelException ignore) {} return new SimpleAxisAlignedBB( - this.x + w, + this.x + f, this.y, - this.z + n, - this.x + e, + this.z + f2, + this.x + f1, this.y + 1, - this.z + s + this.z + f3 ); } public boolean canConnect(Block block) { return block.isSolid() || block.getId() == this.getId() || block.getId() == GLASS_PANE || block.getId() == GLASS; } - } diff --git a/src/main/java/cn/nukkit/block/BlockTilesDeepslate.java b/src/main/java/cn/nukkit/block/BlockTilesDeepslate.java new file mode 100644 index 00000000000..8b71ccd91a3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTilesDeepslate.java @@ -0,0 +1,52 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockTilesDeepslate extends BlockSolid { + + public BlockTilesDeepslate() { + // Does nothing + } + + @Override + public int getId() { + return DEEPSLATE_TILES; + } + + @Override + public String getName() { + return "Deepslate Tiles"; + } + + @Override + public double getHardness() { + return 3.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + + @Override + public BlockColor getColor() { + return BlockColor.DEEPSLATE_GRAY; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTilesDeepslateCracked.java b/src/main/java/cn/nukkit/block/BlockTilesDeepslateCracked.java new file mode 100644 index 00000000000..504757997b9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTilesDeepslateCracked.java @@ -0,0 +1,18 @@ +package cn.nukkit.block; + +public class BlockTilesDeepslateCracked extends BlockTilesDeepslate { + + public BlockTilesDeepslateCracked() { + // Does nothing + } + + @Override + public int getId() { + return CRACKED_DEEPSLATE_TILES; + } + + @Override + public String getName() { + return "Cracked Deepslate Tiles"; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTorch.java b/src/main/java/cn/nukkit/block/BlockTorch.java index f9f1b1316d3..4886ee313b3 100644 --- a/src/main/java/cn/nukkit/block/BlockTorch.java +++ b/src/main/java/cn/nukkit/block/BlockTorch.java @@ -14,7 +14,7 @@ */ public class BlockTorch extends BlockFlowable implements Faceable { - private static final int[] faces = new int[]{ + private static final short[] faces = { 0, //0, never used 5, //1 4, //2 @@ -23,7 +23,7 @@ public class BlockTorch extends BlockFlowable implements Faceable { 1, //5 }; - private static final int[] faces2 = new int[]{ + private static final short[] faces2 = { 0, //0 4, //1 5, //2 @@ -64,7 +64,7 @@ public int onUpdate(int type) { Block block = this.getSide(BlockFace.fromIndex(faces2[side])); int id = block.getId(); - if ((block.isTransparent() && !(side == 0 && (below instanceof BlockFence || below.getId() == COBBLE_WALL))) && id != GLASS && id != STAINED_GLASS) { + if ((block.isTransparent() && !(side == 0 && (below instanceof BlockFence || below.getId() == COBBLE_WALL))) && id != GLASS && id != STAINED_GLASS && id != HARD_STAINED_GLASS) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; } @@ -75,16 +75,20 @@ public int onUpdate(int type) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (block instanceof BlockWater) { + return false; + } + int side = faces[face.getIndex()]; int bid = this.getSide(BlockFace.fromIndex(faces2[side])).getId(); - if ((!target.isTransparent() || bid == GLASS || bid == STAINED_GLASS) && face != BlockFace.DOWN) { + if ((!target.isTransparent() || bid == GLASS || bid == STAINED_GLASS || bid == HARD_STAINED_GLASS) && face != BlockFace.DOWN) { this.setDamage(side); this.getLevel().setBlock(block, this, true, true); return true; } Block below = this.down(); - if (!below.isTransparent() || below instanceof BlockFence || below.getId() == COBBLE_WALL || below.getId() == GLASS || below.getId() == STAINED_GLASS) { + if (!below.isTransparent() || below instanceof BlockFence || below.getId() == COBBLE_WALL || below.getId() == GLASS || below.getId() == STAINED_GLASS || below.getId() == HARD_STAINED_GLASS) { this.setDamage(0); this.getLevel().setBlock(block, this, true, true); return true; @@ -94,7 +98,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override @@ -121,4 +125,9 @@ public BlockFace getBlockFace(int meta) { return BlockFace.UP; } } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockTransparent.java b/src/main/java/cn/nukkit/block/BlockTransparent.java index 3ed36d2ca51..0499a649686 100644 --- a/src/main/java/cn/nukkit/block/BlockTransparent.java +++ b/src/main/java/cn/nukkit/block/BlockTransparent.java @@ -3,7 +3,7 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockTransparent extends Block { @@ -17,5 +17,4 @@ public boolean isTransparent() { public BlockColor getColor() { return BlockColor.TRANSPARENT_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockTransparentMeta.java b/src/main/java/cn/nukkit/block/BlockTransparentMeta.java index 0ddf58d7a67..bc96d517fcb 100644 --- a/src/main/java/cn/nukkit/block/BlockTransparentMeta.java +++ b/src/main/java/cn/nukkit/block/BlockTransparentMeta.java @@ -3,7 +3,7 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockTransparentMeta extends BlockMeta { @@ -25,5 +25,4 @@ public boolean isTransparent() { public BlockColor getColor() { return BlockColor.TRANSPARENT_BLOCK_COLOR; } - } diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoor.java b/src/main/java/cn/nukkit/block/BlockTrapdoor.java index bc6884b0d9b..a96e7f944ba 100644 --- a/src/main/java/cn/nukkit/block/BlockTrapdoor.java +++ b/src/main/java/cn/nukkit/block/BlockTrapdoor.java @@ -7,10 +7,10 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemTool; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; import cn.nukkit.math.SimpleAxisAlignedBB; -import cn.nukkit.network.protocol.LevelEventPacket; import cn.nukkit.utils.BlockColor; import cn.nukkit.utils.Faceable; @@ -22,6 +22,8 @@ public class BlockTrapdoor extends BlockTransparentMeta implements Faceable { public static final int TRAPDOOR_OPEN_BIT = 0x08; public static final int TRAPDOOR_TOP_BIT = 0x04; + private static final int[] faces = {2, 1, 3, 0}; + public BlockTrapdoor() { this(0); } @@ -167,11 +169,16 @@ public double getMaxZ() { @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_REDSTONE) { - if ((!isOpen() && this.level.isBlockPowered(this.getLocation())) || (isOpen() && !this.level.isBlockPowered(this.getLocation()))) { + boolean powered = this.level.isBlockPowered(this); + if ((!isOpen() && powered) || (isOpen() && !powered)) { this.level.getServer().getPluginManager().callEvent(new BlockRedstoneEvent(this, isOpen() ? 15 : 0, isOpen() ? 0 : 15)); this.setDamage(this.getDamage() ^ TRAPDOOR_OPEN_BIT); this.level.setBlock(this, this, true); - this.level.addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_DOOR); + if (this.isOpen()) { + this.level.addSound(this, Sound.RANDOM_DOOR_OPEN); + } else { + this.level.addSound(this, Sound.RANDOM_DOOR_CLOSE); + } return type; } } @@ -193,40 +200,41 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl top = face != BlockFace.UP; } - int[] faces = {2, 1, 3, 0}; - int faceBit = faces[facing.getHorizontalIndex()]; - meta |= faceBit; + meta |= faces[facing.getHorizontalIndex()]; if (top) { meta |= TRAPDOOR_TOP_BIT; } + this.setDamage(meta); - this.getLevel().setBlock(block, this, true, true); + + this.getLevel().setBlock(this, this, true, true); return true; } @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public boolean onActivate(Item item, Player player) { - if(toggle(player)) { - this.level.addLevelEvent(this.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_DOOR); - return true; - } - return false; + return toggle(player); } public boolean toggle(Player player) { DoorToggleEvent ev = new DoorToggleEvent(this, player); - getLevel().getServer().getPluginManager().callEvent(ev); - if(ev.isCancelled()) { + level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { return false; } this.setDamage(this.getDamage() ^ TRAPDOOR_OPEN_BIT); - getLevel().setBlock(this, this, true); + level.setBlock(this, this, true, true); + if (this.isOpen()) { + this.level.addSound(this, Sound.RANDOM_DOOR_OPEN); + } else { + this.level.addSound(this, Sound.RANDOM_DOOR_CLOSE); + } return true; } @@ -245,6 +253,11 @@ public boolean isTop() { @Override public BlockFace getBlockFace() { - return BlockFace.fromHorizontalIndex(this.getDamage() & 0x07); + return BlockFace.fromHorizontalIndex(this.getDamage() & 0x7); + } + + @Override + public boolean canPassThrough() { + return this.isOpen(); } } diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoorAcacia.java b/src/main/java/cn/nukkit/block/BlockTrapdoorAcacia.java new file mode 100644 index 00000000000..ddaa2123418 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTrapdoorAcacia.java @@ -0,0 +1,41 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockTrapdoorAcacia extends BlockTrapdoor { + + public BlockTrapdoorAcacia() { + this(0); + } + + public BlockTrapdoorAcacia(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Acacia Trapdoor"; + } + + @Override + public int getId() { + return ACACIA_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoorBirch.java b/src/main/java/cn/nukkit/block/BlockTrapdoorBirch.java new file mode 100644 index 00000000000..0aced89bb68 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTrapdoorBirch.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockTrapdoorBirch extends BlockTrapdoor { + + public BlockTrapdoorBirch() { + this(0); + } + + public BlockTrapdoorBirch(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Birch Trapdoor"; + } + + @Override + public int getId() { + return BIRCH_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.SAND_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoorDarkOak.java b/src/main/java/cn/nukkit/block/BlockTrapdoorDarkOak.java new file mode 100644 index 00000000000..b0fe6ef8e10 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTrapdoorDarkOak.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockTrapdoorDarkOak extends BlockTrapdoor { + + public BlockTrapdoorDarkOak() { + this(0); + } + + public BlockTrapdoorDarkOak(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Dark Oak Trapdoor"; + } + + @Override + public int getId() { + return DARK_OAK_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.BROWN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoorJungle.java b/src/main/java/cn/nukkit/block/BlockTrapdoorJungle.java new file mode 100644 index 00000000000..93c0c5464f6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTrapdoorJungle.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockTrapdoorJungle extends BlockTrapdoor { + + public BlockTrapdoorJungle() { + this(0); + } + + public BlockTrapdoorJungle(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Jungle Trapdoor"; + } + + @Override + public int getId() { + return JUNGLE_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoorSpruce.java b/src/main/java/cn/nukkit/block/BlockTrapdoorSpruce.java new file mode 100644 index 00000000000..81ee2e6dc9d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTrapdoorSpruce.java @@ -0,0 +1,36 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.utils.BlockColor; + +public class BlockTrapdoorSpruce extends BlockTrapdoor { + + public BlockTrapdoorSpruce() { + this(0); + } + + public BlockTrapdoorSpruce(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Spruce Trapdoor"; + } + + @Override + public int getId() { + return SPRUCE_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public BlockColor getColor() { + return BlockColor.SPRUCE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTrappedChest.java b/src/main/java/cn/nukkit/block/BlockTrappedChest.java index 6b4916c3699..aba41a49f0f 100644 --- a/src/main/java/cn/nukkit/block/BlockTrappedChest.java +++ b/src/main/java/cn/nukkit/block/BlockTrappedChest.java @@ -34,10 +34,8 @@ public String getName() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - int[] faces = {2, 5, 3, 4}; - BlockEntityChest chest = null; - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); for (BlockFace side : Plane.HORIZONTAL) { if ((this.getDamage() == 4 || this.getDamage() == 5) && (side == BlockFace.WEST || side == BlockFace.EAST)) { @@ -55,7 +53,8 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); + CompoundTag nbt = new CompoundTag("") .putList(new ListTag<>("Items")) .putString("id", BlockEntity.CHEST) @@ -74,11 +73,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl } } - BlockEntityChest blockEntity = (BlockEntityChest) BlockEntity.createBlockEntity(BlockEntity.CHEST, this.getLevel().getChunk((int) (this.x) >> 4, (int) (this.z) >> 4), nbt); - - if (blockEntity == null) { - return false; - } + BlockEntityChest blockEntity = (BlockEntityChest) BlockEntity.createBlockEntity(BlockEntity.CHEST, this.getChunk(), nbt); if (chest != null) { chest.pairWith(blockEntity); diff --git a/src/main/java/cn/nukkit/block/BlockTripWire.java b/src/main/java/cn/nukkit/block/BlockTripWire.java index 8d63ebf0564..e0a28186cd3 100644 --- a/src/main/java/cn/nukkit/block/BlockTripWire.java +++ b/src/main/java/cn/nukkit/block/BlockTripWire.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemString; import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; @@ -31,21 +30,6 @@ public String getName() { return "Tripwire"; } - @Override - public boolean canPassThrough() { - return true; - } - - @Override - public double getResistance() { - return 0; - } - - @Override - public double getHardness() { - return 0; - } - @Override public AxisAlignedBB getBoundingBox() { return null; @@ -53,7 +37,7 @@ public AxisAlignedBB getBoundingBox() { @Override public Item toItem() { - return new ItemString(); + return Item.get(Item.STRING); } public boolean isPowered() { @@ -115,7 +99,7 @@ public void updateHook(boolean scheduleUpdate) { hook.calculateState(false, true, i, this); } - /*if(scheduleUpdate) { + /*if (scheduleUpdate) { this.level.scheduleUpdate(hook, 10); }*/ break; @@ -136,7 +120,8 @@ public int onUpdate(int type) { } boolean found = false; - for (Entity entity : this.level.getCollidingEntities(this.getCollisionBoundingBox())) { + Entity[] e = this.level.getCollidingEntities(this.getCollisionBoundingBox()); + for (Entity entity : e) { if (!entity.doesTriggerPressurePlate()) { continue; } @@ -190,4 +175,19 @@ public double getMaxY() { protected AxisAlignedBB recalculateCollisionBoundingBox() { return this; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockTripWireHook.java b/src/main/java/cn/nukkit/block/BlockTripWireHook.java index c286e1be24c..42c8bb4dd79 100644 --- a/src/main/java/cn/nukkit/block/BlockTripWireHook.java +++ b/src/main/java/cn/nukkit/block/BlockTripWireHook.java @@ -82,7 +82,7 @@ public boolean onBreak(Item item) { if (powered) { this.level.updateAroundRedstone(this, null); - this.level.updateAroundRedstone(this.getLocation().getSide(getFacing().getOpposite()), null); + this.level.updateAroundRedstone(this.getSideVec(getFacing().getOpposite()), null); } return true; @@ -90,7 +90,6 @@ public boolean onBreak(Item item) { public void calculateState(boolean onBreak, boolean updateAround, int pos, Block block) { BlockFace facing = getFacing(); - Vector3 v = this.getLocation(); boolean attached = isAttached(); boolean powered = isPowered(); boolean canConnect = !onBreak; @@ -99,8 +98,7 @@ public void calculateState(boolean onBreak, boolean updateAround, int pos, Block Block[] blocks = new Block[42]; for (int i = 1; i < 42; ++i) { - Vector3 vector = v.getSide(facing, i); - Block b = this.level.getBlock(vector); + Block b = this.getSide(facing, i); if (b instanceof BlockTripWireHook) { if (((BlockTripWireHook) b).getFacing() == facing.getOpposite()) { @@ -133,36 +131,36 @@ public void calculateState(boolean onBreak, boolean updateAround, int pos, Block canConnect = canConnect & distance > 1; nextPowered = nextPowered & canConnect; - BlockTripWireHook hook = (BlockTripWireHook) Block.get(BlockID.TRIPWIRE_HOOK); + BlockTripWireHook hook = (BlockTripWireHook) Block.get(TRIPWIRE_HOOK); hook.setAttached(canConnect); hook.setPowered(nextPowered); if (distance > 0) { - Vector3 vec = v.getSide(facing, distance); + Vector3 vec = this.getSideVec(facing, distance); BlockFace face = facing.getOpposite(); hook.setFace(face); this.level.setBlock(vec, hook, true, false); this.level.updateAroundRedstone(vec, null); - this.level.updateAroundRedstone(vec.getSide(face.getOpposite()), null); + this.level.updateAroundRedstone(vec.getSideVec(face.getOpposite()), null); this.addSound(vec, canConnect, nextPowered, attached, powered); } - this.addSound(v, canConnect, nextPowered, attached, powered); + this.addSound(this, canConnect, nextPowered, attached, powered); if (!onBreak) { hook.setFace(facing); - this.level.setBlock(v, hook, true, false); + this.level.setBlock(this, hook, true, false); if (updateAround) { - this.level.updateAroundRedstone(v, null); - this.level.updateAroundRedstone(v.getSide(facing.getOpposite()), null); + this.level.updateAroundRedstone(this, null); + this.level.updateAroundRedstone(this.getSideVec(facing.getOpposite()), null); } } if (attached != canConnect) { for (int i = 1; i < distance; i++) { - Vector3 vc = v.getSide(facing, i); + Vector3 vc = this.getSideVec(facing, i); block = blocks[i]; if (block != null && this.level.getBlockIdAt(vc.getFloorX(), vc.getFloorY(), vc.getFloorZ()) != Block.AIR) { @@ -232,6 +230,21 @@ public int getStrongPower(BlockFace side) { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.FLOW_INTO_BLOCK; + } + + @Override + public boolean canBeFlowedInto() { + return false; + } + + @Override + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockTuff.java b/src/main/java/cn/nukkit/block/BlockTuff.java new file mode 100644 index 00000000000..c2491ae0925 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTuff.java @@ -0,0 +1,51 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockTuff extends BlockSolid { + + public BlockTuff() { + // Does Nothing + } + + @Override + public String getName() { + return "Tuff"; + } + + @Override + public int getId() { + return TUFF; + } + + @Override + public double getHardness() { + return 1.5; + } + + @Override + public double getResistance() { + return 6; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_PICKAXE; + } + + @Override + public int getToolTier() { + return ItemTool.TIER_WOODEN; + } + + @Override + public BlockColor getColor() { + return BlockColor.GRAY_TERRACOTA_BLOCK_COLOR; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTurtleEgg.java b/src/main/java/cn/nukkit/block/BlockTurtleEgg.java new file mode 100644 index 00000000000..5394b752141 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTurtleEgg.java @@ -0,0 +1,111 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; + +public class BlockTurtleEgg extends BlockTransparentMeta { + + public BlockTurtleEgg() { + this(0); + } + + public BlockTurtleEgg(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Turtle Egg"; + } + + @Override + public int getId() { + return TURTLE_EGG; + } + + @Override + public double getResistance() { + return 0.5; + } + + @Override + public double getHardness() { + return 0.5; + } + + @Override + public boolean canHarvestWithHand() { + return false; + } + + @Override + public boolean canSilkTouch() { + return true; + } + + @Override + public Item[] getDrops(Item item) { + return new Item[0]; + } + + @Override + public double getMinX() { + return this.x + 0.2; + } + + @Override + public double getMinZ() { + return this.z + 0.2; + } + + @Override + public double getMaxX() { + return this.x + 0.8; + } + + @Override + public double getMaxY() { + return this.y + 0.45; + } + + @Override + public double getMaxZ() { + return this.z + 0.8; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (!canStayOnFullSolid(this.down())) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } + return 0; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + if (target instanceof BlockTurtleEgg && this.getDamage() < 3) { + this.setDamage(this.getDamage() + 1); + return this.getLevel().setBlock(target, this, true, true); + } + if (!canStayOnFullSolid(this.down())) { + return false; + } + this.getLevel().setBlock(this, this, true, true); + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockTypes.java b/src/main/java/cn/nukkit/block/BlockTypes.java new file mode 100644 index 00000000000..e8a637860db --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockTypes.java @@ -0,0 +1,677 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.material.BlockType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Data; + +public class BlockTypes { + private static final Int2ObjectMap types = new Int2ObjectOpenHashMap<>(); + private static final Object2ObjectMap identifiers = new Object2ObjectOpenHashMap<>(); + + public static final BlockType AIR = register("minecraft:air", BlockID.AIR); + public static final BlockType STONE = register("minecraft:stone", BlockID.STONE); + public static final BlockType GRASS = register("minecraft:grass", BlockID.GRASS); + public static final BlockType DIRT = register("minecraft:dirt", BlockID.DIRT); + public static final BlockType COBBLESTONE = register("minecraft:cobblestone", BlockID.COBBLESTONE); + public static final BlockType COBBLE = register("minecraft:cobblestone", BlockID.COBBLE); + public static final BlockType PLANK = register("minecraft:planks", BlockID.PLANK); + public static final BlockType PLANKS = register("minecraft:planks", BlockID.PLANKS); + public static final BlockType WOODEN_PLANK = register("minecraft:planks", BlockID.WOODEN_PLANK); + public static final BlockType WOODEN_PLANKS = register("minecraft:planks", BlockID.WOODEN_PLANKS); + public static final BlockType SAPLING = register("minecraft:sapling", BlockID.SAPLING); + public static final BlockType SAPLINGS = register("minecraft:sapling", BlockID.SAPLINGS); + public static final BlockType BEDROCK = register("minecraft:bedrock", BlockID.BEDROCK); + public static final BlockType WATER = register("minecraft:flowing_water", BlockID.WATER); + public static final BlockType STILL_WATER = register("minecraft:water", BlockID.STILL_WATER); + public static final BlockType LAVA = register("minecraft:flowing_lava", BlockID.LAVA); + public static final BlockType STILL_LAVA = register("minecraft:lava", BlockID.STILL_LAVA); + public static final BlockType SAND = register("minecraft:sand", BlockID.SAND); + public static final BlockType GRAVEL = register("minecraft:gravel", BlockID.GRAVEL); + public static final BlockType GOLD_ORE = register("minecraft:gold_ore", BlockID.GOLD_ORE); + public static final BlockType IRON_ORE = register("minecraft:iron_ore", BlockID.IRON_ORE); + public static final BlockType COAL_ORE = register("minecraft:coal_ore", BlockID.COAL_ORE); + public static final BlockType LOG = register("minecraft:log", BlockID.LOG); + public static final BlockType WOOD = register("minecraft:log", BlockID.WOOD); + public static final BlockType TRUNK = register("minecraft:log", BlockID.TRUNK); + public static final BlockType LEAVES = register("minecraft:leaves", BlockID.LEAVES); + public static final BlockType LEAVE = register("minecraft:leaves", BlockID.LEAVE); + public static final BlockType SPONGE = register("minecraft:sponge", BlockID.SPONGE); + public static final BlockType GLASS = register("minecraft:glass", BlockID.GLASS); + public static final BlockType LAPIS_ORE = register("minecraft:lapis_ore", BlockID.LAPIS_ORE); + public static final BlockType LAPIS_BLOCK = register("minecraft:lapis_block", BlockID.LAPIS_BLOCK); + public static final BlockType DISPENSER = register("minecraft:dispenser", BlockID.DISPENSER); + public static final BlockType SANDSTONE = register("minecraft:sandstone", BlockID.SANDSTONE); + public static final BlockType NOTEBLOCK = register("minecraft:noteblock", BlockID.NOTEBLOCK); + public static final BlockType BED_BLOCK = register("minecraft:item.bed", BlockID.BED_BLOCK); + public static final BlockType POWERED_RAIL = register("minecraft:golden_rail", BlockID.POWERED_RAIL); + public static final BlockType DETECTOR_RAIL = register("minecraft:detector_rail", BlockID.DETECTOR_RAIL); + public static final BlockType STICKY_PISTON = register("minecraft:sticky_piston", BlockID.STICKY_PISTON); + public static final BlockType COBWEB = register("minecraft:web", BlockID.COBWEB); + public static final BlockType WEB = register("minecraft:web", BlockID.WEB); + public static final BlockType TALL_GRASS = register("minecraft:tallgrass", BlockID.TALL_GRASS); + public static final BlockType BUSH = register("minecraft:deadbush", BlockID.BUSH); + public static final BlockType DEAD_BUSH = register("minecraft:deadbush", BlockID.DEAD_BUSH); + public static final BlockType DEADBUSH = register("minecraft:deadbush", BlockID.DEADBUSH); + public static final BlockType PISTON = register("minecraft:piston", BlockID.PISTON); + public static final BlockType PISTON_HEAD = register("minecraft:piston_arm_collision", BlockID.PISTON_HEAD); + public static final BlockType WOOL = register("minecraft:wool", BlockID.WOOL); + public static final BlockType WHITE_WOOL = register("minecraft:wool", BlockID.WHITE_WOOL); + public static final BlockType DANDELION = register("minecraft:yellow_flower", BlockID.DANDELION); + public static final BlockType POPPY = register("minecraft:red_flower", BlockID.POPPY); + public static final BlockType ROSE = register("minecraft:red_flower", BlockID.ROSE); + public static final BlockType FLOWER = register("minecraft:red_flower", BlockID.FLOWER); + public static final BlockType RED_FLOWER = register("minecraft:red_flower", BlockID.RED_FLOWER); + public static final BlockType BROWN_MUSHROOM = register("minecraft:brown_mushroom", BlockID.BROWN_MUSHROOM); + public static final BlockType RED_MUSHROOM = register("minecraft:red_mushroom", BlockID.RED_MUSHROOM); + public static final BlockType GOLD_BLOCK = register("minecraft:gold_block", BlockID.GOLD_BLOCK); + public static final BlockType IRON_BLOCK = register("minecraft:iron_block", BlockID.IRON_BLOCK); + public static final BlockType DOUBLE_SLAB = register("minecraft:double_stone_block_slab", BlockID.DOUBLE_SLAB); + public static final BlockType DOUBLE_STONE_SLAB = register("minecraft:double_stone_block_slab", BlockID.DOUBLE_STONE_SLAB); + public static final BlockType DOUBLE_SLABS = register("minecraft:double_stone_block_slab", BlockID.DOUBLE_SLABS); + public static final BlockType SLAB = register("minecraft:stone_block_slab", BlockID.SLAB); + public static final BlockType STONE_SLAB = register("minecraft:stone_block_slab", BlockID.STONE_SLAB); + public static final BlockType SLABS = register("minecraft:stone_block_slab", BlockID.SLABS); + public static final BlockType BRICKS = register("minecraft:brick_block", BlockID.BRICKS); + public static final BlockType BRICKS_BLOCK = register("minecraft:brick_block", BlockID.BRICKS_BLOCK); + public static final BlockType TNT = register("minecraft:tnt", BlockID.TNT); + public static final BlockType BOOKSHELF = register("minecraft:bookshelf", BlockID.BOOKSHELF); + public static final BlockType MOSS_STONE = register("minecraft:mossy_cobblestone", BlockID.MOSS_STONE); + public static final BlockType MOSSY_STONE = register("minecraft:mossy_cobblestone", BlockID.MOSSY_STONE); + public static final BlockType OBSIDIAN = register("minecraft:obsidian", BlockID.OBSIDIAN); + public static final BlockType TORCH = register("minecraft:torch", BlockID.TORCH); + public static final BlockType FIRE = register("minecraft:fire", BlockID.FIRE); + public static final BlockType MONSTER_SPAWNER = register("minecraft:mob_spawner", BlockID.MONSTER_SPAWNER); + public static final BlockType WOOD_STAIRS = register("minecraft:oak_stairs", BlockID.WOOD_STAIRS); + public static final BlockType WOODEN_STAIRS = register("minecraft:oak_stairs", BlockID.WOODEN_STAIRS); + public static final BlockType OAK_WOOD_STAIRS = register("minecraft:oak_stairs", BlockID.OAK_WOOD_STAIRS); + public static final BlockType OAK_WOODEN_STAIRS = register("minecraft:oak_stairs", BlockID.OAK_WOODEN_STAIRS); + public static final BlockType CHEST = register("minecraft:chest", BlockID.CHEST); + public static final BlockType REDSTONE_WIRE = register("minecraft:redstone_wire", BlockID.REDSTONE_WIRE); + public static final BlockType DIAMOND_ORE = register("minecraft:diamond_ore", BlockID.DIAMOND_ORE); + public static final BlockType DIAMOND_BLOCK = register("minecraft:diamond_block", BlockID.DIAMOND_BLOCK); + public static final BlockType CRAFTING_TABLE = register("minecraft:crafting_table", BlockID.CRAFTING_TABLE); + public static final BlockType WORKBENCH = register("minecraft:crafting_table", BlockID.WORKBENCH); + public static final BlockType WHEAT_BLOCK = register("minecraft:item.wheat", BlockID.WHEAT_BLOCK); + public static final BlockType FARMLAND = register("minecraft:farmland", BlockID.FARMLAND); + public static final BlockType FURNACE = register("minecraft:furnace", BlockID.FURNACE); + public static final BlockType BURNING_FURNACE = register("minecraft:lit_furnace", BlockID.BURNING_FURNACE); + public static final BlockType LIT_FURNACE = register("minecraft:lit_furnace", BlockID.LIT_FURNACE); + public static final BlockType SIGN_POST = register("minecraft:standing_sign", BlockID.SIGN_POST); + public static final BlockType DOOR_BLOCK = register("minecraft:item.wooden_door", BlockID.DOOR_BLOCK); + public static final BlockType WOODEN_DOOR_BLOCK = register("minecraft:item.wooden_door", BlockID.WOODEN_DOOR_BLOCK); + public static final BlockType WOOD_DOOR_BLOCK = register("minecraft:item.wooden_door", BlockID.WOOD_DOOR_BLOCK); + public static final BlockType LADDER = register("minecraft:ladder", BlockID.LADDER); + public static final BlockType RAIL = register("minecraft:rail", BlockID.RAIL); + public static final BlockType COBBLE_STAIRS = register("minecraft:stone_stairs", BlockID.COBBLE_STAIRS); + public static final BlockType COBBLESTONE_STAIRS = register("minecraft:stone_stairs", BlockID.COBBLESTONE_STAIRS); + public static final BlockType WALL_SIGN = register("minecraft:wall_sign", BlockID.WALL_SIGN); + public static final BlockType LEVER = register("minecraft:lever", BlockID.LEVER); + public static final BlockType STONE_PRESSURE_PLATE = register("minecraft:stone_pressure_plate", BlockID.STONE_PRESSURE_PLATE); + public static final BlockType IRON_DOOR_BLOCK = register("minecraft:item.iron_door", BlockID.IRON_DOOR_BLOCK); + public static final BlockType WOODEN_PRESSURE_PLATE = register("minecraft:wooden_pressure_plate", BlockID.WOODEN_PRESSURE_PLATE); + public static final BlockType REDSTONE_ORE = register("minecraft:redstone_ore", BlockID.REDSTONE_ORE); + public static final BlockType GLOWING_REDSTONE_ORE = register("minecraft:lit_redstone_ore", BlockID.GLOWING_REDSTONE_ORE); + public static final BlockType LIT_REDSTONE_ORE = register("minecraft:lit_redstone_ore", BlockID.LIT_REDSTONE_ORE); + public static final BlockType UNLIT_REDSTONE_TORCH = register("minecraft:unlit_redstone_torch", BlockID.UNLIT_REDSTONE_TORCH); + public static final BlockType REDSTONE_TORCH = register("minecraft:redstone_torch", BlockID.REDSTONE_TORCH); + public static final BlockType STONE_BUTTON = register("minecraft:stone_button", BlockID.STONE_BUTTON); + public static final BlockType SNOW = register("minecraft:snow_layer", BlockID.SNOW); + public static final BlockType SNOW_LAYER = register("minecraft:snow_layer", BlockID.SNOW_LAYER); + public static final BlockType ICE = register("minecraft:ice", BlockID.ICE); + public static final BlockType SNOW_BLOCK = register("minecraft:snow", BlockID.SNOW_BLOCK); + public static final BlockType CACTUS = register("minecraft:cactus", BlockID.CACTUS); + public static final BlockType CLAY_BLOCK = register("minecraft:clay", BlockID.CLAY_BLOCK); + public static final BlockType REEDS = register("minecraft:item.reeds", BlockID.REEDS); + public static final BlockType SUGARCANE_BLOCK = register("minecraft:item.reeds", BlockID.SUGARCANE_BLOCK); + public static final BlockType JUKEBOX = register("minecraft:jukebox", BlockID.JUKEBOX); + public static final BlockType FENCE = register("minecraft:fence", BlockID.FENCE); + public static final BlockType PUMPKIN = register("minecraft:pumpkin", BlockID.PUMPKIN); + public static final BlockType NETHERRACK = register("minecraft:netherrack", BlockID.NETHERRACK); + public static final BlockType SOUL_SAND = register("minecraft:soul_sand", BlockID.SOUL_SAND); + public static final BlockType GLOWSTONE = register("minecraft:glowstone", BlockID.GLOWSTONE); + public static final BlockType GLOWSTONE_BLOCK = register("minecraft:glowstone", BlockID.GLOWSTONE_BLOCK); + public static final BlockType NETHER_PORTAL = register("minecraft:portal", BlockID.NETHER_PORTAL); + public static final BlockType LIT_PUMPKIN = register("minecraft:lit_pumpkin", BlockID.LIT_PUMPKIN); + public static final BlockType JACK_O_LANTERN = register("minecraft:lit_pumpkin", BlockID.JACK_O_LANTERN); + public static final BlockType CAKE_BLOCK = register("minecraft:item.cake", BlockID.CAKE_BLOCK); + public static final BlockType UNPOWERED_REPEATER = register("minecraft:unpowered_repeater", BlockID.UNPOWERED_REPEATER); + public static final BlockType POWERED_REPEATER = register("minecraft:powered_repeater", BlockID.POWERED_REPEATER); + public static final BlockType INVISIBLE_BEDROCK = register("minecraft:invisible_bedrock", BlockID.INVISIBLE_BEDROCK); + public static final BlockType TRAPDOOR = register("minecraft:trapdoor", BlockID.TRAPDOOR); + public static final BlockType MONSTER_EGG = register("minecraft:monster_egg", BlockID.MONSTER_EGG); + public static final BlockType STONE_BRICKS = register("minecraft:stonebrick", BlockID.STONE_BRICKS); + public static final BlockType STONE_BRICK = register("minecraft:stonebrick", BlockID.STONE_BRICK); + public static final BlockType BROWN_MUSHROOM_BLOCK = register("minecraft:brown_mushroom_block", BlockID.BROWN_MUSHROOM_BLOCK); + public static final BlockType RED_MUSHROOM_BLOCK = register("minecraft:red_mushroom_block", BlockID.RED_MUSHROOM_BLOCK); + public static final BlockType IRON_BAR = register("minecraft:iron_bars", BlockID.IRON_BAR); + public static final BlockType IRON_BARS = register("minecraft:iron_bars", BlockID.IRON_BARS); + public static final BlockType GLASS_PANE = register("minecraft:glass_pane", BlockID.GLASS_PANE); + public static final BlockType GLASS_PANEL = register("minecraft:glass_pane", BlockID.GLASS_PANEL); + public static final BlockType MELON_BLOCK = register("minecraft:melon_block", BlockID.MELON_BLOCK); + public static final BlockType PUMPKIN_STEM = register("minecraft:pumpkin_stem", BlockID.PUMPKIN_STEM); + public static final BlockType MELON_STEM = register("minecraft:melon_stem", BlockID.MELON_STEM); + public static final BlockType VINE = register("minecraft:vine", BlockID.VINE); + public static final BlockType VINES = register("minecraft:vine", BlockID.VINES); + public static final BlockType FENCE_GATE = register("minecraft:fence_gate", BlockID.FENCE_GATE); + public static final BlockType FENCE_GATE_OAK = register("minecraft:fence_gate", BlockID.FENCE_GATE_OAK); + public static final BlockType BRICK_STAIRS = register("minecraft:brick_stairs", BlockID.BRICK_STAIRS); + public static final BlockType STONE_BRICK_STAIRS = register("minecraft:stone_brick_stairs", BlockID.STONE_BRICK_STAIRS); + public static final BlockType MYCELIUM = register("minecraft:mycelium", BlockID.MYCELIUM); + public static final BlockType WATER_LILY = register("minecraft:waterlily", BlockID.WATER_LILY); + public static final BlockType WATERLILY = register("minecraft:waterlily", BlockID.WATERLILY); + public static final BlockType LILY_PAD = register("minecraft:waterlily", BlockID.LILY_PAD); + public static final BlockType NETHER_BRICKS = register("minecraft:nether_brick", BlockID.NETHER_BRICKS); + public static final BlockType NETHER_BRICK_BLOCK = register("minecraft:nether_brick", BlockID.NETHER_BRICK_BLOCK); + public static final BlockType NETHER_BRICK_FENCE = register("minecraft:nether_brick_fence", BlockID.NETHER_BRICK_FENCE); + public static final BlockType NETHER_BRICKS_STAIRS = register("minecraft:nether_brick_stairs", BlockID.NETHER_BRICKS_STAIRS); + public static final BlockType NETHER_WART_BLOCK = register("minecraft:item.nether_wart", BlockID.NETHER_WART_BLOCK); + public static final BlockType ENCHANTING_TABLE = register("minecraft:enchanting_table", BlockID.ENCHANTING_TABLE); + public static final BlockType ENCHANT_TABLE = register("minecraft:enchanting_table", BlockID.ENCHANT_TABLE); + public static final BlockType ENCHANTMENT_TABLE = register("minecraft:enchanting_table", BlockID.ENCHANTMENT_TABLE); + public static final BlockType BREWING_STAND_BLOCK = register("minecraft:brewingstandblock", BlockID.BREWING_STAND_BLOCK); + public static final BlockType BREWING_BLOCK = register("minecraft:brewingstandblock", BlockID.BREWING_BLOCK); + public static final BlockType CAULDRON_BLOCK = register("minecraft:item.cauldron", BlockID.CAULDRON_BLOCK); + public static final BlockType END_PORTAL = register("minecraft:end_portal", BlockID.END_PORTAL); + public static final BlockType END_PORTAL_FRAME = register("minecraft:end_portal_frame", BlockID.END_PORTAL_FRAME); + public static final BlockType END_STONE = register("minecraft:end_stone", BlockID.END_STONE); + public static final BlockType DRAGON_EGG = register("minecraft:dragon_egg", BlockID.DRAGON_EGG); + public static final BlockType REDSTONE_LAMP = register("minecraft:redstone_lamp", BlockID.REDSTONE_LAMP); + public static final BlockType LIT_REDSTONE_LAMP = register("minecraft:lit_redstone_lamp", BlockID.LIT_REDSTONE_LAMP); + public static final BlockType DROPPER = register("minecraft:dropper", BlockID.DROPPER); + public static final BlockType ACTIVATOR_RAIL = register("minecraft:activator_rail", BlockID.ACTIVATOR_RAIL); + public static final BlockType COCOA = register("minecraft:cocoa", BlockID.COCOA); + public static final BlockType COCOA_BLOCK = register("minecraft:cocoa", BlockID.COCOA_BLOCK); + public static final BlockType SANDSTONE_STAIRS = register("minecraft:sandstone_stairs", BlockID.SANDSTONE_STAIRS); + public static final BlockType EMERALD_ORE = register("minecraft:emerald_ore", BlockID.EMERALD_ORE); + public static final BlockType ENDER_CHEST = register("minecraft:ender_chest", BlockID.ENDER_CHEST); + public static final BlockType TRIPWIRE_HOOK = register("minecraft:tripwire_hook", BlockID.TRIPWIRE_HOOK); + public static final BlockType TRIPWIRE = register("minecraft:trip_wire", BlockID.TRIPWIRE); + public static final BlockType EMERALD_BLOCK = register("minecraft:emerald_block", BlockID.EMERALD_BLOCK); + public static final BlockType SPRUCE_WOOD_STAIRS = register("minecraft:spruce_stairs", BlockID.SPRUCE_WOOD_STAIRS); + public static final BlockType SPRUCE_WOODEN_STAIRS = register("minecraft:spruce_stairs", BlockID.SPRUCE_WOODEN_STAIRS); + public static final BlockType BIRCH_WOOD_STAIRS = register("minecraft:birch_stairs", BlockID.BIRCH_WOOD_STAIRS); + public static final BlockType BIRCH_WOODEN_STAIRS = register("minecraft:birch_stairs", BlockID.BIRCH_WOODEN_STAIRS); + public static final BlockType JUNGLE_WOOD_STAIRS = register("minecraft:jungle_stairs", BlockID.JUNGLE_WOOD_STAIRS); + public static final BlockType JUNGLE_WOODEN_STAIRS = register("minecraft:jungle_stairs", BlockID.JUNGLE_WOODEN_STAIRS); + public static final BlockType COMMAND_BLOCK = register("minecraft:command_block", BlockID.COMMAND_BLOCK); + public static final BlockType BEACON = register("minecraft:beacon", BlockID.BEACON); + public static final BlockType COBBLE_WALL = register("minecraft:cobblestone_wall", BlockID.COBBLE_WALL); + public static final BlockType STONE_WALL = register("minecraft:cobblestone_wall", BlockID.STONE_WALL); + public static final BlockType COBBLESTONE_WALL = register("minecraft:cobblestone_wall", BlockID.COBBLESTONE_WALL); + public static final BlockType FLOWER_POT_BLOCK = register("minecraft:item.flower_pot", BlockID.FLOWER_POT_BLOCK); + public static final BlockType CARROT_BLOCK = register("minecraft:carrots", BlockID.CARROT_BLOCK); + public static final BlockType POTATO_BLOCK = register("minecraft:potatoes", BlockID.POTATO_BLOCK); + public static final BlockType WOODEN_BUTTON = register("minecraft:wooden_button", BlockID.WOODEN_BUTTON); + public static final BlockType SKULL_BLOCK = register("minecraft:item.skull", BlockID.SKULL_BLOCK); + public static final BlockType ANVIL = register("minecraft:anvil", BlockID.ANVIL); + public static final BlockType TRAPPED_CHEST = register("minecraft:trapped_chest", BlockID.TRAPPED_CHEST); + public static final BlockType LIGHT_WEIGHTED_PRESSURE_PLATE = register("minecraft:light_weighted_pressure_plate", BlockID.LIGHT_WEIGHTED_PRESSURE_PLATE); + public static final BlockType HEAVY_WEIGHTED_PRESSURE_PLATE = register("minecraft:heavy_weighted_pressure_plate", BlockID.HEAVY_WEIGHTED_PRESSURE_PLATE); + public static final BlockType UNPOWERED_COMPARATOR = register("minecraft:unpowered_comparator", BlockID.UNPOWERED_COMPARATOR); + public static final BlockType POWERED_COMPARATOR = register("minecraft:powered_comparator", BlockID.POWERED_COMPARATOR); + public static final BlockType DAYLIGHT_DETECTOR = register("minecraft:daylight_detector", BlockID.DAYLIGHT_DETECTOR); + public static final BlockType REDSTONE_BLOCK = register("minecraft:redstone_block", BlockID.REDSTONE_BLOCK); + public static final BlockType QUARTZ_ORE = register("minecraft:quartz_ore", BlockID.QUARTZ_ORE); + public static final BlockType HOPPER_BLOCK = register("minecraft:item.hopper", BlockID.HOPPER_BLOCK); + public static final BlockType QUARTZ_BLOCK = register("minecraft:quartz_block", BlockID.QUARTZ_BLOCK); + public static final BlockType QUARTZ_STAIRS = register("minecraft:quartz_stairs", BlockID.QUARTZ_STAIRS); + public static final BlockType DOUBLE_WOOD_SLAB = register("minecraft:double_wooden_slab", BlockID.DOUBLE_WOOD_SLAB); + public static final BlockType DOUBLE_WOODEN_SLAB = register("minecraft:double_wooden_slab", BlockID.DOUBLE_WOODEN_SLAB); + public static final BlockType DOUBLE_WOOD_SLABS = register("minecraft:double_wooden_slab", BlockID.DOUBLE_WOOD_SLABS); + public static final BlockType DOUBLE_WOODEN_SLABS = register("minecraft:double_wooden_slab", BlockID.DOUBLE_WOODEN_SLABS); + public static final BlockType WOOD_SLAB = register("minecraft:wooden_slab", BlockID.WOOD_SLAB); + public static final BlockType WOODEN_SLAB = register("minecraft:wooden_slab", BlockID.WOODEN_SLAB); + public static final BlockType WOOD_SLABS = register("minecraft:wooden_slab", BlockID.WOOD_SLABS); + public static final BlockType WOODEN_SLABS = register("minecraft:wooden_slab", BlockID.WOODEN_SLABS); + public static final BlockType STAINED_TERRACOTTA = register("minecraft:stained_hardened_clay", BlockID.STAINED_TERRACOTTA); + public static final BlockType STAINED_HARDENED_CLAY = register("minecraft:stained_hardened_clay", BlockID.STAINED_HARDENED_CLAY); + public static final BlockType STAINED_GLASS_PANE = register("minecraft:stained_glass_pane", BlockID.STAINED_GLASS_PANE); + public static final BlockType LEAVES2 = register("minecraft:leaves2", BlockID.LEAVES2); + public static final BlockType LEAVE2 = register("minecraft:leaves2", BlockID.LEAVE2); + public static final BlockType WOOD2 = register("minecraft:log2", BlockID.WOOD2); + public static final BlockType TRUNK2 = register("minecraft:log2", BlockID.TRUNK2); + public static final BlockType LOG2 = register("minecraft:log2", BlockID.LOG2); + public static final BlockType ACACIA_WOOD_STAIRS = register("minecraft:acacia_stairs", BlockID.ACACIA_WOOD_STAIRS); + public static final BlockType ACACIA_WOODEN_STAIRS = register("minecraft:acacia_stairs", BlockID.ACACIA_WOODEN_STAIRS); + public static final BlockType DARK_OAK_WOOD_STAIRS = register("minecraft:dark_oak_stairs", BlockID.DARK_OAK_WOOD_STAIRS); + public static final BlockType DARK_OAK_WOODEN_STAIRS = register("minecraft:dark_oak_stairs", BlockID.DARK_OAK_WOODEN_STAIRS); + public static final BlockType SLIME_BLOCK = register("minecraft:slime", BlockID.SLIME_BLOCK); + public static final BlockType SLIME = register("minecraft:slime", BlockID.SLIME); + public static final BlockType GLOW_STICK = register("minecraft:glow_stick", BlockID.GLOW_STICK); + public static final BlockType IRON_TRAPDOOR = register("minecraft:iron_trapdoor", BlockID.IRON_TRAPDOOR); + public static final BlockType PRISMARINE = register("minecraft:prismarine", BlockID.PRISMARINE); + public static final BlockType SEA_LANTERN = register("minecraft:sea_lantern", BlockID.SEA_LANTERN); + public static final BlockType HAY_BALE = register("minecraft:hay_block", BlockID.HAY_BALE); + public static final BlockType HAY_BLOCK = register("minecraft:hay_block", BlockID.HAY_BLOCK); + public static final BlockType CARPET = register("minecraft:carpet", BlockID.CARPET); + public static final BlockType TERRACOTTA = register("minecraft:hardened_clay", BlockID.TERRACOTTA); + public static final BlockType HARDENED_CLAY = register("minecraft:hardened_clay", BlockID.HARDENED_CLAY); + public static final BlockType COAL_BLOCK = register("minecraft:coal_block", BlockID.COAL_BLOCK); + public static final BlockType PACKED_ICE = register("minecraft:packed_ice", BlockID.PACKED_ICE); + public static final BlockType DOUBLE_PLANT = register("minecraft:double_plant", BlockID.DOUBLE_PLANT); + public static final BlockType STANDING_BANNER = register("minecraft:standing_banner", BlockID.STANDING_BANNER); + public static final BlockType WALL_BANNER = register("minecraft:wall_banner", BlockID.WALL_BANNER); + public static final BlockType DAYLIGHT_DETECTOR_INVERTED = register("minecraft:daylight_detector_inverted", BlockID.DAYLIGHT_DETECTOR_INVERTED); + public static final BlockType RED_SANDSTONE = register("minecraft:red_sandstone", BlockID.RED_SANDSTONE); + public static final BlockType RED_SANDSTONE_STAIRS = register("minecraft:red_sandstone_stairs", BlockID.RED_SANDSTONE_STAIRS); + public static final BlockType DOUBLE_RED_SANDSTONE_SLAB = register("minecraft:double_stone_block_slab2", BlockID.DOUBLE_RED_SANDSTONE_SLAB); + public static final BlockType RED_SANDSTONE_SLAB = register("minecraft:stone_block_slab2", BlockID.RED_SANDSTONE_SLAB); + public static final BlockType FENCE_GATE_SPRUCE = register("minecraft:spruce_fence_gate", BlockID.FENCE_GATE_SPRUCE); + public static final BlockType FENCE_GATE_BIRCH = register("minecraft:birch_fence_gate", BlockID.FENCE_GATE_BIRCH); + public static final BlockType FENCE_GATE_JUNGLE = register("minecraft:jungle_fence_gate", BlockID.FENCE_GATE_JUNGLE); + public static final BlockType FENCE_GATE_DARK_OAK = register("minecraft:dark_oak_fence_gate", BlockID.FENCE_GATE_DARK_OAK); + public static final BlockType FENCE_GATE_ACACIA = register("minecraft:acacia_fence_gate", BlockID.FENCE_GATE_ACACIA); + public static final BlockType REPEATING_COMMAND_BLOCK = register("minecraft:repeating_command_block", BlockID.REPEATING_COMMAND_BLOCK); + public static final BlockType CHAIN_COMMAND_BLOCK = register("minecraft:chain_command_block", BlockID.CHAIN_COMMAND_BLOCK); + public static final BlockType HARD_GLASS_PANE = register("minecraft:hard_glass_pane", BlockID.HARD_GLASS_PANE); + public static final BlockType HARD_STAINED_GLASS_PANE = register("minecraft:hard_stained_glass_pane", BlockID.HARD_STAINED_GLASS_PANE); + public static final BlockType CHEMICAL_HEAT = register("minecraft:chemical_heat", BlockID.CHEMICAL_HEAT); + public static final BlockType SPRUCE_DOOR_BLOCK = register("minecraft:item.spruce_door", BlockID.SPRUCE_DOOR_BLOCK); + public static final BlockType BIRCH_DOOR_BLOCK = register("minecraft:item.birch_door", BlockID.BIRCH_DOOR_BLOCK); + public static final BlockType JUNGLE_DOOR_BLOCK = register("minecraft:item.jungle_door", BlockID.JUNGLE_DOOR_BLOCK); + public static final BlockType ACACIA_DOOR_BLOCK = register("minecraft:item.acacia_door", BlockID.ACACIA_DOOR_BLOCK); + public static final BlockType DARK_OAK_DOOR_BLOCK = register("minecraft:item.dark_oak_door", BlockID.DARK_OAK_DOOR_BLOCK); + public static final BlockType GRASS_PATH = register("minecraft:grass_path", BlockID.GRASS_PATH); + public static final BlockType ITEM_FRAME_BLOCK = register("minecraft:item.frame", BlockID.ITEM_FRAME_BLOCK); + public static final BlockType CHORUS_FLOWER = register("minecraft:chorus_flower", BlockID.CHORUS_FLOWER); + public static final BlockType PURPUR_BLOCK = register("minecraft:purpur_block", BlockID.PURPUR_BLOCK); + public static final BlockType COLORED_TORCH_RG = register("minecraft:colored_torch_rg", BlockID.COLORED_TORCH_RG); + public static final BlockType PURPUR_STAIRS = register("minecraft:purpur_stairs", BlockID.PURPUR_STAIRS); + public static final BlockType COLORED_TORCH_BP = register("minecraft:colored_torch_bp", BlockID.COLORED_TORCH_BP); + public static final BlockType UNDYED_SHULKER_BOX = register("minecraft:undyed_shulker_box", BlockID.UNDYED_SHULKER_BOX); + public static final BlockType END_BRICKS = register("minecraft:end_bricks", BlockID.END_BRICKS); + public static final BlockType FROSTED_ICE = register("minecraft:frosted_ice", BlockID.FROSTED_ICE); + public static final BlockType ICE_FROSTED = register("minecraft:frosted_ice", BlockID.ICE_FROSTED); + public static final BlockType END_ROD = register("minecraft:end_rod", BlockID.END_ROD); + public static final BlockType END_GATEWAY = register("minecraft:end_gateway", BlockID.END_GATEWAY); + public static final BlockType ALLOW = register("minecraft:allow", BlockID.ALLOW); + public static final BlockType DENY = register("minecraft:deny", BlockID.DENY); + public static final BlockType BORDER_BLOCK = register("minecraft:border_block", BlockID.BORDER_BLOCK); + public static final BlockType MAGMA = register("minecraft:magma", BlockID.MAGMA); + public static final BlockType BLOCK_NETHER_WART_BLOCK = register("minecraft:nether_wart_block", BlockID.BLOCK_NETHER_WART_BLOCK); + public static final BlockType RED_NETHER_BRICK = register("minecraft:red_nether_brick", BlockID.RED_NETHER_BRICK); + public static final BlockType BONE_BLOCK = register("minecraft:bone_block", BlockID.BONE_BLOCK); + public static final BlockType SHULKER_BOX = register("minecraft:shulker_box", BlockID.SHULKER_BOX); + public static final BlockType PURPLE_GLAZED_TERRACOTTA = register("minecraft:purple_glazed_terracotta", BlockID.PURPLE_GLAZED_TERRACOTTA); + public static final BlockType WHITE_GLAZED_TERRACOTTA = register("minecraft:white_glazed_terracotta", BlockID.WHITE_GLAZED_TERRACOTTA); + public static final BlockType ORANGE_GLAZED_TERRACOTTA = register("minecraft:orange_glazed_terracotta", BlockID.ORANGE_GLAZED_TERRACOTTA); + public static final BlockType MAGENTA_GLAZED_TERRACOTTA = register("minecraft:magenta_glazed_terracotta", BlockID.MAGENTA_GLAZED_TERRACOTTA); + public static final BlockType LIGHT_BLUE_GLAZED_TERRACOTTA = register("minecraft:light_blue_glazed_terracotta", BlockID.LIGHT_BLUE_GLAZED_TERRACOTTA); + public static final BlockType YELLOW_GLAZED_TERRACOTTA = register("minecraft:yellow_glazed_terracotta", BlockID.YELLOW_GLAZED_TERRACOTTA); + public static final BlockType LIME_GLAZED_TERRACOTTA = register("minecraft:lime_glazed_terracotta", BlockID.LIME_GLAZED_TERRACOTTA); + public static final BlockType PINK_GLAZED_TERRACOTTA = register("minecraft:pink_glazed_terracotta", BlockID.PINK_GLAZED_TERRACOTTA); + public static final BlockType GRAY_GLAZED_TERRACOTTA = register("minecraft:gray_glazed_terracotta", BlockID.GRAY_GLAZED_TERRACOTTA); + public static final BlockType SILVER_GLAZED_TERRACOTTA = register("minecraft:silver_glazed_terracotta", BlockID.SILVER_GLAZED_TERRACOTTA); + public static final BlockType CYAN_GLAZED_TERRACOTTA = register("minecraft:cyan_glazed_terracotta", BlockID.CYAN_GLAZED_TERRACOTTA); + public static final BlockType BLUE_GLAZED_TERRACOTTA = register("minecraft:blue_glazed_terracotta", BlockID.BLUE_GLAZED_TERRACOTTA); + public static final BlockType BROWN_GLAZED_TERRACOTTA = register("minecraft:brown_glazed_terracotta", BlockID.BROWN_GLAZED_TERRACOTTA); + public static final BlockType GREEN_GLAZED_TERRACOTTA = register("minecraft:green_glazed_terracotta", BlockID.GREEN_GLAZED_TERRACOTTA); + public static final BlockType RED_GLAZED_TERRACOTTA = register("minecraft:red_glazed_terracotta", BlockID.RED_GLAZED_TERRACOTTA); + public static final BlockType BLACK_GLAZED_TERRACOTTA = register("minecraft:black_glazed_terracotta", BlockID.BLACK_GLAZED_TERRACOTTA); + public static final BlockType CONCRETE = register("minecraft:concrete", BlockID.CONCRETE); + public static final BlockType CONCRETE_POWDER = register("minecraft:concrete_powder", BlockID.CONCRETE_POWDER); + public static final BlockType CHEMISTRY_TABLE = register("minecraft:chemistry_table", BlockID.CHEMISTRY_TABLE); + public static final BlockType UNDERWATER_TORCH = register("minecraft:underwater_torch", BlockID.UNDERWATER_TORCH); + public static final BlockType CHORUS_PLANT = register("minecraft:chorus_plant", BlockID.CHORUS_PLANT); + public static final BlockType STAINED_GLASS = register("minecraft:stained_glass", BlockID.STAINED_GLASS); + public static final BlockType CAMERA_BLOCK = register("minecraft:item.camera", BlockID.CAMERA_BLOCK); + public static final BlockType PODZOL = register("minecraft:podzol", BlockID.PODZOL); + public static final BlockType BEETROOT_BLOCK = register("minecraft:item.beetroot", BlockID.BEETROOT_BLOCK); + public static final BlockType STONECUTTER = register("minecraft:stonecutter", BlockID.STONECUTTER); + public static final BlockType GLOWING_OBSIDIAN = register("minecraft:glowingobsidian", BlockID.GLOWING_OBSIDIAN); + public static final BlockType NETHER_REACTOR = register("minecraft:netherreactor", BlockID.NETHER_REACTOR); + public static final BlockType INFO_UPDATE = register("minecraft:info_update", BlockID.INFO_UPDATE); + public static final BlockType INFO_UPDATE2 = register("minecraft:info_update2", BlockID.INFO_UPDATE2); + public static final BlockType PISTON_EXTENSION = register("minecraft:moving_block", BlockID.PISTON_EXTENSION); + public static final BlockType MOVING_BLOCK = register("minecraft:moving_block", BlockID.MOVING_BLOCK); + public static final BlockType OBSERVER = register("minecraft:observer", BlockID.OBSERVER); + public static final BlockType STRUCTURE_BLOCK = register("minecraft:structure_block", BlockID.STRUCTURE_BLOCK); + public static final BlockType HARD_GLASS = register("minecraft:hard_glass", BlockID.HARD_GLASS); + public static final BlockType HARD_STAINED_GLASS = register("minecraft:hard_stained_glass", BlockID.HARD_STAINED_GLASS); + public static final BlockType RESERVED6 = register("minecraft:reserved6", BlockID.RESERVED6); + public static final BlockType PRISMARINE_STAIRS = register("minecraft:prismarine_stairs", BlockID.PRISMARINE_STAIRS); + public static final BlockType DARK_PRISMARINE_STAIRS = register("minecraft:dark_prismarine_stairs", BlockID.DARK_PRISMARINE_STAIRS); + public static final BlockType PRISMARINE_BRICKS_STAIRS = register("minecraft:prismarine_bricks_stairs", BlockID.PRISMARINE_BRICKS_STAIRS); + public static final BlockType STRIPPED_SPRUCE_LOG = register("minecraft:stripped_spruce_log", BlockID.STRIPPED_SPRUCE_LOG); + public static final BlockType STRIPPED_BIRCH_LOG = register("minecraft:stripped_birch_log", BlockID.STRIPPED_BIRCH_LOG); + public static final BlockType STRIPPED_JUNGLE_LOG = register("minecraft:stripped_jungle_log", BlockID.STRIPPED_JUNGLE_LOG); + public static final BlockType STRIPPED_ACACIA_LOG = register("minecraft:stripped_acacia_log", BlockID.STRIPPED_ACACIA_LOG); + public static final BlockType STRIPPED_DARK_OAK_LOG = register("minecraft:stripped_dark_oak_log", BlockID.STRIPPED_DARK_OAK_LOG); + public static final BlockType STRIPPED_OAK_LOG = register("minecraft:stripped_oak_log", BlockID.STRIPPED_OAK_LOG); + public static final BlockType BLUE_ICE = register("minecraft:blue_ice", BlockID.BLUE_ICE); + public static final BlockType SEAGRASS = register("minecraft:seagrass", BlockID.SEAGRASS); + public static final BlockType CORAL = register("minecraft:coral", BlockID.CORAL); + public static final BlockType CORAL_BLOCK = register("minecraft:coral_block", BlockID.CORAL_BLOCK); + public static final BlockType CORAL_FAN = register("minecraft:coral_fan", BlockID.CORAL_FAN); + public static final BlockType CORAL_FAN_DEAD = register("minecraft:coral_fan_dead", BlockID.CORAL_FAN_DEAD); + public static final BlockType CORAL_FAN_HANG = register("minecraft:coral_fan_hang", BlockID.CORAL_FAN_HANG); + public static final BlockType CORAL_FAN_HANG2 = register("minecraft:coral_fan_hang2", BlockID.CORAL_FAN_HANG2); + public static final BlockType CORAL_FAN_HANG3 = register("minecraft:coral_fan_hang3", BlockID.CORAL_FAN_HANG3); + public static final BlockType BLOCK_KELP = register("minecraft:item.kelp", BlockID.BLOCK_KELP); + public static final BlockType DRIED_KELP_BLOCK = register("minecraft:dried_kelp_block", BlockID.DRIED_KELP_BLOCK); + public static final BlockType ACACIA_BUTTON = register("minecraft:acacia_button", BlockID.ACACIA_BUTTON); + public static final BlockType BIRCH_BUTTON = register("minecraft:birch_button", BlockID.BIRCH_BUTTON); + public static final BlockType DARK_OAK_BUTTON = register("minecraft:dark_oak_button", BlockID.DARK_OAK_BUTTON); + public static final BlockType JUNGLE_BUTTON = register("minecraft:jungle_button", BlockID.JUNGLE_BUTTON); + public static final BlockType SPRUCE_BUTTON = register("minecraft:spruce_button", BlockID.SPRUCE_BUTTON); + public static final BlockType ACACIA_TRAPDOOR = register("minecraft:acacia_trapdoor", BlockID.ACACIA_TRAPDOOR); + public static final BlockType BIRCH_TRAPDOOR = register("minecraft:birch_trapdoor", BlockID.BIRCH_TRAPDOOR); + public static final BlockType DARK_OAK_TRAPDOOR = register("minecraft:dark_oak_trapdoor", BlockID.DARK_OAK_TRAPDOOR); + public static final BlockType JUNGLE_TRAPDOOR = register("minecraft:jungle_trapdoor", BlockID.JUNGLE_TRAPDOOR); + public static final BlockType SPRUCE_TRAPDOOR = register("minecraft:spruce_trapdoor", BlockID.SPRUCE_TRAPDOOR); + public static final BlockType ACACIA_PRESSURE_PLATE = register("minecraft:acacia_pressure_plate", BlockID.ACACIA_PRESSURE_PLATE); + public static final BlockType BIRCH_PRESSURE_PLATE = register("minecraft:birch_pressure_plate", BlockID.BIRCH_PRESSURE_PLATE); + public static final BlockType DARK_OAK_PRESSURE_PLATE = register("minecraft:dark_oak_pressure_plate", BlockID.DARK_OAK_PRESSURE_PLATE); + public static final BlockType JUNGLE_PRESSURE_PLATE = register("minecraft:jungle_pressure_plate", BlockID.JUNGLE_PRESSURE_PLATE); + public static final BlockType SPRUCE_PRESSURE_PLATE = register("minecraft:spruce_pressure_plate", BlockID.SPRUCE_PRESSURE_PLATE); + public static final BlockType CARVED_PUMPKIN = register("minecraft:carved_pumpkin", BlockID.CARVED_PUMPKIN); + public static final BlockType SEA_PICKLE = register("minecraft:sea_pickle", BlockID.SEA_PICKLE); + public static final BlockType CONDUIT = register("minecraft:conduit", BlockID.CONDUIT); + public static final BlockType TURTLE_EGG = register("minecraft:turtle_egg", BlockID.TURTLE_EGG); + public static final BlockType BUBBLE_COLUMN = register("minecraft:bubble_column", BlockID.BUBBLE_COLUMN); + public static final BlockType BARRIER = register("minecraft:barrier", BlockID.BARRIER); + public static final BlockType STONE_SLAB3 = register("minecraft:stone_block_slab3", BlockID.STONE_SLAB3); + public static final BlockType BAMBOO = register("minecraft:bamboo", BlockID.BAMBOO); + public static final BlockType BAMBOO_SAPLING = register("minecraft:bamboo_sapling", BlockID.BAMBOO_SAPLING); + public static final BlockType SCAFFOLDING = register("minecraft:scaffolding", BlockID.SCAFFOLDING); + public static final BlockType STONE_SLAB4 = register("minecraft:stone_block_slab4", BlockID.STONE_SLAB4); + public static final BlockType DOUBLE_STONE_SLAB3 = register("minecraft:double_stone_block_slab3", BlockID.DOUBLE_STONE_SLAB3); + public static final BlockType DOUBLE_STONE_SLAB4 = register("minecraft:double_stone_block_slab4", BlockID.DOUBLE_STONE_SLAB4); + public static final BlockType GRANITE_STAIRS = register("minecraft:granite_stairs", BlockID.GRANITE_STAIRS); + public static final BlockType DIORITE_STAIRS = register("minecraft:diorite_stairs", BlockID.DIORITE_STAIRS); + public static final BlockType ANDESITE_STAIRS = register("minecraft:andesite_stairs", BlockID.ANDESITE_STAIRS); + public static final BlockType POLISHED_GRANITE_STAIRS = register("minecraft:polished_granite_stairs", BlockID.POLISHED_GRANITE_STAIRS); + public static final BlockType POLISHED_DIORITE_STAIRS = register("minecraft:polished_diorite_stairs", BlockID.POLISHED_DIORITE_STAIRS); + public static final BlockType POLISHED_ANDESITE_STAIRS = register("minecraft:polished_andesite_stairs", BlockID.POLISHED_ANDESITE_STAIRS); + public static final BlockType MOSSY_STONE_BRICK_STAIRS = register("minecraft:mossy_stone_brick_stairs", BlockID.MOSSY_STONE_BRICK_STAIRS); + public static final BlockType SMOOTH_RED_SANDSTONE_STAIRS = register("minecraft:smooth_red_sandstone_stairs", BlockID.SMOOTH_RED_SANDSTONE_STAIRS); + public static final BlockType SMOOTH_SANDSTONE_STAIRS = register("minecraft:smooth_sandstone_stairs", BlockID.SMOOTH_SANDSTONE_STAIRS); + public static final BlockType END_BRICK_STAIRS = register("minecraft:end_brick_stairs", BlockID.END_BRICK_STAIRS); + public static final BlockType MOSSY_COBBLESTONE_STAIRS = register("minecraft:mossy_cobblestone_stairs", BlockID.MOSSY_COBBLESTONE_STAIRS); + public static final BlockType NORMAL_STONE_STAIRS = register("minecraft:normal_stone_stairs", BlockID.NORMAL_STONE_STAIRS); + public static final BlockType SPRUCE_STANDING_SIGN = register("minecraft:spruce_standing_sign", BlockID.SPRUCE_STANDING_SIGN); + public static final BlockType SPRUCE_WALL_SIGN = register("minecraft:spruce_wall_sign", BlockID.SPRUCE_WALL_SIGN); + public static final BlockType SMOOTH_STONE = register("minecraft:smooth_stone", BlockID.SMOOTH_STONE); + public static final BlockType RED_NETHER_BRICK_STAIRS = register("minecraft:red_nether_brick_stairs", BlockID.RED_NETHER_BRICK_STAIRS); + public static final BlockType SMOOTH_QUARTZ_STAIRS = register("minecraft:smooth_quartz_stairs", BlockID.SMOOTH_QUARTZ_STAIRS); + public static final BlockType BIRCH_STANDING_SIGN = register("minecraft:birch_standing_sign", BlockID.BIRCH_STANDING_SIGN); + public static final BlockType BIRCH_WALL_SIGN = register("minecraft:birch_wall_sign", BlockID.BIRCH_WALL_SIGN); + public static final BlockType JUNGLE_STANDING_SIGN = register("minecraft:jungle_standing_sign", BlockID.JUNGLE_STANDING_SIGN); + public static final BlockType JUNGLE_WALL_SIGN = register("minecraft:jungle_wall_sign", BlockID.JUNGLE_WALL_SIGN); + public static final BlockType ACACIA_STANDING_SIGN = register("minecraft:acacia_standing_sign", BlockID.ACACIA_STANDING_SIGN); + public static final BlockType ACACIA_WALL_SIGN = register("minecraft:acacia_wall_sign", BlockID.ACACIA_WALL_SIGN); + public static final BlockType DARK_OAK_STANDING_SIGN = register("minecraft:darkoak_standing_sign", BlockID.DARK_OAK_STANDING_SIGN); + public static final BlockType DARK_OAK_WALL_SIGN = register("minecraft:darkoak_wall_sign", BlockID.DARK_OAK_WALL_SIGN); + public static final BlockType LECTERN = register("minecraft:lectern", BlockID.LECTERN); + public static final BlockType GRINDSTONE = register("minecraft:grindstone", BlockID.GRINDSTONE); + public static final BlockType BLAST_FURNACE = register("minecraft:blast_furnace", BlockID.BLAST_FURNACE); + public static final BlockType STONECUTTER_BLOCK = register("minecraft:stonecutter_block", BlockID.STONECUTTER_BLOCK); + public static final BlockType SMOKER = register("minecraft:smoker", BlockID.SMOKER); + public static final BlockType LIT_SMOKER = register("minecraft:lit_smoker", BlockID.LIT_SMOKER); + public static final BlockType CARTOGRAPHY_TABLE = register("minecraft:cartography_table", BlockID.CARTOGRAPHY_TABLE); + public static final BlockType FLETCHING_TABLE = register("minecraft:fletching_table", BlockID.FLETCHING_TABLE); + public static final BlockType SMITHING_TABLE = register("minecraft:smithing_table", BlockID.SMITHING_TABLE); + public static final BlockType BARREL = register("minecraft:barrel", BlockID.BARREL); + public static final BlockType LOOM = register("minecraft:loom", BlockID.LOOM); + public static final BlockType BELL = register("minecraft:bell", BlockID.BELL); + public static final BlockType SWEET_BERRY_BUSH = register("minecraft:sweet_berry_bush", BlockID.SWEET_BERRY_BUSH); + public static final BlockType LANTERN = register("minecraft:lantern", BlockID.LANTERN); + public static final BlockType CAMPFIRE_BLOCK = register("minecraft:item.campfire", BlockID.CAMPFIRE_BLOCK); + public static final BlockType LAVA_CAULDRON = register("minecraft:lava_cauldron", BlockID.LAVA_CAULDRON); + public static final BlockType JIGSAW = register("minecraft:jigsaw", BlockID.JIGSAW); + public static final BlockType WOOD_BARK = register("minecraft:wood", BlockID.WOOD_BARK); + public static final BlockType COMPOSTER = register("minecraft:composter", BlockID.COMPOSTER); + public static final BlockType LIT_BLAST_FURNACE = register("minecraft:lit_blast_furnace", BlockID.LIT_BLAST_FURNACE); + public static final BlockType LIGHT_BLOCK = register("minecraft:light_block", BlockID.LIGHT_BLOCK); + public static final BlockType WITHER_ROSE = register("minecraft:wither_rose", BlockID.WITHER_ROSE); + public static final BlockType PISTON_HEAD_STICKY = register("minecraft:sticky_piston_arm_collision", BlockID.PISTON_HEAD_STICKY); + public static final BlockType BEE_NEST = register("minecraft:bee_nest", BlockID.BEE_NEST); + public static final BlockType BEEHIVE = register("minecraft:beehive", BlockID.BEEHIVE); + public static final BlockType HONEY_BLOCK = register("minecraft:honey_block", BlockID.HONEY_BLOCK); + public static final BlockType HONEYCOMB_BLOCK = register("minecraft:honeycomb_block", BlockID.HONEYCOMB_BLOCK); + public static final BlockType LODESTONE = register("minecraft:lodestone", BlockID.LODESTONE); + public static final BlockType CRIMSON_ROOTS = register("minecraft:crimson_roots", BlockID.CRIMSON_ROOTS); + public static final BlockType WARPED_ROOTS = register("minecraft:warped_roots", BlockID.WARPED_ROOTS); + public static final BlockType CRIMSON_STEM = register("minecraft:crimson_stem", BlockID.CRIMSON_STEM); + public static final BlockType WARPED_STEM = register("minecraft:warped_stem", BlockID.WARPED_STEM); + public static final BlockType WARPED_WART_BLOCK = register("minecraft:warped_wart_block", BlockID.WARPED_WART_BLOCK); + public static final BlockType CRIMSON_FUNGUS = register("minecraft:crimson_fungus", BlockID.CRIMSON_FUNGUS); + public static final BlockType WARPED_FUNGUS = register("minecraft:warped_fungus", BlockID.WARPED_FUNGUS); + public static final BlockType SHROOMLIGHT = register("minecraft:shroomlight", BlockID.SHROOMLIGHT); + public static final BlockType WEEPING_VINES = register("minecraft:weeping_vines", BlockID.WEEPING_VINES); + public static final BlockType CRIMSON_NYLIUM = register("minecraft:crimson_nylium", BlockID.CRIMSON_NYLIUM); + public static final BlockType WARPED_NYLIUM = register("minecraft:warped_nylium", BlockID.WARPED_NYLIUM); + public static final BlockType BASALT = register("minecraft:basalt", BlockID.BASALT); + public static final BlockType POLISHED_BASALT = register("minecraft:polished_basalt", BlockID.POLISHED_BASALT); + public static final BlockType SOUL_SOIL = register("minecraft:soul_soil", BlockID.SOUL_SOIL); + public static final BlockType SOUL_FIRE = register("minecraft:soul_fire", BlockID.SOUL_FIRE); + public static final BlockType NETHER_SPROUTS_BLOCK = register("minecraft:item.nether_sprouts", BlockID.NETHER_SPROUTS_BLOCK); + public static final BlockType TARGET = register("minecraft:target", BlockID.TARGET); + public static final BlockType STRIPPED_CRIMSON_STEM = register("minecraft:stripped_crimson_stem", BlockID.STRIPPED_CRIMSON_STEM); + public static final BlockType STRIPPED_WARPED_STEM = register("minecraft:stripped_warped_stem", BlockID.STRIPPED_WARPED_STEM); + public static final BlockType CRIMSON_PLANKS = register("minecraft:crimson_planks", BlockID.CRIMSON_PLANKS); + public static final BlockType WARPED_PLANKS = register("minecraft:warped_planks", BlockID.WARPED_PLANKS); + public static final BlockType CRIMSON_DOOR_BLOCK = register("minecraft:item.crimson_door", BlockID.CRIMSON_DOOR_BLOCK); + public static final BlockType WARPED_DOOR_BLOCK = register("minecraft:item.warped_door", BlockID.WARPED_DOOR_BLOCK); + public static final BlockType CRIMSON_TRAPDOOR = register("minecraft:crimson_trapdoor", BlockID.CRIMSON_TRAPDOOR); + public static final BlockType WARPED_TRAPDOOR = register("minecraft:warped_trapdoor", BlockID.WARPED_TRAPDOOR); + public static final BlockType CRIMSON_STANDING_SIGN = register("minecraft:crimson_standing_sign", BlockID.CRIMSON_STANDING_SIGN); + public static final BlockType WARPED_STANDING_SIGN = register("minecraft:warped_standing_sign", BlockID.WARPED_STANDING_SIGN); + public static final BlockType CRIMSON_WALL_SIGN = register("minecraft:crimson_wall_sign", BlockID.CRIMSON_WALL_SIGN); + public static final BlockType WARPED_WALL_SIGN = register("minecraft:warped_wall_sign", BlockID.WARPED_WALL_SIGN); + public static final BlockType CRIMSON_STAIRS = register("minecraft:crimson_stairs", BlockID.CRIMSON_STAIRS); + public static final BlockType WARPED_STAIRS = register("minecraft:warped_stairs", BlockID.WARPED_STAIRS); + public static final BlockType CRIMSON_FENCE = register("minecraft:crimson_fence", BlockID.CRIMSON_FENCE); + public static final BlockType WARPED_FENCE = register("minecraft:warped_fence", BlockID.WARPED_FENCE); + public static final BlockType CRIMSON_FENCE_GATE = register("minecraft:crimson_fence_gate", BlockID.CRIMSON_FENCE_GATE); + public static final BlockType WARPED_FENCE_GATE = register("minecraft:warped_fence_gate", BlockID.WARPED_FENCE_GATE); + public static final BlockType CRIMSON_BUTTON = register("minecraft:crimson_button", BlockID.CRIMSON_BUTTON); + public static final BlockType WARPED_BUTTON = register("minecraft:warped_button", BlockID.WARPED_BUTTON); + public static final BlockType CRIMSON_PRESSURE_PLATE = register("minecraft:crimson_pressure_plate", BlockID.CRIMSON_PRESSURE_PLATE); + public static final BlockType WARPED_PRESSURE_PLATE = register("minecraft:warped_pressure_plate", BlockID.WARPED_PRESSURE_PLATE); + public static final BlockType CRIMSON_SLAB = register("minecraft:crimson_slab", BlockID.CRIMSON_SLAB); + public static final BlockType WARPED_SLAB = register("minecraft:warped_slab", BlockID.WARPED_SLAB); + public static final BlockType CRIMSON_DOUBLE_SLAB = register("minecraft:crimson_double_slab", BlockID.CRIMSON_DOUBLE_SLAB); + public static final BlockType WARPED_DOUBLE_SLAB = register("minecraft:warped_double_slab", BlockID.WARPED_DOUBLE_SLAB); + public static final BlockType SOUL_TORCH = register("minecraft:soul_torch", BlockID.SOUL_TORCH); + public static final BlockType SOUL_LANTERN = register("minecraft:soul_lantern", BlockID.SOUL_LANTERN); + public static final BlockType NETHERITE_BLOCK = register("minecraft:netherite_block", BlockID.NETHERITE_BLOCK); + public static final BlockType ANCIENT_DEBRIS = register("minecraft:ancient_debris", BlockID.ANCIENT_DEBRIS); + public static final BlockType RESPAWN_ANCHOR = register("minecraft:respawn_anchor", BlockID.RESPAWN_ANCHOR); + public static final BlockType BLACKSTONE = register("minecraft:blackstone", BlockID.BLACKSTONE); + public static final BlockType POLISHED_BLACKSTONE_BRICKS = register("minecraft:polished_blackstone_bricks", BlockID.POLISHED_BLACKSTONE_BRICKS); + public static final BlockType POLISHED_BLACKSTONE_BRICK_STAIRS = register("minecraft:polished_blackstone_brick_stairs", BlockID.POLISHED_BLACKSTONE_BRICK_STAIRS); + public static final BlockType BLACKSTONE_STAIRS = register("minecraft:blackstone_stairs", BlockID.BLACKSTONE_STAIRS); + public static final BlockType BLACKSTONE_WALL = register("minecraft:blackstone_wall", BlockID.BLACKSTONE_WALL); + public static final BlockType POLISHED_BLACKSTONE_BRICK_WALL = register("minecraft:polished_blackstone_brick_wall", BlockID.POLISHED_BLACKSTONE_BRICK_WALL); + public static final BlockType CHISELED_POLISHED_BLACKSTONE = register("minecraft:chiseled_polished_blackstone", BlockID.CHISELED_POLISHED_BLACKSTONE); + public static final BlockType CRACKED_POLISHED_BLACKSTONE_BRICKS = register("minecraft:cracked_polished_blackstone_bricks", BlockID.CRACKED_POLISHED_BLACKSTONE_BRICKS); + public static final BlockType GILDED_BLACKSTONE = register("minecraft:gilded_blackstone", BlockID.GILDED_BLACKSTONE); + public static final BlockType BLACKSTONE_SLAB = register("minecraft:blackstone_slab", BlockID.BLACKSTONE_SLAB); + public static final BlockType BLACKSTONE_DOUBLE_SLAB = register("minecraft:blackstone_double_slab", BlockID.BLACKSTONE_DOUBLE_SLAB); + public static final BlockType POLISHED_BLACKSTONE_BRICK_SLAB = register("minecraft:polished_blackstone_brick_slab", BlockID.POLISHED_BLACKSTONE_BRICK_SLAB); + public static final BlockType POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB = register("minecraft:polished_blackstone_brick_double_slab", BlockID.POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB); + public static final BlockType CHAIN_BLOCK = register("minecraft:item.chain", BlockID.CHAIN_BLOCK); + public static final BlockType TWISTING_VINES = register("minecraft:twisting_vines", BlockID.TWISTING_VINES); + public static final BlockType NETHER_GOLD_ORE = register("minecraft:nether_gold_ore", BlockID.NETHER_GOLD_ORE); + public static final BlockType CRYING_OBSIDIAN = register("minecraft:crying_obsidian", BlockID.CRYING_OBSIDIAN); + public static final BlockType SOUL_CAMPFIRE_BLOCK = register("minecraft:item.soul_campfire", BlockID.SOUL_CAMPFIRE_BLOCK); + public static final BlockType POLISHED_BLACKSTONE = register("minecraft:polished_blackstone", BlockID.POLISHED_BLACKSTONE); + public static final BlockType POLISHED_BLACKSTONE_STAIRS = register("minecraft:polished_blackstone_stairs", BlockID.POLISHED_BLACKSTONE_STAIRS); + public static final BlockType POLISHED_BLACKSTONE_SLAB = register("minecraft:polished_blackstone_slab", BlockID.POLISHED_BLACKSTONE_SLAB); + public static final BlockType POLISHED_BLACKSTONE_DOUBLE_SLAB = register("minecraft:polished_blackstone_double_slab", BlockID.POLISHED_BLACKSTONE_DOUBLE_SLAB); + public static final BlockType POLISHED_BLACKSTONE_PRESSURE_PLATE = register("minecraft:polished_blackstone_pressure_plate", BlockID.POLISHED_BLACKSTONE_PRESSURE_PLATE); + public static final BlockType POLISHED_BLACKSTONE_BUTTON = register("minecraft:polished_blackstone_button", BlockID.POLISHED_BLACKSTONE_BUTTON); + public static final BlockType POLISHED_BLACKSTONE_WALL = register("minecraft:polished_blackstone_wall", BlockID.POLISHED_BLACKSTONE_WALL); + public static final BlockType WARPED_HYPHAE = register("minecraft:warped_hyphae", BlockID.WARPED_HYPHAE); + public static final BlockType CRIMSON_HYPHAE = register("minecraft:crimson_hyphae", BlockID.CRIMSON_HYPHAE); + public static final BlockType STRIPPED_CRIMSON_HYPHAE = register("minecraft:stripped_crimson_hyphae", BlockID.STRIPPED_CRIMSON_HYPHAE); + public static final BlockType STRIPPED_WARPED_HYPHAE = register("minecraft:stripped_warped_hyphae", BlockID.STRIPPED_WARPED_HYPHAE); + public static final BlockType CHISELED_NETHER_BRICKS = register("minecraft:chiseled_nether_bricks", BlockID.CHISELED_NETHER_BRICKS); + public static final BlockType CRACKED_NETHER_BRICKS = register("minecraft:cracked_nether_bricks", BlockID.CRACKED_NETHER_BRICKS); + public static final BlockType QUARTZ_BRICKS = register("minecraft:quartz_bricks", BlockID.QUARTZ_BRICKS); + public static final BlockType POWDER_SNOW = register("minecraft:powder_snow", BlockID.POWDER_SNOW); + public static final BlockType SCULK_SENSOR = register("minecraft:sculk_sensor", BlockID.SCULK_SENSOR); + public static final BlockType POINTED_DRIPSTONE = register("minecraft:pointed_dripstone", BlockID.POINTED_DRIPSTONE); + public static final BlockType COPPER_ORE = register("minecraft:copper_ore", BlockID.COPPER_ORE); + public static final BlockType LIGHTNING_ROD = register("minecraft:lightning_rod", BlockID.LIGHTNING_ROD); + public static final BlockType DRIPSTONE_BLOCK = register("minecraft:dripstone_block", BlockID.DRIPSTONE_BLOCK); + public static final BlockType ROOTED_DIRT = register("minecraft:dirt_with_roots", BlockID.ROOTED_DIRT); + public static final BlockType HANGING_ROOTS = register("minecraft:hanging_roots", BlockID.HANGING_ROOTS); + public static final BlockType MOSS_BLOCK = register("minecraft:moss_block", BlockID.MOSS_BLOCK); + public static final BlockType SPORE_BLOSSOM = register("minecraft:spore_blossom", BlockID.SPORE_BLOSSOM); + public static final BlockType CAVE_VINES = register("minecraft:cave_vines", BlockID.CAVE_VINES); + public static final BlockType BIG_DRIPLEAF = register("minecraft:big_dripleaf", BlockID.BIG_DRIPLEAF); + public static final BlockType AZALEA_LEAVES = register("minecraft:azalea_leaves", BlockID.AZALEA_LEAVES); + public static final BlockType AZALEA_LEAVES_FLOWERED = register("minecraft:azalea_leaves_flowered", BlockID.AZALEA_LEAVES_FLOWERED); + public static final BlockType CALCITE = register("minecraft:calcite", BlockID.CALCITE); + public static final BlockType AMETHYST_BLOCK = register("minecraft:amethyst_block", BlockID.AMETHYST_BLOCK); + public static final BlockType BUDDING_AMETHYST = register("minecraft:budding_amethyst", BlockID.BUDDING_AMETHYST); + public static final BlockType AMETHYST_CLUSTER = register("minecraft:amethyst_cluster", BlockID.AMETHYST_CLUSTER); + public static final BlockType LARGE_AMETHYST_BUD = register("minecraft:large_amethyst_bud", BlockID.LARGE_AMETHYST_BUD); + public static final BlockType MEDIUM_AMETHYST_BUD = register("minecraft:medium_amethyst_bud", BlockID.MEDIUM_AMETHYST_BUD); + public static final BlockType SMALL_AMETHYST_BUD = register("minecraft:small_amethyst_bud", BlockID.SMALL_AMETHYST_BUD); + public static final BlockType TUFF = register("minecraft:tuff", BlockID.TUFF); + public static final BlockType TINTED_GLASS = register("minecraft:tinted_glass", BlockID.TINTED_GLASS); + public static final BlockType MOSS_CARPET = register("minecraft:moss_carpet", BlockID.MOSS_CARPET); + public static final BlockType SMALL_DRIPLEAF = register("minecraft:small_dripleaf_block", BlockID.SMALL_DRIPLEAF); + public static final BlockType AZALEA = register("minecraft:azalea", BlockID.AZALEA); + public static final BlockType FLOWERING_AZALEA = register("minecraft:flowering_azalea", BlockID.FLOWERING_AZALEA); + public static final BlockType GLOW_FRAME = register("minecraft:item.glow_frame", BlockID.GLOW_FRAME); + public static final BlockType COPPER_BLOCK = register("minecraft:copper_block", BlockID.COPPER_BLOCK); + public static final BlockType EXPOSED_COPPER = register("minecraft:exposed_copper", BlockID.EXPOSED_COPPER); + public static final BlockType WEATHERED_COPPER = register("minecraft:weathered_copper", BlockID.WEATHERED_COPPER); + public static final BlockType OXIDIZED_COPPER = register("minecraft:oxidized_copper", BlockID.OXIDIZED_COPPER); + public static final BlockType WAXED_COPPER = register("minecraft:waxed_copper", BlockID.WAXED_COPPER); + public static final BlockType WAXED_EXPOSED_COPPER = register("minecraft:waxed_exposed_copper", BlockID.WAXED_EXPOSED_COPPER); + public static final BlockType WAXED_WEATHERED_COPPER = register("minecraft:waxed_weathered_copper", BlockID.WAXED_WEATHERED_COPPER); + public static final BlockType CUT_COPPER = register("minecraft:cut_copper", BlockID.CUT_COPPER); + public static final BlockType EXPOSED_CUT_COPPER = register("minecraft:exposed_cut_copper", BlockID.EXPOSED_CUT_COPPER); + public static final BlockType WEATHERED_CUT_COPPER = register("minecraft:weathered_cut_copper", BlockID.WEATHERED_CUT_COPPER); + public static final BlockType OXIDIZED_CUT_COPPER = register("minecraft:oxidized_cut_copper", BlockID.OXIDIZED_CUT_COPPER); + public static final BlockType WAXED_CUT_COPPER = register("minecraft:waxed_cut_copper", BlockID.WAXED_CUT_COPPER); + public static final BlockType WAXED_EXPOSED_CUT_COPPER = register("minecraft:waxed_exposed_cut_copper", BlockID.WAXED_EXPOSED_CUT_COPPER); + public static final BlockType WAXED_WEATHERED_CUT_COPPER = register("minecraft:waxed_weathered_cut_copper", BlockID.WAXED_WEATHERED_CUT_COPPER); + public static final BlockType CUT_COPPER_STAIRS = register("minecraft:cut_copper_stairs", BlockID.CUT_COPPER_STAIRS); + public static final BlockType EXPOSED_CUT_COPPER_STAIRS = register("minecraft:exposed_cut_copper_stairs", BlockID.EXPOSED_CUT_COPPER_STAIRS); + public static final BlockType WEATHERED_CUT_COPPER_STAIRS = register("minecraft:weathered_cut_copper_stairs", BlockID.WEATHERED_CUT_COPPER_STAIRS); + public static final BlockType OXIDIZED_CUT_COPPER_STAIRS = register("minecraft:oxidized_cut_copper_stairs", BlockID.OXIDIZED_CUT_COPPER_STAIRS); + public static final BlockType WAXED_CUT_COPPER_STAIRS = register("minecraft:waxed_cut_copper_stairs", BlockID.WAXED_CUT_COPPER_STAIRS); + public static final BlockType WAXED_EXPOSED_CUT_COPPER_STAIRS = register("minecraft:waxed_exposed_cut_copper_stairs", BlockID.WAXED_EXPOSED_CUT_COPPER_STAIRS); + public static final BlockType WAXED_WEATHERED_CUT_COPPER_STAIRS = register("minecraft:waxed_weathered_cut_copper_stairs", BlockID.WAXED_WEATHERED_CUT_COPPER_STAIRS); + public static final BlockType CUT_COPPER_SLAB = register("minecraft:cut_copper_slab", BlockID.CUT_COPPER_SLAB); + public static final BlockType EXPOSED_CUT_COPPER_SLAB = register("minecraft:exposed_cut_copper_slab", BlockID.EXPOSED_CUT_COPPER_SLAB); + public static final BlockType WEATHERED_CUT_COPPER_SLAB = register("minecraft:weathered_cut_copper_slab", BlockID.WEATHERED_CUT_COPPER_SLAB); + public static final BlockType OXIDIZED_CUT_COPPER_SLAB = register("minecraft:oxidized_cut_copper_slab", BlockID.OXIDIZED_CUT_COPPER_SLAB); + public static final BlockType WAXED_CUT_COPPER_SLAB = register("minecraft:waxed_cut_copper_slab", BlockID.WAXED_CUT_COPPER_SLAB); + public static final BlockType WAXED_EXPOSED_CUT_COPPER_SLAB = register("minecraft:waxed_exposed_cut_copper_slab", BlockID.WAXED_EXPOSED_CUT_COPPER_SLAB); + public static final BlockType WAXED_WEATHERED_CUT_COPPER_SLAB = register("minecraft:waxed_weathered_cut_copper_slab", BlockID.WAXED_WEATHERED_CUT_COPPER_SLAB); + public static final BlockType DOUBLE_CUT_COPPER_SLAB = register("minecraft:double_cut_copper_slab", BlockID.DOUBLE_CUT_COPPER_SLAB); + public static final BlockType EXPOSED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:exposed_double_cut_copper_slab", BlockID.EXPOSED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType WEATHERED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:weathered_double_cut_copper_slab", BlockID.WEATHERED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType OXIDIZED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:oxidized_double_cut_copper_slab", BlockID.OXIDIZED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType WAXED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:waxed_double_cut_copper_slab", BlockID.WAXED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:waxed_exposed_double_cut_copper_slab", BlockID.WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:waxed_weathered_double_cut_copper_slab", BlockID.WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType CAVE_VINES_BODY_WITH_BERRIES = register("minecraft:cave_vines_body_with_berries", BlockID.CAVE_VINES_BODY_WITH_BERRIES); + public static final BlockType CAVE_VINES_HEAD_WITH_BERRIES = register("minecraft:cave_vines_head_with_berries", BlockID.CAVE_VINES_HEAD_WITH_BERRIES); + public static final BlockType SMOOTH_BASALT = register("minecraft:smooth_basalt", BlockID.SMOOTH_BASALT); + public static final BlockType DEEPSLATE = register("minecraft:deepslate", BlockID.DEEPSLATE); + public static final BlockType COBBLED_DEEPSLATE = register("minecraft:cobbled_deepslate", BlockID.COBBLED_DEEPSLATE); + public static final BlockType COBBLED_DEEPSLATE_SLAB = register("minecraft:cobbled_deepslate_slab", BlockID.COBBLED_DEEPSLATE_SLAB); + public static final BlockType COBBLED_DEEPSLATE_STAIRS = register("minecraft:cobbled_deepslate_stairs", BlockID.COBBLED_DEEPSLATE_STAIRS); + public static final BlockType COBBLED_DEEPSLATE_WALL = register("minecraft:cobbled_deepslate_wall", BlockID.COBBLED_DEEPSLATE_WALL); + public static final BlockType POLISHED_DEEPSLATE = register("minecraft:polished_deepslate", BlockID.POLISHED_DEEPSLATE); + public static final BlockType POLISHED_DEEPSLATE_SLAB = register("minecraft:polished_deepslate_slab", BlockID.POLISHED_DEEPSLATE_SLAB); + public static final BlockType POLISHED_DEEPSLATE_STAIRS = register("minecraft:polished_deepslate_stairs", BlockID.POLISHED_DEEPSLATE_STAIRS); + public static final BlockType POLISHED_DEEPSLATE_WALL = register("minecraft:polished_deepslate_wall", BlockID.POLISHED_DEEPSLATE_WALL); + public static final BlockType DEEPSLATE_TILES = register("minecraft:deepslate_tiles", BlockID.DEEPSLATE_TILES); + public static final BlockType DEEPSLATE_TILE_SLAB = register("minecraft:deepslate_tile_slab", BlockID.DEEPSLATE_TILE_SLAB); + public static final BlockType DEEPSLATE_TILE_STAIRS = register("minecraft:deepslate_tile_stairs", BlockID.DEEPSLATE_TILE_STAIRS); + public static final BlockType DEEPSLATE_TILE_WALL = register("minecraft:deepslate_tile_wall", BlockID.DEEPSLATE_TILE_WALL); + public static final BlockType DEEPSLATE_BRICKS = register("minecraft:deepslate_bricks", BlockID.DEEPSLATE_BRICKS); + public static final BlockType DEEPSLATE_BRICK_SLAB = register("minecraft:deepslate_brick_slab", BlockID.DEEPSLATE_BRICK_SLAB); + public static final BlockType DEEPSLATE_BRICK_STAIRS = register("minecraft:deepslate_brick_stairs", BlockID.DEEPSLATE_BRICK_STAIRS); + public static final BlockType DEEPSLATE_BRICK_WALL = register("minecraft:deepslate_brick_wall", BlockID.DEEPSLATE_BRICK_WALL); + public static final BlockType CHISELED_DEEPSLATE = register("minecraft:chiseled_deepslate", BlockID.CHISELED_DEEPSLATE); + public static final BlockType COBBLED_DEEPSLATE_DOUBLE_SLAB = register("minecraft:cobbled_deepslate_double_slab", BlockID.COBBLED_DEEPSLATE_DOUBLE_SLAB); + public static final BlockType POLISHED_DEEPSLATE_DOUBLE_SLAB = register("minecraft:polished_deepslate_double_slab", BlockID.POLISHED_DEEPSLATE_DOUBLE_SLAB); + public static final BlockType DEEPSLATE_TILE_DOUBLE_SLAB = register("minecraft:deepslate_tile_double_slab", BlockID.DEEPSLATE_TILE_DOUBLE_SLAB); + public static final BlockType DEEPSLATE_BRICK_DOUBLE_SLAB = register("minecraft:deepslate_brick_double_slab", BlockID.DEEPSLATE_BRICK_DOUBLE_SLAB); + public static final BlockType DEEPSLATE_LAPIS_ORE = register("minecraft:deepslate_lapis_ore", BlockID.DEEPSLATE_LAPIS_ORE); + public static final BlockType DEEPSLATE_IRON_ORE = register("minecraft:deepslate_iron_ore", BlockID.DEEPSLATE_IRON_ORE); + public static final BlockType DEEPSLATE_GOLD_ORE = register("minecraft:deepslate_gold_ore", BlockID.DEEPSLATE_GOLD_ORE); + public static final BlockType DEEPSLATE_REDSTONE_ORE = register("minecraft:deepslate_redstone_ore", BlockID.DEEPSLATE_REDSTONE_ORE); + public static final BlockType LIT_DEEPSLATE_REDSTONE_ORE = register("minecraft:lit_deepslate_redstone_ore", BlockID.LIT_DEEPSLATE_REDSTONE_ORE); + public static final BlockType DEEPSLATE_DIAMOND_ORE = register("minecraft:deepslate_diamond_ore", BlockID.DEEPSLATE_DIAMOND_ORE); + public static final BlockType DEEPSLATE_COAL_ORE = register("minecraft:deepslate_coal_ore", BlockID.DEEPSLATE_COAL_ORE); + public static final BlockType DEEPSLATE_EMERALD_ORE = register("minecraft:deepslate_emerald_ore", BlockID.DEEPSLATE_EMERALD_ORE); + public static final BlockType DEEPSLATE_COPPER_ORE = register("minecraft:deepslate_copper_ore", BlockID.DEEPSLATE_COPPER_ORE); + public static final BlockType CRACKED_DEEPSLATE_TILES = register("minecraft:cracked_deepslate_tiles", BlockID.CRACKED_DEEPSLATE_TILES); + public static final BlockType CRACKED_DEEPSLATE_BRICKS = register("minecraft:cracked_deepslate_bricks", BlockID.CRACKED_DEEPSLATE_BRICKS); + public static final BlockType GLOW_LICHEN = register("minecraft:glow_lichen", BlockID.GLOW_LICHEN); + public static final BlockType CANDLE = register("minecraft:candle", BlockID.CANDLE); + public static final BlockType WHITE_CANDLE = register("minecraft:white_candle", BlockID.WHITE_CANDLE); + public static final BlockType ORANGE_CANDLE = register("minecraft:orange_candle", BlockID.ORANGE_CANDLE); + public static final BlockType MAGENTA_CANDLE = register("minecraft:magenta_candle", BlockID.MAGENTA_CANDLE); + public static final BlockType LIGHT_BLUE_CANDLE = register("minecraft:light_blue_candle", BlockID.LIGHT_BLUE_CANDLE); + public static final BlockType YELLOW_CANDLE = register("minecraft:yellow_candle", BlockID.YELLOW_CANDLE); + public static final BlockType LIME_CANDLE = register("minecraft:lime_candle", BlockID.LIME_CANDLE); + public static final BlockType PINK_CANDLE = register("minecraft:pink_candle", BlockID.PINK_CANDLE); + public static final BlockType GRAY_CANDLE = register("minecraft:gray_candle", BlockID.GRAY_CANDLE); + public static final BlockType LIGHT_GRAY_CANDLE = register("minecraft:light_gray_candle", BlockID.LIGHT_GRAY_CANDLE); + public static final BlockType CYAN_CANDLE = register("minecraft:cyan_candle", BlockID.CYAN_CANDLE); + public static final BlockType PURPLE_CANDLE = register("minecraft:purple_candle", BlockID.PURPLE_CANDLE); + public static final BlockType BLUE_CANDLE = register("minecraft:blue_candle", BlockID.BLUE_CANDLE); + public static final BlockType BROWN_CANDLE = register("minecraft:brown_candle", BlockID.BROWN_CANDLE); + public static final BlockType GREEN_CANDLE = register("minecraft:green_candle", BlockID.GREEN_CANDLE); + public static final BlockType RED_CANDLE = register("minecraft:red_candle", BlockID.RED_CANDLE); + public static final BlockType BLACK_CANDLE = register("minecraft:black_candle", BlockID.BLACK_CANDLE); + public static final BlockType WAXED_OXIDIZED_COPPER = register("minecraft:waxed_oxidized_copper", BlockID.WAXED_OXIDIZED_COPPER); + public static final BlockType WAXED_OXIDIZED_CUT_COPPER = register("minecraft:waxed_oxidized_cut_copper", BlockID.WAXED_OXIDIZED_CUT_COPPER); + public static final BlockType WAXED_OXIDIZED_CUT_COPPER_STAIRS = register("minecraft:waxed_oxidized_cut_copper_stairs", BlockID.WAXED_OXIDIZED_CUT_COPPER_STAIRS); + public static final BlockType WAXED_OXIDIZED_CUT_COPPER_SLAB = register("minecraft:waxed_oxidized_cut_copper_slab", BlockID.WAXED_OXIDIZED_CUT_COPPER_SLAB); + public static final BlockType WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB = register("minecraft:waxed_oxidized_double_cut_copper_slab", BlockID.WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB); + public static final BlockType RAW_IRON_BLOCK = register("minecraft:raw_iron_block", BlockID.RAW_IRON_BLOCK); + public static final BlockType RAW_COPPER_BLOCK = register("minecraft:raw_copper_block", BlockID.RAW_COPPER_BLOCK); + public static final BlockType RAW_GOLD_BLOCK = register("minecraft:raw_gold_block", BlockID.RAW_GOLD_BLOCK); + public static final BlockType INFESTED_DEEPSLATE = register("minecraft:infested_deepslate", BlockID.INFESTED_DEEPSLATE); + public static final BlockType MUD = register("minecraft:mud", BlockID.MUD); + public static final BlockType MUD_BRICKS = register("minecraft:mud_bricks", BlockID.MUD_BRICKS); + public static final BlockType PACKED_MUD = register("minecraft:packed_mud", BlockID.PACKED_MUD); + public static final BlockType MUD_BRICK_SLAB = register("minecraft:mud_brick_slab", BlockID.MUD_BRICK_SLAB); + public static final BlockType MUD_BRICK_DOUBLE_SLAB = register("minecraft:mud_brick_double_slab", BlockID.MUD_BRICK_DOUBLE_SLAB); + public static final BlockType MUD_BRICK_STAIRS = register("minecraft:mud_brick_stairs", BlockID.MUD_BRICK_STAIRS); + public static final BlockType MUD_BRICK_WALL = register("minecraft:mud_brick_wall", BlockID.MUD_BRICK_WALL); + + + private static BlockType register(String identifier, int legacyId) { + return register(new BlockTypeImpl(identifier, legacyId)); + } + + private static BlockType register(BlockType blockType) { + BlockType old = types.putIfAbsent(blockType.getLegacyId(), blockType); + /*if (old != null) { // TODO: there are alternate names for some items + throw new IllegalArgumentException("Block type with id " + itemType.getLegacyId() + " already exists: " + old); + }*/ + identifiers.putIfAbsent(blockType.getIdentifier(), blockType); // TODO: using identifiers.put() would be better + return old == null ? blockType : old; + } + + public static BlockType getFromLegacy(int legacyId) { + return types.get(legacyId); + } + + public static BlockType get(String identifier) { + return identifiers.get(identifier); + } + + @Data + private static class BlockTypeImpl implements BlockType { + private final String identifier; + private final int legacyId; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java b/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java index 15f33862f1d..510cefa29a1 100644 --- a/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java +++ b/src/main/java/cn/nukkit/block/BlockUndyedShulkerBox.java @@ -1,34 +1,15 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package cn.nukkit.block; -import cn.nukkit.Player; import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.blockentity.BlockEntityShulkerBox; -import cn.nukkit.inventory.ShulkerBoxInventory; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; -import cn.nukkit.item.ItemTool; -import cn.nukkit.math.BlockFace; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.nbt.tag.Tag; +import cn.nukkit.inventory.ContainerInventory; +import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.utils.BlockColor; +import cn.nukkit.utils.DyeColor; -import java.util.Map; - -/** - * - * @author Reece Mackie - */ -public class BlockUndyedShulkerBox extends BlockTransparent { +public class BlockUndyedShulkerBox extends BlockShulkerBox { public BlockUndyedShulkerBox() { - super(); + super(0); } @Override @@ -42,123 +23,32 @@ public String getName() { } @Override - public double getHardness() { - return 2; - } - - @Override - public double getResistance() { - return 10; - } - - @Override - public boolean canBeActivated() { - return true; + public BlockColor getColor() { + return BlockColor.PURPLE_BLOCK_COLOR; } @Override - public int getToolType() { - return ItemTool.TYPE_PICKAXE; + public DyeColor getDyeColor() { + return null; } @Override - public Item toItem() { - ItemBlock item = new ItemBlock(this, this.getDamage(), 1); - - BlockEntityShulkerBox t = (BlockEntityShulkerBox) this.getLevel().getBlockEntity(this); - - if (t != null) { - ShulkerBoxInventory i = t.getRealInventory(); - - if (!i.isEmpty()) { - CompoundTag nbt = item.getNamedTag(); - if (nbt == null) - nbt = new CompoundTag(""); - - ListTag items = new ListTag<>(); - - for (int it = 0; it < i.getSize(); it++) { - if (i.getItem(it).getId() != Item.AIR) { - CompoundTag d = NBTIO.putItemHelper(i.getItem(it), it); - items.add(d); - } - } - - nbt.put("Items", items); - - item.setCompoundTag(nbt); - } - - if (t.hasName()) { - item.setCustomName(t.getName()); - } - } - - return item; + public void setDamage(int meta) { } @Override - public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.getLevel().setBlock(block, this, true); - CompoundTag nbt = BlockEntity.getDefaultCompound(this, BlockEntity.SHULKER_BOX) - .putByte("facing", face.getIndex()); - - if (item.hasCustomName()) { - nbt.putString("CustomName", item.getCustomName()); - } - - CompoundTag t = item.getNamedTag(); - - // This code gets executed when the player has broken the shulker box and placed it back (©Kevims 2020) - if (t != null && t.contains("Items")) { - nbt.putList(t.getList("Items")); - } - - // This code gets executed when the player has copied the shulker box in creative mode (©Kevims 2020) - if (item.hasCustomBlockData()) { - Map customData = item.getCustomBlockData().getTags(); - for (Map.Entry tag : customData.entrySet()) { - nbt.put(tag.getKey(), tag.getValue()); - } - } - - BlockEntityShulkerBox box = (BlockEntityShulkerBox) BlockEntity.createBlockEntity(BlockEntity.SHULKER_BOX, this.getLevel().getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), nbt); - return box != null; - } - - @Override - public boolean canHarvestWithHand() { - return false; + public boolean hasComparatorInputOverride() { + return true; } @Override - public boolean onActivate(Item item, Player player) { - if (player != null) { - BlockEntity t = this.getLevel().getBlockEntity(this); - BlockEntityShulkerBox box; - if (t instanceof BlockEntityShulkerBox) { - box = (BlockEntityShulkerBox) t; - } else { - CompoundTag nbt = BlockEntity.getDefaultCompound(this, BlockEntity.SHULKER_BOX); - box = (BlockEntityShulkerBox) BlockEntity.createBlockEntity(BlockEntity.SHULKER_BOX, this.getLevel().getChunk(this.getFloorX() >> 4, this.getFloorZ() >> 4), nbt); - if (box == null) { - return false; - } - } - - Block block = this.getSide(BlockFace.fromIndex(box.namedTag.getByte("facing"))); - if (!(block instanceof BlockAir) && !(block instanceof BlockLiquid) && !(block instanceof BlockFlowable)) { - return true; - } + public int getComparatorInputOverride() { + BlockEntity be = this.getLevel().getBlockEntity(this); - player.addWindow(box.getInventory()); + if (!(be instanceof InventoryHolder)) { + return 0; } - return true; - } - - @Override - public BlockColor getColor() { - return BlockColor.PURPLE_BLOCK_COLOR; + return ContainerInventory.calculateRedstone(((InventoryHolder) be).getInventory()); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockUnknown.java b/src/main/java/cn/nukkit/block/BlockUnknown.java index 4a89311d83b..514e145976b 100644 --- a/src/main/java/cn/nukkit/block/BlockUnknown.java +++ b/src/main/java/cn/nukkit/block/BlockUnknown.java @@ -1,7 +1,7 @@ package cn.nukkit.block; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockUnknown extends BlockMeta { @@ -22,6 +22,11 @@ public int getId() { return id; } + @Override + public double getHardness() { + return 0.1; + } + @Override public String getName() { return "Unknown"; diff --git a/src/main/java/cn/nukkit/block/BlockVine.java b/src/main/java/cn/nukkit/block/BlockVine.java index bc097954203..72f49fb7bfb 100644 --- a/src/main/java/cn/nukkit/block/BlockVine.java +++ b/src/main/java/cn/nukkit/block/BlockVine.java @@ -13,9 +13,7 @@ import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.utils.BlockColor; -import java.util.EnumSet; import java.util.Random; -import java.util.Set; import java.util.concurrent.ThreadLocalRandom; /** @@ -74,7 +72,6 @@ public boolean canBeClimbed() { @Override public void onEntityCollide(Entity entity) { entity.resetFallDistance(); - entity.onGround = true; } @Override @@ -140,7 +137,7 @@ protected AxisAlignedBB recalculateBoundingBox() { public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (block.getId() != VINE && target.isSolid() && face.getHorizontalIndex() != -1) { this.setDamage(getMetaFromFace(face.getOpposite())); - this.getLevel().setBlock(block, this, true, true); + this.getLevel().setBlock(this, this, true, true); return true; } @@ -160,25 +157,24 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { + int meta = this.getDamage(); Block up = this.up(); - Set upFaces = up instanceof BlockVine ? ((BlockVine) up).getFaces() : null; - Set faces = this.getFaces(); for (BlockFace face : BlockFace.Plane.HORIZONTAL) { - if (!this.getSide(face).isSolid() && (upFaces == null || !upFaces.contains(face))) { - faces.remove(face); + int faceMeta = getMetaFromFace(face); + if (!this.getSide(face).isSolid() && (up.getId() != VINE || (up.getDamage() & faceMeta) != faceMeta)) { + meta &= ~faceMeta; } } - if (faces.isEmpty() && !up.isSolid()) { + if (meta == 0 && !up.isSolid()) { this.getLevel().useBreakOn(this, null, null, true); return Level.BLOCK_UPDATE_NORMAL; } - int meta = getMetaFromFaces(faces); if (meta != this.getDamage()) { this.level.setBlock(this, Block.get(VINE, meta), true); return Level.BLOCK_UPDATE_NORMAL; @@ -191,7 +187,7 @@ public int onUpdate(int type) { int faceMeta = getMetaFromFace(face); int meta = this.getDamage(); - if (this.y < 255 && face == BlockFace.UP && block.getId() == AIR) { + if (this.y < this.level.getMaxBlockY() && face == BlockFace.UP && block.getId() == AIR) { if (this.canSpread()) { for (BlockFace horizontalFace : BlockFace.Plane.HORIZONTAL) { if (random.nextBoolean() || !this.getSide(horizontalFace).getSide(face).isSolid()) { @@ -228,7 +224,7 @@ public int onUpdate(int type) { putVine(this, meta, null); } } - } else if (this.y > 0) { + } else if (this.y > this.level.getMinBlockY()) { Block below = this.down(); int id = below.getId(); if (id == AIR || id == VINE) { @@ -255,7 +251,7 @@ private boolean canSpread() { for (int x = blockX - 4; x <= blockX + 4; x++) { for (int z = blockZ - 4; z <= blockZ + 4; z++) { for (int y = blockY - 1; y <= blockY + 1; y++) { - if (this.level.getBlock(x, y, z).getId() == VINE) { + if (this.level.getBlockIdAt(x, y, z) == VINE) { if (++count >= 5) return false; } } @@ -294,37 +290,6 @@ private void putVineOnHorizontalFace(Block block, int meta, Block source) { } } - private Set getFaces() { - Set faces = EnumSet.noneOf(BlockFace.class); - - int meta = this.getDamage(); - if ((meta & 1) > 0) { - faces.add(BlockFace.SOUTH); - } - if ((meta & 2) > 0) { - faces.add(BlockFace.WEST); - } - if ((meta & 4) > 0) { - faces.add(BlockFace.NORTH); - } - if ((meta & 8) > 0) { - faces.add(BlockFace.EAST); - } - - return faces; - } - - private static int getMetaFromFaces(Set faces) { - int meta = 0; - - for (BlockFace face : faces) { - meta |= getMetaFromFace(face); - - } - - return meta; - } - private static int getMetaFromFace(BlockFace face) { switch (face) { case SOUTH: @@ -353,4 +318,19 @@ public BlockColor getColor() { public boolean canSilkTouch() { return true; } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockVinesNether.java b/src/main/java/cn/nukkit/block/BlockVinesNether.java new file mode 100644 index 00000000000..faaf54f1963 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockVinesNether.java @@ -0,0 +1,398 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.Entity; +import cn.nukkit.event.block.BlockGrowEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemDye; +import cn.nukkit.item.ItemID; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.math.BlockFace; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Implements the main logic of all nether vines. + * @author joserobjr + */ +public abstract class BlockVinesNether extends BlockTransparentMeta { + + public BlockVinesNether() { + this(0); + } + + public BlockVinesNether(int meta) { + super(meta); + } + + /** + * The direction that the vine will grow, vertical direction is expected but future implementations + * may also add horizontal directions. + * @return Normally, up or down. + */ + public abstract BlockFace getGrowthDirection(); + + /** + * The current age of this block. + */ + public abstract int getVineAge(); + + /** + * Changes the age of this block. + * @param vineAge The new age + */ + public abstract void setVineAge(int vineAge); + + /** + * The maximum accepted age of this block. + * @return Positive, inclusive value. + */ + public abstract int getMaxVineAge(); + + /** + * Changes the current vine age to a random new random age. + * + * @param pseudorandom If the the randomization should be pseudorandom. + */ + public void randomizeVineAge(boolean pseudorandom) { + if (pseudorandom) { + setVineAge(ThreadLocalRandom.current().nextInt(getMaxVineAge())); + return; + } + + double chance = 1.0D; + int age; + + ThreadLocalRandom random = ThreadLocalRandom.current(); + for(age = 0; random.nextDouble() < chance; ++age) { + chance *= 0.826D; + } + + setVineAge(age); + } + + @Override + public boolean place(@Nonnull Item item, @Nonnull Block block, @Nonnull Block target, @Nonnull BlockFace face, double fx, double fy, double fz, @Nullable Player player) { + Block support = getSide(getGrowthDirection().getOpposite()); + if (!isSupportValid(support)) { + return false; + } + + if (support.getId() == getId()) { + setVineAge(Math.min(getMaxVineAge(), ((BlockVinesNether) support).getVineAge() + 1)); + } else { + randomizeVineAge(true); + } + + return super.place(item, block, target, face, fx, fy, fz, player); + } + + @Override + public int onUpdate(int type) { + switch (type) { + case Level.BLOCK_UPDATE_RANDOM: + int maxVineAge = getMaxVineAge(); + if (getVineAge() < maxVineAge && ThreadLocalRandom.current().nextInt(10) == 0 + && findVineAge(true).orElse(maxVineAge) < maxVineAge) { + grow(); + } + return Level.BLOCK_UPDATE_RANDOM; + case Level.BLOCK_UPDATE_NORMAL: + if (!this.isSupportValid()) { + this.getLevel().useBreakOn(this); + } + return Level.BLOCK_UPDATE_NORMAL; + default: + return 0; + } + } + + /** + * Grow a single vine if possible. Calls {@link BlockGrowEvent} passing the positioned new state and the source block. + * @return If the vine grew successfully. + */ + public boolean grow() { + Block pos = getSide(getGrowthDirection()); + if (pos.getId() != AIR || pos.y < 0 || 255 < pos.y) { + return false; + } + + BlockVinesNether growing = clone(); + growing.x = pos.x; + growing.y = pos.y; + growing.z = pos.z; + growing.setVineAge(Math.min(getVineAge() + 1, getMaxVineAge())); + + BlockGrowEvent ev = new BlockGrowEvent(this, growing); + Server.getInstance().getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + return false; + } + + if (level.setBlock(pos, growing)) { + increaseRootAge(); + return true; + } + return false; + } + + /** + * Grow a random amount of vines. + * Calls {@link BlockGrowEvent} passing the positioned new state and the source block for each new vine being added + * to the world, if one of the events gets cancelled the growth gets interrupted. + * @return How many vines grew + */ + public int growMultiple() { + BlockFace growthDirection = getGrowthDirection(); + int age = getVineAge() + 1; + int maxAge = getMaxVineAge(); + BlockVinesNether growing = clone(); + growing.randomizeVineAge(false); + int blocksToGrow = growing.getVineAge(); + + int grew = 0; + for (int distance = 1; distance <= blocksToGrow; distance++) { + Block pos = getSide(growthDirection, distance); + if (pos.getId() != AIR || pos.y < 0 || 255 < pos.y) { + break; + } + + growing.setVineAge(Math.min(age++, maxAge)); + growing.x = pos.x; + growing.y = pos.y; + growing.z = pos.z; + + BlockGrowEvent ev = new BlockGrowEvent(this, growing.clone()); + Server.getInstance().getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + break; + } + + if (!level.setBlock(pos, ev.getNewState())) { + break; + } + + grew++; + } + + if (grew > 0) { + increaseRootAge(); + } + + return grew; + } + + /** + * Attempt to get the age of the root or the head of the vine. + * @param base True to get the age of the base (oldest block), false to get the age of the head (newest block) + * @return Empty if the target could not be reached. The age of the target if it was found. + */ + @Nonnull + public OptionalInt findVineAge(boolean base) { + return findVineBlock(base) + .map(vine-> OptionalInt.of(vine.getVineAge())) + .orElse(OptionalInt.empty()); + } + + /** + * Attempt to find the root or the head of the vine transversing the growth direction for up to 256 blocks. + * @param base True to find the base (oldest block), false to find the head (newest block) + * @return Empty if the target could not be reached or the block there isn't an instance of {@link BlockVinesNether}. + * The positioned block of the target if it was found. + */ + @Nonnull + public Optional findVineBlock(boolean base) { + return findVine(base) + .map(Position::getLevelBlock) + .filter(BlockVinesNether.class::isInstance) + .map(BlockVinesNether.class::cast); + } + + /** + * Attempt to find the root or the head of the vine transversing the growth direction for up to 256 blocks. + * @param base True to find the base (oldest block), false to find the head (newest block) + * @return Empty if the target could not be reached. The position of the target if it was found. + */ + @Nonnull + public Optional findVine(boolean base) { + BlockFace supportFace = getGrowthDirection(); + if (base) { + supportFace = supportFace.getOpposite(); + } + Position result = getLocation(); + int id = getId(); + int limit = 256; + while (--limit > 0) { + Position next = result.getSide(supportFace); + if (next.getLevelBlock().getId() == id) { + result = next; + } else { + break; + } + } + + return Optional.of(result); + } + + public Optional increaseRootAge() { + Block base = findVine(true).map(Position::getLevelBlock).orElse(null); + if (!(base instanceof BlockVinesNether)) { + return Optional.empty(); + } + + BlockVinesNether baseVine = (BlockVinesNether) base; + int vineAge = baseVine.getVineAge(); + if (vineAge < baseVine.getMaxVineAge()) { + baseVine.setVineAge(vineAge + 1); + if (getLevel().setBlock(baseVine, baseVine)) { + return Optional.of(true); + } + } + + return Optional.of(false); + } + + @Override + public boolean onActivate(@Nonnull Item item, @Nullable Player player) { + if (!(item.getId() == ItemID.DYE && item.getDamage() == ItemDye.BONE_MEAL)) { + return false; + } + + this.getLevel().addParticle(new BoneMealParticle(this)); + this.findVineBlock(false).ifPresent(BlockVinesNether::growMultiple); + + if (player != null && !player.isCreative()) { + item.count--; + } + return true; + } + + @Override + public Item[] getDrops(Item item) { + // They have a 33% (3/9) chance to drop a single weeping vine when broken, + // increased to 55% (5/9) with Fortune I, + // 77% (7/9) with Fortune II, + // and 100% with Fortune III. + // + // They always drop a single weeping vine when broken with shears or a tool enchanted with Silk Touch. + + int enchantmentLevel; + if (item.isShears() || (enchantmentLevel = item.getEnchantmentLevel(Enchantment.ID_FORTUNE_DIGGING)) >= 3) { + return new Item[]{ toItem() }; + } + + int chance = 3 + enchantmentLevel * 2; + if (ThreadLocalRandom.current().nextInt(9) < chance) { + return new Item[]{ toItem() }; + } + + return new Item[0]; + } + + protected boolean isSupportValid(@Nonnull Block support) { + return support.getId() == getId() || !support.isTransparent(); + } + + public boolean isSupportValid() { + return isSupportValid(getSide(getGrowthDirection().getOpposite())); + } + + @Override + public void onEntityCollide(Entity entity) { + entity.resetFallDistance(); + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + public double getHardness() { + return 0; + } + + @Override + public double getResistance() { + return 0; + } + + @Override + public boolean canBeClimbed() { + return true; + } + + @Override + public boolean canBeFlowedInto() { + return true; + } + + @Override + public boolean isSolid() { + return false; + } + + @Override + public double getMinX() { + return x+ (4/16.0); + } + + @Override + public double getMinZ() { + return z+ (4/16.0); + } + + @Override + public double getMaxX() { + return x+ (12/16.0); + } + + @Override + public double getMaxZ() { + return z+ (12/16.0); + } + + @Override + public double getMaxY() { + return y+ (15/16.0); + } + + @Override + public boolean canPassThrough() { + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } + + @Override + public boolean canBePushed() { + return false; + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean canSilkTouch() { + return true; + } + @Override + public BlockVinesNether clone() { + return (BlockVinesNether) super.clone(); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockVinesTwisting.java b/src/main/java/cn/nukkit/block/BlockVinesTwisting.java new file mode 100644 index 00000000000..2404d0f542d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockVinesTwisting.java @@ -0,0 +1,50 @@ +package cn.nukkit.block; + +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockVinesTwisting extends BlockVinesNether { + + public BlockVinesTwisting() { + this(0); + } + + public BlockVinesTwisting(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Twisting Vines"; + } + + @Override + public int getId() { + return TWISTING_VINES; + } + + @Override + public BlockFace getGrowthDirection() { + return BlockFace.UP; + } + + @Override + public int getVineAge() { + return this.getDamage(); + } + + @Override + public void setVineAge(int vineAge) { + this.setDamage(vineAge & 0x19); + } + + @Override + public int getMaxVineAge() { + return 25; + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWall.java b/src/main/java/cn/nukkit/block/BlockWall.java index 64414a248a4..802c57e6cc9 100644 --- a/src/main/java/cn/nukkit/block/BlockWall.java +++ b/src/main/java/cn/nukkit/block/BlockWall.java @@ -6,14 +6,14 @@ import cn.nukkit.math.SimpleAxisAlignedBB; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockWall extends BlockTransparentMeta { + public static final int NONE_MOSSY_WALL = 0; public static final int MOSSY_WALL = 1; - public BlockWall() { this(0); } @@ -53,7 +53,6 @@ public String getName() { @Override protected AxisAlignedBB recalculateBoundingBox() { - boolean north = this.canConnect(this.getSide(BlockFace.NORTH)); boolean south = this.canConnect(this.getSide(BlockFace.SOUTH)); boolean west = this.canConnect(this.getSide(BlockFace.WEST)); @@ -95,4 +94,9 @@ public int getToolType() { public boolean canHarvestWithHand() { return false; } + + @Override + public WaterloggingType getWaterloggingType() { + return WaterloggingType.WHEN_PLACED_IN_WATER; + } } diff --git a/src/main/java/cn/nukkit/block/BlockWallBanner.java b/src/main/java/cn/nukkit/block/BlockWallBanner.java index a9f5f8a35db..4efe0ff6a6e 100644 --- a/src/main/java/cn/nukkit/block/BlockWallBanner.java +++ b/src/main/java/cn/nukkit/block/BlockWallBanner.java @@ -3,9 +3,6 @@ import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; -/** - * Created by PetteriM1 - */ public class BlockWallBanner extends BlockBanner { public BlockWallBanner() { diff --git a/src/main/java/cn/nukkit/block/BlockWallBrickDeepslate.java b/src/main/java/cn/nukkit/block/BlockWallBrickDeepslate.java new file mode 100644 index 00000000000..394580d90b8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWallBrickDeepslate.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockWallBrickDeepslate extends BlockWall { + + public BlockWallBrickDeepslate() { + this(0); + } + + public BlockWallBrickDeepslate(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Deepslate Brick Wall"; + } + + @Override + public int getId() { + return DEEPSLATE_BRICK_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWallDeepslateCobbled.java b/src/main/java/cn/nukkit/block/BlockWallDeepslateCobbled.java new file mode 100644 index 00000000000..99f04665d12 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWallDeepslateCobbled.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockWallDeepslateCobbled extends BlockWall { + + public BlockWallDeepslateCobbled() { + this(0); + } + + public BlockWallDeepslateCobbled(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Cobbled Deepslate Wall"; + } + + @Override + public int getId() { + return COBBLED_DEEPSLATE_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWallDeepslatePolished.java b/src/main/java/cn/nukkit/block/BlockWallDeepslatePolished.java new file mode 100644 index 00000000000..3d2a245416d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWallDeepslatePolished.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockWallDeepslatePolished extends BlockWall { + + public BlockWallDeepslatePolished() { + this(0); + } + + public BlockWallDeepslatePolished(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Polished Deepslate Wall"; + } + + @Override + public int getId() { + return POLISHED_DEEPSLATE_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWallSign.java b/src/main/java/cn/nukkit/block/BlockWallSign.java index d7cb421ea03..695352b0b60 100644 --- a/src/main/java/cn/nukkit/block/BlockWallSign.java +++ b/src/main/java/cn/nukkit/block/BlockWallSign.java @@ -9,6 +9,13 @@ */ public class BlockWallSign extends BlockSignPost { + private static final int[] faces = { + 3, + 2, + 5, + 4, + }; + public BlockWallSign() { this(0); } @@ -29,12 +36,6 @@ public String getName() { @Override public int onUpdate(int type) { - int[] faces = { - 3, - 2, - 5, - 4, - }; if (type == Level.BLOCK_UPDATE_NORMAL) { if (this.getDamage() >= 2 && this.getDamage() <= 5) { if (this.getSide(BlockFace.fromIndex(faces[this.getDamage() - 2])).getId() == Item.AIR) { diff --git a/src/main/java/cn/nukkit/block/BlockWallTileDeepslate.java b/src/main/java/cn/nukkit/block/BlockWallTileDeepslate.java new file mode 100644 index 00000000000..74d84b78bb6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWallTileDeepslate.java @@ -0,0 +1,22 @@ +package cn.nukkit.block; + +public class BlockWallTileDeepslate extends BlockWall { + + public BlockWallTileDeepslate() { + this(0); + } + + public BlockWallTileDeepslate(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Deepslate Tile Wall"; + } + + @Override + public int getId() { + return DEEPSLATE_TILE_WALL; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedDoor.java b/src/main/java/cn/nukkit/block/BlockWarpedDoor.java new file mode 100644 index 00000000000..a761896fcc1 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedDoor.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockWarpedDoor extends BlockDoor { + + public BlockWarpedDoor() { + this(0); + } + + public BlockWarpedDoor(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Door"; + } + + @Override + public int getId() { + return WARPED_DOOR_BLOCK; + } + + @Override + public Item toItem() { + return Item.get(Item.WARPED_DOOR); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedFungus.java b/src/main/java/cn/nukkit/block/BlockWarpedFungus.java new file mode 100644 index 00000000000..ed95a37d7c9 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedFungus.java @@ -0,0 +1,99 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.level.generator.object.tree.ObjectWarpedTree; +import cn.nukkit.level.particle.BoneMealParticle; +import cn.nukkit.level.Position; +import cn.nukkit.utils.BlockColor; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; +import cn.nukkit.utils.DyeColor; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockWarpedFungus extends BlockFungus { + + public BlockWarpedFungus() { + // Does nothing + } + + @Override + public int getId() { + return WARPED_FUNGUS; + } + + @Override + public String getName() { + return "Warped Fungus"; + } + + @Override + protected boolean canGrowOn(Block support) { + return support.getId() == WARPED_NYLIUM; + } + + @Override + public boolean grow(Player cause) { + // TODO: + return false; + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } + + @Override + public int onUpdate(int type) { + if (type == Level.BLOCK_UPDATE_NORMAL) { + if (this.down().isTransparent()) { + this.getLevel().useBreakOn(this); + return Level.BLOCK_UPDATE_NORMAL; + } + } + + return 0; + } + + @Override + public boolean canPlaceOn(Block floor, Position pos) { + switch (floor.getId()) { + case BlockID.GRASS: + case BlockID.DIRT: + case BlockID.PODZOL: + case BlockID.FARMLAND: + case BlockID.CRIMSON_NYLIUM: + case BlockID.WARPED_NYLIUM: + case BlockID.MYCELIUM: + case BlockID.SOUL_SOIL: + return true; + default: + return false; + } + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.getId() == Item.DYE && item.getDamage() == DyeColor.WHITE.getDyeData()) { + if (player != null && (player.gamemode & 0x01) == 0) { + item.count--; + } + + if (ThreadLocalRandom.current().nextFloat() < 0.4 && this.level.getBlockIdAt((int) this.x, (int) this.y - 1, (int) this.z) == WARPED_NYLIUM) { + new ObjectWarpedTree().placeObject(this.level, (int) this.x, (int) this.y, (int) this.z, new NukkitRandom()); + this.level.setBlock(new Vector3((int) this.x, (int) this.y - 1, (int) this.z), Block.get(NETHERRACK), false, true); + } + + this.level.addParticle(new BoneMealParticle(this)); + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedNylium.java b/src/main/java/cn/nukkit/block/BlockWarpedNylium.java new file mode 100644 index 00000000000..8f087440bc5 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedNylium.java @@ -0,0 +1,21 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedNylium extends BlockNylium { + + @Override + public String getName() { + return "Warped Nylium"; + } + + @Override + public int getId() { + return WARPED_NYLIUM; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_NYLIUM_BLOCK_COLOR; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockWarpedPlanks.java b/src/main/java/cn/nukkit/block/BlockWarpedPlanks.java new file mode 100644 index 00000000000..be1be0f9d77 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedPlanks.java @@ -0,0 +1,55 @@ +package cn.nukkit.block; + +import cn.nukkit.item.ItemTool; +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedPlanks extends BlockSolid { + + public BlockWarpedPlanks() { + this(0); + } + + public BlockWarpedPlanks(int meta) { + // super(meta); + } + + @Override + public int getId() { + return WARPED_PLANKS; + } + + @Override + public String getName() { + return "Warped Planks"; + } + + @Override + public double getHardness() { + return 2; + } + + @Override + public double getResistance() { + return 3; + } + + @Override + public int getToolType() { + return ItemTool.TYPE_AXE; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockWarpedRoots.java b/src/main/java/cn/nukkit/block/BlockWarpedRoots.java new file mode 100644 index 00000000000..6b223c1921d --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedRoots.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedRoots extends BlockRoots { + + public BlockWarpedRoots() { + this(0); + } + + public BlockWarpedRoots(int meta) { + super(meta); + } + + @Override + public int getId() { + return WARPED_ROOTS; + } + + @Override + public String getName() { + return "Warped Roots"; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } + + @Override + public boolean canBeReplaced() { + return true; + } + + @Override + public boolean breakWhenPushed() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedSign.java b/src/main/java/cn/nukkit/block/BlockWarpedSign.java new file mode 100644 index 00000000000..84b5c9d4934 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedSign.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +public class BlockWarpedSign extends BlockSignPost { + + public BlockWarpedSign() { + this(0); + } + + public BlockWarpedSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Sign"; + } + + @Override + public int getId() { + return WARPED_STANDING_SIGN; + } + + @Override + public Item toItem() { + return Item.get(ItemID.WARPED_SIGN); + } + + @Override + protected int getPostId() { + return WARPED_STANDING_SIGN; + } + + @Override + protected int getWallId() { + return WARPED_WALL_SIGN; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedStairs.java b/src/main/java/cn/nukkit/block/BlockWarpedStairs.java new file mode 100644 index 00000000000..50a3b0551b6 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedStairs.java @@ -0,0 +1,39 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedStairs extends BlockStairsWood { + + public BlockWarpedStairs() { + this(0); + } + + public BlockWarpedStairs(int meta) { + super(meta); + } + + @Override + public int getId() { + return WARPED_STAIRS; + } + + @Override + public String getName() { + return "Warped Wood Stairs"; + } + + @Override + public BlockColor getColor() { + return BlockColor.CYAN_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedStem.java b/src/main/java/cn/nukkit/block/BlockWarpedStem.java new file mode 100644 index 00000000000..a94b86608ff --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedStem.java @@ -0,0 +1,44 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedStem extends BlockStem { + + public BlockWarpedStem() { + this(0); + } + + public BlockWarpedStem(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Stem"; + } + + @Override + public int getId() { + return WARPED_STEM; + } + + @Override + public int getStrippedId() { + return STRIPPED_WARPED_STEM; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_STEM_BLOCK_COLOR; + } + + @Override + public int getBurnChance() { + return 0; + } + + @Override + public int getBurnAbility() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedTrapdoor.java b/src/main/java/cn/nukkit/block/BlockWarpedTrapdoor.java new file mode 100644 index 00000000000..e12c5fb9fa2 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedTrapdoor.java @@ -0,0 +1,30 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; + +public class BlockWarpedTrapdoor extends BlockTrapdoor { + + public BlockWarpedTrapdoor() { + this(0); + } + + public BlockWarpedTrapdoor(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Trapdoor"; + } + + @Override + public int getId() { + return WARPED_TRAPDOOR; + } + + @Override + public Item toItem() { + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedWallSign.java b/src/main/java/cn/nukkit/block/BlockWarpedWallSign.java new file mode 100644 index 00000000000..8619a4fe6e3 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedWallSign.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.item.Item; + +public class BlockWarpedWallSign extends BlockWallSign { + + public BlockWarpedWallSign() { + this(0); + } + + public BlockWarpedWallSign(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Warped Wall Sign"; + } + + @Override + public int getId() { + return WARPED_WALL_SIGN; + } + + @Override + public Item toItem() { + return Item.get(Item.WARPED_SIGN); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWarpedWartBlock.java b/src/main/java/cn/nukkit/block/BlockWarpedWartBlock.java new file mode 100644 index 00000000000..13a7c7f0c67 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWarpedWartBlock.java @@ -0,0 +1,35 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWarpedWartBlock extends BlockNetherWartBlock { + + public BlockWarpedWartBlock() { + super(); + } + + @Override + public String getName() { + return "Warped Wart Block"; + } + + @Override + public int getId() { + return WARPED_WART_BLOCK; + } + + @Override + public double getResistance() { + return 1; + } + + @Override + public double getHardness() { + return 1; + } + + @Override + public BlockColor getColor() { + return BlockColor.WARPED_WART_BLOCK_COLOR; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/BlockWater.java b/src/main/java/cn/nukkit/block/BlockWater.java index c2a4e1fcc23..ae35555ee39 100644 --- a/src/main/java/cn/nukkit/block/BlockWater.java +++ b/src/main/java/cn/nukkit/block/BlockWater.java @@ -2,16 +2,28 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.event.block.WaterFrostEvent; import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.anvil.Anvil; import cn.nukkit.math.BlockFace; import cn.nukkit.utils.BlockColor; +import java.util.concurrent.ThreadLocalRandom; + /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockWater extends BlockLiquid { + /** + * Used to cache biome check for freezing + * 1 = can't freeze, 2 = can freeze + */ + private byte freezing; public BlockWater() { this(0); @@ -46,7 +58,7 @@ public BlockColor getColor() { @Override public BlockLiquid getBlock(int meta) { - return (BlockLiquid) Block.get(BlockID.WATER, meta); + return (BlockLiquid) Block.get(WATER, meta); } @Override @@ -62,4 +74,30 @@ public void onEntityCollide(Entity entity) { public int tickRate() { return 5; } + + @Override + public int onUpdate(int type) { + if (freezing != 1 && type == Level.BLOCK_UPDATE_RANDOM && this.getDamage() == 0) { + FullChunk chunk = getChunk(); + if (freezing < 1) { + freezing = Biome.getBiome(chunk.getBiomeId((int) this.x & 0x0f, (int) this.z & 0x0f)).isFreezing() ? (byte) 2 : (byte) 1; + } + if (freezing == 2) { + if (ThreadLocalRandom.current().nextInt(10) == 0 && chunk.getBlockLight((int) this.x & 0x0f, (int) this.y, (int) this.z & 0x0f) < 12 && chunk.getHighestBlockAt((int) this.x & 0x0f, (int) this.z & 0x0f, false) <= this.y) { + WaterFrostEvent ev = new WaterFrostEvent(this); + level.getServer().getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + level.setBlock(this, Block.get(Block.ICE), true, true); + } + } + } + return Level.BLOCK_UPDATE_RANDOM; + } + return super.onUpdate(type); + } + + @Override + public boolean usesWaterLogging() { + return level == null || !(level.getProvider() instanceof Anvil); + } } diff --git a/src/main/java/cn/nukkit/block/BlockWaterLily.java b/src/main/java/cn/nukkit/block/BlockWaterLily.java index fb4095683fc..7a7e1ad855f 100644 --- a/src/main/java/cn/nukkit/block/BlockWaterLily.java +++ b/src/main/java/cn/nukkit/block/BlockWaterLily.java @@ -2,7 +2,6 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; @@ -19,7 +18,6 @@ public BlockWaterLily() { } public BlockWaterLily(int meta) { - // Lily pad can't have meta. Also stops the server from throwing an exception with the block palette. super(0); } @@ -33,6 +31,11 @@ public int getId() { return WATER_LILY; } + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return this; + } + @Override public double getMinX() { return this.x + 0.0625; @@ -58,11 +61,6 @@ public double getMaxZ() { return this.z + 0.9375; } - @Override - protected AxisAlignedBB recalculateBoundingBox() { - return this; - } - @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { if (target instanceof BlockWater) { @@ -86,11 +84,6 @@ public int onUpdate(int type) { return 0; } - @Override - public Item toItem() { - return new ItemBlock(this, 0); - } - @Override public BlockColor getColor() { return BlockColor.FOLIAGE_BLOCK_COLOR; @@ -102,12 +95,7 @@ public boolean canPassThrough() { } @Override - public int getFullId() { - return this.getId() << 4; - } - - @Override - public void setDamage(int meta) { - + public boolean breakWhenPushed() { + return true; } } diff --git a/src/main/java/cn/nukkit/block/BlockWaterStill.java b/src/main/java/cn/nukkit/block/BlockWaterStill.java index ba28df1d508..40822a32fe6 100644 --- a/src/main/java/cn/nukkit/block/BlockWaterStill.java +++ b/src/main/java/cn/nukkit/block/BlockWaterStill.java @@ -1,7 +1,7 @@ package cn.nukkit.block; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class BlockWaterStill extends BlockWater { @@ -26,7 +26,6 @@ public String getName() { @Override public BlockLiquid getBlock(int meta) { - return (BlockLiquid) Block.get(BlockID.STILL_WATER, meta); + return (BlockLiquid) Block.get(STILL_WATER, meta); } - } diff --git a/src/main/java/cn/nukkit/block/BlockWeepingVines.java b/src/main/java/cn/nukkit/block/BlockWeepingVines.java new file mode 100644 index 00000000000..9735f1858eb --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWeepingVines.java @@ -0,0 +1,49 @@ +package cn.nukkit.block; + +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.BlockColor; + +public class BlockWeepingVines extends BlockVinesNether { + + public BlockWeepingVines() { + this(0); + } + + public BlockWeepingVines(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Weeping Vines"; + } + + @Override + public int getId() { + return WEEPING_VINES; + } + + @Override + public BlockFace getGrowthDirection() { + return BlockFace.DOWN; + } + + @Override + public int getVineAge() { + return this.getDamage(); + } + + @Override + public void setVineAge(int vineAge) { + this.setDamage(vineAge & 0x19); + } + + public int getMaxVineAge() { + return 25; + } + + @Override + public BlockColor getColor() { + return BlockColor.NETHERRACK_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateHeavy.java b/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateHeavy.java index c71f007a618..af93d51bafd 100644 --- a/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateHeavy.java +++ b/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateHeavy.java @@ -48,7 +48,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -59,7 +59,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateLight.java b/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateLight.java index e718b675c81..2c397f3bff7 100644 --- a/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateLight.java +++ b/src/main/java/cn/nukkit/block/BlockWeightedPressurePlateLight.java @@ -48,7 +48,7 @@ public int getToolType() { @Override public Item[] getDrops(Item item) { - if (item.isPickaxe() && item.getTier() >= ItemTool.TIER_WOODEN) { + if (item.isPickaxe()) { return new Item[]{ toItem() }; @@ -59,7 +59,7 @@ public Item[] getDrops(Item item) { @Override public Item toItem() { - return new ItemBlock(this, 0); + return new ItemBlock(Block.get(this.getId(), 0), 0); } @Override diff --git a/src/main/java/cn/nukkit/block/BlockWheat.java b/src/main/java/cn/nukkit/block/BlockWheat.java index c20f9007ae1..d4cf81c5fc9 100644 --- a/src/main/java/cn/nukkit/block/BlockWheat.java +++ b/src/main/java/cn/nukkit/block/BlockWheat.java @@ -1,8 +1,7 @@ package cn.nukkit.block; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemSeedsWheat; -import cn.nukkit.item.ItemWheat; +import cn.nukkit.utils.Utils; /** * Created on 2015/12/2 by xtypr. @@ -30,20 +29,25 @@ public int getId() { @Override public Item toItem() { - return new ItemSeedsWheat(); + return Item.get(Item.WHEAT_SEEDS); } @Override public Item[] getDrops(Item item) { if (this.getDamage() >= 0x07) { return new Item[]{ - new ItemWheat(), - new ItemSeedsWheat(0, (int) (4d * Math.random())) + Item.get(Item.WHEAT), + Item.get(Item.WHEAT_SEEDS, 0, Utils.random.nextInt(0, 4)) }; } else { return new Item[]{ - new ItemSeedsWheat() + Item.get(Item.WHEAT_SEEDS) }; } } + + @Override + public boolean breakWhenPushed() { + return true; + } } diff --git a/src/main/java/cn/nukkit/block/BlockWitherRose.java b/src/main/java/cn/nukkit/block/BlockWitherRose.java new file mode 100644 index 00000000000..d6117960841 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWitherRose.java @@ -0,0 +1,75 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityLiving; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.potion.Effect; + +public class BlockWitherRose extends BlockFlower { + + public BlockWitherRose() { + this(0); + } + + public BlockWitherRose(int meta) { + super(0); + } + + @Override + public int getId() { + return WITHER_ROSE; + } + @Override + public boolean canBeActivated() { + return false; + } + + @Override + public void onEntityCollide(Entity entity) { + if (level.getServer().getDifficulty() != 0 && entity instanceof EntityLiving) { + EntityLiving living = (EntityLiving) entity; + if (!living.invulnerable && !living.hasEffect(Effect.WITHER) + && (!(living instanceof Player) || !((Player) living).isCreative() && !((Player) living).isSpectator())) { + Effect effect = Effect.getEffect(Effect.WITHER); + effect.setDuration(40); + living.addEffect(effect); + } + } + } + + @Override + public boolean hasEntityCollision() { + return true; + } + + @Override + protected AxisAlignedBB recalculateBoundingBox() { + return this; + } + + @Override + public double getMinX() { + return this.x + 0.2; + } + + @Override + public double getMinZ() { + return this.z + 0.2; + } + + @Override + public double getMaxX() { + return this.x + 0.8; + } + + @Override + public double getMaxY() { + return this.y + 0.8; + } + + @Override + public double getMaxZ() { + return this.z + 0.8; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWood.java b/src/main/java/cn/nukkit/block/BlockWood.java index c9aebf02a9f..befbfb5e12f 100644 --- a/src/main/java/cn/nukkit/block/BlockWood.java +++ b/src/main/java/cn/nukkit/block/BlockWood.java @@ -8,15 +8,31 @@ import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockWood extends BlockSolidMeta { + public static final int OAK = 0; public static final int SPRUCE = 1; public static final int BIRCH = 2; public static final int JUNGLE = 3; + private static final short[] FACES = { + 0, + 0, + 0b1000, + 0b1000, + 0b0100, // full bark + 0b0100 + }; + + private static final int[] strippedIds = { + STRIPPED_OAK_LOG, + STRIPPED_SPRUCE_LOG, + STRIPPED_BIRCH_LOG, + STRIPPED_JUNGLE_LOG + }; public BlockWood() { this(0); @@ -38,12 +54,12 @@ public double getHardness() { @Override public double getResistance() { - return 10; + return 2; } @Override public String getName() { - String[] names = new String[]{ + String[] names = { "Oak Wood", "Spruce Wood", "Birch Wood", @@ -65,23 +81,17 @@ public int getBurnAbility() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - short[] faces = new short[]{ - 0, - 0, - 0b1000, - 0b1000, - 0b0100, - 0b0100 - }; - - this.setDamage(((this.getDamage() & 0x03) | faces[face.getIndex()])); + this.setDamage(((this.getDamage() & 0x03) | FACES[face.getIndex()])); this.getLevel().setBlock(block, this, true, true); - return true; } @Override public Item toItem() { + if (this.getDamage() > 11) { + int variant = this.getDamage() & 0x03; + return new ItemBlock(Block.get(WOOD_BARK, variant), variant); + } return new ItemBlock(this, this.getDamage() & 0x03); } @@ -92,7 +102,7 @@ public int getToolType() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x03) { default: case OAK: return BlockColor.WOOD_BLOCK_COLOR; @@ -104,4 +114,38 @@ public BlockColor getColor() { return BlockColor.DIRT_BLOCK_COLOR; } } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Item item, Player player) { + if (item.isAxe() && player != null && (player.isSurvival() || player.isCreative()) && (!(this instanceof BlockWoodBark) || this.getDamage() < 8)) { + Block strippedBlock = Block.get(getStrippedId(), getStrippedDamage()); + item.useOn(this); + this.level.setBlock(this, strippedBlock, true, true); + return true; + } + return false; + } + + protected int getStrippedId() { + int damage = getDamage(); + if ((damage & 0b1100) == 0b1100) { // Only bark + return WOOD_BARK; + } + + return strippedIds[damage & 0x03]; + } + + protected int getStrippedDamage() { + int damage = getDamage(); + if ((damage & 0b1100) == 0b1100) { // Only bark + return damage & 0x03 | 0x8; + } + + return damage >> 2; + } } diff --git a/src/main/java/cn/nukkit/block/BlockWood2.java b/src/main/java/cn/nukkit/block/BlockWood2.java index 1de3f2c7eaf..668978c1c32 100644 --- a/src/main/java/cn/nukkit/block/BlockWood2.java +++ b/src/main/java/cn/nukkit/block/BlockWood2.java @@ -1,9 +1,11 @@ package cn.nukkit.block; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; import cn.nukkit.utils.BlockColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockWood2 extends BlockWood { @@ -11,7 +13,7 @@ public class BlockWood2 extends BlockWood { public static final int ACACIA = 0; public static final int DARK_OAK = 1; - private static final String[] NAMES = new String[]{ + private static final String[] NAMES = { "Acacia Wood", "Dark Oak Wood", "" @@ -37,7 +39,7 @@ public String getName() { @Override public BlockColor getColor() { - switch(getDamage() & 0x07){ + switch (getDamage() & 0x07) { case ACACIA: return BlockColor.ORANGE_BLOCK_COLOR; case DARK_OAK: @@ -46,4 +48,48 @@ public BlockColor getColor() { return BlockColor.WOOD_BLOCK_COLOR; } } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public Item toItem() { + if (this.getDamage() > 11) { + int variant = this.getDamage() & 0x07; + return new ItemBlock(Block.get(WOOD_BARK, variant), variant); + } + return new ItemBlock(this, this.getDamage() & 0x03); + } + + @Override + protected int getStrippedId() { + int damage = getDamage(); + if ((damage & 0b1100) == 0b1100) { // Only bark + return WOOD_BARK; + } + + int typeId = damage & 0x3; + if (typeId == 0) { + return STRIPPED_ACACIA_LOG; + } else { + return STRIPPED_DARK_OAK_LOG; + } + } + + @Override + protected int getStrippedDamage() { + int damage = getDamage(); + if ((damage & 0b1100) == 0b1100) { // Only bark + int typeId = damage & 0x3; + if (typeId == 0) { + return 0x4 | 0x8; + } else { + return 0x5 | 0x8; + } + } + + return super.getStrippedDamage(); + } } diff --git a/src/main/java/cn/nukkit/block/BlockWoodBark.java b/src/main/java/cn/nukkit/block/BlockWoodBark.java new file mode 100644 index 00000000000..38c6db436a8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodBark.java @@ -0,0 +1,75 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; + +public class BlockWoodBark extends BlockWood { + + private static final String[] names = { + "Oak Wood", + "Spruce Wood", + "Birch Wood", + "Jungle Wood", + "Acacia Wood", + "Dark Oak Wood", + }; + + + public BlockWoodBark() { + this(0); + } + + public BlockWoodBark(int meta) { + super(meta); + } + + @Override + public void setDamage(int meta) { + super.setDamage(meta); + } + + @Override + public int getId() { + return WOOD_BARK; + } + + @Override + public String getName() { + int variant = (this.getDamage() & 0x7); + if (names.length <= variant) { + return names[0]; + } + return names[variant]; + } + + @Override + protected int getStrippedId() { + return this.getId(); + } + + @Override + protected int getStrippedDamage() { + return getDamage() | 0x8; + } + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + /*if (face.getAxis().isHorizontal()) { + if (face.getAxis() == BlockFace.Axis.X) { + setDamage(getDamage() | 0x10); + } else { + setDamage(getDamage() | 0x20); + } + }*/ + this.getLevel().setBlock(block, this, true, true); + return true; + } + + @Override + public Item toItem() { + int meta = this.getDamage() & 0xF; + return new ItemBlock(Block.get(WOOD_BARK, meta), meta); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStripped.java b/src/main/java/cn/nukkit/block/BlockWoodStripped.java new file mode 100644 index 00000000000..fbee7af91b8 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStripped.java @@ -0,0 +1,51 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.math.BlockFace; + +public abstract class BlockWoodStripped extends BlockWood { + + private static final short[] FACES = { + 0, + 0, + 0b10, + 0b10, + 0b01, + 0b01 + }; + + public BlockWoodStripped() { + this(0); + } + + public BlockWoodStripped(int meta) { + super(meta); + } + + @Override + public abstract int getId(); + + @Override + public abstract String getName(); + + @Override + public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { + this.setDamage(FACES[face.getIndex()]); + this.getLevel().setBlock(block, this, true, true); + return true; + } + + @Override + public boolean canBeActivated() { + return false; + } + + @Override + public Item toItem() { + // I was this before merge from upstream + // return new ItemBlock(this, this.getDamage()); + return new ItemBlock(Block.get(this.getId(), 0), 0); + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedAcacia.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedAcacia.java new file mode 100644 index 00000000000..8dda9abec07 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedAcacia.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedAcacia extends BlockWoodStripped { + + public BlockWoodStrippedAcacia() { + this(0); + } + + public BlockWoodStrippedAcacia(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Acacia Log"; + } + + @Override + public int getId() { + return STRIPPED_ACACIA_LOG; + } + + @Override + public BlockColor getColor() { + return BlockColor.ORANGE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedBirch.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedBirch.java new file mode 100644 index 00000000000..116747d2844 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedBirch.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedBirch extends BlockWoodStripped { + + public BlockWoodStrippedBirch() { + this(0); + } + + public BlockWoodStrippedBirch(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Birch Log"; + } + + @Override + public int getId() { + return STRIPPED_BIRCH_LOG; + } + + @Override + public BlockColor getColor() { + return BlockColor.SAND_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedDarkOak.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedDarkOak.java new file mode 100644 index 00000000000..63466210a97 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedDarkOak.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedDarkOak extends BlockWoodStripped { + + public BlockWoodStrippedDarkOak() { + this(0); + } + + public BlockWoodStrippedDarkOak(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Dark Oak Log"; + } + + @Override + public int getId() { + return STRIPPED_DARK_OAK_LOG; + } + + @Override + public BlockColor getColor() { + return BlockColor.BROWN_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedJungle.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedJungle.java new file mode 100644 index 00000000000..04f8d706fc4 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedJungle.java @@ -0,0 +1,29 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedJungle extends BlockWoodStripped { + + public BlockWoodStrippedJungle() { + this(0); + } + + public BlockWoodStrippedJungle(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Jungle Log"; + } + + @Override + public int getId() { + return STRIPPED_JUNGLE_LOG; + } + + @Override + public BlockColor getColor() { + return BlockColor.DIRT_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedOak.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedOak.java new file mode 100644 index 00000000000..7a3bae4643f --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedOak.java @@ -0,0 +1,28 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedOak extends BlockWoodStripped { + + public BlockWoodStrippedOak() { + this(0); + } + + public BlockWoodStrippedOak(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Oak Log"; + } + + @Override + public int getId() { + return STRIPPED_OAK_LOG; + } + @Override + public BlockColor getColor() { + return BlockColor.WOOD_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/BlockWoodStrippedSpruce.java b/src/main/java/cn/nukkit/block/BlockWoodStrippedSpruce.java new file mode 100644 index 00000000000..1154cad9487 --- /dev/null +++ b/src/main/java/cn/nukkit/block/BlockWoodStrippedSpruce.java @@ -0,0 +1,28 @@ +package cn.nukkit.block; + +import cn.nukkit.utils.BlockColor; + +public class BlockWoodStrippedSpruce extends BlockWoodStripped { + + public BlockWoodStrippedSpruce() { + this(0); + } + + public BlockWoodStrippedSpruce(int meta) { + super(meta); + } + + @Override + public String getName() { + return "Stripped Spruce Log"; + } + + @Override + public int getId() { + return STRIPPED_SPRUCE_LOG; + } + @Override + public BlockColor getColor() { + return BlockColor.SPRUCE_BLOCK_COLOR; + } +} diff --git a/src/main/java/cn/nukkit/block/Blocks.java b/src/main/java/cn/nukkit/block/Blocks.java new file mode 100644 index 00000000000..caa69bb63f2 --- /dev/null +++ b/src/main/java/cn/nukkit/block/Blocks.java @@ -0,0 +1,609 @@ +package cn.nukkit.block; + +import static cn.nukkit.block.Block.list; +import static cn.nukkit.block.BlockID.*; + +class Blocks { + + static { + list[AIR] = BlockAir.class; //0 + list[STONE] = BlockStone.class; //1 + list[GRASS] = BlockGrass.class; //2 + list[DIRT] = BlockDirt.class; //3 + list[COBBLESTONE] = BlockCobblestone.class; //4 + list[PLANKS] = BlockPlanks.class; //5 + list[SAPLING] = BlockSapling.class; //6 + list[BEDROCK] = BlockBedrock.class; //7 + list[WATER] = BlockWater.class; //8 + list[STILL_WATER] = BlockWaterStill.class; //9 + list[LAVA] = BlockLava.class; //10 + list[STILL_LAVA] = BlockLavaStill.class; //11 + list[SAND] = BlockSand.class; //12 + list[GRAVEL] = BlockGravel.class; //13 + list[GOLD_ORE] = BlockOreGold.class; //14 + list[IRON_ORE] = BlockOreIron.class; //15 + list[COAL_ORE] = BlockOreCoal.class; //16 + list[WOOD] = BlockWood.class; //17 + list[LEAVES] = BlockLeaves.class; //18 + list[SPONGE] = BlockSponge.class; //19 + list[GLASS] = BlockGlass.class; //20 + list[LAPIS_ORE] = BlockOreLapis.class; //21 + list[LAPIS_BLOCK] = BlockLapis.class; //22 + list[DISPENSER] = BlockDispenser.class; //23 + list[SANDSTONE] = BlockSandstone.class; //24 + list[NOTEBLOCK] = BlockNoteblock.class; //25 + list[BED_BLOCK] = BlockBed.class; //26 + list[POWERED_RAIL] = BlockRailPowered.class; //27 + list[DETECTOR_RAIL] = BlockRailDetector.class; //28 + list[STICKY_PISTON] = BlockPistonSticky.class; //29 + list[COBWEB] = BlockCobweb.class; //30 + list[TALL_GRASS] = BlockTallGrass.class; //31 + list[DEAD_BUSH] = BlockDeadBush.class; //32 + list[PISTON] = BlockPiston.class; //33 + list[PISTON_HEAD] = BlockPistonHead.class; //34 + list[WOOL] = BlockWool.class; //35 + list[DANDELION] = BlockDandelion.class; //37 + list[FLOWER] = BlockFlower.class; //38 + list[BROWN_MUSHROOM] = BlockMushroomBrown.class; //39 + list[RED_MUSHROOM] = BlockMushroomRed.class; //40 + list[GOLD_BLOCK] = BlockGold.class; //41 + list[IRON_BLOCK] = BlockIron.class; //42 + list[DOUBLE_STONE_SLAB] = BlockDoubleSlabStone.class; //43 + list[STONE_SLAB] = BlockSlabStone.class; //44 + list[BRICKS_BLOCK] = BlockBricks.class; //45 + list[TNT] = BlockTNT.class; //46 + list[BOOKSHELF] = BlockBookshelf.class; //47 + list[MOSS_STONE] = BlockMossStone.class; //48 + list[OBSIDIAN] = BlockObsidian.class; //49 + list[TORCH] = BlockTorch.class; //50 + list[FIRE] = BlockFire.class; //51 + list[MONSTER_SPAWNER] = BlockMobSpawner.class; //52 + list[WOOD_STAIRS] = BlockStairsWood.class; //53 + list[CHEST] = BlockChest.class; //54 + list[REDSTONE_WIRE] = BlockRedstoneWire.class; //55 + list[DIAMOND_ORE] = BlockOreDiamond.class; //56 + list[DIAMOND_BLOCK] = BlockDiamond.class; //57 + list[WORKBENCH] = BlockCraftingTable.class; //58 + list[WHEAT_BLOCK] = BlockWheat.class; //59 + list[FARMLAND] = BlockFarmland.class; //60 + list[FURNACE] = BlockFurnace.class; //61 + list[BURNING_FURNACE] = BlockFurnaceBurning.class; //62 + list[SIGN_POST] = BlockSignPost.class; //63 + list[WOOD_DOOR_BLOCK] = BlockDoorWood.class; //64 + list[LADDER] = BlockLadder.class; //65 + list[RAIL] = BlockRail.class; //66 + list[COBBLESTONE_STAIRS] = BlockStairsCobblestone.class; //67 + list[WALL_SIGN] = BlockWallSign.class; //68 + list[LEVER] = BlockLever.class; //69 + list[STONE_PRESSURE_PLATE] = BlockPressurePlateStone.class; //70 + list[IRON_DOOR_BLOCK] = BlockDoorIron.class; //71 + list[WOODEN_PRESSURE_PLATE] = BlockPressurePlateWood.class; //72 + list[REDSTONE_ORE] = BlockOreRedstone.class; //73 + list[GLOWING_REDSTONE_ORE] = BlockOreRedstoneGlowing.class; //74 + list[UNLIT_REDSTONE_TORCH] = BlockRedstoneTorchUnlit.class; + list[REDSTONE_TORCH] = BlockRedstoneTorch.class; //76 + list[STONE_BUTTON] = BlockButtonStone.class; //77 + list[SNOW_LAYER] = BlockSnowLayer.class; //78 + list[ICE] = BlockIce.class; //79 + list[SNOW_BLOCK] = BlockSnow.class; //80 + list[CACTUS] = BlockCactus.class; //81 + list[CLAY_BLOCK] = BlockClay.class; //82 + list[SUGARCANE_BLOCK] = BlockSugarcane.class; //83 + list[JUKEBOX] = BlockJukebox.class; //84 + list[FENCE] = BlockFence.class; //85 + list[PUMPKIN] = BlockPumpkin.class; //86 + list[NETHERRACK] = BlockNetherrack.class; //87 + list[SOUL_SAND] = BlockSoulSand.class; //88 + list[GLOWSTONE_BLOCK] = BlockGlowstone.class; //89 + list[NETHER_PORTAL] = BlockNetherPortal.class; //90 + list[LIT_PUMPKIN] = BlockPumpkinLit.class; //91 + list[CAKE_BLOCK] = BlockCake.class; //92 + list[UNPOWERED_REPEATER] = BlockRedstoneRepeaterUnpowered.class; //93 + list[POWERED_REPEATER] = BlockRedstoneRepeaterPowered.class; //94 + list[INVISIBLE_BEDROCK] = BlockBedrockInvisible.class; //95 + list[TRAPDOOR] = BlockTrapdoor.class; //96 + list[MONSTER_EGG] = BlockMonsterEgg.class; //97 + list[STONE_BRICKS] = BlockBricksStone.class; //98 + list[BROWN_MUSHROOM_BLOCK] = BlockHugeMushroomBrown.class; //99 + list[RED_MUSHROOM_BLOCK] = BlockHugeMushroomRed.class; //100 + list[IRON_BARS] = BlockIronBars.class; //101 + list[GLASS_PANE] = BlockGlassPane.class; //102 + list[MELON_BLOCK] = BlockMelon.class; //103 + list[PUMPKIN_STEM] = BlockStemPumpkin.class; //104 + list[MELON_STEM] = BlockStemMelon.class; //105 + list[VINE] = BlockVine.class; //106 + list[FENCE_GATE] = BlockFenceGate.class; //107 + list[BRICK_STAIRS] = BlockStairsBrick.class; //108 + list[STONE_BRICK_STAIRS] = BlockStairsStoneBrick.class; //109 + list[MYCELIUM] = BlockMycelium.class; //110 + list[WATER_LILY] = BlockWaterLily.class; //111 + list[NETHER_BRICKS] = BlockBricksNether.class; //112 + list[NETHER_BRICK_FENCE] = BlockFenceNetherBrick.class; //113 + list[NETHER_BRICKS_STAIRS] = BlockStairsNetherBrick.class; //114 + list[NETHER_WART_BLOCK] = BlockNetherWart.class; //115 + list[ENCHANTING_TABLE] = BlockEnchantingTable.class; //116 + list[BREWING_STAND_BLOCK] = BlockBrewingStand.class; //117 + list[CAULDRON_BLOCK] = BlockCauldron.class; //118 + list[END_PORTAL] = BlockEndPortal.class; //119 + list[END_PORTAL_FRAME] = BlockEndPortalFrame.class; //120 + list[END_STONE] = BlockEndStone.class; //121 + list[DRAGON_EGG] = BlockDragonEgg.class; //122 + list[REDSTONE_LAMP] = BlockRedstoneLamp.class; //123 + list[LIT_REDSTONE_LAMP] = BlockRedstoneLampLit.class; //124 + list[DROPPER] = BlockDropper.class; //125 + list[ACTIVATOR_RAIL] = BlockRailActivator.class; //126 + list[COCOA] = BlockCocoa.class; //127 + list[SANDSTONE_STAIRS] = BlockStairsSandstone.class; //128 + list[EMERALD_ORE] = BlockOreEmerald.class; //129 + list[ENDER_CHEST] = BlockEnderChest.class; //130 + list[TRIPWIRE_HOOK] = BlockTripWireHook.class; + list[TRIPWIRE] = BlockTripWire.class; //132 + list[EMERALD_BLOCK] = BlockEmerald.class; //133 + list[SPRUCE_WOOD_STAIRS] = BlockStairsSpruce.class; //134 + list[BIRCH_WOOD_STAIRS] = BlockStairsBirch.class; //135 + list[JUNGLE_WOOD_STAIRS] = BlockStairsJungle.class; //136 + list[COMMAND_BLOCK] = BlockCommandBlock.class; //137 + list[BEACON] = BlockBeacon.class; //138 + list[STONE_WALL] = BlockWall.class; //139 + list[FLOWER_POT_BLOCK] = BlockFlowerPot.class; //140 + list[CARROT_BLOCK] = BlockCarrot.class; //141 + list[POTATO_BLOCK] = BlockPotato.class; //142 + list[WOODEN_BUTTON] = BlockButtonWooden.class; //143 + list[SKULL_BLOCK] = BlockSkull.class; //144 + list[ANVIL] = BlockAnvil.class; //145 + list[TRAPPED_CHEST] = BlockTrappedChest.class; //146 + list[LIGHT_WEIGHTED_PRESSURE_PLATE] = BlockWeightedPressurePlateLight.class; //147 + list[HEAVY_WEIGHTED_PRESSURE_PLATE] = BlockWeightedPressurePlateHeavy.class; //148 + list[UNPOWERED_COMPARATOR] = BlockRedstoneComparatorUnpowered.class; //149 + list[POWERED_COMPARATOR] = BlockRedstoneComparatorPowered.class; //149 + list[DAYLIGHT_DETECTOR] = BlockDaylightDetector.class; //151 + list[REDSTONE_BLOCK] = BlockRedstone.class; //152 + list[QUARTZ_ORE] = BlockOreQuartz.class; //153 + list[HOPPER_BLOCK] = BlockHopper.class; //154 + list[QUARTZ_BLOCK] = BlockQuartz.class; //155 + list[QUARTZ_STAIRS] = BlockStairsQuartz.class; //156 + list[DOUBLE_WOOD_SLAB] = BlockDoubleSlabWood.class; //157 + list[WOOD_SLAB] = BlockSlabWood.class; //158 + list[STAINED_TERRACOTTA] = BlockTerracottaStained.class; //159 + list[STAINED_GLASS_PANE] = BlockGlassPaneStained.class; //160 + list[LEAVES2] = BlockLeaves2.class; //161 + list[WOOD2] = BlockWood2.class; //162 + list[ACACIA_WOOD_STAIRS] = BlockStairsAcacia.class; //163 + list[DARK_OAK_WOOD_STAIRS] = BlockStairsDarkOak.class; //164 + list[SLIME_BLOCK] = BlockSlime.class; //165 + //list[GLOW_STICK] = BlockGlowStick.class; //166 + list[IRON_TRAPDOOR] = BlockTrapdoorIron.class; //167 + list[PRISMARINE] = BlockPrismarine.class; //168 + list[SEA_LANTERN] = BlockSeaLantern.class; //169 + list[HAY_BALE] = BlockHayBale.class; //170 + list[CARPET] = BlockCarpet.class; //171 + list[TERRACOTTA] = BlockTerracotta.class; //172 + list[COAL_BLOCK] = BlockCoal.class; //173 + list[PACKED_ICE] = BlockIcePacked.class; //174 + list[DOUBLE_PLANT] = BlockDoublePlant.class; //175 + list[STANDING_BANNER] = BlockBanner.class; //176 + list[WALL_BANNER] = BlockWallBanner.class; //177 + list[DAYLIGHT_DETECTOR_INVERTED] = BlockDaylightDetectorInverted.class; //178 + list[RED_SANDSTONE] = BlockRedSandstone.class; //179 + list[RED_SANDSTONE_STAIRS] = BlockStairsRedSandstone.class; //180 + list[DOUBLE_RED_SANDSTONE_SLAB] = BlockDoubleSlabRedSandstone.class; //181 + list[RED_SANDSTONE_SLAB] = BlockSlabRedSandstone.class; //182 + list[FENCE_GATE_SPRUCE] = BlockFenceGateSpruce.class; //183 + list[FENCE_GATE_BIRCH] = BlockFenceGateBirch.class; //184 + list[FENCE_GATE_JUNGLE] = BlockFenceGateJungle.class; //185 + list[FENCE_GATE_DARK_OAK] = BlockFenceGateDarkOak.class; //186 + list[FENCE_GATE_ACACIA] = BlockFenceGateAcacia.class; //187 + list[REPEATING_COMMAND_BLOCK] = BlockCommandBlockRepeating.class; //188 + list[CHAIN_COMMAND_BLOCK] = BlockCommandBlockChain.class; //189 + //list[HARD_GLASS_PANE] = BlockHardGlassPane.class; //190 + //list[HARD_STAINED_GLASS_PANE] = BlockHardGlassPaneStained.class; //191 + //list[CHEMICAL_HEAT] = BlockChemicalHeat.class; //192 + list[SPRUCE_DOOR_BLOCK] = BlockDoorSpruce.class; //193 + list[BIRCH_DOOR_BLOCK] = BlockDoorBirch.class; //194 + list[JUNGLE_DOOR_BLOCK] = BlockDoorJungle.class; //195 + list[ACACIA_DOOR_BLOCK] = BlockDoorAcacia.class; //196 + list[DARK_OAK_DOOR_BLOCK] = BlockDoorDarkOak.class; //197 + list[GRASS_PATH] = BlockGrassPath.class; //198 + list[ITEM_FRAME_BLOCK] = BlockItemFrame.class; //199 + list[CHORUS_FLOWER] = BlockChorusFlower.class; //200 + list[PURPUR_BLOCK] = BlockPurpur.class; //201 + //list[COLORED_TORCH_RG] = BlockColoredTorchRG.class; //202 + list[PURPUR_STAIRS] = BlockStairsPurpur.class; //203 + //list[COLORED_TORCH_BP] = BlockColoredTorchBP.class; //204 + list[UNDYED_SHULKER_BOX] = BlockUndyedShulkerBox.class; //205 + list[END_BRICKS] = BlockBricksEndStone.class; //206 + list[FROSTED_ICE] = BlockIceFrosted.class; //207 + list[END_ROD] = BlockEndRod.class; //208 + list[END_GATEWAY] = BlockEndGateway.class; //209 + list[ALLOW] = BlockAllow.class; //210 + list[DENY] = BlockDeny.class; //211 + list[BORDER_BLOCK] = BlockBorder.class; //212 + list[MAGMA] = BlockMagma.class; //213 + list[BLOCK_NETHER_WART_BLOCK] = BlockNetherWartBlock.class; //214 + list[RED_NETHER_BRICK] = BlockBricksRedNether.class; //215 + list[BONE_BLOCK] = BlockBone.class; //216 + // 217 not yet in Minecraft + list[SHULKER_BOX] = BlockShulkerBox.class; //218 + list[PURPLE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedPurple.class; //219 + list[WHITE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedWhite.class; //220 + list[ORANGE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedOrange.class; //221 + list[MAGENTA_GLAZED_TERRACOTTA] = BlockTerracottaGlazedMagenta.class; //222 + list[LIGHT_BLUE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedLightBlue.class; //223 + list[YELLOW_GLAZED_TERRACOTTA] = BlockTerracottaGlazedYellow.class; //224 + list[LIME_GLAZED_TERRACOTTA] = BlockTerracottaGlazedLime.class; //225 + list[PINK_GLAZED_TERRACOTTA] = BlockTerracottaGlazedPink.class; //226 + list[GRAY_GLAZED_TERRACOTTA] = BlockTerracottaGlazedGray.class; //227 + list[SILVER_GLAZED_TERRACOTTA] = BlockTerracottaGlazedSilver.class; //228 + list[CYAN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedCyan.class; //229 + // 230 Chalkboard in Education Edition + list[BLUE_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBlue.class; //231 + list[BROWN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBrown.class; //232 + list[GREEN_GLAZED_TERRACOTTA] = BlockTerracottaGlazedGreen.class; //233 + list[RED_GLAZED_TERRACOTTA] = BlockTerracottaGlazedRed.class; //234 + list[BLACK_GLAZED_TERRACOTTA] = BlockTerracottaGlazedBlack.class; //235 + list[CONCRETE] = BlockConcrete.class; //236 + list[CONCRETE_POWDER] = BlockConcretePowder.class; //237 + //list[CHEMISTRY_TABLE] = BlockChemistryTable.class; //238 + //list[UNDERWATER_TORCH] = BlockUnderwaterTorch.class; //239 + list[CHORUS_PLANT] = BlockChorusPlant.class; //240 + list[STAINED_GLASS] = BlockGlassStained.class; //241 + // 242 Camera in Education Edition + list[PODZOL] = BlockPodzol.class; //243 + list[BEETROOT_BLOCK] = BlockBeetroot.class; //244 + list[STONECUTTER] = BlockStonecutter.class; //244 + list[GLOWING_OBSIDIAN] = BlockObsidianGlowing.class; //246 + list[NETHER_REACTOR] = BlockNetherReactor.class; //247 + list[INFO_UPDATE] = BlockInfoUpdate.class; //248 + list[INFO_UPDATE2] = BlockInfoUpdate2.class; //249 + list[PISTON_EXTENSION] = BlockPistonExtension.class; //250 + list[OBSERVER] = BlockObserver.class; //251 + list[STRUCTURE_BLOCK] = BlockStructureBlock.class; //252 + //list[HARD_GLASS] = BlockHardGlass.class; //253 + //list[HARD_STAINED_GLASS] = BlockHardGlassStained.class; //254 + //list[RESERVED6] = BlockReserved6.class; //255 + // 256 not yet in Minecraft + list[PRISMARINE_STAIRS] = BlockStairsPrismarine.class; //257 + list[DARK_PRISMARINE_STAIRS] = BlockStairsDarkPrismarine.class; //258 + list[PRISMARINE_BRICKS_STAIRS] = BlockStairsPrismarineBrick.class; //259 + list[STRIPPED_SPRUCE_LOG] = BlockWoodStrippedSpruce.class; //260 + list[STRIPPED_BIRCH_LOG] = BlockWoodStrippedBirch.class; //261 + list[STRIPPED_JUNGLE_LOG] = BlockWoodStrippedJungle.class; //262 + list[STRIPPED_ACACIA_LOG] = BlockWoodStrippedAcacia.class; //263 + list[STRIPPED_DARK_OAK_LOG] = BlockWoodStrippedDarkOak.class; //264 + list[STRIPPED_OAK_LOG] = BlockWoodStrippedOak.class; //265 + list[BLUE_ICE] = BlockBlueIce.class; //266 + // + list[SEAGRASS] = BlockSeagrass.class; //385 + list[CORAL] = BlockCoral.class; //386 + list[CORAL_BLOCK] = BlockCoralBlock.class; //387 + list[CORAL_FAN] = BlockCoralFan.class; //388 + list[CORAL_FAN_DEAD] = BlockCoralFanDead.class; //389 + list[CORAL_FAN_HANG] = BlockCoralFanHang.class; //390 + list[CORAL_FAN_HANG2] = BlockCoralFanHang2.class; //391 + list[CORAL_FAN_HANG3] = BlockCoralFanHang3.class; //392 + list[BLOCK_KELP] = BlockKelp.class; //393 + list[DRIED_KELP_BLOCK] = BlockDriedKelpBlock.class; //394 + list[ACACIA_BUTTON] = BlockButtonAcacia.class; //395 + list[BIRCH_BUTTON] = BlockButtonBirch.class; //396 + list[DARK_OAK_BUTTON] = BlockButtonDarkOak.class; //397 + list[JUNGLE_BUTTON] = BlockButtonJungle.class; //398 + list[SPRUCE_BUTTON] = BlockButtonSpruce.class; //399 + list[ACACIA_TRAPDOOR] = BlockTrapdoorAcacia.class; //400 + list[BIRCH_TRAPDOOR] = BlockTrapdoorBirch.class; //401 + list[DARK_OAK_TRAPDOOR] = BlockTrapdoorDarkOak.class; //402 + list[JUNGLE_TRAPDOOR] = BlockTrapdoorJungle.class; //403 + list[SPRUCE_TRAPDOOR] = BlockTrapdoorSpruce.class; //404 + list[ACACIA_PRESSURE_PLATE] = BlockPressurePlateAcacia.class; //405 + list[BIRCH_PRESSURE_PLATE] = BlockPressurePlateBirch.class; //406 + list[DARK_OAK_PRESSURE_PLATE] = BlockPressurePlateDarkOak.class; //407 + list[JUNGLE_PRESSURE_PLATE] = BlockPressurePlateJungle.class; //408 + list[SPRUCE_PRESSURE_PLATE] = BlockPressurePlateSpruce.class; //409 + list[CARVED_PUMPKIN] = BlockPumpkinCarved.class; //410 + list[SEA_PICKLE] = BlockSeaPickle.class; //411 + list[CONDUIT] = BlockConduit.class; //412 + // 413 not yet in Minecraft + list[TURTLE_EGG] = BlockTurtleEgg.class; //414 + list[BUBBLE_COLUMN] = BlockBubbleColumn.class; //415 + list[BARRIER] = BlockBarrier.class; //416 + list[STONE_SLAB3] = BlockSlabStone3.class; //417 + list[BAMBOO] = BlockBamboo.class; //418 + list[BAMBOO_SAPLING] = BlockBambooSapling.class; //419 + list[STONE_SLAB4] = BlockSlabStone4.class; //421 + list[SCAFFOLDING] = BlockScaffolding.class; //420 + list[DOUBLE_STONE_SLAB3] = BlockDoubleSlabStone3.class; //422 + list[DOUBLE_STONE_SLAB4] = BlockDoubleSlabStone4.class; //423 + list[GRANITE_STAIRS] = BlockStairsGranite.class; //424 + list[DIORITE_STAIRS] = BlockStairsDiorite.class; //425 + list[ANDESITE_STAIRS] = BlockStairsAndesite.class; //426 + list[POLISHED_GRANITE_STAIRS] = BlockStairsGranitePolished.class; //427 + list[POLISHED_DIORITE_STAIRS] = BlockStairsDioritePolished.class; //428 + list[POLISHED_ANDESITE_STAIRS] = BlockStairsAndesitePolished.class; //429 + list[MOSSY_STONE_BRICK_STAIRS] = BlockStairsMossyStoneBrick.class; //430 + list[SMOOTH_RED_SANDSTONE_STAIRS] = BlockStairsSmoothRedSandstone.class; //431 + list[SMOOTH_SANDSTONE_STAIRS] = BlockStairsSmoothSandstone.class; //432 + list[END_BRICK_STAIRS] = BlockStairsEndBrick.class; //433 + list[MOSSY_COBBLESTONE_STAIRS] = BlockStairsMossyCobblestone.class; //434 + list[NORMAL_STONE_STAIRS] = BlockStairsStone.class; //435 + list[SPRUCE_STANDING_SIGN] = BlockSpruceSignStanding.class; //436 + list[SPRUCE_WALL_SIGN] = BlockSpruceWallSign.class; //437 + list[SMOOTH_STONE] = BlockSmoothStone.class; //438 + list[RED_NETHER_BRICK_STAIRS] = BlockStairsRedNetherBrick.class; //439 + list[SMOOTH_QUARTZ_STAIRS] = BlockStairsSmoothQuartz.class; //440 + list[BIRCH_STANDING_SIGN] = BlockBirchSignStanding.class; //441 + list[BIRCH_WALL_SIGN] = BlockBirchWallSign.class; //442 + list[JUNGLE_STANDING_SIGN] = BlockJungleSignStanding.class; //443 + list[JUNGLE_WALL_SIGN] = BlockJungleWallSign.class; //444 + list[ACACIA_STANDING_SIGN] = BlockAcaciaSignStanding.class; //445 + list[ACACIA_WALL_SIGN] = BlockAcaciaWallSign.class; //446 + list[DARK_OAK_STANDING_SIGN] = BlockDarkOakSignStanding.class; //447 + list[DARK_OAK_WALL_SIGN] = BlockDarkOakWallSign.class; //448 + list[LECTERN] = BlockLectern.class; //449 + list[GRINDSTONE] = BlockGrindstone.class; //450 + list[BLAST_FURNACE] = BlockBlastFurnace.class; //451 + list[STONECUTTER_BLOCK] = BlockStonecutterBlock.class; //452 + list[SMOKER] = BlockSmoker.class; //453 + list[LIT_SMOKER] = BlockSmokerLit.class; //454 + list[CARTOGRAPHY_TABLE] = BlockCartographyTable.class; //455 + list[FLETCHING_TABLE] = BlockFletchingTable.class; //456 + list[SMITHING_TABLE] = BlockSmithingTable.class; //457 + list[BARREL] = BlockBarrel.class; //458 + list[LOOM] = BlockLoom.class; //459 + // 460 unused + list[BELL] = BlockBell.class; //461 + list[SWEET_BERRY_BUSH] = BlockSweetBerryBush.class; //462 + list[LANTERN] = BlockLantern.class; //463 + list[CAMPFIRE_BLOCK] = BlockCampfire.class; //464 + list[LAVA_CAULDRON] = BlockCauldronLava.class; //465 + list[JIGSAW] = BlockJigsaw.class; //466 + list[WOOD_BARK] = BlockWoodBark.class; //467 + list[COMPOSTER] = BlockComposter.class; //468 + list[LIT_BLAST_FURNACE] = BlockBlastFurnaceLit.class; //469 + list[LIGHT_BLOCK] = BlockLightBlock.class; //470 + list[WITHER_ROSE] = BlockWitherRose.class; //471 + list[PISTON_HEAD_STICKY] = BlockPistonHeadSticky.class; //472 + list[BEE_NEST] = BlockBeeNest.class; //473 + list[BEEHIVE] = BlockBeehive.class; //474 + list[HONEY_BLOCK] = BlockHoneyBlock.class; //475 + list[HONEYCOMB_BLOCK] = BlockHoneycombBlock.class; //476 + list[LODESTONE] = BlockLodestone.class; //477 + list[CRIMSON_ROOTS] = BlockCrimsonRoots.class; //478 + list[WARPED_ROOTS] = BlockWarpedRoots.class; //479 + list[CRIMSON_STEM] = BlockCrimsonStem.class; //480 + list[WARPED_STEM] = BlockWarpedStem.class; //481 + list[WARPED_WART_BLOCK] = BlockWarpedWartBlock.class; //482 + list[CRIMSON_FUNGUS] = BlockCrimsonFungus.class; //483 + list[WARPED_FUNGUS] = BlockWarpedFungus.class; //484 + list[SHROOMLIGHT] = BlockShroomlight.class; //485 + list[WEEPING_VINES] = BlockWeepingVines.class; //486 + list[CRIMSON_NYLIUM] = BlockCrimsonNylium.class; //487 + list[WARPED_NYLIUM] = BlockWarpedNylium.class; //488 + list[BASALT] = BlockBasalt.class; //489 + list[POLISHED_BASALT] = BlockPolishedBasalt.class; //490 + list[SOUL_SOIL] = BlockSoulSoil.class; //491 + list[SOUL_FIRE] = BlockSoulFire.class; //492 + list[NETHER_SPROUTS_BLOCK] = BlockNetherSprouts.class; //493 + list[TARGET] = BlockTarget.class; //494 + list[STRIPPED_CRIMSON_STEM] = BlockStrippedCrimsonStem.class; //495 + list[STRIPPED_WARPED_STEM] = BlockStrippedWarpedStem.class; //496 + list[CRIMSON_PLANKS] = BlockCrimsonPlanks.class; //497 + list[WARPED_PLANKS] = BlockWarpedPlanks.class; //498 + list[CRIMSON_DOOR_BLOCK] = BlockCrimsonDoor.class; //499 + list[WARPED_DOOR_BLOCK] = BlockWarpedDoor.class; //500 + list[CRIMSON_TRAPDOOR] = BlockCrimsonTrapdoor.class; //501 + list[WARPED_TRAPDOOR] = BlockWarpedTrapdoor.class; //502 + // 503-504 (Unused) + list[CRIMSON_STANDING_SIGN] = BlockCrimsonSign.class; //505 + list[WARPED_STANDING_SIGN] = BlockWarpedSign.class; //506 + list[CRIMSON_WALL_SIGN] = BlockCrimsonWallSign.class; //507 + list[WARPED_WALL_SIGN] = BlockWarpedWallSign.class; //508 + list[CRIMSON_STAIRS] = BlockCrimsonStairs.class; //509 + list[WARPED_STAIRS] = BlockWarpedStairs.class; //510 + list[CRIMSON_FENCE] = BlockFenceCrimson.class; // 511 + list[WARPED_FENCE] = BlockFenceWarped.class; // 512 + list[CRIMSON_FENCE_GATE] = BlockFenceGateCrimson.class; // 513 + list[WARPED_FENCE_GATE] = BlockFenceGateWarped.class; // 514 + list[CRIMSON_BUTTON] = BlockButtonCrimson.class; // 515 + list[WARPED_BUTTON] = BlockButtonWarped.class; // 516 + list[CRIMSON_PRESSURE_PLATE] = BlockPressurePlateCrimson.class; // 517 + list[WARPED_PRESSURE_PLATE] = BlockPressurePlateWarped.class; // 518 + list[CRIMSON_SLAB] = BlockSlabCrimson.class; //519 + list[WARPED_SLAB] = BlockSlabWarped.class; //520 + list[CRIMSON_DOUBLE_SLAB] = BlockDoubleSlabCrimson.class; //521 + list[WARPED_DOUBLE_SLAB] = BlockDoubleSlabWarped.class; //522 + list[SOUL_TORCH] = BlockSoulTorch.class; //523 + list[SOUL_LANTERN] = BlockSoulLantern.class; //524 + list[NETHERITE_BLOCK] = BlockNetheriteBlock.class; //525 + list[ANCIENT_DEBRIS] = BlockAncientDebris.class; //526 + list[RESPAWN_ANCHOR] = BlockRespawnAnchor.class; //527 + list[BLACKSTONE] = BlockBlackstone.class; //528 + list[POLISHED_BLACKSTONE_BRICKS] = BlockBricksBlackstonePolished.class; //529 + list[POLISHED_BLACKSTONE_BRICK_STAIRS] = BlockStairsBrickBlackstonePolished.class; //530 + list[BLACKSTONE_STAIRS] = BlockStairsBlackstone.class; //531 + list[BLACKSTONE_WALL] = BlockBlackstoneWall.class; //532 + list[POLISHED_BLACKSTONE_BRICK_WALL] = BlockPolishedBlackstoneBrickWall.class; //533 + list[CHISELED_POLISHED_BLACKSTONE] = BlockBlackstonePolishedChiseled.class; //534 + list[CRACKED_POLISHED_BLACKSTONE_BRICKS] = BlockBricksBlackstonePolishedCracked.class; //535 + list[GILDED_BLACKSTONE] = BlockBlackstoneGilded.class; //536 + list[BLACKSTONE_SLAB] = BlockSlabBlackstone.class; //537 + list[BLACKSTONE_DOUBLE_SLAB] = BlockDoubleSlabBlackstone.class; //538 + list[POLISHED_BLACKSTONE_BRICK_SLAB] = BlockSlabBrickBlackstonePolished.class; //539 + list[POLISHED_BLACKSTONE_BRICK_DOUBLE_SLAB] = BlockDoubleSlabBrickBlackstonePolished.class; //540 + list[CHAIN_BLOCK] = BlockChain.class; //541 + list[TWISTING_VINES] = BlockVinesTwisting.class; //542 + list[NETHER_GOLD_ORE] = BlockOreGoldNether.class; //543 + list[CRYING_OBSIDIAN] = BlockObsidianCrying.class; //544 + list[SOUL_CAMPFIRE_BLOCK] = BlockCampfireSoul.class; //545 + list[POLISHED_BLACKSTONE] = BlockBlackstonePolished.class; //546 + list[POLISHED_BLACKSTONE_STAIRS] = BlockStairsBlackstonePolished.class; //547 + list[POLISHED_BLACKSTONE_SLAB] = BlockSlabBlackstonePolished.class; //548 + list[POLISHED_BLACKSTONE_DOUBLE_SLAB] = BlockDoubleSlabBlackstonePolished.class; //549 + list[POLISHED_BLACKSTONE_PRESSURE_PLATE] = BlockPressurePlatePolishedBlackstone.class; //550 + list[POLISHED_BLACKSTONE_BUTTON] = BlockButtonPolishedBlackstone.class; //551 + list[POLISHED_BLACKSTONE_WALL] = BlockPolishedBlackstoneWall.class; //552 + list[WARPED_HYPHAE] = BlockHyphaeWarped.class; //553 + list[CRIMSON_HYPHAE] = BlockHyphaeCrimson.class; //554 + list[STRIPPED_CRIMSON_HYPHAE] = BlockHyphaeStrippedCrimson.class; //555 + list[STRIPPED_WARPED_HYPHAE] = BlockHyphaeStrippedWarped.class; //556 + list[CHISELED_NETHER_BRICKS] = BlockBricksNetherChiseled.class; //557 + list[CRACKED_NETHER_BRICKS] = BlockBricksNetherCracked.class; //558 + list[QUARTZ_BRICKS] = BlockQuartzBricks.class; //559 + // 560 unknown + list[POWDER_SNOW] = BlockPowderSnow.class; // 561 + // list[SCULK_SENSOR] = BlockSculkSensor.class; // 562 + list[POINTED_DRIPSTONE] = BlockPointedDripstone.class; // 563 + // 564-565 (Unused) + list[COPPER_ORE] = BlockOreCopper.class; //566 + list[LIGHTNING_ROD] = BlockLightningRod.class; //567 + // 568-571 (Unused) + list[DRIPSTONE_BLOCK] = BlockDripstone.class; //572 + list[ROOTED_DIRT] = BlockDirtRooted.class; //573 + list[HANGING_ROOTS] = BlockRootsHanging.class; //574 + list[MOSS_BLOCK] = BlockMoss.class; //575 + list[SPORE_BLOSSOM] = BlockSporeBlossom.class; //576 + list[CAVE_VINES] = BlockCaveVines.class; //577 + list[BIG_DRIPLEAF] = BlockDripleafBig.class; //578 + list[AZALEA_LEAVES] = BlockAzaleaLeaves.class; //579 + list[AZALEA_LEAVES_FLOWERED] = BlockAzaleaLeavesFlowered.class; //580 + list[CALCITE] = BlockCalcite.class; //581 + list[AMETHYST_BLOCK] = BlockAmethyst.class; //582 + list[BUDDING_AMETHYST] = BlockBuddingAmethyst.class; //583 + list[AMETHYST_CLUSTER] = BlockAmethystCluster.class; //584 + list[LARGE_AMETHYST_BUD] = BlockAmethystBudLarge.class; //585 + list[MEDIUM_AMETHYST_BUD] = BlockAmethystBudMedium.class; //586 + list[SMALL_AMETHYST_BUD] = BlockAmethystBudSmall.class; //587 + list[TUFF] = BlockTuff.class; //588 + list[TINTED_GLASS] = BlockGlassTinted.class; //589 + list[MOSS_CARPET] = BlockMossCarpet.class; //590 + list[SMALL_DRIPLEAF] = BlockDripleafSmall.class; //591 + list[AZALEA] = BlockAzalea.class; //592 + list[FLOWERING_AZALEA] = BlockAzaleaFlowering.class; //593 + list[GLOW_FRAME] = BlockItemFrameGlow.class; //594 + list[COPPER_BLOCK] = BlockCopper.class; //595 + list[EXPOSED_COPPER] = BlockCopperExposed.class; //596 + list[WEATHERED_COPPER] = BlockCopperWeathered.class; //597 + list[OXIDIZED_COPPER] = BlockCopperOxidized.class; //598 + list[WAXED_COPPER] = BlockCopperWaxed.class; //599 + list[WAXED_EXPOSED_COPPER] = BlockCopperExposedWaxed.class; //600 + list[WAXED_WEATHERED_COPPER] = BlockCopperWeatheredWaxed.class; //601 + list[CUT_COPPER] = BlockCopperCut.class; //602 + list[EXPOSED_CUT_COPPER] = BlockCopperCutExposed.class; //603 + list[WEATHERED_CUT_COPPER] = BlockCopperCutWeathered.class; //604 + list[OXIDIZED_CUT_COPPER] = BlockCopperCutOxidized.class; //605 + list[WAXED_CUT_COPPER] = BlockCopperCutWaxed.class; //606 + list[WAXED_EXPOSED_CUT_COPPER] = BlockCopperCutExposedWaxed.class; //607 + list[WAXED_WEATHERED_CUT_COPPER] = BlockCopperCutWeatheredWaxed.class; //608 + list[CUT_COPPER_STAIRS] = BlockStairsCopperCut.class; //609 + list[EXPOSED_CUT_COPPER_STAIRS] = BlockStairsCopperCutExposed.class; //610 + list[WEATHERED_CUT_COPPER_STAIRS] = BlockStairsCopperCutWeathered.class; //611 + list[OXIDIZED_CUT_COPPER_STAIRS] = BlockStairsCopperCutOxidized.class; //612 + list[WAXED_CUT_COPPER_STAIRS] = BlockStairsCopperCutWaxed.class; //613 + list[WAXED_EXPOSED_CUT_COPPER_STAIRS] = BlockStairsCopperCutExposedWaxed.class; //614 + list[WAXED_WEATHERED_CUT_COPPER_STAIRS] = BlockStairsCopperCutWeatheredWaxed.class; //615 + list[CUT_COPPER_SLAB] = BlockSlabCopperCut.class; //616 + list[EXPOSED_CUT_COPPER_SLAB] = BlockSlabCopperCutExposed.class; //617 + list[WEATHERED_CUT_COPPER_SLAB] = BlockSlabCopperCutWeathered.class; //618 + list[OXIDIZED_CUT_COPPER_SLAB] = BlockSlabCopperCutOxidized.class; //619 + list[WAXED_CUT_COPPER_SLAB] = BlockSlabCopperCutWaxed.class; //620 + list[WAXED_EXPOSED_CUT_COPPER_SLAB] = BlockSlabCopperCutExposedWaxed.class; //621 + list[WAXED_WEATHERED_CUT_COPPER_SLAB] = BlockSlabCopperCutWeatheredWaxed.class; //622 + list[DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCut.class; //623 + list[EXPOSED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutExposed.class; //624 + list[WEATHERED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutWeathered.class; //625 + list[OXIDIZED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutOxidized.class; //626 + list[WAXED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutWaxed.class; //627 + list[WAXED_EXPOSED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutExposedWaxed.class; //628 + list[WAXED_WEATHERED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutWeatheredWaxed.class; //629 + list[CAVE_VINES_BODY_WITH_BERRIES] = BlockCaveVinesBerriesBody.class; //630 + list[CAVE_VINES_HEAD_WITH_BERRIES] = BlockCaveVinesBerriesHead.class; //631 + list[SMOOTH_BASALT] = BlockBasaltSmooth.class; //632 + list[DEEPSLATE] = BlockDeepslate.class; //633 + list[COBBLED_DEEPSLATE] = BlockDeepslateCobbled.class; //634 + list[COBBLED_DEEPSLATE_SLAB] = BlockSlabDeepslateCobbled.class; //635 + list[COBBLED_DEEPSLATE_STAIRS] = BlockStairsDeepslateCobbled.class; //636 + list[COBBLED_DEEPSLATE_WALL] = BlockWallDeepslateCobbled.class; //637 + list[POLISHED_DEEPSLATE] = BlockDeepslatePolished.class; //638 + list[POLISHED_DEEPSLATE_SLAB] = BlockSlabDeepslatePolished.class; //639 + list[POLISHED_DEEPSLATE_STAIRS] = BlockStairsDeepslatePolished.class; //640 + list[POLISHED_DEEPSLATE_WALL] = BlockWallDeepslatePolished.class; //641 + list[DEEPSLATE_TILES] = BlockTilesDeepslate.class; //642 + list[DEEPSLATE_TILE_SLAB] = BlockSlabTileDeepslate.class; //643 + list[DEEPSLATE_TILE_STAIRS] = BlockStairsTileDeepslate.class; //644 + list[DEEPSLATE_TILE_WALL] = BlockWallTileDeepslate.class; //645 + list[DEEPSLATE_BRICKS] = BlockBricksDeepslate.class; //646 + list[DEEPSLATE_BRICK_SLAB] = BlockSlabBrickDeepslate.class; //647 + list[DEEPSLATE_BRICK_STAIRS] = BlockStairsBrickDeepslate.class; //648 + list[DEEPSLATE_BRICK_WALL] = BlockWallBrickDeepslate.class; //649 + list[CHISELED_DEEPSLATE] = BlockDeepslateChiseled.class; //650 + list[COBBLED_DEEPSLATE_DOUBLE_SLAB] = BlockDoubleSlabDeepslateCobbled.class; //651 + list[POLISHED_DEEPSLATE_DOUBLE_SLAB] = BlockDoubleSlabDeepslatePolished.class; //652 + list[DEEPSLATE_TILE_DOUBLE_SLAB] = BlockDoubleSlabTileDeepslate.class; //653 + list[DEEPSLATE_BRICK_DOUBLE_SLAB] = BlockDoubleSlabBrickDeepslate.class; //654 + list[DEEPSLATE_LAPIS_ORE] = BlockOreLapisDeepslate.class; //655 + list[DEEPSLATE_IRON_ORE] = BlockOreIronDeepslate.class; //656 + list[DEEPSLATE_GOLD_ORE] = BlockOreGoldDeepslate.class; //657 + list[DEEPSLATE_REDSTONE_ORE] = BlockOreRedstoneDeepslate.class; //658 + list[LIT_DEEPSLATE_REDSTONE_ORE] = BlockOreRedstoneDeepslateGlowing.class; //659 + list[DEEPSLATE_DIAMOND_ORE] = BlockOreDiamondDeepslate.class; //660 + list[DEEPSLATE_COAL_ORE] = BlockOreCoalDeepslate.class; //661 + list[DEEPSLATE_EMERALD_ORE] = BlockOreEmeraldDeepslate.class; //662 + list[DEEPSLATE_COPPER_ORE] = BlockOreCopperDeepslate.class; //663 + list[CRACKED_DEEPSLATE_TILES] = BlockTilesDeepslateCracked.class; //664 + list[CRACKED_DEEPSLATE_BRICKS] = BlockBricksDeepslateCracked.class; //665 + list[GLOW_LICHEN] = BlockGlowLichen.class; //666 + + // TODO: candle & candle_cake +// list[CANDLE] = BlockCandle.class; //667 +// list[WHITE_CANDLE] = BlockCandleWhite.class; //668 +// list[ORANGE_CANDLE] = BlockCandleOrange.class; //669 +// list[MAGENTA_CANDLE] = BlockCandleMagenta.class; //670 +// list[LIGHT_BLUE_CANDLE] = BlockCandleLightBlue.class; //671 +// list[YELLOW_CANDLE] = BlockCandleYellow.class; //672 +// list[LIME_CANDLE] = BlockCandleLime.class; //673 +// list[PINK_CANDLE] = BlockCandlePink.class; //674 +// list[GRAY_CANDLE] = BlockCandleGray.class; //675 +// list[LIGHT_GRAY_CANDLE] = BlockCandleLightGray.class; //676 +// list[CYAN_CANDLE] = BlockCandleCyan.class; //677 +// list[PURPLE_CANDLE] = BlockCandlePurple.class; //678 +// list[BLUE_CANDLE] = BlockCandleBlue.class; //679 +// list[BROWN_CANDLE] = BlockCandleBrown.class; //680 +// list[GREEN_CANDLE] = BlockCandleGreen.class; //681 +// list[RED_CANDLE] = BlockCandleRed.class; //682 +// list[BLACK_CANDLE] = BlockCandleBlack.class; //683 + + // Unused 684 - 700 + list[WAXED_OXIDIZED_COPPER] = BlockCopperOxidizedWaxed.class; //701 + list[WAXED_OXIDIZED_CUT_COPPER] = BlockCopperCutOxidizedWaxed.class; //702 + list[WAXED_OXIDIZED_CUT_COPPER_STAIRS] = BlockStairsCopperCutOxidizedWaxed.class; //703 + list[WAXED_OXIDIZED_CUT_COPPER_SLAB] = BlockSlabCopperCutOxidizedWaxed.class; //704 + list[WAXED_OXIDIZED_DOUBLE_CUT_COPPER_SLAB] = BlockDoubleSlabCopperCutOxidizedWaxed.class; //705 + list[RAW_IRON_BLOCK] = BlockRawIron.class; //706 + list[RAW_COPPER_BLOCK] = BlockRawCopper.class; //707 + list[RAW_GOLD_BLOCK] = BlockRawGold.class; //708 + list[INFESTED_DEEPSLATE] = BlockInfestedDeepslate.class; //709 + // 710-712 (Unused) + // sculk + // sculk_vein + // sculk_catalyst + // sculk_shrieker + // 717-719 (Unused) + // client_request_placeholder_block + // mysterious_frame + // mysterious_frame_slot + // frog_spawn + // pearlescent_froglight + // verdant_froglight + // ochre_froglight + // 727 + list[MUD] = BlockMud.class; //728 + list[MUD_BRICKS] = BlockMudBrick.class; //730 + list[PACKED_MUD] = BlockPackedMud.class; //732 + list[MUD_BRICK_SLAB] = BlockMudBrickSlab.class; //733 + list[MUD_BRICK_DOUBLE_SLAB] = BlockDoubleMudBrickSlab.class; //734 + list[MUD_BRICK_STAIRS] = BlockMudBrickStairs.class; //735 + list[MUD_BRICK_WALL] = BlockMudBrickWall.class; //736 + } + + static void init() { + // Init + } +} diff --git a/src/main/java/cn/nukkit/block/Oxidizable.java b/src/main/java/cn/nukkit/block/Oxidizable.java new file mode 100644 index 00000000000..7c9869f2c2b --- /dev/null +++ b/src/main/java/cn/nukkit/block/Oxidizable.java @@ -0,0 +1,118 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.block.properties.OxidizationLevel; +import cn.nukkit.event.block.BlockFadeEvent; +import cn.nukkit.item.Item; +import cn.nukkit.level.Level; +import cn.nukkit.level.Location; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.ScrapeParticle; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author joserobjr + */ +public interface Oxidizable { + + Location getLocation(); + + default int onUpdate(int type) { + if (type != Level.BLOCK_UPDATE_RANDOM) { + return 0; + } + ThreadLocalRandom random = ThreadLocalRandom.current(); + if (!(random.nextFloat() < 64F / 1125F)) { + return 0; + } + + int oxiLvl = this.getOxidizationLevel().ordinal(); + if (oxiLvl == OxidizationLevel.OXIDIZED.ordinal()) { + return 0; + } + + // Just to make sure we don't accidentally degrade a waxed block. + if ((this instanceof Waxable) && ((Waxable) this).isWaxed()) { + return 0; + } + + Block block = this instanceof Block? (Block) this : getLocation().getLevelBlock(); + Location mutableLocation = block.getLocation(); + + int odds = 0; + int cons = 0; + + scan: + for (int x = -4; x <= 4; x++) { + for (int y = -4; y <= 4; y++) { + for (int z = -4; z <= 4; z++) { + if (x == 0 && y == 0 && z == 0) { + continue; + } + mutableLocation.setComponents(block.x + x, block.y + y, block.z + z); + if (block.distanceSquared(mutableLocation) > 4) { + continue ; + } + Block relative = mutableLocation.getLevelBlock(); + if (!(relative instanceof Oxidizable)) { + continue; + } + int relOxiLvl = ((Oxidizable) relative).getOxidizationLevel().ordinal(); + if (relOxiLvl < oxiLvl) { + return type; + } + + if (relOxiLvl > oxiLvl) { + cons++; + } else { + odds++; + } + } + } + } + + float chance = (float)(cons + 1) / (float)(cons + odds + 1); + float multiplier = oxiLvl == 0? 0.75F : 1.0F; + chance = chance * chance * multiplier; + if (random.nextFloat() < chance) { + Block nextBlock = this.getStateWithOxidizationLevel(OxidizationLevel.values()[oxiLvl + 1]); + BlockFadeEvent event = new BlockFadeEvent(block, nextBlock); + block.getLevel().getServer().getPluginManager().callEvent(event); + if (!event.isCancelled()) { + block.getLevel().setBlock(block, event.getNewState()); + } + } + return type; + } + + + default boolean onActivate(Item item, Player player) { + if (!item.isAxe()) { + return false; + } + + OxidizationLevel oxidizationLevel = getOxidizationLevel(); + if (OxidizationLevel.UNAFFECTED.equals(oxidizationLevel)) { + return false; + } + + oxidizationLevel = OxidizationLevel.values()[oxidizationLevel.ordinal() - 1]; + if (!setOxidizationLevel(oxidizationLevel)) { + return false; + } + + Position location = this instanceof Block? (Position) this : getLocation(); + if (player == null || !player.isCreative()) { + item.useOn(this instanceof Block? (Block) this : location.getLevelBlock()); + } + location.getLevel().addParticle(new ScrapeParticle(location)); + return true; + } + + OxidizationLevel getOxidizationLevel(); + + boolean setOxidizationLevel(OxidizationLevel oxidizationLevel); + + Block getStateWithOxidizationLevel(OxidizationLevel oxidizationLevel); +} diff --git a/src/main/java/cn/nukkit/block/Waxable.java b/src/main/java/cn/nukkit/block/Waxable.java new file mode 100644 index 00000000000..e8408a1626a --- /dev/null +++ b/src/main/java/cn/nukkit/block/Waxable.java @@ -0,0 +1,40 @@ +package cn.nukkit.block; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Location; +import cn.nukkit.level.Position; +import cn.nukkit.level.particle.WaxOffParticle; +import cn.nukkit.level.particle.WaxOnParticle; + +public interface Waxable { + + Location getLocation(); + + default boolean onActivate(Item item, Player player) { + boolean waxed = isWaxed(); + if ((item.getId() != ItemID.HONEYCOMB || waxed) && (!item.isAxe() || !waxed)) { + return false; + } + + waxed = !waxed; + if (!setWaxed(waxed)) { + return false; + } + + Position location = this instanceof Block? (Position) this : getLocation(); + if (player == null || !player.isCreative()) { + if (waxed) { + item.count--; + } else { + item.useOn(this instanceof Block? (Block) this : location.getLevelBlock()); + } + } + location.getLevel().addParticle(waxed ? new WaxOnParticle(location) : new WaxOffParticle(location)); + return true; + } + + boolean isWaxed(); + boolean setWaxed(boolean waxed); +} diff --git a/src/main/java/cn/nukkit/block/properties/BlockNotImplemented.java b/src/main/java/cn/nukkit/block/properties/BlockNotImplemented.java new file mode 100644 index 00000000000..756637b9b54 --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/BlockNotImplemented.java @@ -0,0 +1,32 @@ +package cn.nukkit.block.properties; + +import cn.nukkit.block.BlockMeta; + +public class BlockNotImplemented extends BlockMeta { + + private final int id; + + public BlockNotImplemented(int id) { + this(id, 0); + } + + public BlockNotImplemented(int id, int meta) { + super(meta); + this.id = id; + } + + @Override + public int getId() { + return this.id; + } + + @Override + public double getHardness() { + return 0.1; + } + + @Override + public String getName() { + return "Not Implemented"; + } +} diff --git a/src/main/java/cn/nukkit/block/properties/BlockPropertiesHelper.java b/src/main/java/cn/nukkit/block/properties/BlockPropertiesHelper.java new file mode 100644 index 00000000000..8507639c66e --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/BlockPropertiesHelper.java @@ -0,0 +1,26 @@ +package cn.nukkit.block.properties; + +import cn.nukkit.customblock.container.BlockStorageContainer; + +public interface BlockPropertiesHelper extends BlockStorageContainer { + + int getId(); + + int getDamage(); + void setDamage(int meta); + + @Override + default int getStorage() { + return this.getDamage(); + } + + @Override + default void setStorage(int damage) { + this.setDamage(damage); + } + + @Override + default int getNukkitId() { + return this.getId(); + } +} diff --git a/src/main/java/cn/nukkit/block/properties/DripleafTilt.java b/src/main/java/cn/nukkit/block/properties/DripleafTilt.java new file mode 100644 index 00000000000..97c1e897b22 --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/DripleafTilt.java @@ -0,0 +1,19 @@ +package cn.nukkit.block.properties; + +import lombok.Getter; + +@Getter +public enum DripleafTilt { + NONE(true, -1), + UNSTABLE(false, 10), + PARTIAL_TILT(true, 10), + FULL_TILT(false, 100); + + private final boolean stable; + private final int netxStateDelay; + + DripleafTilt(boolean stable, int netxStateDelay) { + this.stable = stable; + this.netxStateDelay = netxStateDelay; + } +} diff --git a/src/main/java/cn/nukkit/block/properties/DripstoneThickness.java b/src/main/java/cn/nukkit/block/properties/DripstoneThickness.java new file mode 100644 index 00000000000..f8da5c92fdb --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/DripstoneThickness.java @@ -0,0 +1,9 @@ +package cn.nukkit.block.properties; + +public enum DripstoneThickness { + TIP, + FRUSTUM, + MIDDLE, + BASE, + MERGE; +} diff --git a/src/main/java/cn/nukkit/block/properties/OxidizationLevel.java b/src/main/java/cn/nukkit/block/properties/OxidizationLevel.java new file mode 100644 index 00000000000..4ac6a610f44 --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/OxidizationLevel.java @@ -0,0 +1,8 @@ +package cn.nukkit.block.properties; + +public enum OxidizationLevel { + UNAFFECTED, + EXPOSED, + WEATHERED, + OXIDIZED +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/properties/VanillaProperties.java b/src/main/java/cn/nukkit/block/properties/VanillaProperties.java new file mode 100644 index 00000000000..6ae345f4e4b --- /dev/null +++ b/src/main/java/cn/nukkit/block/properties/VanillaProperties.java @@ -0,0 +1,20 @@ +package cn.nukkit.block.properties; + +import cn.nukkit.customblock.properties.BlockProperty; +import cn.nukkit.customblock.properties.BooleanBlockProperty; +import cn.nukkit.customblock.properties.EnumBlockProperty; +import cn.nukkit.math.BlockFace; + +public class VanillaProperties { + + public static final BooleanBlockProperty UPPER_BLOCK = new BooleanBlockProperty("upper_block_bit", false); + + public static final BlockProperty DIRECTION = new EnumBlockProperty<>("direction", false, + new BlockFace[]{ BlockFace.SOUTH, BlockFace.WEST, BlockFace.NORTH, BlockFace.EAST }).ordinal(true); + + public static final BlockProperty FACING_DIRECTION = new EnumBlockProperty<>("facing_direction", false, + new BlockFace[] { BlockFace.DOWN, BlockFace.UP, BlockFace.NORTH, BlockFace.SOUTH, BlockFace.WEST, BlockFace.EAST }).ordinal(true); + + public static final BlockProperty STAIRS_DIRECTION = new EnumBlockProperty<>("weirdo_direction", false, + new BlockFace[]{ BlockFace.EAST, BlockFace.WEST, BlockFace.SOUTH, BlockFace.NORTH }).ordinal(true); +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntity.java b/src/main/java/cn/nukkit/blockentity/BlockEntity.java index cbc97c453e7..ae65f0fac85 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntity.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntity.java @@ -2,14 +2,15 @@ import cn.nukkit.Server; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.Position; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.level.persistence.impl.PersistentDataContainerBlockWrapper; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.utils.ChunkException; -import cn.nukkit.utils.MainLogger; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; +import cn.nukkit.utils.LevelException; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -19,6 +20,7 @@ * @author MagicDroidX */ public abstract class BlockEntity extends Position { + //WARNING: DO NOT CHANGE ANY NAME HERE, OR THE CLIENT WILL CRASH public static final String CHEST = "Chest"; public static final String ENDER_CHEST = "EnderChest"; @@ -42,41 +44,49 @@ public abstract class BlockEntity extends Position { public static final String JUKEBOX = "Jukebox"; public static final String SHULKER_BOX = "ShulkerBox"; public static final String BANNER = "Banner"; - + public static final String DROPPER = "Dropper"; + public static final String DISPENSER = "Dispenser"; + public static final String BARREL = "Barrel"; + public static final String CAMPFIRE = "Campfire"; + public static final String BELL = "Bell"; + public static final String LECTERN = "Lectern"; + public static final String BLAST_FURNACE = "BlastFurnace"; + public static final String SMOKER = "Smoker"; + + // Not a vanilla block entity + public static final String PERSISTENT_CONTAINER = "PersistentContainer"; public static long count = 1; - private static final BiMap> knownBlockEntities = HashBiMap.create(21); + private static final BiMap> knownBlockEntities = HashBiMap.create(30); public FullChunk chunk; public String name; public long id; - public boolean movable = true; + public boolean movable; public boolean closed = false; public CompoundTag namedTag; - protected long lastUpdate; protected Server server; - protected Timing timing; + + private PersistentDataContainer persistentContainer; public BlockEntity(FullChunk chunk, CompoundTag nbt) { if (chunk == null || chunk.getProvider() == null) { - throw new ChunkException("Invalid garbage Chunk given to Block Entity"); + throw new ChunkException("Invalid garbage chunk given to BlockEntity"); } - this.timing = Timings.getBlockEntityTiming(this); this.server = chunk.getProvider().getLevel().getServer(); this.chunk = chunk; this.setLevel(chunk.getProvider().getLevel()); this.namedTag = nbt; this.name = ""; - this.lastUpdate = System.currentTimeMillis(); this.id = BlockEntity.count++; this.x = this.namedTag.getInt("x"); this.y = this.namedTag.getInt("y"); this.z = this.namedTag.getInt("z"); - this.movable = this.namedTag.getBoolean("isMovable"); + this.movable = this.namedTag.getBoolean("isMovable", true); this.initBlockEntity(); @@ -84,12 +94,9 @@ public BlockEntity(FullChunk chunk, CompoundTag nbt) { this.getLevel().addBlockEntity(this); } - protected void initBlockEntity() { - - } + protected void initBlockEntity() {} public static BlockEntity createBlockEntity(String type, FullChunk chunk, CompoundTag nbt, Object... args) { - type = type.replaceFirst("BlockEntity", ""); //TODO: Remove this after the first release BlockEntity blockEntity = null; if (knownBlockEntities.containsKey(type)) { @@ -99,7 +106,7 @@ public static BlockEntity createBlockEntity(String type, FullChunk chunk, Compou return null; } - for (Constructor constructor : clazz.getConstructors()) { + for (Constructor constructor : clazz.getConstructors()) { if (blockEntity != null) { break; } @@ -120,11 +127,10 @@ public static BlockEntity createBlockEntity(String type, FullChunk chunk, Compou blockEntity = (BlockEntity) constructor.newInstance(objects); } - } catch (Exception e) { - MainLogger.getLogger().logException(e); - } - + } catch (Exception ignored) {} } + } else { + Server.getInstance().getLogger().warning("Tried to create block entity that doesn't exists: " + type); } return blockEntity; @@ -159,7 +165,7 @@ public CompoundTag getCleanedNBT() { this.saveNBT(); CompoundTag tag = this.namedTag.clone(); tag.remove("x").remove("y").remove("z").remove("id"); - if (tag.getTags().size() > 0) { + if (!tag.getTags().isEmpty()) { return tag; } else { return null; @@ -170,6 +176,18 @@ public Block getBlock() { return this.getLevelBlock(); } + @Override + public Block getLevelBlock() { + if (this.isValid()) return this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ(), BlockLayer.NORMAL, true); + else throw new LevelException("Undefined Level reference"); + } + + @Override + public Block getLevelBlock(BlockLayer layer) { + if (this.isValid()) return this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ(), layer, true); + else throw new LevelException("Undefined Level reference"); + } + public abstract boolean isBlockEntityValid(); public boolean onUpdate() { @@ -177,6 +195,9 @@ public boolean onUpdate() { } public final void scheduleUpdate() { + if (this.level.isBeingConverted) { + return; + } this.level.scheduleBlockEntityUpdate(this); } @@ -193,12 +214,12 @@ public void close() { } } - public void onBreak() { - - } + public void onBreak() {} public void setDirty() { - chunk.setChanged(); + if (this.chunk != null) { + this.chunk.setChanged(); + } } public String getName() { @@ -210,10 +231,34 @@ public boolean isMovable() { } public static CompoundTag getDefaultCompound(Vector3 pos, String id) { - return new CompoundTag("") + return new CompoundTag() .putString("id", id) .putInt("x", pos.getFloorX()) .putInt("y", pos.getFloorY()) .putInt("z", pos.getFloorZ()); } -} + + public PersistentDataContainer getPersistentDataContainer() { + if (this.persistentContainer == null) { + this.persistentContainer = new PersistentDataContainerBlockWrapper(this); + } + return this.persistentContainer; + } + + public boolean hasPersistentDataContainer() { + return !this.getPersistentDataContainer().isEmpty(); + } + + public void onReplacedWith(BlockEntity blockEntity) { + blockEntity.getPersistentDataContainer().setStorage(this.getPersistentDataContainer().getStorage().clone()); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof BlockEntity && this.getClass().equals(obj.getClass()) && super.equals(obj); + } + + public boolean canSaveToStorage() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBanner.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBanner.java index 66a7a38cfff..94bbeadc7d6 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBanner.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBanner.java @@ -28,7 +28,8 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - return this.getBlock().getId() == Block.WALL_BANNER || this.getBlock().getId() == Block.STANDING_BANNER; + int id = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return id == Block.WALL_BANNER || id == Block.STANDING_BANNER; } @Override @@ -48,6 +49,7 @@ public int getBaseColor() { public void setBaseColor(DyeColor color) { this.namedTag.putInt("Base", color.getDyeData() & 0x0f); + setDirty(); } public int getType() { @@ -56,6 +58,7 @@ public int getType() { public void setType(int type) { this.namedTag.putInt("Type", type); + setDirty(); } public void addPattern(BannerPattern pattern) { @@ -64,6 +67,7 @@ public void addPattern(BannerPattern pattern) { putInt("Color", pattern.getColor().getDyeData() & 0x0f). putString("Pattern", pattern.getType().getName())); this.namedTag.putList(patterns); + setDirty(); } public BannerPattern getPattern(int index) { @@ -72,9 +76,10 @@ public BannerPattern getPattern(int index) { public void removePattern(int index) { ListTag patterns = this.namedTag.getList("Patterns", CompoundTag.class); - if(patterns.size() > index && index >= 0) { + if (patterns.size() > index && index >= 0) { patterns.remove(index); } + setDirty(); } public int getPatternsSize() { diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBarrel.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBarrel.java new file mode 100644 index 00000000000..6ac918397fe --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBarrel.java @@ -0,0 +1,169 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.BarrelInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +import java.util.ArrayList; + +public class BlockEntityBarrel extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { + + protected BarrelInventory inventory; + + public BlockEntityBarrel(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + private void initInventory() { + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new BarrelInventory(this); + + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } + } + } + + @Override + public void close() { + if (!this.closed && this.inventory != null) { + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); + } + } + + super.close(); + } + + @Override + public void onBreak() { + if (this.inventory == null) { + this.initInventory(); + } + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + inventory.clearAll(); // Stop items from being moved around by another player in the inventory + } + + @Override + public void saveNBT() { + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } + } + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == BlockID.BARREL; + } + + @Override + public int getSize() { + return 27; + } + + protected int getSlotIndex(int index) { + ListTag list = this.namedTag.getList("Items", CompoundTag.class); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getByte("Slot") == index) { + return i; + } + } + + return -1; + } + + @Override + public Item getItem(int index) { + int i = this.getSlotIndex(index); + if (i < 0) { + return new ItemBlock(Block.get(BlockID.AIR), 0, 0); + } else { + CompoundTag data = (CompoundTag) this.namedTag.getList("Items").get(i); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + int i = this.getSlotIndex(index); + + CompoundTag d = NBTIO.putItemHelper(item, index); + + // If item is air or count less than 0, remove the item from the "Items" list + if (item.getId() == Item.AIR || item.getCount() <= 0) { + if (i >= 0) { + this.namedTag.getList("Items").remove(i); + } + } else if (i < 0) { + // If it is less than i, then it is a new item, so we are going to add it at the end of the list + (this.namedTag.getList("Items", CompoundTag.class)).add(d); + } else { + // If it is more than i, then it is an update on a inventorySlot, so we are going to overwrite the item in the list + (this.namedTag.getList("Items", CompoundTag.class)).add(i, d); + } + } + + @Override + public BarrelInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } + return this.inventory; + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Barrel"; + } + + @Override + public boolean hasName() { + return this.namedTag.contains("CustomName"); + } + + @Override + public void setName(String name) { + if (name == null || name.isEmpty()) { + this.namedTag.remove("CustomName"); + return; + } + + this.namedTag.putString("CustomName", name); + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.BARREL) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java index f84df308af3..993b6473d1b 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBeacon.java @@ -1,19 +1,20 @@ package cn.nukkit.blockentity; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; -import cn.nukkit.inventory.BeaconInventory; -import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.potion.Effect; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import java.util.Map; /** - * author: Rover656 + * @author Rover656 */ public class BlockEntityBeacon extends BlockEntitySpawnable { @@ -39,15 +40,14 @@ protected void initBlockEntity() { namedTag.putInt("Secondary", 0); } - scheduleUpdate(); + this.scheduleUpdate(); super.initBlockEntity(); } @Override public boolean isBlockEntityValid() { - int blockID = getBlock().getId(); - return blockID == Block.BEACON; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.BEACON; } @Override @@ -67,18 +67,26 @@ public CompoundTag getSpawnCompound() { @Override public boolean onUpdate() { + if (this.closed) { + return false; + } + //Only apply effects every 4 secs if (currentTick++ % 80 != 0) { return true; } - int oldPowerLevel = this.getPowerLevel(); //Get the power level based on the pyramid - setPowerLevel(calculatePowerLevel()); - int newPowerLevel = this.getPowerLevel(); + int power = this.calculatePowerLevel(); + if (power == -1) { //Couldn't calculate due to unloaded chunks + return true; + } + + int oldPowerLevel = this.getPowerLevel(); + this.setPowerLevel(power); //Skip beacons that do not have a pyramid or sky access - if (newPowerLevel < 1 || !hasSkyAccess()) { + if (this.getPowerLevel() < 1 || !hasSkyAccess()) { if (oldPowerLevel > 0) { this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BEACON_DEACTIVATE); } @@ -89,18 +97,15 @@ public boolean onUpdate() { this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BEACON_AMBIENT); } - //Get all players in game - Map players = this.level.getPlayers(); - - //Calculate vars for beacon power - int range = 10 + getPowerLevel() * 10; - int duration = 9 + getPowerLevel() * 2; + int powerLevel = getPowerLevel(); + //In seconds + int duration = (9 + (powerLevel << 1)) * 20; - for(Map.Entry entry : players.entrySet()) { + for (Map.Entry entry : this.level.getPlayers().entrySet()) { Player p = entry.getValue(); //If the player is in range - if (p.distance(this) < range) { + if (p.distance(this) < 10 + powerLevel * 10) { Effect e; if (getPrimaryPower() != 0) { @@ -108,7 +113,7 @@ public boolean onUpdate() { e = Effect.getEffect(getPrimaryPower()); //Set duration - e.setDuration(duration * 20); + e.setDuration(duration); //If secondary is selected as the primary too, apply 2 amplification if (getSecondaryPower() == getPrimaryPower()) { @@ -130,10 +135,10 @@ public boolean onUpdate() { e = Effect.getEffect(Effect.REGENERATION); //Set duration - e.setDuration(duration * 20); + e.setDuration(duration); //Regen I - e.setAmplifier(1); + e.setAmplifier(0); //Hide particles e.setVisible(false); @@ -141,6 +146,10 @@ public boolean onUpdate() { //Add effect p.addEffect(e); } + + if (powerLevel >= POWER_LEVEL_MAX) { + p.awardAchievement("fullBeacon"); + } } } @@ -155,9 +164,9 @@ private boolean hasSkyAccess() { int tileZ = getFloorZ(); //Check every block from our y coord to the top of the world - for (int y = tileY + 1; y <= 255; y++) { - int testBlockId = level.getBlockIdAt(tileX, y, tileZ); - if (!Block.transparent[testBlockId]) { + for (int y = tileY + 1; y <= this.level.getMaxBlockY(); y++) { + int testBlockId = level.getBlockIdAt(chunk, tileX, y, tileZ); + if (!Block.isBlockTransparentById(testBlockId)) { //There is no sky access return false; } @@ -178,7 +187,11 @@ private int calculatePowerLevel() { for (int queryX = tileX - powerLevel; queryX <= tileX + powerLevel; queryX++) { for (int queryZ = tileZ - powerLevel; queryZ <= tileZ + powerLevel; queryZ++) { - int testBlockId = level.getBlockIdAt(queryX, queryY, queryZ); + int testBlockId = getBlockIdIfLoaded(queryX, queryY, queryZ); + if (testBlockId == -1) { + return -1; + } + if ( testBlockId != Block.IRON_BLOCK && testBlockId != Block.GOLD_BLOCK && @@ -187,7 +200,6 @@ private int calculatePowerLevel() { ) { return powerLevel - 1; } - } } } @@ -195,6 +207,26 @@ private int calculatePowerLevel() { return POWER_LEVEL_MAX; } + private int getBlockIdIfLoaded(int bx, int by, int bz) { + if (by < this.level.getMinBlockY() || by > this.level.getMaxBlockY()) return 0; + int cx = bx >> 4; + int cz = bz >> 4; + FullChunk fullChunk = this.chunk; + if (fullChunk == null || cx != fullChunk.getX() || cz != fullChunk.getZ()) { + fullChunk = level.getChunkIfLoaded(cx, cz); + if (fullChunk == null) { + return -1; + } + } + return fullChunk.getBlockId(bx & 0x0f, by, bz & 0x0f); + } + + @Override + public void setDirty() { + super.setDirty(); + this.spawnToAll(); + } + public int getPowerLevel() { return namedTag.getInt("Level"); } @@ -204,7 +236,6 @@ public void setPowerLevel(int level) { if (level != currentLevel) { namedTag.putInt("Level", level); setDirty(); - this.spawnToAll(); } } @@ -217,7 +248,6 @@ public void setPrimaryPower(int power) { if (power != currentPower) { namedTag.putInt("Primary", power); setDirty(); - this.spawnToAll(); } } @@ -230,24 +260,34 @@ public void setSecondaryPower(int power) { if (power != currentPower) { namedTag.putInt("Secondary", power); setDirty(); - this.spawnToAll(); } } + private static final IntSet ALLOWED_EFFECTS = new IntOpenHashSet(new int[]{0, Effect.SPEED, Effect.HASTE, Effect.DAMAGE_RESISTANCE, Effect.JUMP, Effect.STRENGTH, Effect.REGENERATION}); + @Override public boolean updateCompoundTag(CompoundTag nbt, Player player) { if (!nbt.getString("id").equals(BlockEntity.BEACON)) { return false; } - this.setPrimaryPower(nbt.getInt("primary")); - this.setSecondaryPower(nbt.getInt("secondary")); + int primary = nbt.getInt("primary"); + if (ALLOWED_EFFECTS.contains(primary)) { + this.setPrimaryPower(primary); + } else { + Server.getInstance().getLogger().debug(player.getName() + " tried to set an invalid primary effect to a beacon: " + primary); + } - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BEACON_POWER); + int secondary = nbt.getInt("secondary"); + if (ALLOWED_EFFECTS.contains(secondary)) { + this.setSecondaryPower(secondary); + } else { + Server.getInstance().getLogger().debug(player.getName() + " tried to set an invalid secondary effect to a beacon: " + secondary); + } - BeaconInventory inv = (BeaconInventory)player.getWindowById(Player.BEACON_WINDOW_ID); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BEACON_POWER); - inv.setItem(0, new ItemBlock(Block.get(BlockID.AIR), 0, 0)); + player.getWindowById(Player.BEACON_WINDOW_ID).setItem(0, Item.get(Item.AIR)); return true; } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBed.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBed.java index 9fc6c2b8e70..db6851e73d4 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBed.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBed.java @@ -29,7 +29,7 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - return this.level.getBlockIdAt(this.getFloorX(), this.getFloorY(), this.getFloorZ()) == Item.BED_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Item.BED_BLOCK; } @Override diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java new file mode 100644 index 00000000000..8b7169e7419 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java @@ -0,0 +1,149 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.Player; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.ByteTag; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.IntTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; + +public class BlockEntityBell extends BlockEntitySpawnable { + + private boolean ringing; + private int direction; + private int ticks; + public final LongOpenHashSet spawnExceptions = new LongOpenHashSet(2); + + public BlockEntityBell(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + protected void initBlockEntity() { + if (!namedTag.contains("Ringing") || !(namedTag.get("Ringing") instanceof ByteTag)) { + ringing = false; + } else { + ringing = namedTag.getBoolean("Ringing"); + } + + if (!namedTag.contains("Direction") || !(namedTag.get("Direction") instanceof IntTag)) { + direction = 255; + } else { + direction = namedTag.getInt("Direction"); + } + + if (!namedTag.contains("Ticks") || !(namedTag.get("Ticks") instanceof IntTag)) { + ticks = 0; + } else { + ticks = namedTag.getInt("Ticks"); + } + + super.initBlockEntity(); + scheduleUpdate(); + } + + @Override + public void saveNBT() { + namedTag.putBoolean("Ringing", ringing); + namedTag.putInt("Direction", direction); + namedTag.putInt("Ticks", ticks); + super.saveNBT(); + } + + @Override + public boolean onUpdate() { + if (ringing) { + if (ticks == 0) { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_BELL_HIT); + spawnToAllWithExceptions(); + spawnExceptions.clear(); + } else if (ticks >= 50) { + ringing = false; + ticks = 0; + spawnToAllWithExceptions(); + spawnExceptions.clear(); + return false; + } + //spawnToAll(); + ticks++; + return true; + } else if (ticks > 0) { + ticks = 0; + spawnToAllWithExceptions(); + spawnExceptions.clear(); + } + + return false; + } + + private void spawnToAllWithExceptions() { + if (this.closed) { + return; + } + + for (Player player : this.getLevel().getChunkPlayers(this.chunk.getX(), this.chunk.getZ()).values()) { + if (player.spawned && !spawnExceptions.contains(player.getId())) { + this.spawnTo(player); + } + } + } + + public boolean isRinging() { + return ringing; + } + + public void setRinging(boolean ringing) { + if (this.level != null && this.ringing != ringing) { + this.ringing = ringing; + scheduleUpdate(); + setDirty(); + } + } + + public int getDirection() { + return direction; + } + + public void setDirection(int direction) { + if (this.direction != direction) { + this.direction = direction; + setDirty(); + } + } + + public int getTicks() { + return ticks; + } + + public void setTicks(int ticks) { + if (this.ticks != ticks) { + this.ticks = ticks; + setDirty(); + } + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag tag = new CompoundTag() + .putString("id", BlockEntity.BELL) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putBoolean("Ringing", this.ringing) + .putInt("Direction", this.direction) + .putInt("Ticks", this.ticks); + return tag; + } + + @Override + public String getName() { + return "Bell"; + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == BlockID.BELL; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBlastFurnace.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBlastFurnace.java new file mode 100644 index 00000000000..71678eaec1e --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBlastFurnace.java @@ -0,0 +1,149 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.event.inventory.FurnaceSmeltEvent; +import cn.nukkit.inventory.FurnaceRecipe; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemArmor; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemTool; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockEntityBlastFurnace extends BlockEntityFurnace { + + public BlockEntityBlastFurnace(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Blast Furnace"; + } + + @Override + public boolean isBlockEntityValid() { + int blockID = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return blockID == Block.BLAST_FURNACE || blockID == Block.LIT_BLAST_FURNACE; + } + + private static final IntSet CAN_SMELT_EXCLUDING_TOOLS_AND_ARMOR = new IntOpenHashSet(new int[]{ + Item.IRON_ORE, Item.GOLD_ORE, Item.DIAMOND_ORE, Item.LAPIS_ORE, Item.REDSTONE_ORE, Item.COAL_ORE, Item.EMERALD_ORE, Item.QUARTZ_ORE, + 255 - Block.NETHER_GOLD_ORE, 255 - Block.ANCIENT_DEBRIS, Item.RAW_COPPER, Item.RAW_IRON, Item.RAW_GOLD, + 255 - Block.DEEPSLATE_COAL_ORE, 255 - Block.DEEPSLATE_IRON_ORE, 255 - Block.DEEPSLATE_GOLD_ORE, 255 - Block.DEEPSLATE_LAPIS_ORE, + 255 - Block.DEEPSLATE_EMERALD_ORE, 255 - Block.DEEPSLATE_COPPER_ORE, 255 - Block.DEEPSLATE_REDSTONE_ORE, 255 - Block.DEEPSLATE_DIAMOND_ORE + }); + + @Override + public boolean onUpdate() { + if (this.closed) { + return false; + } + + Item raw = this.inventory.getSmelting(); + // TODO: blast furnace recipes + if (!CAN_SMELT_EXCLUDING_TOOLS_AND_ARMOR.contains(raw.getId()) && !(raw instanceof ItemTool && (raw.getTier() == ItemTool.TIER_IRON || raw.getTier() == ItemTool.TIER_GOLD)) && !(raw instanceof ItemArmor && raw.getTier() >= ItemArmor.TIER_IRON && raw.getTier() <= ItemArmor.TIER_GOLD)) { + if (burnTime > 0) { + burnTime--; + burnDuration = (int) Math.ceil((float) burnTime / maxTime * 100); + + if (burnTime == 0) { + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == BlockID.LIT_BLAST_FURNACE) { + this.level.setBlock(this, Block.get(BlockID.BLAST_FURNACE, block.getDamage()), true); + } + return false; + } + } + + cookTime = 0; + sendPacket(); + return true; + } + + boolean ret = false; + Item product = this.inventory.getResult(); + FurnaceRecipe smelt = this.server.getCraftingManager().matchFurnaceRecipe(raw); + boolean canSmelt = (smelt != null && raw.getCount() > 0 && ((smelt.getResult().equals(product) && product.getCount() < product.getMaxStackSize()) || product.getId() == Item.AIR)); + + Item fuel; + if (burnTime <= 0 && canSmelt && (fuel = this.inventory.getItemFast(1)).getFuelTime() != null && fuel.getCount() > 0) { + this.checkFuel(fuel.clone()); + } + + if (burnTime > 0) { + burnTime--; + burnDuration = (int) Math.ceil((float) burnTime / maxTime * 100); + + if (this.crackledTime-- <= 0) { + this.crackledTime = ThreadLocalRandom.current().nextInt(30, 110); + this.getLevel().addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_BLOCK_FURNACE_LIT); + } + + if (smelt != null && canSmelt) { + cookTime++; + if (cookTime >= 100) { + product = Item.get(smelt.getResult().getId(), smelt.getResult().getDamage(), product.isNull() ? 1 : product.getCount() + 1); + + FurnaceSmeltEvent ev = new FurnaceSmeltEvent(this, raw, product); + this.server.getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + this.inventory.setResult(ev.getResult()); + this.experience += FURNACE_XP.getOrDefault(ev.getResult().getId(), 0d); + raw.setCount(raw.getCount() - 1); + if (raw.getCount() == 0) { + raw = new ItemBlock(Block.get(BlockID.AIR), 0, 0); + } + this.inventory.setSmelting(raw); + } + + cookTime -= 100; + } + } else if (burnTime <= 0) { + burnTime = 0; + cookTime = 0; + burnDuration = 0; + } else { + cookTime = 0; + } + ret = true; + } else { + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == BlockID.LIT_BLAST_FURNACE) { + this.level.setBlock(this, Block.get(BlockID.BLAST_FURNACE, block.getDamage()), true); + } + burnTime = 0; + cookTime = 0; + burnDuration = 0; + crackledTime = 0; + } + + sendPacket(); + + return ret; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.BLAST_FURNACE) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putShort("BurnDuration", burnDuration) + .putShort("BurnTime", burnTime) + .putShort("CookTime", cookTime); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBrewingStand.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBrewingStand.java index 29045ca294e..cf2508e01f1 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBrewingStand.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBrewingStand.java @@ -1,18 +1,15 @@ package cn.nukkit.blockentity; import cn.nukkit.Player; -import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.block.BlockBrewingStand; import cn.nukkit.block.BlockID; import cn.nukkit.event.inventory.BrewEvent; import cn.nukkit.event.inventory.StartBrewEvent; -import cn.nukkit.inventory.BrewingInventory; -import cn.nukkit.inventory.BrewingRecipe; -import cn.nukkit.inventory.ContainerRecipe; -import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.inventory.*; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemID; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; @@ -21,9 +18,6 @@ import cn.nukkit.network.protocol.LevelSoundEventPacket; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; public class BlockEntityBrewingStand extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { @@ -35,12 +29,6 @@ public class BlockEntityBrewingStand extends BlockEntitySpawnable implements Inv public int fuelTotal; public int fuelAmount; - public static final List ingredients = new ArrayList() { - { - addAll(Arrays.asList(Item.NETHER_WART, Item.GHAST_TEAR, Item.GLOWSTONE_DUST, Item.REDSTONE_DUST, Item.GUNPOWDER, Item.MAGMA_CREAM, Item.BLAZE_POWDER, Item.GOLDEN_CARROT, Item.SPIDER_EYE, Item.FERMENTED_SPIDER_EYE, Item.GLISTERING_MELON, Item.SUGAR, Item.RABBIT_FOOT, Item.PUFFERFISH, Item.TURTLE_SHELL, Item.PHANTOM_MEMBRANE, 437)); - } - }; - public BlockEntityBrewingStand(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -53,8 +41,12 @@ protected void initBlockEntity() { namedTag.putList(new ListTag("Items")); } - for (int i = 0; i < getSize(); i++) { - inventory.setItem(i, this.getItem(i)); + ListTag list = (ListTag) this.namedTag.getList("Items"); + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } } if (!namedTag.contains("CookTime") || namedTag.getShort("CookTime") > MAX_BREW_TIME) { @@ -85,7 +77,7 @@ public boolean hasName() { @Override public void setName(String name) { - if (name == null || name.equals("")) { + if (name == null || name.isEmpty()) { namedTag.remove("CustomName"); return; } @@ -96,8 +88,8 @@ public void setName(String name) { @Override public void close() { if (!closed) { - for (Player player : new HashSet<>(getInventory().getViewers())) { - player.removeWindow(getInventory()); + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); } super.close(); } @@ -108,11 +100,12 @@ public void onBreak() { for (Item content : inventory.getContents().values()) { level.dropItem(this, content); } - this.inventory.clearAll(); + inventory.clearAll(); } @Override public void saveNBT() { + super.saveNBT(); namedTag.putList(new ListTag("Items")); for (int index = 0; index < getSize(); index++) { this.setItem(index, inventory.getItem(index)); @@ -125,7 +118,7 @@ public void saveNBT() { @Override public boolean isBlockEntityValid() { - return getBlock().getId() == Block.BREWING_STAND_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.BREWING_STAND_BLOCK; } @Override @@ -177,101 +170,130 @@ public BrewingInventory getInventory() { return inventory; } - protected boolean checkIngredient(Item ingredient) { - return ingredients.contains(ingredient.getId()); - } - @Override public boolean onUpdate() { if (closed) { return false; } - boolean ret = false; + restockFuel(); - Item ingredient = this.inventory.getIngredient(); - boolean canBrew = false; + if (this.fuelAmount <= 0 || matchRecipes(true)[0] == null) { + stopBrewing(); + return false; + } - Item fuel = this.getInventory().getFuel(); - if (this.fuelAmount <= 0 && fuel.getId() == Item.BLAZE_POWDER && fuel.getCount() > 0) { - fuel.count--; - this.fuelAmount = 20; - this.fuelTotal = 20; + if (brewTime == MAX_BREW_TIME) { + StartBrewEvent e = new StartBrewEvent(this); + this.server.getPluginManager().callEvent(e); - this.inventory.setFuel(fuel); - this.sendFuel(); + if (e.isCancelled()) { + return false; + } + + this.sendBrewTime(); } - if (this.fuelAmount > 0) { - for (int i = 1; i <= 3; i++) { - if (this.inventory.getItem(i).getId() == Item.POTION) { - canBrew = true; - } + if (--brewTime > 0) { + + if (brewTime % 40 == 0) { + sendBrewTime(); } - if (this.brewTime <= MAX_BREW_TIME && canBrew && ingredient.getCount() > 0) { - if (!this.checkIngredient(ingredient)) { - canBrew = false; + return true; + } + + //20 seconds + BrewEvent e = new BrewEvent(this); + this.server.getPluginManager().callEvent(e); + + if (e.isCancelled()) { + stopBrewing(); + return true; + } + + boolean mixed = false; + MixRecipe[] recipes = matchRecipes(false); + for (int i = 0; i < 3; i++) { + MixRecipe recipe = recipes[i]; + if (recipe == null) { + continue; + } + + Item previous = inventory.getItem(i + 1); + if (!previous.isNull()) { + Item result = recipe.getResult(); + result.setCount(previous.getCount()); + if (recipe instanceof ContainerRecipe) { + result.setDamage(previous.getDamage()); } - } else { - canBrew = false; + inventory.setItem(i + 1, result); + mixed = true; } } - if (canBrew) { - if (this.brewTime == MAX_BREW_TIME) { - this.sendBrewTime(); - StartBrewEvent e = new StartBrewEvent(this); - this.server.getPluginManager().callEvent(e); + if (mixed) { + Item ingredient = this.inventory.getIngredient(); + ingredient.count--; + this.inventory.setIngredient(ingredient); - if (e.isCancelled()) { - return false; - } + this.fuelAmount--; + this.sendFuel(); + + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POTION_BREWED); + } + + stopBrewing(); + return true; + } + + private void restockFuel() { + Item fuel = this.getInventory().getFuel(); + if (this.fuelAmount > 0 || fuel.getId() != ItemID.BLAZE_POWDER || fuel.getCount() <= 0) { + return; + } + + fuel.count--; + this.fuelAmount = 20; + this.fuelTotal = 20; + + this.inventory.setFuel(fuel); + this.sendFuel(); + } + + private void stopBrewing() { + this.brewTime = 0; + this.sendBrewTime(); + this.brewTime = MAX_BREW_TIME; + } + + private MixRecipe[] matchRecipes(boolean quickTest) { + MixRecipe[] recipes = new MixRecipe[quickTest? 1 : 3]; + Item ingredient = inventory.getIngredient(); + CraftingManager craftingManager = getLevel().getServer().getCraftingManager(); + for (int i = 0; i < 3; i++) { + Item potion = inventory.getItem(i + 1); + if (potion.isNull()) { + continue; } - this.brewTime--; - - if (this.brewTime <= 0) { //20 seconds - BrewEvent e = new BrewEvent(this); - this.server.getPluginManager().callEvent(e); - - if (!e.isCancelled()) { - for (int i = 1; i <= 3; i++) { - Item potion = this.inventory.getItem(i); - - ContainerRecipe containerRecipe = Server.getInstance().getCraftingManager().matchContainerRecipe(ingredient, potion); - if (containerRecipe != null) { - Item result = containerRecipe.getResult(); - result.setDamage(potion.getDamage()); - this.inventory.setItem(i, result); - } else { - BrewingRecipe recipe = Server.getInstance().getCraftingManager().matchBrewingRecipe(ingredient, potion); - if (recipe != null) { - this.inventory.setItem(i, recipe.getResult()); - } - } - } - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_POTION_BREWED); - - ingredient.count--; - this.inventory.setIngredient(ingredient); - - this.fuelAmount--; - this.sendFuel(); - } + MixRecipe recipe = craftingManager.matchBrewingRecipe(ingredient, potion); + if (recipe == null) { + recipe = craftingManager.matchContainerRecipe(ingredient, potion); + } + if (recipe == null) { + continue; + } - this.brewTime = MAX_BREW_TIME; + if (quickTest) { + recipes[0] = recipe; + return recipes; } - ret = true; - } else { - this.brewTime = MAX_BREW_TIME; + recipes[i] = recipe; } - //this.sendBrewTime(); - lastUpdate = System.currentTimeMillis(); - - return ret; + return recipes; } protected void sendFuel() { @@ -328,6 +350,10 @@ public void updateBlock() { block.setDamage(meta); this.level.setBlock(block, block, false, false); + + if (brewTime != MAX_BREW_TIME && matchRecipes(true)[0] == null) { + stopBrewing(); + } } public int getFuel() { @@ -358,4 +384,4 @@ public CompoundTag getSpawnCompound() { return nbt; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityCampfire.java b/src/main/java/cn/nukkit/blockentity/BlockEntityCampfire.java new file mode 100644 index 00000000000..33819d9e5f0 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityCampfire.java @@ -0,0 +1,229 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockCampfire; +import cn.nukkit.block.BlockID; +import cn.nukkit.event.block.CampfireSmeltEvent; +import cn.nukkit.inventory.CampfireInventory; +import cn.nukkit.inventory.CampfireRecipe; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; + +public class BlockEntityCampfire extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer { + + private CampfireInventory inventory; + private int[] burnTime; + private CampfireRecipe[] recipes; + private boolean[] keepItem; + + public BlockEntityCampfire(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + protected void initBlockEntity() { + this.inventory = new CampfireInventory(this); + this.burnTime = new int[4]; + this.recipes = new CampfireRecipe[4]; + this.keepItem = new boolean[4]; + + for (int i = 1; i <= burnTime.length; i++) { + burnTime[i -1] = namedTag.getInt("ItemTime" + i); + keepItem[i -1] = namedTag.getBoolean("KeepItem" + 1); + + if (this.namedTag.contains("Item" + i) && this.namedTag.get("Item" + i) instanceof CompoundTag) { + inventory.setItem(i - 1, NBTIO.getItemHelper(this.namedTag.getCompound("Item" + i))); + } + } + + super.initBlockEntity(); + this.scheduleUpdate(); + } + + @Override + public boolean onUpdate() { + if (this.closed) { + return false; + } + + boolean needsUpdate = false; + Block block = this.getBlock(); + boolean isLit = block instanceof BlockCampfire && !((BlockCampfire) block).isExtinguished(); + for (int slot = 0; slot < inventory.getSize(); slot++) { + Item item = inventory.getItem(slot); + if (item == null || item.getId() == BlockID.AIR || item.getCount() <= 0) { + burnTime[slot] = 0; + recipes[slot] = null; + } else if (!keepItem[slot]) { + CampfireRecipe recipe = recipes[slot]; + if (recipe == null) { + recipe = this.server.getCraftingManager().matchCampfireRecipe(item); + if (recipe == null) { + inventory.setItem(slot, Item.get(0)); + ThreadLocalRandom random = ThreadLocalRandom.current(); + this.level.dropItem(add(random.nextFloat(), 0.5, random.nextFloat()), item); + burnTime[slot] = 0; + recipes[slot] = null; + continue; + } else { + burnTime[slot] = 600; + recipes[slot] = recipe; + } + } + + int burnTimeLeft = burnTime[slot]; + if (burnTimeLeft <= 0) { + Item product = Item.get(recipe.getResult().getId(), recipe.getResult().getDamage(), item.getCount()); + CampfireSmeltEvent event = new CampfireSmeltEvent(this, item, product); + if (!event.isCancelled()) { + inventory.setItem(slot, Item.get(0)); + ThreadLocalRandom random = ThreadLocalRandom.current(); + this.level.dropItem(add(random.nextFloat(), 0.5, random.nextFloat()), event.getResult()); + burnTime[slot] = 0; + recipes[slot] = null; + } else if (event.getKeepItem()) { + keepItem[slot] = true; + burnTime[slot] = 0; + recipes[slot] = null; + } + } else if (isLit) { + burnTime[slot]--; + needsUpdate = true; + } else { + burnTime[slot] = 600; + } + } + } + + return needsUpdate; + } + + public boolean getKeepItem(int slot) { + if (slot < 0 || slot >= keepItem.length) { + return false; + } + return keepItem[slot]; + } + + public void setKeepItem(int slot, boolean keep) { + if (slot < 0 || slot >= keepItem.length) { + return; + } + this.keepItem[slot] = keep; + } + + @Override + public void saveNBT() { + super.saveNBT(); + + for (int i = 1; i <= burnTime.length; i++) { + Item item = inventory.getItem(i - 1); + if (item == null || item.getId() == BlockID.AIR || item.getCount() <= 0) { + namedTag.remove("Item"+i); + namedTag.putInt("ItemTime" + i, 0); + namedTag.remove("KeepItem"+i); + } else { + namedTag.putCompound("Item"+i, NBTIO.putItemHelper(item)); + namedTag.putInt("ItemTime" + i, burnTime[i - 1]); + namedTag.putBoolean("KeepItem"+i, keepItem[i-1]); + } + } + } + + public void setRecipe(int index, CampfireRecipe recipe) { + this.recipes[index] = recipe; + } + + @Override + public void close() { + if (!closed) { + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); + } + super.close(); + } + } + + @Override + public void onBreak() { + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + inventory.clearAll(); + } + + @Override + public String getName() { + return "Campfire"; + } + + @Override + public void spawnTo(Player player) { + if (!this.closed) { + player.dataPacket(this.createSpawnPacket()); + } + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.CAMPFIRE) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + for (int i = 1; i <= burnTime.length; i++) { + Item item = inventory.getItem(i - 1); + if (item == null || item.getId() == BlockID.AIR || item.getCount() <= 0) { + c.remove("Item"+i); + } else { + c.putCompound("Item"+i, NBTIO.putNetworkItemHelper(item)); + } + } + + return c; + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == BlockID.CAMPFIRE_BLOCK; + } + + @Override + public int getSize() { + return 4; + } + + @Override + public Item getItem(int index) { + if (index < 0 || index >= getSize()) { + return new ItemBlock(Block.get(0), 0, 0); + } else { + CompoundTag data = this.namedTag.getCompound("Item" + (index + 1)); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + if (index < 0 || index >= getSize()) { + return; + } + + CompoundTag nbt = NBTIO.putItemHelper(item); + this.namedTag.putCompound("Item" + (index + 1), nbt); + } + + @Override + public CampfireInventory getInventory() { + return inventory; + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityCauldron.java b/src/main/java/cn/nukkit/blockentity/BlockEntityCauldron.java index e986c1578ac..0c3742b9e4c 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityCauldron.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityCauldron.java @@ -1,29 +1,46 @@ package cn.nukkit.blockentity; +import cn.nukkit.Player; import cn.nukkit.block.Block; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; - +import cn.nukkit.network.protocol.UpdateBlockPacket; import cn.nukkit.utils.BlockColor; +import java.util.Collection; + /** - * author: CreeperFace + * @author CreeperFace * Nukkit Project */ public class BlockEntityCauldron extends BlockEntitySpawnable { + public static final int POTION_TYPE_EMPTY = 0xFFFF; + public static final int POTION_TYPE_NORMAL = 0; + public static final int POTION_TYPE_SPLASH = 1; + public static final int POTION_TYPE_LINGERING = 2; + public BlockEntityCauldron(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override protected void initBlockEntity() { + int potionId; if (!namedTag.contains("PotionId")) { namedTag.putShort("PotionId", 0xffff); } + potionId = namedTag.getShort("PotionId"); - if (!namedTag.contains("SplashPotion")) { - namedTag.putByte("SplashPotion", 0); + int potionType = (potionId & 0xFFFF) == 0xFFFF? POTION_TYPE_EMPTY : POTION_TYPE_NORMAL; + if (namedTag.getBoolean("SplashPotion")) { + potionType = POTION_TYPE_SPLASH; + namedTag.remove("SplashPotion"); + } + + if (!namedTag.contains("PotionType")) { + namedTag.putShort("PotionType", potionType); } super.initBlockEntity(); @@ -35,6 +52,7 @@ public int getPotionId() { public void setPotionId(int potionId) { namedTag.putShort("PotionId", potionId); + setDirty(); this.spawnToAll(); } @@ -42,12 +60,22 @@ public boolean hasPotion() { return getPotionId() != 0xffff; } + public void setPotionType(int potionType) { + this.namedTag.putShort("PotionType", potionType & 0xFFFF); + setDirty(); + } + + public int getPotionType() { + return this.namedTag.getShort("PotionType") & 0xFFFF; + } + public boolean isSplashPotion() { - return namedTag.getByte("SplashPotion") > 0; + return namedTag.getShort("PotionType") == POTION_TYPE_SPLASH; } public void setSplashPotion(boolean value) { - namedTag.putByte("SplashPotion", value ? 1 : 0); + namedTag.putShort("PotionType", value ? 1 : 0); + setDirty(); } public BlockColor getCustomColor() { @@ -75,28 +103,53 @@ public void setCustomColor(BlockColor color) { public void setCustomColor(int r, int g, int b) { int color = (r << 16 | g << 8 | b) & 0xffffff; - namedTag.putInt("CustomColor", color); - spawnToAll(); + if (color != namedTag.getInt("CustomColor")) { + namedTag.putInt("CustomColor", color); + Block block = getBlock(); + Collection pl = level.getChunkPlayers(getChunkX(), getChunkZ()).values(); + for (Player p : pl) { + UpdateBlockPacket air = new UpdateBlockPacket(); + air.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(0); + air.flags = UpdateBlockPacket.FLAG_ALL_PRIORITY; + air.x = (int) x; + air.y = (int) y; + air.z = (int) z; + UpdateBlockPacket self = (UpdateBlockPacket) air.clone(); + self.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(block.getId(), block.getDamage()); + p.dataPacket(air); + p.dataPacket(self); + } + + setDirty(); + + spawnToAll(); + } } public void clearCustomColor() { namedTag.remove("CustomColor"); + setDirty(); spawnToAll(); } @Override public boolean isBlockEntityValid() { - return getBlock().getId() == Block.CAULDRON_BLOCK; + int id = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return id == Block.CAULDRON_BLOCK || id == Block.LAVA_CAULDRON; } @Override public CompoundTag getSpawnCompound() { - return new CompoundTag() + CompoundTag compoundTag = new CompoundTag() .putString("id", BlockEntity.CAULDRON) .putInt("x", (int) this.x) .putInt("y", (int) this.y) .putInt("z", (int) this.z) .putShort("PotionId", namedTag.getShort("PotionId")) - .putByte("SplashPotion", namedTag.getByte("SplashPotion")); + .putByte("PotionType", namedTag.getShort("PotionType")); + if (namedTag.contains("CustomColor")) { + compoundTag.putInt("CustomColor", namedTag.getInt("CustomColor")); + } + return compoundTag; } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityChest.java b/src/main/java/cn/nukkit/blockentity/BlockEntityChest.java index a7a1d58495e..b793c2a19ca 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityChest.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityChest.java @@ -15,63 +15,63 @@ import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; -import java.util.HashSet; +import java.util.ArrayList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockEntityChest extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { protected ChestInventory inventory; - protected DoubleChestInventory doubleInventory = null; + protected DoubleChestInventory doubleInventory; public BlockEntityChest(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - @Override - protected void initBlockEntity() { - this.inventory = new ChestInventory(this); - + private void initInventory() { if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { this.namedTag.putList(new ListTag("Items")); } + ListTag list = (ListTag) this.namedTag.getList("Items"); - /* for (int i = 0; i < this.getSize(); i++) { - this.inventory.setItem(i, this.getItem(i)); - } */ + this.inventory = new ChestInventory(this); - ListTag list = (ListTag) this.namedTag.getList("Items"); for (CompoundTag compound : list.getAll()) { Item item = NBTIO.getItemHelper(compound); - this.inventory.slots.put(compound.getByte("Slot"), item); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } } - - super.initBlockEntity(); } @Override public void close() { - if (!closed) { + if (!this.closed && this.inventory != null) { + if (this.doubleInventory != null) { + for (Player player : new ArrayList<>(this.doubleInventory.getViewers())) { + player.removeWindow(this.doubleInventory); + } - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getInventory()); + this.doubleInventory = null; } - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getRealInventory()); + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); } - super.close(); } + + super.close(); } @Override public void onBreak() { - if (this.isPaired()) { - unpair(); + if (this.inventory == null) { + this.initInventory(); } + unpair(); for (Item content : inventory.getContents().values()) { level.dropItem(this, content); } @@ -80,16 +80,20 @@ public void onBreak() { @Override public void saveNBT() { - this.namedTag.putList(new ListTag("Items")); - for (int index = 0; index < this.getSize(); index++) { - this.setItem(index, this.inventory.getItem(index)); + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } } } @Override public boolean isBlockEntityValid() { - int blockID = this.getBlock().getId(); - return blockID == Block.CHEST || blockID == Block.TRAPPED_CHEST; + int id = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return id == Block.CHEST || id == Block.TRAPPED_CHEST; } @Override @@ -141,6 +145,9 @@ public void setItem(int index, Item item) { @Override public BaseInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } if (this.doubleInventory == null && this.isPaired()) { this.checkPairing(); } @@ -149,6 +156,9 @@ public BaseInventory getInventory() { } public ChestInventory getRealInventory() { + if (this.inventory == null) { + this.initInventory(); + } return inventory; } @@ -191,7 +201,7 @@ public boolean hasName() { @Override public void setName(String name) { - if (name == null || name.equals("")) { + if (name == null || name.isEmpty()) { this.namedTag.remove("CustomName"); return; } @@ -205,7 +215,7 @@ public boolean isPaired() { public BlockEntityChest getPair() { if (this.isPaired()) { - BlockEntity blockEntity = this.getLevel().getBlockEntityIfLoaded(new Vector3(this.namedTag.getInt("pairx"), this.y, this.namedTag.getInt("pairz"))); + BlockEntity blockEntity = this.getLevel().getBlockEntityIfLoaded(this.chunk, new Vector3(this.namedTag.getInt("pairx"), this.y, this.namedTag.getInt("pairz"))); if (blockEntity instanceof BlockEntityChest) { return (BlockEntityChest) blockEntity; } @@ -285,5 +295,4 @@ public CompoundTag getSpawnCompound() { return c; } - } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityComparator.java b/src/main/java/cn/nukkit/blockentity/BlockEntityComparator.java index 5409ec5245c..ed2fc7c796d 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityComparator.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityComparator.java @@ -1,6 +1,6 @@ package cn.nukkit.blockentity; -import cn.nukkit.block.BlockRedstoneComparator; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; @@ -23,7 +23,8 @@ public BlockEntityComparator(FullChunk chunk, CompoundTag nbt) { @Override public boolean isBlockEntityValid() { - return this.getLevelBlock() instanceof BlockRedstoneComparator; + int blockID = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return blockID == Block.POWERED_COMPARATOR || blockID == Block.UNPOWERED_COMPARATOR; } public int getOutputSignal() { @@ -31,7 +32,10 @@ public int getOutputSignal() { } public void setOutputSignal(int outputSignal) { - this.outputSignal = outputSignal; + if (this.outputSignal != outputSignal) { + this.outputSignal = outputSignal; + setDirty(); + } } @Override diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityContainer.java b/src/main/java/cn/nukkit/blockentity/BlockEntityContainer.java index 79e21d0e4f9..e7082f26472 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityContainer.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityContainer.java @@ -5,15 +5,14 @@ /** * 表达一个容器的接口。
* An interface describes a container. - * - *

{@code BlockEntityContainer}容器必须包含物品的{@code Item}对象。
- * A {@code BlockEntityContainer} must contain items as {@code Item} objects.

+ * + * {@code BlockEntityContainer}容器必须包含物品的{@code Item}对象。
+ * A {@code BlockEntityContainer} must contain items as {@code Item} objects. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see BlockEntityChest * @see BlockEntityFurnace - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface BlockEntityContainer { @@ -23,20 +22,18 @@ public interface BlockEntityContainer { * * @param index 这个物品的索引序号。
The index number of this item. * @return 这个物品的 {@code Item}对象。
An {@code Item} object for this item. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Item getItem(int index); /** * 把一个物品存储进容器。
* Sets or stores this item into this container. - * - *

注意:如果这个容器相应的索引序号已经有了物品,那么新存储的物品将会替换原有的物品。
- * Notice: If there is already an item for this index number, the new item being stored will REPLACE the old one.

+ * + * 注意:如果这个容器相应的索引序号已经有了物品,那么新存储的物品将会替换原有的物品。
+ * Notice: If there is already an item for this index number, the new item being stored will REPLACE the old one. * * @param index 这个物品的索引序号。
The index number of this item. * @param item 描述这个物品的 {@code Item}对象。
The {@code Item} object that describes this item. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void setItem(int index, Item item); @@ -45,7 +42,6 @@ public interface BlockEntityContainer { * Returns the max number of items that this container can contain. * * @return 最多能包含的物品数量。
The max number. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ int getSize(); } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java b/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java new file mode 100644 index 00000000000..7d571b38b8f --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityDispenser.java @@ -0,0 +1,166 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.DispenserInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +import java.util.ArrayList; + +public class BlockEntityDispenser extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { + + protected DispenserInventory inventory; + + public BlockEntityDispenser(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + private void initInventory() { + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new DispenserInventory(this); + + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } + } + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == BlockID.DISPENSER; + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Dispenser"; + } + + @Override + public boolean hasName() { + return this.namedTag.contains("CustomName"); + } + + @Override + public void setName(String name) { + if (name == null || name.isEmpty()) { + this.namedTag.remove("CustomName"); + return; + } + + this.namedTag.putString("CustomName", name); + } + + @Override + public int getSize() { + return 9; + } + + protected int getSlotIndex(int index) { + ListTag list = this.namedTag.getList("Items", CompoundTag.class); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getByte("Slot") == index) { + return i; + } + } + + return -1; + } + + @Override + public Item getItem(int index) { + int i = this.getSlotIndex(index); + if (i < 0) { + return new ItemBlock(Block.get(BlockID.AIR), 0, 0); + } else { + CompoundTag data = (CompoundTag) this.namedTag.getList("Items").get(i); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + int i = this.getSlotIndex(index); + + CompoundTag d = NBTIO.putItemHelper(item, index); + + if (item.getId() == Item.AIR || item.getCount() <= 0) { + if (i >= 0) { + this.namedTag.getList("Items").getAll().remove(i); + } + } else if (i < 0) { + (this.namedTag.getList("Items", CompoundTag.class)).add(d); + } else { + (this.namedTag.getList("Items", CompoundTag.class)).add(i, d); + } + } + + @Override + public void saveNBT() { + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } + } + } + + @Override + public DispenserInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } + return this.inventory; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.DISPENSER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } + + @Override + public void close() { + if (!this.closed && this.inventory != null) { + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); + } + } + + super.close(); + } + + @Override + public void onBreak() { + if (this.inventory == null) { + this.initInventory(); + } + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + inventory.clearAll(); + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java b/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java new file mode 100644 index 00000000000..1dd8d944aa5 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityDropper.java @@ -0,0 +1,166 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.DropperInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +import java.util.ArrayList; + +public class BlockEntityDropper extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { + + protected DropperInventory inventory; + + public BlockEntityDropper(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + private void initInventory() { + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new DropperInventory(this); + + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } + } + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.DROPPER; + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Dropper"; + } + + @Override + public boolean hasName() { + return this.namedTag.contains("CustomName"); + } + + @Override + public void setName(String name) { + if (name == null || name.isEmpty()) { + this.namedTag.remove("CustomName"); + return; + } + + this.namedTag.putString("CustomName", name); + } + + @Override + public int getSize() { + return 9; + } + + protected int getSlotIndex(int index) { + ListTag list = this.namedTag.getList("Items", CompoundTag.class); + for (int i = 0; i < list.size(); i++) { + if (list.get(i).getByte("Slot") == index) { + return i; + } + } + + return -1; + } + + @Override + public Item getItem(int index) { + int i = this.getSlotIndex(index); + if (i < 0) { + return new ItemBlock(Block.get(BlockID.AIR), 0, 0); + } else { + CompoundTag data = (CompoundTag) this.namedTag.getList("Items").get(i); + return NBTIO.getItemHelper(data); + } + } + + @Override + public void setItem(int index, Item item) { + int i = this.getSlotIndex(index); + + CompoundTag d = NBTIO.putItemHelper(item, index); + + if (item.getId() == Item.AIR || item.getCount() <= 0) { + if (i >= 0) { + this.namedTag.getList("Items").getAll().remove(i); + } + } else if (i < 0) { + (this.namedTag.getList("Items", CompoundTag.class)).add(d); + } else { + (this.namedTag.getList("Items", CompoundTag.class)).add(i, d); + } + } + + @Override + public void saveNBT() { + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } + } + } + + @Override + public DropperInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } + return this.inventory; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.DROPPER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } + + @Override + public void close() { + if (!this.closed && this.inventory != null) { + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); + } + } + + super.close(); + } + + @Override + public void onBreak() { + if (this.inventory == null) { + this.initInventory(); + } + for (Item content : inventory.getContents().values()) { + level.dropItem(this, content); + } + inventory.clearAll(); + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityEnchantTable.java b/src/main/java/cn/nukkit/blockentity/BlockEntityEnchantTable.java index 34cb11043e5..f6324121805 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityEnchantTable.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityEnchantTable.java @@ -5,7 +5,7 @@ import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockEntityEnchantTable extends BlockEntitySpawnable implements BlockEntityNameable { @@ -16,7 +16,7 @@ public BlockEntityEnchantTable(FullChunk chunk, CompoundTag nbt) { @Override public boolean isBlockEntityValid() { - return getBlock().getId() == Block.ENCHANT_TABLE; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.ENCHANT_TABLE; } @Override @@ -31,7 +31,9 @@ public boolean hasName() { @Override public void setName(String name) { - if (name == null || name.equals("")) { + setDirty(); + + if (name == null || name.isEmpty()) { this.namedTag.remove("CustomName"); return; } @@ -53,5 +55,4 @@ public CompoundTag getSpawnCompound() { return c; } - } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityEnderChest.java b/src/main/java/cn/nukkit/blockentity/BlockEntityEnderChest.java index ef834c937cd..a515f6bbaba 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityEnderChest.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityEnderChest.java @@ -12,7 +12,7 @@ public BlockEntityEnderChest(FullChunk chunk, CompoundTag nbt) { @Override public boolean isBlockEntityValid() { - return this.getBlock().getId() == Block.ENDER_CHEST; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.ENDER_CHEST; } @Override diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityFlowerPot.java b/src/main/java/cn/nukkit/blockentity/BlockEntityFlowerPot.java index b83e63e07fb..1ee488b0f90 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityFlowerPot.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityFlowerPot.java @@ -1,8 +1,10 @@ package cn.nukkit.blockentity; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; /** * Created by Snake1999 on 2016/2/4. @@ -33,8 +35,16 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - int blockID = getBlock().getId(); - return blockID == Block.FLOWER_POT_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.FLOWER_POT_BLOCK; + } + + private static final Int2ObjectOpenHashMap HAS_STRING_ITEM_OVERRIDE = new Int2ObjectOpenHashMap<>(); + + static { + HAS_STRING_ITEM_OVERRIDE.put(BlockID.CRIMSON_ROOTS, "minecraft:crimson_roots"); + HAS_STRING_ITEM_OVERRIDE.put(BlockID.WARPED_ROOTS, "minecraft:warped_roots"); + HAS_STRING_ITEM_OVERRIDE.put(BlockID.CRIMSON_FUNGUS, "minecraft:crimson_fungus"); + HAS_STRING_ITEM_OVERRIDE.put(BlockID.WARPED_FUNGUS, "minecraft:warped_fungus"); } @Override @@ -47,10 +57,14 @@ public CompoundTag getSpawnCompound() { int item = namedTag.getShort("item"); if (item != Block.AIR) { - tag.putShort("item", this.namedTag.getShort("item")) - .putInt("mData", this.namedTag.getInt("data")); + // Fix latest game versions not displaying legacy items correctly + if (HAS_STRING_ITEM_OVERRIDE.containsKey(item)) { + tag.putCompound("PlantBlock", new CompoundTag().putString("name", HAS_STRING_ITEM_OVERRIDE.get(item))); + } else { + tag.putShort("item", this.namedTag.getShort("item")) + .putInt("mData", this.namedTag.getInt("data")); + } } return tag; } - -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityFurnace.java b/src/main/java/cn/nukkit/blockentity/BlockEntityFurnace.java index 6bf2223a965..da4685be253 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityFurnace.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityFurnace.java @@ -1,22 +1,24 @@ package cn.nukkit.blockentity; import cn.nukkit.Player; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; import cn.nukkit.event.inventory.FurnaceBurnEvent; import cn.nukkit.event.inventory.FurnaceSmeltEvent; -import cn.nukkit.inventory.FurnaceInventory; -import cn.nukkit.inventory.FurnaceRecipe; -import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.inventory.*; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.NukkitMath; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.ContainerSetDataPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ThreadLocalRandom; /** @@ -30,8 +32,45 @@ public class BlockEntityFurnace extends BlockEntitySpawnable implements Inventor protected int burnDuration; protected int cookTime; protected int maxTime; - - private int crackledTime; + protected int crackledTime; + protected double experience; + + public static final Map FURNACE_XP = new HashMap<>(); + + static { + FURNACE_XP.put(Item.BAKED_POTATO, 0.35d); + FURNACE_XP.put(Item.DRIED_KELP, 0.1d); + FURNACE_XP.put(Item.STEAK, 0.35d); + FURNACE_XP.put(Item.COOKED_PORKCHOP, 0.35d); + FURNACE_XP.put(Item.COOKED_MUTTON, 0.35d); + FURNACE_XP.put(Item.COOKED_CHICKEN, 0.35d); + FURNACE_XP.put(Item.COOKED_RABBIT, 0.35d); + FURNACE_XP.put(Item.COOKED_FISH, 0.35d); + FURNACE_XP.put(Item.COOKED_SALMON, 0.35d); + + FURNACE_XP.put(Item.REDSTONE_DUST, 0.3d); + FURNACE_XP.put(Item.COAL, 0.1d); + FURNACE_XP.put(Item.EMERALD, 1d); + FURNACE_XP.put(Item.DYE, 0.2d); // Lapis & Cactus + FURNACE_XP.put(Item.DIAMOND, 1d); + FURNACE_XP.put(Item.NETHER_QUARTZ, 0.2d); + FURNACE_XP.put(Item.IRON_INGOT, 0.7d); + FURNACE_XP.put(Item.COPPER_INGOT, 0.7d); + FURNACE_XP.put(Item.GOLD_INGOT, 1d); + FURNACE_XP.put(Item.NETHERITE_SCRAP, 1d); + FURNACE_XP.put(Item.IRON_NUGGET, 0.1d); + FURNACE_XP.put(Item.GOLD_NUGGET, 0.1d); + + FURNACE_XP.put(Item.STONE, 0.1d); + FURNACE_XP.put(Item.TERRACOTTA, 0.35d); + FURNACE_XP.put(Item.GLASS, 0.1d); + FURNACE_XP.put(Item.SPONGE, 0.15d); + FURNACE_XP.put(Item.POPPED_CHORUS_FRUIT, 0.1d); + FURNACE_XP.put(Item.BRICK, 0.3d); + FURNACE_XP.put(Item.NETHER_BRICK, 0.1d); + + FURNACE_XP.put(255 - Item.SMOOTH_BASALT, 0.1d); + } public BlockEntityFurnace(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -39,14 +78,24 @@ public BlockEntityFurnace(FullChunk chunk, CompoundTag nbt) { @Override protected void initBlockEntity() { - this.inventory = new FurnaceInventory(this); + if (this instanceof BlockEntityBlastFurnace) { + this.inventory = new BlastFurnaceInventory((BlockEntityBlastFurnace) this); + } else if (this instanceof BlockEntitySmoker) { + this.inventory = new SmokerInventory((BlockEntitySmoker) this); + } else { + this.inventory = new FurnaceInventory(this); + } if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { this.namedTag.putList(new ListTag("Items")); } - for (int i = 0; i < this.getSize(); i++) { - this.inventory.setItem(i, this.getItem(i)); + ListTag list = (ListTag) this.namedTag.getList("Items"); + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } } if (!this.namedTag.contains("BurnTime") || this.namedTag.getShort("BurnTime") < 0) { @@ -79,6 +128,12 @@ protected void initBlockEntity() { this.namedTag.remove("BurnTicks"); } + if (this.namedTag.contains("Experience") && this.namedTag.getDouble("Experience") > 0) { + this.experience = this.namedTag.getDouble("Experience"); + } else { + this.experience = 0; + } + if (burnTime > 0) { this.scheduleUpdate(); } @@ -98,7 +153,7 @@ public boolean hasName() { @Override public void setName(String name) { - if (name == null || name.equals("")) { + if (name == null || name.isEmpty()) { this.namedTag.remove("CustomName"); return; } @@ -109,9 +164,10 @@ public void setName(String name) { @Override public void close() { if (!closed) { - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getInventory()); + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); } + super.close(); } } @@ -121,11 +177,12 @@ public void onBreak() { for (Item content : inventory.getContents().values()) { level.dropItem(this, content); } - this.inventory.clearAll(); + inventory.clearAll(); } @Override public void saveNBT() { + super.saveNBT(); this.namedTag.putList(new ListTag("Items")); for (int index = 0; index < this.getSize(); index++) { this.setItem(index, this.inventory.getItem(index)); @@ -135,11 +192,12 @@ public void saveNBT() { this.namedTag.putShort("BurnTime", burnTime); this.namedTag.putShort("BurnDuration", burnDuration); this.namedTag.putShort("MaxTime", maxTime); + this.namedTag.putDouble("Experience", experience); } @Override public boolean isBlockEntityValid() { - int blockID = getBlock().getId(); + int blockID = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); return blockID == Block.FURNACE || blockID == Block.BURNING_FURNACE; } @@ -194,7 +252,9 @@ public FurnaceInventory getInventory() { protected void checkFuel(Item fuel) { FurnaceBurnEvent ev = new FurnaceBurnEvent(this, fuel, fuel.getFuelTime() == null ? 0 : fuel.getFuelTime()); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { return; } @@ -202,8 +262,14 @@ protected void checkFuel(Item fuel) { maxTime = ev.getBurnTime(); burnTime = ev.getBurnTime(); burnDuration = 0; - if (this.getBlock().getId() == Item.FURNACE) { - this.getLevel().setBlock(this, Block.get(BlockID.BURNING_FURNACE, this.getBlock().getDamage()), true); + + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == Item.FURNACE) { + this.getLevel().setBlock(this, Block.get(BlockID.BURNING_FURNACE, block.getDamage()), true); + } else if (block.getId() == Item.SMOKER) { + this.getLevel().setBlock(this, Block.get(BlockID.LIT_SMOKER, block.getDamage()), true); + } else if (block.getId() == Item.BLAST_FURNACE) { + this.getLevel().setBlock(this, Block.get(BlockID.LIT_BLAST_FURNACE, block.getDamage()), true); } if (burnTime > 0 && ev.isBurning()) { @@ -226,17 +292,15 @@ public boolean onUpdate() { return false; } - this.timing.startTiming(); - boolean ret = false; - Item fuel = this.inventory.getFuel(); Item raw = this.inventory.getSmelting(); Item product = this.inventory.getResult(); FurnaceRecipe smelt = this.server.getCraftingManager().matchFurnaceRecipe(raw); - boolean canSmelt = (smelt != null && raw.getCount() > 0 && ((smelt.getResult().equals(product, true) && product.getCount() < product.getMaxStackSize()) || product.getId() == Item.AIR)); + boolean canSmelt = (smelt != null && raw.getCount() > 0 && ((smelt.getResult().equals(product) && product.getCount() < product.getMaxStackSize()) || product.getId() == Item.AIR)); - if (burnTime <= 0 && canSmelt && fuel.getFuelTime() != null && fuel.getCount() > 0) { - this.checkFuel(fuel); + Item fuel; + if (burnTime <= 0 && canSmelt && (fuel = this.inventory.getItemFast(1)).getFuelTime() != null && fuel.getCount() > 0) { + this.checkFuel(fuel.clone()); } if (burnTime > 0) { @@ -244,19 +308,20 @@ public boolean onUpdate() { burnDuration = (int) Math.ceil((float) burnTime / maxTime * 200); if (this.crackledTime-- <= 0) { - this.crackledTime = ThreadLocalRandom.current().nextInt(20, 100); + this.crackledTime = ThreadLocalRandom.current().nextInt(30, 110); this.getLevel().addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_BLOCK_FURNACE_LIT); } if (smelt != null && canSmelt) { cookTime++; if (cookTime >= 200) { - product = Item.get(smelt.getResult().getId(), smelt.getResult().getDamage(), product.getCount() + 1); + product = Item.get(smelt.getResult().getId(), smelt.getResult().getDamage(), product.isNull() ? 1 : product.getCount() + 1); FurnaceSmeltEvent ev = new FurnaceSmeltEvent(this, raw, product); this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { this.inventory.setResult(ev.getResult()); + this.experience += FURNACE_XP.getOrDefault(ev.getResult().getId(), 0d); raw.setCount(raw.getCount() - 1); if (raw.getCount() == 0) { raw = new ItemBlock(Block.get(BlockID.AIR), 0, 0); @@ -275,22 +340,28 @@ public boolean onUpdate() { } ret = true; } else { - if (this.getBlock().getId() == Item.BURNING_FURNACE) { - this.getLevel().setBlock(this, Block.get(BlockID.FURNACE, this.getBlock().getDamage()), true); + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == Item.BURNING_FURNACE) { + this.getLevel().setBlock(this, Block.get(BlockID.FURNACE, block.getDamage()), true); } burnTime = 0; cookTime = 0; burnDuration = 0; - this.crackledTime = 0; + crackledTime = 0; } - for (Player player : this.getInventory().getViewers()) { - int windowId = player.getWindowId(this.getInventory()); + sendPacket(); + + return ret; + } + + protected void sendPacket() { + for (Player player : this.inventory.getViewers()) { + int windowId = player.getWindowId(this.inventory); if (windowId > 0) { ContainerSetDataPacket pk = new ContainerSetDataPacket(); pk.windowId = windowId; pk.property = ContainerSetDataPacket.PROPERTY_FURNACE_TICK_COUNT; - pk.value = cookTime; player.dataPacket(pk); @@ -301,12 +372,6 @@ public boolean onUpdate() { player.dataPacket(pk); } } - - this.lastUpdate = System.currentTimeMillis(); - - this.timing.stopTiming(); - - return ret; } @Override @@ -358,4 +423,20 @@ public int getMaxTime() { public void setMaxTime(int maxTime) { this.maxTime = maxTime; } + + public double getExperience() { + return this.experience; + } + + public void setExperience(double experience) { + this.experience = experience; + } + + public void releaseExperience() { + int experience = NukkitMath.floorDouble(this.experience); + if (experience >= 1) { + this.experience = 0; + this.level.dropExpOrb(this, experience); + } + } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java b/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java index db34f1f3bf9..245f0267a7e 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityHopper.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockComposter; import cn.nukkit.block.BlockID; import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityItem; @@ -16,8 +17,9 @@ import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; +import lombok.Setter; -import java.util.HashSet; +import java.util.ArrayList; /** * Created by CreeperFace on 8.5.2017. @@ -30,6 +32,11 @@ public class BlockEntityHopper extends BlockEntitySpawnable implements Inventory private AxisAlignedBB pickupArea; + @Setter + private InventoryHolder minecartPickupInventory; + @Setter + private InventoryHolder minecartPushInventory; + public BlockEntityHopper(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -48,8 +55,12 @@ protected void initBlockEntity() { this.namedTag.putList(new ListTag("Items")); } - for (int i = 0; i < this.getSize(); i++) { - this.inventory.setItem(i, this.getItem(i)); + ListTag list = (ListTag) this.namedTag.getList("Items"); + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } } this.pickupArea = new SimpleAxisAlignedBB(this.x, this.y, this.z, this.x + 1, this.y + 2, this.z + 1); @@ -61,7 +72,7 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - return this.level.getBlockIdAt(this.getFloorX(), this.getFloorY(), this.getFloorZ()) == Block.HOPPER_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.HOPPER_BLOCK; } @Override @@ -76,7 +87,7 @@ public boolean hasName() { @Override public void setName(String name) { - if (name == null || name.equals("")) { + if (name == null || name.isEmpty()) { this.namedTag.remove("CustomName"); return; } @@ -138,6 +149,7 @@ public void setItem(int index, Item item) { @Override public void saveNBT() { + super.saveNBT(); this.namedTag.putList(new ListTag("Items")); for (int index = 0; index < this.getSize(); index++) { this.setItem(index, this.inventory.getItem(index)); @@ -158,21 +170,23 @@ public boolean onUpdate() { } this.transferCooldown--; - - if (this.level.isBlockPowered(getBlock())) { - return true; - } if (!this.isOnTransferCooldown()) { - BlockEntity blockEntity = this.level.getBlockEntity(this.up()); + if (this.level.isBlockPowered(this.chunk, this)) { + return true; + } + + // Note: Initial inventory full/empty checks are moved here from pull/push methods - boolean changed = pushItems(); + boolean changed = !this.inventory.slots.isEmpty() && (pushItems() || pushItemsToMinecart()); - if (!changed) { - if (!(blockEntity instanceof BlockEntityContainer)) { - changed = pickupItems(); + if (!changed && !this.inventory.isFull()) { + BlockEntity blockEntity = this.level.getBlockEntity(this.chunk, this.up()); + Block block = null; + if (blockEntity instanceof BlockEntityContainer || (block = this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY() + 1, this.getFloorZ(), false)) instanceof BlockComposter) { + changed = pullItems(blockEntity, block); } else { - changed = pullItems(); + changed = pullItemsFromMinecart() || pickupItems(); } } @@ -186,13 +200,93 @@ public boolean onUpdate() { return true; } - public boolean pullItems() { - if (this.inventory.isFull()) { - return false; + private boolean pullItemsFromMinecart() { + if (this.minecartPickupInventory != null) { + Inventory inv = this.minecartPickupInventory.getInventory(); + + for (int i = 0; i < inv.getSize(); i++) { + Item item = inv.getItem(i); + + if (!item.isNull()) { + Item itemToAdd = item.clone(); + itemToAdd.count = 1; + if (!this.inventory.canAddItem(itemToAdd)) { + continue; + } + + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(inv, this.inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + continue; + } + + Item[] items = this.inventory.addItem(itemToAdd); + if (items.length >= 1) { + continue; + } + + item.count--; + inv.setItem(i, item); + + this.minecartPickupInventory = null; + return true; + } + } } - BlockEntity blockEntity = this.level.getBlockEntity(this.up()); - //Fix for furnace outputs + return false; + } + + private boolean pushItemsToMinecart() { + if (this.minecartPushInventory != null) { + Inventory holderInventory = this.minecartPushInventory.getInventory(); + + if (holderInventory.isFull()) { + return false; + } + + for (int i = 0; i < this.inventory.getSize(); i++) { + Item item = this.inventory.getItem(i); + + if (!item.isNull()) { + Item itemToAdd = item.clone(); + itemToAdd.setCount(1); + + if (!holderInventory.canAddItem(itemToAdd)) { + continue; + } + + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(this.inventory, holderInventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + continue; + } + + Item[] items = holderInventory.addItem(itemToAdd); + if (items.length > 0) { + continue; + } + + item.count--; + this.inventory.setItem(i, item); + + this.minecartPushInventory = null; + return true; + } + } + } + + return false; + } + + public boolean pullItems() { + return this.pullItems( + this.level.getBlockEntity(this.chunk, this.up()), + this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY() + 1, this.getFloorZ(), false)); + } + + private boolean pullItems(BlockEntity blockEntity, Block block) { if (blockEntity instanceof BlockEntityFurnace) { FurnaceInventory inv = ((BlockEntityFurnace) blockEntity).getInventory(); Item item = inv.getResult(); @@ -214,7 +308,7 @@ public boolean pullItems() { Item[] items = this.inventory.addItem(itemToAdd); - if (items.length <= 0) { + if (items.length == 0) { item.count--; inv.setResult(item); return true; @@ -253,15 +347,29 @@ public boolean pullItems() { return true; } } + } else if (block instanceof BlockComposter) { + BlockComposter composter = (BlockComposter) block; + Item item = composter.empty(); + if (item == null || item.isNull()) { + return false; + } + Item itemToAdd = item.clone(); + itemToAdd.setCount(1); + if (!this.inventory.canAddItem(itemToAdd)) { + return false; + } + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(null, this.inventory, this, item, InventoryMoveItemEvent.Action.PICKUP); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + Item[] items = inventory.addItem(itemToAdd); + return items.length < 1; } return false; } public boolean pickupItems() { - if (this.inventory.isFull()) { - return false; - } - boolean pickedUpItem = false; for (Entity entity : this.level.getCollidingEntities(this.pickupArea)) { @@ -303,15 +411,14 @@ public boolean pickupItems() { } } - //TODO: check for minecart return pickedUpItem; } @Override public void close() { if (!closed) { - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getInventory()); + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); } super.close(); } @@ -322,25 +429,21 @@ public void onBreak() { for (Item content : inventory.getContents().values()) { level.dropItem(this, content); } - this.inventory.clearAll(); + inventory.clearAll(); } public boolean pushItems() { - if (this.inventory.isEmpty()) { - return false; - } + int blockData = this.level.getBlockDataAt(this.chunk, (int) x, (int) y, (int) z, Block.LAYER_NORMAL); + BlockEntity be = this.level.getBlockEntity(this.chunk, this.getSide(BlockFace.fromIndex(blockData))); - BlockEntity be = this.level.getBlockEntity(this.getSide(BlockFace.fromIndex(this.level.getBlockDataAt(this.getFloorX(), this.getFloorY(), this.getFloorZ())))); - - if (be instanceof BlockEntityHopper && this.getBlock().getDamage() == 0 || !(be instanceof InventoryHolder)) + if (!(be instanceof InventoryHolder) || (be instanceof BlockEntityHopper && blockData == 0)) { return false; + } InventoryMoveItemEvent event; - //Fix for furnace inputs if (be instanceof BlockEntityFurnace) { - BlockEntityFurnace furnace = (BlockEntityFurnace) be; - FurnaceInventory inventory = furnace.getInventory(); + FurnaceInventory inventory = ((BlockEntityFurnace) be).getInventory(); if (inventory.isFull()) { return false; } @@ -353,8 +456,7 @@ public boolean pushItems() { Item itemToAdd = item.clone(); itemToAdd.setCount(1); - //Check direction of hopper - if (this.getBlock().getDamage() == 0) { + if (blockData == 0) { Item smelting = inventory.getSmelting(); if (smelting.isNull()) { event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); @@ -365,7 +467,7 @@ public boolean pushItems() { item.count--; pushedItem = true; } - } else if (inventory.getSmelting().getId() == itemToAdd.getId() && inventory.getSmelting().getDamage() == itemToAdd.getDamage() && smelting.count < smelting.getMaxStackSize()) { + } else if (smelting.getId() == itemToAdd.getId() && smelting.getDamage() == itemToAdd.getDamage() && smelting.count < smelting.getMaxStackSize()) { event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); this.server.getPluginManager().callEvent(event); @@ -402,11 +504,89 @@ public boolean pushItems() { if (pushedItem) { this.inventory.setItem(i, item); + return true; } } } - return pushedItem; + return false; + } else if (be instanceof BlockEntityBrewingStand) { + BrewingInventory inventory = ((BlockEntityBrewingStand) be).getInventory(); + if (inventory.isFull()) { + return false; + } + + boolean pushedItem = false; + + for (int i = 0; i < this.inventory.getSize(); i++) { + Item item = this.inventory.getItem(i); + if (!item.isNull()) { + Item itemToAdd = item.clone(); + itemToAdd.setCount(1); + + boolean isPotion = itemToAdd.getId() == Item.GLASS_BOTTLE || itemToAdd.getId() == Item.POTION || itemToAdd.getId() == Item.SPLASH_POTION; + + if (blockData == 0) { // Hopper is above the brewing stand + if (isPotion) { + continue; // No potions as ingredient + } + + Item ingredient = inventory.getIngredient(); + if (ingredient.isNull()) { + event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + inventory.setIngredient(itemToAdd); + pushedItem = true; + } + } else if (ingredient.getId() == itemToAdd.getId() && ingredient.getDamage() == itemToAdd.getDamage() && ingredient.count < ingredient.getMaxStackSize()) { + event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + ingredient.count++; + inventory.setIngredient(ingredient); + pushedItem = true; + } + } + } else if (itemToAdd.getId() == Item.BLAZE_POWDER) { // Only blaze powder to fuel slot + Item fuel = inventory.getFuel(); + if (fuel.isNull()) { + event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + inventory.setFuel(itemToAdd); + pushedItem = true; + } + } else if (fuel.getId() == itemToAdd.getId() && fuel.getDamage() == itemToAdd.getDamage() && fuel.count < fuel.getMaxStackSize()) { + event = new InventoryMoveItemEvent(this.inventory, inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + fuel.count++; + inventory.setFuel(fuel); + pushedItem = true; + } + } + } else if (isPotion) { // Only bottles/potions to bottle slots + if (inventory.addItem(itemToAdd).length > 0) { // BrewingInventory overrides addItemSize to check the correct slots + continue; + } else { + pushedItem = true; + } + } + + if (pushedItem) { + item.count--; + this.inventory.setItem(i, item); + return true; + } + } + } + + return false; } else { Inventory inventory = ((InventoryHolder) be).getInventory(); @@ -445,7 +625,6 @@ public boolean pushItems() { } } - //TODO: check for minecart return false; } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java b/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java index 5a6b34700a5..1327d244dbc 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityItemFrame.java @@ -10,6 +10,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.LevelEventPacket; import java.util.concurrent.ThreadLocalRandom; @@ -19,6 +20,8 @@ */ public class BlockEntityItemFrame extends BlockEntitySpawnable { + private Item item_; + public BlockEntityItemFrame(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -26,11 +29,13 @@ public BlockEntityItemFrame(FullChunk chunk, CompoundTag nbt) { @Override protected void initBlockEntity() { if (!namedTag.contains("Item")) { - namedTag.putCompound("Item", NBTIO.putItemHelper(new ItemBlock(Block.get(BlockID.AIR)))); + namedTag.putCompound("Item", NBTIO.putItemHelper(item_ = new ItemBlock(Block.get(BlockID.AIR)))); } + if (!namedTag.contains("ItemRotation")) { namedTag.putByte("ItemRotation", 0); } + if (!namedTag.contains("ItemDropChance")) { namedTag.putFloat("ItemDropChance", 1.0f); } @@ -47,7 +52,7 @@ public String getName() { @Override public boolean isBlockEntityValid() { - return this.getBlock().getId() == Block.ITEM_FRAME_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.ITEM_FRAME_BLOCK; } public int getItemRotation() { @@ -61,8 +66,11 @@ public void setItemRotation(int itemRotation) { } public Item getItem() { - CompoundTag NBTTag = this.namedTag.getCompound("Item"); - return NBTIO.getItemHelper(NBTTag); + if (item_ == null) { + CompoundTag NBTTag = this.namedTag.getCompound("Item"); + item_ = NBTIO.getItemHelper(NBTTag); + } + return item_; } public void setItem(Item item) { @@ -70,6 +78,7 @@ public void setItem(Item item) { } public void setItem(Item item, boolean setChanged) { + item_ = null; this.namedTag.putCompound("Item", NBTIO.putItemHelper(item)); if (setChanged) { this.setDirty(); @@ -84,11 +93,13 @@ public float getItemDropChance() { public void setItemDropChance(float chance) { this.namedTag.putFloat("ItemDropChance", chance); + super.setDirty(); // No need to spawnToAll } + @Override public void setDirty() { - this.spawnToAll(); super.setDirty(); + this.spawnToAll(); } @Override @@ -96,19 +107,50 @@ public CompoundTag getSpawnCompound() { if (!this.namedTag.contains("Item")) { this.setItem(new ItemBlock(Block.get(BlockID.AIR)), false); } - CompoundTag item = namedTag.getCompound("Item").copy(); - item.setName("Item"); + CompoundTag itemOriginal = namedTag.getCompound("Item"); + CompoundTag tag = new CompoundTag() .putString("id", BlockEntity.ITEM_FRAME) .putInt("x", (int) this.x) .putInt("y", (int) this.y) .putInt("z", (int) this.z); - int itemId = item.getShort("id"); + int itemId = itemOriginal.getShort("id"); if (itemId != Item.AIR) { - String identifier = RuntimeItems.getMapping().toRuntime(itemId, item.getShort("Damage")).getIdentifier(); - item.putString("Name", identifier); - item.remove("id"); + CompoundTag item; + if (itemId == Item.MAP) { + item = itemOriginal.copy(); + item.setName("Item"); + + String identifier = RuntimeItems.getMapping().toRuntime(itemId, itemOriginal.getShort("Damage")).getIdentifier(); + item.putString("Name", identifier); + item.remove("id"); + + item.getCompound("tag").remove("Colors"); + } else { + // Instead of copying the item's whole nbt just send the data necessary to display the item + item = new CompoundTag("Item") + .putByte("Count", itemOriginal.getByte("Count")) + .putShort("Damage", itemOriginal.getShort("Damage")); + + String identifier = RuntimeItems.getMapping().toRuntime(itemId, itemOriginal.getShort("Damage")).getIdentifier(); + item.putString("Name", identifier); + + if (itemOriginal.contains("tag")) { + CompoundTag oldTag = itemOriginal.getCompound("tag"); + CompoundTag newTag = new CompoundTag(); + + if (oldTag.contains("ench")) { + newTag.putList(new ListTag<>("ench")); + } + + if (oldTag.contains("display") && oldTag.get("display") instanceof CompoundTag) { + newTag.putCompound("display", new CompoundTag("display").putString("Name", ((CompoundTag) oldTag.get("display")).getString("Name"))); + } + + item.put("tag", newTag); + } + } tag.putCompound("Item", item) .putByte("ItemRotation", this.getItemRotation()); @@ -131,12 +173,11 @@ public boolean dropItem(Player player) { return true; } } - + this.setItem(Item.get(Item.AIR)); + this.setItemRotation(0); if (this.getItemDropChance() > ThreadLocalRandom.current().nextFloat()) { this.level.dropItem(this.add(0.5, 0, 0.5), item); } - this.setItem(Item.get(Item.AIR)); - this.setItemRotation(0); this.level.addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_ITEM_REMOVED); return true; } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityJukebox.java b/src/main/java/cn/nukkit/blockentity/BlockEntityJukebox.java index ce7e18ee4ff..eb3b7938f2c 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityJukebox.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityJukebox.java @@ -34,12 +34,13 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - return this.getLevel().getBlockIdAt(getFloorX(), getFloorY(), getFloorZ()) == Block.JUKEBOX; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.JUKEBOX; } public void setRecordItem(Item recordItem) { Objects.requireNonNull(recordItem, "Record item cannot be null"); this.recordItem = recordItem; + setDirty(); } public Item getRecordItem() { @@ -110,6 +111,7 @@ public void dropItem() { stop(); this.level.dropItem(this.up(), this.recordItem); this.recordItem = Item.get(0); + setDirty(); } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityLectern.java b/src/main/java/cn/nukkit/blockentity/BlockEntityLectern.java new file mode 100644 index 00000000000..1d5517f41c2 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityLectern.java @@ -0,0 +1,133 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.BlockID; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.format.Chunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.IntTag; + +public class BlockEntityLectern extends BlockEntitySpawnable { + + private int totalPages; + + public BlockEntityLectern(Chunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + protected void initBlockEntity() { + if (!(this.namedTag.get("book") instanceof CompoundTag)) { + this.namedTag.remove("book"); + } + + if (!(this.namedTag.get("page") instanceof IntTag)) { + this.namedTag.remove("page"); + } + + updateTotalPages(false); + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.LECTERN) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + + Item book = getBook(); + if (book.getId() != BlockID.AIR) { + c.putCompound("book", NBTIO.putNetworkItemHelper(book)); + c.putBoolean("hasBook", true); + c.putInt("page", getRawPage()); + c.putInt("totalPages", totalPages); + } else { + c.putBoolean("hasBook", false); + } + + return c; + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == BlockID.LECTERN; + } + + @Override + public void onBreak() { + Item book = getBook(); + if (book.getId() != BlockID.AIR) { + level.dropItem(this, book); + } + this.namedTag.remove("book"); + } + + public boolean hasBook() { + return this.namedTag.contains("book") && this.namedTag.get("book") instanceof CompoundTag; + } + + public Item getBook() { + if (!hasBook()) { + return Item.get(BlockID.AIR, 0, 0); + } else { + return NBTIO.getItemHelper(this.namedTag.getCompound("book")); + } + } + + public void setBook(Item item) { + if (item.getId() == ItemID.WRITTEN_BOOK || item.getId() == ItemID.BOOK_AND_QUILL) { + this.namedTag.putCompound("book", NBTIO.putItemHelper(item)); + } else { + this.namedTag.remove("book"); + this.namedTag.remove("page"); + } + + updateTotalPages(true); + setDirty(); + } + + public int getLeftPage() { + return (getRawPage() * 2) + 1; + } + + public int getRightPage() { + return getLeftPage() + 1; + } + + public void setLeftPage(int newLeftPage) { + setRawPage((newLeftPage - 1) /2); + } + + public void setRightPage(int newRightPage) { + setLeftPage(newRightPage - 1); + } + + public void setRawPage(int page) { + this.namedTag.putInt("page", Math.min(page, totalPages)); + setDirty(); + this.getLevel().updateAround(this); + } + + public int getRawPage() { + return this.namedTag.getInt("page"); + } + + public int getTotalPages() { + return totalPages; + } + + private void updateTotalPages(boolean updateRedstone) { + Item book = getBook(); + if (book.getId() == BlockID.AIR || !book.hasCompoundTag()) { + totalPages = 0; + } else { + totalPages = book.getNamedTag().getList("pages", CompoundTag.class).size(); + } + + if (updateRedstone) { + this.getLevel().updateAroundRedstone(this, null); + } + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java b/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java index 564a9649fb4..b823d250a1d 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityMovingBlock.java @@ -42,7 +42,7 @@ public Block getBlock() { @Override public boolean isBlockEntityValid() { - return true; + return true; // TODO } @Override @@ -54,4 +54,4 @@ public CompoundTag getSpawnCompound() { .putInt("pistonPosY", this.piston.y) .putInt("pistonPosZ", this.piston.z); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityMusic.java b/src/main/java/cn/nukkit/blockentity/BlockEntityMusic.java index 101c13a9cc3..d465e6384fc 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityMusic.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityMusic.java @@ -15,6 +15,7 @@ protected void initBlockEntity() { if (!this.namedTag.contains("note")) { this.namedTag.putByte("note", 0); } + if (!this.namedTag.contains("powered")) { this.namedTag.putBoolean("powered", false); } @@ -24,11 +25,12 @@ protected void initBlockEntity() { @Override public boolean isBlockEntityValid() { - return this.getBlock().getId() == Block.NOTEBLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.NOTEBLOCK; } public void changePitch() { this.namedTag.putByte("note", (this.namedTag.getByte("note") + 1) % 25); + setDirty(); } public int getPitch() { @@ -37,6 +39,7 @@ public int getPitch() { public void setPowered(boolean powered) { this.namedTag.putBoolean("powered", powered); + setDirty(); } public boolean isPowered() { diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityNameable.java b/src/main/java/cn/nukkit/blockentity/BlockEntityNameable.java index e7bea12b7cf..914c93ef096 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityNameable.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityNameable.java @@ -6,7 +6,6 @@ * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface BlockEntityNameable { @@ -15,7 +14,6 @@ public interface BlockEntityNameable { * Gets the name of this object. * * @return 这个事物的名字。
The name of this object. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ String getName(); @@ -24,7 +22,6 @@ public interface BlockEntityNameable { * Changes the name of this object, or names it. * * @param name 这个事物的新名字。
The new name of this object. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void setName(String name); @@ -33,7 +30,6 @@ public interface BlockEntityNameable { * Whether this object has a name. * * @return 如果有名字,返回 {@code true}。
{@code true} for this object has a name. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean hasName(); } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java b/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java index c722c423bbc..b7f9aba31b4 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityPistonArm.java @@ -1,10 +1,8 @@ package cn.nukkit.blockentity; -import cn.nukkit.entity.Entity; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.BlockFace; -import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.IntTag; @@ -13,18 +11,17 @@ /** * @author CreeperFace */ -public class BlockEntityPistonArm extends BlockEntity { +public class BlockEntityPistonArm extends BlockEntitySpawnable { - public float progress = 1.0F; - public float lastProgress = 1.0F; + public float progress; + public float lastProgress; public BlockFace facing; public boolean extending = false; public boolean sticky = false; - public byte state = 1; + public byte state; public byte newState = 1; public Vector3 attachedBlock = null; public boolean isMovable = true; - public boolean powered = false; public BlockEntityPistonArm(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -32,6 +29,8 @@ public BlockEntityPistonArm(FullChunk chunk, CompoundTag nbt) { @Override protected void initBlockEntity() { + this.isMovable = true; + if (namedTag.contains("Progress")) { this.progress = namedTag.getFloat("Progress"); } @@ -48,8 +47,8 @@ protected void initBlockEntity() { this.extending = namedTag.getBoolean("Extending"); } - if (namedTag.contains("powered")) { - this.powered = namedTag.getBoolean("powered"); + if (namedTag.contains("State")) { + this.state = (byte) namedTag.getByte("State"); } if (namedTag.contains("AttachedBlocks")) { @@ -64,25 +63,25 @@ protected void initBlockEntity() { super.initBlockEntity(); } - private void pushEntities() { - float lastProgress = this.getExtendedProgress(this.lastProgress); - double x = lastProgress * (float) this.facing.getXOffset(); - double y = lastProgress * (float) this.facing.getYOffset(); - double z = lastProgress * (float) this.facing.getZOffset(); - AxisAlignedBB bb = new SimpleAxisAlignedBB(x, y, z, x + 1.0D, y + 1.0D, z + 1.0D); - Entity[] entities = this.level.getCollidingEntities(bb); - if (entities.length != 0) { - - } + public void setExtended(boolean extending) { + this.extending = extending; + this.newState = this.state; + this.lastProgress = this.progress; + this.state = (byte) (extending ? 1 : 0); + this.progress = extending ? 1.0f : 0; + } + public boolean isExtended() { + return this.extending; } - private float getExtendedProgress(float progress) { - return this.extending ? progress - 1.0F : 1.0F - progress; + public void broadcastMove() { + this.level.addChunkPacket(this.getChunkX(), this.getChunkZ(), this.createSpawnPacket()); } public boolean isBlockEntityValid() { - return true; + int blockId = getBlock().getId(); + return blockId == Block.PISTON || blockId == Block.STICKY_PISTON; } public void saveNBT() { @@ -92,10 +91,19 @@ public void saveNBT() { this.namedTag.putByte("NewState", this.newState); this.namedTag.putFloat("Progress", this.progress); this.namedTag.putFloat("LastProgress", this.lastProgress); - this.namedTag.putBoolean("powered", this.powered); + this.namedTag.putBoolean("Sticky", this.sticky); } public CompoundTag getSpawnCompound() { - return (new CompoundTag()).putString("id", "PistonArm").putInt("x", (int) this.x).putInt("y", (int) this.y).putInt("z", (int) this.z); + return new CompoundTag() + .putString("id", BlockEntity.PISTON_ARM) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putFloat("Progress", this.progress) + .putFloat("LastProgress", this.lastProgress) + .putBoolean("Sticky", this.sticky) + .putByte("State", this.state) + .putByte("NewState", this.newState); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java index a9362fb6b43..4bcde9efb8a 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java @@ -13,11 +13,8 @@ import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; -import java.util.HashSet; +import java.util.ArrayList; -/** - * Created by PetteriM1 - */ public class BlockEntityShulkerBox extends BlockEntitySpawnable implements InventoryHolder, BlockEntityContainer, BlockEntityNameable { protected ShulkerBoxInventory inventory; @@ -28,46 +25,55 @@ public BlockEntityShulkerBox(FullChunk chunk, CompoundTag nbt) { @Override protected void initBlockEntity() { - this.inventory = new ShulkerBoxInventory(this); + if (!this.namedTag.contains("facing")) { + this.namedTag.putByte("facing", 0); + } + + super.initBlockEntity(); + } + private void initInventory() { if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { this.namedTag.putList(new ListTag("Items")); } - ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new ShulkerBoxInventory(this); + for (CompoundTag compound : list.getAll()) { Item item = NBTIO.getItemHelper(compound); - this.inventory.slots.put(compound.getByte("Slot"), item); - } - - if (!this.namedTag.contains("facing")) { - this.namedTag.putByte("facing", 0); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } } - - super.initBlockEntity(); } @Override public void close() { - if (!closed) { - for (Player player : new HashSet<>(this.getInventory().getViewers())) { - player.removeWindow(this.getInventory()); + if (!this.closed && this.inventory != null) { + for (Player player : new ArrayList<>(this.inventory.getViewers())) { + player.removeWindow(this.inventory); } - super.close(); } + + super.close(); } @Override public void saveNBT() { - this.namedTag.putList(new ListTag("Items")); - for (int index = 0; index < this.getSize(); index++) { - this.setItem(index, this.inventory.getItem(index)); + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int index = 0; index < this.getSize(); index++) { + this.setItem(index, this.inventory.getItem(index)); + } } } @Override public boolean isBlockEntityValid() { - int blockID = this.getBlock().getId(); + int blockID = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); return blockID == Block.SHULKER_BOX || blockID == Block.UNDYED_SHULKER_BOX; } @@ -117,11 +123,17 @@ public void setItem(int index, Item item) { @Override public BaseInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } return this.inventory; } public ShulkerBoxInventory getRealInventory() { - return inventory; + if (this.inventory == null) { + this.initInventory(); + } + return this.inventory; } @Override diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySign.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySign.java index ca41f212770..1c50aad232f 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntitySign.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySign.java @@ -1,7 +1,7 @@ package cn.nukkit.blockentity; import cn.nukkit.Player; -import cn.nukkit.block.Block; +import cn.nukkit.block.BlockSignPost; import cn.nukkit.event.block.SignChangeEvent; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.ByteTag; @@ -15,7 +15,7 @@ import java.util.Objects; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockEntitySign extends BlockEntitySpawnable { @@ -31,26 +31,28 @@ protected void initBlockEntity() { text = new String[4]; if (!namedTag.contains("Text")) { - - for (int i = 1; i <= 4; i++) { - String key = "Text" + i; - - if (namedTag.contains(key)) { - String line = namedTag.getString(key); - - this.text[i - 1] = line; - - this.namedTag.remove(key); + if (namedTag.contains("FrontText")) { // Bedrock vanilla + this.setText(((CompoundTag) namedTag.removeAndGet("FrontText")).getString("Text").split("\n", 4)); + } else { + for (int i = 1; i <= 4; i++) { + String key = "Text" + i; + + if (namedTag.contains(key)) { + String line = namedTag.getString(key); + this.text[i - 1] = line; + this.namedTag.remove(key); + } } } } else { String[] lines = namedTag.getString("Text").split("\n", 4); for (int i = 0; i < text.length; i++) { - if (i < lines.length) + if (i < lines.length) { text[i] = lines[i]; - else + } else { text[i] = ""; + } } } @@ -59,10 +61,10 @@ protected void initBlockEntity() { sanitizeText(text); } - if (!this.namedTag.contains("SignTextColor") || !(this.namedTag.get("SignTextColor") instanceof IntTag)) { + if (!(this.namedTag.get("SignTextColor") instanceof IntTag)) { this.setColor(DyeColor.BLACK.getSignColor()); } - if (!this.namedTag.contains("IgnoreLighting") || !(this.namedTag.get("IgnoreLighting") instanceof ByteTag)) { + if (!(this.namedTag.get("IgnoreLighting") instanceof ByteTag)) { this.setGlowing(false); } @@ -77,8 +79,7 @@ public void saveNBT() { @Override public boolean isBlockEntityValid() { - int blockID = getBlock().getId(); - return blockID == Block.SIGN_POST || blockID == Block.WALL_SIGN; + return getLevelBlock() instanceof BlockSignPost; } public boolean setText(String... lines) { @@ -90,12 +91,9 @@ public boolean setText(String... lines) { } this.namedTag.putString("Text", String.join("\n", text)); - this.spawnToAll(); - - if (this.chunk != null) { - setDirty(); - } + setDirty(); + this.spawnToAll(); return true; } @@ -109,6 +107,7 @@ public BlockColor getColor() { public void setColor(BlockColor color) { this.namedTag.putInt("SignTextColor", color.getARGB()); + setDirty(); } public boolean isGlowing() { @@ -117,6 +116,7 @@ public boolean isGlowing() { public void setGlowing(boolean glowing) { this.namedTag.putBoolean("IgnoreLighting", glowing); + setDirty(); } @Override @@ -126,7 +126,8 @@ public boolean updateCompoundTag(CompoundTag nbt, Player player) { } String[] lines = new String[4]; Arrays.fill(lines, ""); - String[] splitLines = nbt.getCompound("FrontText").getString("Text").split("\n", 4); + String receivedText = nbt.getCompound("FrontText").getString("Text"); + String[] splitLines = receivedText.split("\n", 4); System.arraycopy(splitLines, 0, lines, 0, splitLines.length); sanitizeText(lines); @@ -168,9 +169,9 @@ public CompoundTag getSpawnCompound() { private static void sanitizeText(String[] lines) { for (int i = 0; i < lines.length; i++) { - // Don't allow excessive text per line. + // Don't allow excessive text per line if (lines[i] != null) { - lines[i] = lines[i].substring(0, Math.min(255, lines[i].length())); + lines[i] = lines[i].substring(0, Math.min(200, lines[i].length())); } } } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySkull.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySkull.java index 2d141e79dfd..3a70362f3d6 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntitySkull.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySkull.java @@ -33,7 +33,7 @@ public void saveNBT() { @Override public boolean isBlockEntityValid() { - return getBlock().getId() == Block.SKULL_BLOCK; + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.SKULL_BLOCK; } @Override @@ -46,5 +46,4 @@ public CompoundTag getSpawnCompound() { .putInt("z", (int) this.z) .put("Rot", this.namedTag.get("Rot")); } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySmoker.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySmoker.java new file mode 100644 index 00000000000..8faa4ac7b67 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySmoker.java @@ -0,0 +1,144 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.event.inventory.FurnaceSmeltEvent; +import cn.nukkit.inventory.FurnaceRecipe; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import java.util.concurrent.ThreadLocalRandom; + +public class BlockEntitySmoker extends BlockEntityFurnace { + + public BlockEntitySmoker(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public String getName() { + return this.hasName() ? this.namedTag.getString("CustomName") : "Smoker"; + } + + @Override + public boolean isBlockEntityValid() { + int blockID = level.getBlockIdAt(chunk, (int) x, (int) y, (int) z); + return blockID == Block.SMOKER || blockID == Block.LIT_SMOKER; + } + + private static final IntSet CAN_SMELT = new IntOpenHashSet(new int[]{ + Item.RAW_PORKCHOP, Item.RAW_BEEF, Item.RAW_RABBIT, Item.RAW_FISH, Item.RAW_CHICKEN, Item.RAW_MUTTON, Item.RAW_SALMON, Item.POTATO + }); + + @Override + public boolean onUpdate() { + if (this.closed) { + return false; + } + + Item raw = this.inventory.getSmelting(); + // TODO: smoker recipes + if (!CAN_SMELT.contains(raw.getId())) { + if (burnTime > 0) { + burnTime--; + burnDuration = (int) Math.ceil((float) burnTime / maxTime * 100); + + if (burnTime == 0) { + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == BlockID.LIT_SMOKER) { + this.level.setBlock(this, Block.get(BlockID.SMOKER, block.getDamage()), true); + } + return false; + } + } + + cookTime = 0; + sendPacket(); + return true; + } + + boolean ret = false; + Item product = this.inventory.getResult(); + FurnaceRecipe smelt = this.server.getCraftingManager().matchFurnaceRecipe(raw); + boolean canSmelt = (smelt != null && raw.getCount() > 0 && ((smelt.getResult().equals(product) && product.getCount() < product.getMaxStackSize()) || product.getId() == Item.AIR)); + + Item fuel; + if (burnTime <= 0 && canSmelt && (fuel = this.inventory.getItemFast(1)).getFuelTime() != null && fuel.getCount() > 0) { + this.checkFuel(fuel.clone()); + } + + if (burnTime > 0) { + burnTime--; + burnDuration = (int) Math.ceil((float) burnTime / maxTime * 100); + + if (this.crackledTime-- <= 0) { + this.crackledTime = ThreadLocalRandom.current().nextInt(30, 110); + this.getLevel().addLevelSoundEvent(this.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_BLOCK_FURNACE_LIT); + } + + if (smelt != null && canSmelt) { + cookTime++; + if (cookTime >= 100) { + product = Item.get(smelt.getResult().getId(), smelt.getResult().getDamage(), product.isNull() ? 1 : product.getCount() + 1); + + FurnaceSmeltEvent ev = new FurnaceSmeltEvent(this, raw, product); + this.server.getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + this.inventory.setResult(ev.getResult()); + this.experience += FURNACE_XP.getOrDefault(ev.getResult().getId(), 0d); + raw.setCount(raw.getCount() - 1); + if (raw.getCount() == 0) { + raw = new ItemBlock(Block.get(BlockID.AIR), 0, 0); + } + this.inventory.setSmelting(raw); + } + + cookTime -= 100; + } + } else if (burnTime <= 0) { + burnTime = 0; + cookTime = 0; + burnDuration = 0; + } else { + cookTime = 0; + } + ret = true; + } else { + Block block = this.level.getBlock(this.chunk, (int) x, (int) y, (int) z, true); + if (block.getId() == BlockID.LIT_SMOKER) { + this.level.setBlock(this, Block.get(BlockID.SMOKER, block.getDamage()), true); + } + burnTime = 0; + cookTime = 0; + burnDuration = 0; + crackledTime = 0; + } + + sendPacket(); + + return ret; + } + + @Override + public CompoundTag getSpawnCompound() { + CompoundTag c = new CompoundTag() + .putString("id", BlockEntity.SMOKER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z) + .putShort("BurnDuration", burnDuration) + .putShort("BurnTime", burnTime) + .putShort("CookTime", cookTime); + + if (this.hasName()) { + c.put("CustomName", this.namedTag.get("CustomName")); + } + + return c; + } +} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java index e0d8651346d..309311986ac 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawnable.java @@ -10,7 +10,7 @@ import java.nio.ByteOrder; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BlockEntitySpawnable extends BlockEntity { @@ -28,22 +28,27 @@ protected void initBlockEntity() { public abstract CompoundTag getSpawnCompound(); - public void spawnTo(Player player) { - if (this.closed) { - return; - } - + public BlockEntityDataPacket createSpawnPacket() { CompoundTag tag = this.getSpawnCompound(); + BlockEntityDataPacket pk = new BlockEntityDataPacket(); pk.x = (int) this.x; pk.y = (int) this.y; pk.z = (int) this.z; + try { pk.namedTag = NBTIO.write(tag, ByteOrder.LITTLE_ENDIAN, true); } catch (IOException e) { throw new RuntimeException(e); } - player.dataPacket(pk); + + return pk; + } + + public void spawnTo(Player player) { + if (!this.closed) { + player.dataPacket(this.createSpawnPacket()); + } } public void spawnToAll() { diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntitySpawner.java b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawner.java new file mode 100644 index 00000000000..32433b4877c --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/BlockEntitySpawner.java @@ -0,0 +1,26 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.Block; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.*; + +public class BlockEntitySpawner extends BlockEntitySpawnable { + + public BlockEntitySpawner(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public CompoundTag getSpawnCompound() { + return new CompoundTag() + .putString("id", BlockEntity.MOB_SPAWNER) + .putInt("x", (int) this.x) + .putInt("y", (int) this.y) + .putInt("z", (int) this.z); + } + + @Override + public boolean isBlockEntityValid() { + return level.getBlockIdAt(chunk, (int) x, (int) y, (int) z) == Block.MONSTER_SPAWNER; + } +} diff --git a/src/main/java/cn/nukkit/blockentity/PersistentDataContainerBlockEntity.java b/src/main/java/cn/nukkit/blockentity/PersistentDataContainerBlockEntity.java new file mode 100644 index 00000000000..be41d5241b2 --- /dev/null +++ b/src/main/java/cn/nukkit/blockentity/PersistentDataContainerBlockEntity.java @@ -0,0 +1,70 @@ +package cn.nukkit.blockentity; + +import cn.nukkit.block.BlockID; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import lombok.ToString; + +@ToString(callSuper = true) +public class PersistentDataContainerBlockEntity extends BlockEntity implements PersistentDataContainer { + + public PersistentDataContainerBlockEntity(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public boolean onUpdate() { + if (!this.isBlockEntityValid()) { + this.close(); + } + return false; + } + + @Override + public boolean isBlockEntityValid() { + return this.level.getBlockIdAt(this.chunk, (int) this.x, (int) this.y, (int) this.z) != BlockID.AIR; + } + + @Override + public boolean isValid() { + return super.isValid() && this.isBlockEntityValid(); + } + + @Override + public CompoundTag getStorage() { + if (this.namedTag.contains(STORAGE_TAG)) { + return this.namedTag.getCompound(STORAGE_TAG); + } + + CompoundTag storage = new CompoundTag(); + this.setStorage(storage); + return storage; + } + + @Override + public void setStorage(CompoundTag storage) { + this.namedTag.put(STORAGE_TAG, storage); + setDirty(); + } + + @Override + public PersistentDataContainer getPersistentDataContainer() { + return this; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PersistentDataContainerBlockEntity && super.equals(obj); + } + + @Override + public boolean canSaveToStorage() { + return !this.isEmpty(); + } + + @Override + public void write() { + setDirty(); + } +} diff --git a/src/main/java/cn/nukkit/command/Command.java b/src/main/java/cn/nukkit/command/Command.java index fbbd4954eb7..c1b81b9b192 100644 --- a/src/main/java/cn/nukkit/command/Command.java +++ b/src/main/java/cn/nukkit/command/Command.java @@ -7,19 +7,15 @@ import cn.nukkit.lang.TranslationContainer; import cn.nukkit.permission.Permissible; import cn.nukkit.utils.TextFormat; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Command { - private static CommandData defaultDataTemplate = null; - protected CommandData commandData; private final String name; @@ -28,15 +24,15 @@ public abstract class Command { private String label; - private String[] aliases = new String[0]; + private String[] aliases; - private String[] activeAliases = new String[0]; + private String[] activeAliases; private CommandMap commandMap = null; - protected String description = ""; + protected String description; - protected String usageMessage = ""; + protected String usageMessage; private String permission = null; @@ -44,8 +40,6 @@ public abstract class Command { protected Map commandParameters = new HashMap<>(); - public Timing timing; - public Command(String name) { this(name, "", null, new String[0]); } @@ -60,15 +54,14 @@ public Command(String name, String description, String usageMessage) { public Command(String name, String description, String usageMessage, String[] aliases) { this.commandData = new CommandData(); - this.name = name.toLowerCase(); // Uppercase letters crash the client?!? + this.name = name.toLowerCase(); // Prevent client crash this.nextLabel = name; this.label = name; this.description = description; - this.usageMessage = usageMessage == null ? "/" + name : usageMessage; + this.usageMessage = usageMessage == null ? '/' + name : usageMessage; this.aliases = aliases; this.activeAliases = aliases; - this.timing = Timings.getCommandTiming(this); - this.commandParameters.put("default", new CommandParameter[]{CommandParameter.newType("args", true, CommandParamType.RAWTEXT)}); + this.commandParameters.put("default", new CommandParameter[]{new CommandParameter("args", CommandParamType.RAWTEXT, true)}); } /** @@ -104,7 +97,7 @@ public void addCommandParameters(String key, CommandParameter[] parameters) { * @return CommandData|null */ public CommandDataVersions generateCustomCommandData(Player player) { - if (!this.testPermission(player)) { + if (!this.testPermissionSilent(player)) { return null; } @@ -125,7 +118,7 @@ public CommandDataVersions generateCustomCommandData(Player player) { overload.input.parameters = par; customData.overloads.put(key, overload); }); - if (customData.overloads.size() == 0) customData.overloads.put("default", new CommandOverload()); + if (customData.overloads.isEmpty()) customData.overloads.put("default", new CommandOverload()); CommandDataVersions versions = new CommandDataVersions(); versions.versions.add(customData); return versions; @@ -156,7 +149,7 @@ public boolean testPermission(CommandSender target) { if (this.permissionMessage == null) { target.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.unknown", this.name)); - } else if (!this.permissionMessage.equals("")) { + } else if (!this.permissionMessage.isEmpty()) { target.sendMessage(this.permissionMessage.replace("", this.permission)); } @@ -164,7 +157,7 @@ public boolean testPermission(CommandSender target) { } public boolean testPermissionSilent(CommandSender target) { - if (this.permission == null || this.permission.equals("")) { + if (this.permission == null || this.permission.isEmpty()) { return true; } @@ -186,7 +179,6 @@ public boolean setLabel(String name) { this.nextLabel = name; if (!this.isRegistered()) { this.label = name; - this.timing = Timings.getCommandTiming(this); return true; } return false; @@ -254,10 +246,7 @@ public void setUsage(String usageMessage) { } public static CommandData generateDefaultData() { - if (defaultDataTemplate == null) { - //defaultDataTemplate = new Gson().fromJson(new InputStreamReader(Server.class.getClassLoader().getResourceAsStream("command_default.json"))); - } - return defaultDataTemplate.clone(); + return null; //defaultDataTemplate.clone(); } public static void broadcastCommandMessage(CommandSender source, String message) { @@ -292,7 +281,7 @@ public static void broadcastCommandMessage(CommandSender source, TextContainer m public static void broadcastCommandMessage(CommandSender source, TextContainer message, boolean sendToSource) { TextContainer m = message.clone(); - String resultStr = "[" + source.getName() + ": " + (!m.getText().equals(source.getServer().getLanguage().get(m.getText())) ? "%" : "") + m.getText() + "]"; + String resultStr = '[' + source.getName() + ": " + (!m.getText().equals(source.getServer().getLanguage().get(m.getText())) ? "%" : "") + m.getText() + ']'; Set users = source.getServer().getPluginManager().getPermissionSubscriptions(Server.BROADCAST_CHANNEL_ADMINISTRATIVE); @@ -322,5 +311,4 @@ public static void broadcastCommandMessage(CommandSender source, TextContainer m public String toString() { return this.name; } - } diff --git a/src/main/java/cn/nukkit/command/CommandExecutor.java b/src/main/java/cn/nukkit/command/CommandExecutor.java index 3ad4780b7ba..fd2066f4613 100644 --- a/src/main/java/cn/nukkit/command/CommandExecutor.java +++ b/src/main/java/cn/nukkit/command/CommandExecutor.java @@ -8,31 +8,30 @@ * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.plugin.PluginBase * @see cn.nukkit.command.CommandExecutor#onCommand - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface CommandExecutor { /** * 在命令执行时会调用的方法。
* Called when a command is executed. - * - *

一个命令可以是{@code /a_LABEL an_arg1 AN_ARG2...}的形式,这时{@code label}变量的值为{@code "a_label"}, + * + * 一个命令可以是{@code /a_LABEL an_arg1 AN_ARG2...}的形式,这时{@code label}变量的值为{@code "a_label"}, * {@code args}数组的元素有{@code "an_arg1","AN_ARG2",...}。注意到{@code label}变量会被转化成小写, * 而{@code args}数组内字符串元素的大小写不变。
* A command can be such a form like {@code /a_LABEL an_arg1 AN_ARG2...}. At this time, the value of * variable {@code label} is {@code "a_label"}, and the values of elements of array {@code args} are * {@code "an_arg1","AN_ARG2",...}. Notice that the value of variable {@code label} will be converted to - * lower case, but the cases of elements of array {@code args} won't change.

- * - *

关于返回值,如果返回{@code false},Nukkit会给sender发送这个命令的使用方法等信息,来表示这个命令没有使用成功。 + * lower case, but the cases of elements of array {@code args} won't change. + * + * 关于返回值,如果返回{@code false},Nukkit会给sender发送这个命令的使用方法等信息,来表示这个命令没有使用成功。 * 如果你的命令成功的发挥了作用,你应该返回{@code true}来表示这个命令已执行成功。
* If this function returns {@code false}, Nukkit will send command usages to command sender, to explain that * the command didn't work normally. If your command works properly, a {@code true} should be returned to explain - * that the command works.

- * - *

如果你想测试一个命令发送者是否有权限执行这个命令, + * that the command works. + * + * 如果你想测试一个命令发送者是否有权限执行这个命令, * 可以使用{@link cn.nukkit.command.Command#testPermissionSilent}。
* If you want to test whether a command sender has the permission to execute a command, - * you can use {@link cn.nukkit.command.Command#testPermissionSilent}.

+ * you can use {@link cn.nukkit.command.Command#testPermissionSilent}. * * @param sender 这个命令的发送者,可以是玩家或控制台等。
* The sender of this command, this can be a player or a console. @@ -43,7 +42,6 @@ public interface CommandExecutor { * @param args 这个命令的参数列表。
* Arguments of this command. * @return 这个命令执行是否执行成功。
whether this command is executed successfully. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean onCommand(CommandSender sender, Command command, String label, String[] args); } diff --git a/src/main/java/cn/nukkit/command/CommandMap.java b/src/main/java/cn/nukkit/command/CommandMap.java index 0bc577e6746..7d1be68a5f9 100644 --- a/src/main/java/cn/nukkit/command/CommandMap.java +++ b/src/main/java/cn/nukkit/command/CommandMap.java @@ -3,7 +3,7 @@ import java.util.List; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface CommandMap { @@ -21,5 +21,4 @@ public interface CommandMap { void clearCommands(); Command getCommand(String name); - } diff --git a/src/main/java/cn/nukkit/command/CommandSender.java b/src/main/java/cn/nukkit/command/CommandSender.java index 00d6e119138..6bc9ecb2c2d 100644 --- a/src/main/java/cn/nukkit/command/CommandSender.java +++ b/src/main/java/cn/nukkit/command/CommandSender.java @@ -7,14 +7,13 @@ /** * 能发送命令的人。
* Who sends commands. - * - *

可以是一个玩家或者一个控制台。
- * That can be a player or a console.

+ * + * 可以是一个玩家或者一个控制台。
+ * That can be a player or a console. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.command.CommandExecutor#onCommand - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface CommandSender extends Permissible { @@ -24,7 +23,6 @@ public interface CommandSender extends Permissible { * * @param message 要发送的信息。
Message to send. * @see cn.nukkit.utils.TextFormat - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void sendMessage(String message); @@ -33,7 +31,6 @@ public interface CommandSender extends Permissible { * Sends a message to the command sender. * * @param message 要发送的信息。
Message to send. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void sendMessage(TextContainer message); @@ -42,30 +39,27 @@ public interface CommandSender extends Permissible { * Returns the server of the command sender. * * @return 命令发送者所在的服务器。
the server of the command sender. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Server getServer(); /** * 返回命令发送者的名称。
* Returns the name of the command sender. - * - *

如果命令发送者是一个玩家,将会返回他的玩家名字(name)不是显示名字(display name)。 + * + * 如果命令发送者是一个玩家,将会返回他的玩家名字(name)不是显示名字(display name)。 * 如果命令发送者是控制台,将会返回{@code "CONSOLE"}。
* If this command sender is a player, will return his/her player name(not display name). - * If it is a console, will return {@code "CONSOLE"}.

- *

当你需要判断命令的执行者是不是控制台时,可以用这个:
+ * If it is a console, will return {@code "CONSOLE"}. + * 当你需要判断命令的执行者是不是控制台时,可以用这个:
* When you need to determine if the sender is a console, use this:
- * {@code if(sender instanceof ConsoleCommandSender) .....;}

+ * {@code if (sender instanceof ConsoleCommandSender) .....;} * * @return 命令发送者的名称。
the name of the command sender. * @see cn.nukkit.Player#getName() * @see cn.nukkit.command.ConsoleCommandSender#getName() * @see cn.nukkit.plugin.PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ String getName(); boolean isPlayer(); - } diff --git a/src/main/java/cn/nukkit/command/ConsoleCommandSender.java b/src/main/java/cn/nukkit/command/ConsoleCommandSender.java index 95e363de72d..a2c001ed2f8 100644 --- a/src/main/java/cn/nukkit/command/ConsoleCommandSender.java +++ b/src/main/java/cn/nukkit/command/ConsoleCommandSender.java @@ -12,7 +12,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ConsoleCommandSender implements CommandSender { @@ -107,6 +107,5 @@ public boolean isOp() { @Override public void setOp(boolean value) { - } } diff --git a/src/main/java/cn/nukkit/command/FormattedCommandAlias.java b/src/main/java/cn/nukkit/command/FormattedCommandAlias.java index 8e84261e176..25126ccb55c 100644 --- a/src/main/java/cn/nukkit/command/FormattedCommandAlias.java +++ b/src/main/java/cn/nukkit/command/FormattedCommandAlias.java @@ -9,7 +9,7 @@ import java.util.List; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FormattedCommandAlias extends Command { @@ -55,13 +55,13 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } private String buildCommand(String formatString, String[] args) { - int index = formatString.indexOf("$"); + int index = formatString.indexOf('$'); while (index != -1) { int start = index; if (index > 0 && formatString.charAt(start - 1) == '\\') { formatString = formatString.substring(0, start - 1) + formatString.substring(start); - index = formatString.indexOf("$", index); + index = formatString.indexOf('$', index); continue; } @@ -120,12 +120,12 @@ private String buildCommand(String formatString, String[] args) { replacement.append(args[position]); } - formatString = formatString.substring(0, start) + replacement.toString() + formatString.substring(end); + formatString = formatString.substring(0, start) + replacement + formatString.substring(end); // Move index past the replaced data so we don't process it again index = start + replacement.length(); // Move to the next replacement token - index = formatString.indexOf("$", index); + index = formatString.indexOf('$', index); } return formatString; @@ -134,5 +134,4 @@ private String buildCommand(String formatString, String[] args) { private static boolean inRange(int i, int j, int k) { return i >= j && i <= k; } - } diff --git a/src/main/java/cn/nukkit/command/PluginCommand.java b/src/main/java/cn/nukkit/command/PluginCommand.java index 597ecedfb43..07d07da0a12 100644 --- a/src/main/java/cn/nukkit/command/PluginCommand.java +++ b/src/main/java/cn/nukkit/command/PluginCommand.java @@ -4,7 +4,7 @@ import cn.nukkit.plugin.Plugin; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PluginCommand extends Command implements PluginIdentifiableCommand { @@ -32,7 +32,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) boolean success = this.executor.onCommand(sender, this, commandLabel, args); - if (!success && !this.usageMessage.equals("")) { + if (!success && !this.usageMessage.isEmpty()) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); } diff --git a/src/main/java/cn/nukkit/command/PluginIdentifiableCommand.java b/src/main/java/cn/nukkit/command/PluginIdentifiableCommand.java index c8aaf4c873e..a22ab9446c0 100644 --- a/src/main/java/cn/nukkit/command/PluginIdentifiableCommand.java +++ b/src/main/java/cn/nukkit/command/PluginIdentifiableCommand.java @@ -3,7 +3,7 @@ import cn.nukkit.plugin.Plugin; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface PluginIdentifiableCommand { diff --git a/src/main/java/cn/nukkit/command/RemoteConsoleCommandSender.java b/src/main/java/cn/nukkit/command/RemoteConsoleCommandSender.java index f0d08618309..b2864caea13 100644 --- a/src/main/java/cn/nukkit/command/RemoteConsoleCommandSender.java +++ b/src/main/java/cn/nukkit/command/RemoteConsoleCommandSender.java @@ -8,12 +8,13 @@ * @author Tee7even */ public class RemoteConsoleCommandSender extends ConsoleCommandSender { + private final StringBuilder messages = new StringBuilder(); @Override public void sendMessage(String message) { message = this.getServer().getLanguage().translateString(message); - this.messages.append(message.trim()).append("\n"); + this.messages.append(message.trim()).append('\n'); } @Override @@ -25,6 +26,10 @@ public String getMessages() { return messages.toString(); } + public void clearMessages() { + messages.delete(0, messages.length()); + } + @Override public String getName() { return "Rcon"; diff --git a/src/main/java/cn/nukkit/command/SimpleCommandMap.java b/src/main/java/cn/nukkit/command/SimpleCommandMap.java index 0d5e0da2bfa..a821b53b315 100644 --- a/src/main/java/cn/nukkit/command/SimpleCommandMap.java +++ b/src/main/java/cn/nukkit/command/SimpleCommandMap.java @@ -5,7 +5,6 @@ import cn.nukkit.command.defaults.*; import cn.nukkit.command.simple.*; import cn.nukkit.lang.TranslationContainer; -import cn.nukkit.utils.MainLogger; import cn.nukkit.utils.TextFormat; import cn.nukkit.utils.Utils; @@ -15,10 +14,11 @@ import java.util.stream.Collectors; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class SimpleCommandMap implements CommandMap { + protected final Map knownCommands = new HashMap<>(); private final Server server; @@ -31,51 +31,48 @@ public SimpleCommandMap(Server server) { private void setDefaultCommands() { this.register("nukkit", new VersionCommand("version")); this.register("nukkit", new PluginsCommand("plugins")); - this.register("nukkit", new SeedCommand("seed")); this.register("nukkit", new HelpCommand("help")); this.register("nukkit", new StopCommand("stop")); this.register("nukkit", new TellCommand("tell")); - this.register("nukkit", new DefaultGamemodeCommand("defaultgamemode")); this.register("nukkit", new BanCommand("ban")); this.register("nukkit", new BanIpCommand("ban-ip")); this.register("nukkit", new BanListCommand("banlist")); this.register("nukkit", new PardonCommand("pardon")); this.register("nukkit", new PardonIpCommand("pardon-ip")); - this.register("nukkit", new SayCommand("say")); - this.register("nukkit", new MeCommand("me")); this.register("nukkit", new ListCommand("list")); - this.register("nukkit", new DifficultyCommand("difficulty")); this.register("nukkit", new KickCommand("kick")); this.register("nukkit", new OpCommand("op")); this.register("nukkit", new DeopCommand("deop")); - this.register("nukkit", new WhitelistCommand("whitelist")); - this.register("nukkit", new SaveOnCommand("save-on")); - this.register("nukkit", new SaveOffCommand("save-off")); this.register("nukkit", new SaveCommand("save-all")); this.register("nukkit", new GiveCommand("give")); this.register("nukkit", new ClearCommand("clear")); this.register("nukkit", new EffectCommand("effect")); this.register("nukkit", new EnchantCommand("enchant")); - this.register("nukkit", new ParticleCommand("particle")); this.register("nukkit", new GamemodeCommand("gamemode")); - this.register("nukkit", new GameruleCommand("gamerule")); this.register("nukkit", new KillCommand("kill")); - this.register("nukkit", new SpawnpointCommand("spawnpoint")); this.register("nukkit", new SetWorldSpawnCommand("setworldspawn")); this.register("nukkit", new TeleportCommand("tp")); this.register("nukkit", new TimeCommand("time")); - this.register("nukkit", new TitleCommand("title")); this.register("nukkit", new ReloadCommand("reload")); this.register("nukkit", new WeatherCommand("weather")); this.register("nukkit", new XpCommand("xp")); - -// if ((boolean) this.server.getConfig("debug.commands", false)) { this.register("nukkit", new StatusCommand("status")); + this.register("nukkit", new SummonCommand("summon")); + this.register("nukkit", new WhitelistCommand("whitelist")); + this.register("nukkit", new GameruleCommand("gamerule")); + this.register("nukkit", new ConvertCommand("convert")); + this.register("nukkit", new DefaultGamemodeCommand("defaultgamemode")); + this.register("nukkit", new SayCommand("say")); + this.register("nukkit", new MeCommand("me")); + this.register("nukkit", new SaveOnCommand("save-on")); + this.register("nukkit", new SaveOffCommand("save-off")); + this.register("nukkit", new DifficultyCommand("difficulty")); + this.register("nukkit", new ParticleCommand("particle")); + this.register("nukkit", new SpawnpointCommand("spawnpoint")); + this.register("nukkit", new TitleCommand("title")); + this.register("nukkit", new SeedCommand("seed")); + this.register("nukkit", new PlaySoundCommand("playsound")); this.register("nukkit", new GarbageCollectorCommand("gc")); - this.register("nukkit", new TimingsCommand("timings")); - //this.register("nukkit", new DebugPasteCommand("debugpaste")); // No more unauthenticated API access, TODO: find a replacement for hastebin.com - //this.register("nukkit", new DumpMemoryCommand("dumpmemory")); -// } } @Override @@ -111,7 +108,7 @@ public boolean register(String fallbackPrefix, Command command, String label) { command.setAliases(aliases.toArray(new String[0])); if (!registered) { - command.setLabel(fallbackPrefix + ":" + label); + command.setLabel(fallbackPrefix + ':' + label); } command.register(this); @@ -145,7 +142,7 @@ public void registerSimpleCommands(Object object) { if (commandParameters != null) { Map map = Arrays.stream(commandParameters.parameters()) .collect(Collectors.toMap(Parameters::name, parameters -> Arrays.stream(parameters.parameters()) - .map(parameter -> CommandParameter.newType(parameter.name(), parameter.optional(), parameter.type())) + .map(parameter -> new CommandParameter(parameter.name(), parameter.type(), parameter.optional())) .distinct() .toArray(CommandParameter[]::new))); @@ -158,7 +155,7 @@ public void registerSimpleCommands(Object object) { } private boolean registerAlias(Command command, boolean isAlias, String fallbackPrefix, String label) { - this.knownCommands.put(fallbackPrefix + ":" + label, command); + this.knownCommands.put(fallbackPrefix + ':' + label, command); //if you're registering a command alias that is already registered, then return false boolean alreadyRegistered = this.knownCommands.containsKey(label); @@ -169,15 +166,15 @@ private boolean registerAlias(Command command, boolean isAlias, String fallbackP return false; } - //if you're registering a name (alias or label) which is identical to another command who's primary name is the same - //so basically we can't override the main name of a command, but we can override aliases if we're not an alias + // If you're registering a name (alias or label) which is identical to another command who's primary name is the same + // So basically we can't override the main name of a command, but we can override aliases if we're not an alias - //added the last statement which will allow us to override a VanillaCommand unconditionally + // Added the last statement which will allow us to override a VanillaCommand unconditionally if (alreadyRegistered && existingCommand.getLabel() != null && existingCommand.getLabel().equals(label) && existingCommandIsNotVanilla) { return false; } - //you can now assume that the command is either uniquely named, or overriding another command's alias (and is not itself, an alias) + // You can now assume that the command is either uniquely named, or overriding another command's alias (and is not itself, an alias) if (!isAlias) { command.setLabel(label); @@ -206,7 +203,7 @@ private boolean registerAlias(Command command, boolean isAlias, String fallbackP return true; } - private ArrayList parseArguments(String cmdLine) { + private static ArrayList parseArguments(String cmdLine) { StringBuilder sb = new StringBuilder(cmdLine); ArrayList args = new ArrayList<>(); boolean notQuoted = true; @@ -241,7 +238,7 @@ private ArrayList parseArguments(String cmdLine) { @Override public boolean dispatch(CommandSender sender, String cmdLine) { ArrayList parsed = parseArguments(cmdLine); - if (parsed.size() == 0) { + if (parsed.isEmpty()) { return false; } @@ -253,18 +250,12 @@ public boolean dispatch(CommandSender sender, String cmdLine) { return false; } - target.timing.startTiming(); try { target.execute(sender, sentCommandLabel, args); } catch (Exception e) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.exception")); - this.server.getLogger().critical(this.server.getLanguage().translateString("nukkit.command.exception", cmdLine, target.toString(), Utils.getExceptionMessage(e))); - MainLogger logger = sender.getServer().getLogger(); - if (logger != null) { - logger.logException(e); - } + this.server.getLogger().critical(this.server.getLanguage().translateString("nukkit.command.exception", cmdLine, target.toString(), Utils.getExceptionMessage(e)), e); } - target.timing.stopTiming(); return true; } @@ -280,10 +271,7 @@ public void clearCommands() { @Override public Command getCommand(String name) { - if (this.knownCommands.containsKey(name)) { - return this.knownCommands.get(name); - } - return null; + return this.knownCommands.get(name); } public Map getCommands() { diff --git a/src/main/java/cn/nukkit/command/data/CommandArgs.java b/src/main/java/cn/nukkit/command/data/CommandArgs.java index 3c369db69f9..1a67906822b 100644 --- a/src/main/java/cn/nukkit/command/data/CommandArgs.java +++ b/src/main/java/cn/nukkit/command/data/CommandArgs.java @@ -5,5 +5,4 @@ import java.util.HashMap; public class CommandArgs extends HashMap { - } diff --git a/src/main/java/cn/nukkit/command/data/CommandDataVersions.java b/src/main/java/cn/nukkit/command/data/CommandDataVersions.java index 0434d8542e4..6950dee9917 100644 --- a/src/main/java/cn/nukkit/command/data/CommandDataVersions.java +++ b/src/main/java/cn/nukkit/command/data/CommandDataVersions.java @@ -6,5 +6,4 @@ public class CommandDataVersions { public List versions = new ArrayList<>(); - } diff --git a/src/main/java/cn/nukkit/command/data/CommandEnum.java b/src/main/java/cn/nukkit/command/data/CommandEnum.java index 6e18e36039b..9ef7ea7bf52 100644 --- a/src/main/java/cn/nukkit/command/data/CommandEnum.java +++ b/src/main/java/cn/nukkit/command/data/CommandEnum.java @@ -14,8 +14,7 @@ public class CommandEnum { public static final CommandEnum ENUM_BOOLEAN = new CommandEnum("Boolean", ImmutableList.of("true", "false")); - public static final CommandEnum ENUM_GAMEMODE = new CommandEnum("GameMode", - ImmutableList.of("survival", "creative", "s", "c", "adventure", "a", "spectator", "view", "v", "spc")); + public static final CommandEnum ENUM_GAMEMODE = new CommandEnum("GameMode", ImmutableList.of("survival", "creative", "s", "c", "adventure", "a", "spectator", "view", "v", "spc")); public static final CommandEnum ENUM_BLOCK; public static final CommandEnum ENUM_ITEM; @@ -25,7 +24,6 @@ public class CommandEnum { blocks.add(field.getName().toLowerCase()); } ENUM_BLOCK = new CommandEnum("Block", blocks.build()); - ImmutableList.Builder items = ImmutableList.builder(); for (Field field : ItemID.class.getDeclaredFields()) { items.add(field.getName().toLowerCase()); @@ -34,8 +32,8 @@ public class CommandEnum { ENUM_ITEM = new CommandEnum("Item", items.build()); } - private String name; - private List values; + private final String name; + private final List values; public CommandEnum(String name, String... values) { this(name, Arrays.asList(values)); diff --git a/src/main/java/cn/nukkit/command/data/CommandInput.java b/src/main/java/cn/nukkit/command/data/CommandInput.java index 9fb056dc5a0..124e916320d 100644 --- a/src/main/java/cn/nukkit/command/data/CommandInput.java +++ b/src/main/java/cn/nukkit/command/data/CommandInput.java @@ -3,5 +3,4 @@ public class CommandInput { public CommandParameter[] parameters = new CommandParameter[0]; - } diff --git a/src/main/java/cn/nukkit/command/data/CommandOutput.java b/src/main/java/cn/nukkit/command/data/CommandOutput.java index cc168f58506..b3ff8811c66 100644 --- a/src/main/java/cn/nukkit/command/data/CommandOutput.java +++ b/src/main/java/cn/nukkit/command/data/CommandOutput.java @@ -3,5 +3,4 @@ public class CommandOutput { public String[] format_strings = new String[0]; - } diff --git a/src/main/java/cn/nukkit/command/data/CommandOverload.java b/src/main/java/cn/nukkit/command/data/CommandOverload.java index 7b2bbd31696..ab7c819083e 100644 --- a/src/main/java/cn/nukkit/command/data/CommandOverload.java +++ b/src/main/java/cn/nukkit/command/data/CommandOverload.java @@ -4,5 +4,4 @@ public class CommandOverload { public CommandInput input = new CommandInput(); public CommandOutput output = new CommandOutput(); - } diff --git a/src/main/java/cn/nukkit/command/data/CommandParamType.java b/src/main/java/cn/nukkit/command/data/CommandParamType.java index 8f109fa4850..a4e05316377 100644 --- a/src/main/java/cn/nukkit/command/data/CommandParamType.java +++ b/src/main/java/cn/nukkit/command/data/CommandParamType.java @@ -6,6 +6,7 @@ * @author CreeperFace */ public enum CommandParamType { + INT(ARG_TYPE_INT), FLOAT(ARG_TYPE_FLOAT), VALUE(ARG_TYPE_VALUE), @@ -37,4 +38,4 @@ public enum CommandParamType { public int getId() { return id; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/data/CommandParameter.java b/src/main/java/cn/nukkit/command/data/CommandParameter.java index a657455d1b0..82d3cc71dd6 100644 --- a/src/main/java/cn/nukkit/command/data/CommandParameter.java +++ b/src/main/java/cn/nukkit/command/data/CommandParameter.java @@ -1,9 +1,28 @@ package cn.nukkit.command.data; + import java.util.ArrayList; +import java.util.Arrays; public class CommandParameter { + public final static String ARG_TYPE_STRING = "string"; + public final static String ARG_TYPE_STRING_ENUM = "stringenum"; + public final static String ARG_TYPE_BOOL = "bool"; + public final static String ARG_TYPE_TARGET = "target"; + public final static String ARG_TYPE_PLAYER = "target"; + public final static String ARG_TYPE_BLOCK_POS = "blockpos"; + public final static String ARG_TYPE_RAW_TEXT = "rawtext"; + public final static String ARG_TYPE_INT = "int"; + + public static final String ENUM_TYPE_ITEM_LIST = "Item"; + public static final String ENUM_TYPE_BLOCK_LIST = "Block"; + public static final String ENUM_TYPE_COMMAND_LIST = "commandName"; + public static final String ENUM_TYPE_ENCHANTMENT_LIST = "enchantmentType"; + public static final String ENUM_TYPE_ENTITY_LIST = "entityType"; + public static final String ENUM_TYPE_EFFECT_LIST = "effectType"; + public static final String ENUM_TYPE_PARTICLE_LIST = "particleType"; + public String name; public CommandParamType type; public boolean optional; @@ -12,44 +31,24 @@ public class CommandParameter { public CommandEnum enumData; public String postFix; - /** - * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead - */ - @Deprecated public CommandParameter(String name, String type, boolean optional) { this(name, fromString(type), optional); } - /** - * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead - */ - @Deprecated public CommandParameter(String name, CommandParamType type, boolean optional) { this.name = name; this.type = type; this.optional = optional; } - /** - * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead - */ - @Deprecated public CommandParameter(String name, boolean optional) { this(name, CommandParamType.RAWTEXT, optional); } - /** - * @deprecated use {@link #newType(String, CommandParamType)} instead - */ - @Deprecated public CommandParameter(String name) { this(name, false); } - /** - * @deprecated use {@link #newEnum(String, boolean, String)} instead - */ - @Deprecated public CommandParameter(String name, boolean optional, String enumType) { this.name = name; this.type = CommandParamType.RAWTEXT; @@ -57,29 +56,17 @@ public CommandParameter(String name, boolean optional, String enumType) { this.enumData = new CommandEnum(enumType, new ArrayList<>()); } - /** - * @deprecated use {@link #newEnum(String, boolean, String[])} instead - */ - @Deprecated public CommandParameter(String name, boolean optional, String[] enumValues) { this.name = name; this.type = CommandParamType.RAWTEXT; this.optional = optional; - this.enumData = new CommandEnum(name + "Enums", enumValues); + this.enumData = new CommandEnum(name + "Enums", Arrays.asList(enumValues)); } - /** - * @deprecated use {@link #newEnum(String, String)} instead - */ - @Deprecated public CommandParameter(String name, String enumType) { this(name, false, enumType); } - /** - * @deprecated use {@link #newEnum(String, String[])} instead - */ - @Deprecated public CommandParameter(String name, String[] enumValues) { this(name, false, enumValues); } diff --git a/src/main/java/cn/nukkit/command/data/args/CommandArg.java b/src/main/java/cn/nukkit/command/data/args/CommandArg.java deleted file mode 100644 index c2f8a888b5f..00000000000 --- a/src/main/java/cn/nukkit/command/data/args/CommandArg.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.command.data.args; - -public class CommandArg { - - private CommandArgRules[] rules; - private String selector; - - public CommandArgRules[] getRules() { - return rules; - } - - public String getSelector() { - return selector; - } -} diff --git a/src/main/java/cn/nukkit/command/data/args/CommandArgBlockVector.java b/src/main/java/cn/nukkit/command/data/args/CommandArgBlockVector.java deleted file mode 100644 index 83cc78b7ca8..00000000000 --- a/src/main/java/cn/nukkit/command/data/args/CommandArgBlockVector.java +++ /dev/null @@ -1,35 +0,0 @@ -package cn.nukkit.command.data.args; - -public class CommandArgBlockVector { - - private int x; - private int y; - private int z; - private boolean xrelative; - private boolean yrelative; - private boolean zrelative; - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public int getZ() { - return z; - } - - public boolean isXrelative() { - return xrelative; - } - - public boolean isYrelative() { - return yrelative; - } - - public boolean isZrelative() { - return zrelative; - } -} diff --git a/src/main/java/cn/nukkit/command/data/args/CommandArgRules.java b/src/main/java/cn/nukkit/command/data/args/CommandArgRules.java deleted file mode 100644 index e719c44c2a1..00000000000 --- a/src/main/java/cn/nukkit/command/data/args/CommandArgRules.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.nukkit.command.data.args; - -public class CommandArgRules { - - private boolean inverted; - private String name; - private String value; - - public boolean isInverted() { - return inverted; - } - - public String getName() { - return name; - } - - public String getValue() { - return value; - } - -} diff --git a/src/main/java/cn/nukkit/command/defaults/BanCommand.java b/src/main/java/cn/nukkit/command/defaults/BanCommand.java index 33282b8c4c4..6147eda08fc 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanCommand.java @@ -9,7 +9,7 @@ import cn.nukkit.lang.TranslationContainer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BanCommand extends VanillaCommand { @@ -20,8 +20,8 @@ public BanCommand(String name) { this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("reason", true, CommandParamType.STRING) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("reason", CommandParamType.STRING, true) }); } @@ -40,7 +40,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) String name = args[0].replace("@s", sender.getName()); StringBuilder reason = new StringBuilder(); for (int i = 1; i < args.length; i++) { - reason.append(args[i]).append(" "); + reason.append(args[i]).append(' '); } if (reason.length() > 0) { @@ -51,7 +51,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) Player player = sender.getServer().getPlayerExact(name); if (player != null) { - player.kick(PlayerKickEvent.Reason.NAME_BANNED, (reason.length() > 0) ? "Banned by admin. Reason: " + reason : "Banned by admin"); + player.kick(PlayerKickEvent.Reason.NAME_BANNED, (reason.length() > 0) ? "You are banned! Reason: " + reason : "You are banned!", true); } Command.broadcastCommandMessage(sender, new TranslationContainer("%commands.ban.success", player != null ? player.getName() : name)); diff --git a/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java b/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java index 7a1b48bb89e..1a6369a97ed 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanIpCommand.java @@ -11,15 +11,14 @@ import cn.nukkit.nbt.tag.CompoundTag; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.ArrayList; +import java.nio.file.Files; import java.util.regex.Pattern; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BanIpCommand extends VanillaCommand { @@ -30,12 +29,8 @@ public BanIpCommand(String name) { this.setAliases(new String[]{"banip"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("reason", true, CommandParamType.STRING) - }); - this.commandParameters.put("byIp", new CommandParameter[]{ - CommandParameter.newType("ip", CommandParamType.STRING), - CommandParameter.newType("reason", true, CommandParamType.STRING) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("reason", CommandParamType.STRING, true) }); } @@ -54,7 +49,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) String value = args[0]; StringBuilder reason = new StringBuilder(); for (int i = 1; i < args.length; i++) { - reason.append(args[i]).append(" "); + reason.append(args[i]).append(' '); } if (reason.length() > 0) { @@ -62,13 +57,13 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (Pattern.matches("^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$", value)) { - this.processIPBan(value, sender, reason.toString()); + processIPBan(value, sender, reason.toString()); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.banip.success", value)); } else { - Player player = sender.getServer().getPlayer(value); + Player player = sender.getServer().getPlayerExact(value); if (player != null) { - this.processIPBan(player.getAddress(), sender, reason.toString()); + processIPBan(player.getAddress(), sender, reason.toString()); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.banip.success.players", player.getAddress(), player.getName())); } else { @@ -78,14 +73,14 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) CompoundTag nbt = null; if (file.exists()) { try { - nbt = NBTIO.readCompressed(new FileInputStream(file)); + nbt = NBTIO.readCompressed(Files.newInputStream(file.toPath())); } catch (IOException e) { throw new RuntimeException(e); } } if (nbt != null && nbt.contains("lastIP") && Pattern.matches("^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$", (value = nbt.getString("lastIP")))) { - this.processIPBan(value, sender, reason.toString()); + processIPBan(value, sender, reason.toString()); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.banip.success", value)); } else { @@ -98,19 +93,17 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - private void processIPBan(String ip, CommandSender sender, String reason) { + private static void processIPBan(String ip, CommandSender sender, String reason) { sender.getServer().getIPBans().addBan(ip, reason, null, sender.getName()); - for (Player player : new ArrayList<>(sender.getServer().getOnlinePlayers().values())) { + for (Player player : /*new ArrayList<>(*/sender.getServer().getOnlinePlayers().values()/*)*/) { if (player.getAddress().equals(ip)) { - player.kick(PlayerKickEvent.Reason.IP_BANNED, !reason.isEmpty() ? reason : "IP banned"); + player.kick(PlayerKickEvent.Reason.IP_BANNED, !reason.isEmpty() ? reason : "IP banned", true); } } try { - sender.getServer().getNetwork().blockAddress(InetAddress.getByName(ip), -1); - } catch (UnknownHostException e) { - // ignore - } + sender.getServer().getNetwork().blockAddress(InetAddress.getByName(ip)); + } catch (UnknownHostException ignore) {} } } diff --git a/src/main/java/cn/nukkit/command/defaults/BanListCommand.java b/src/main/java/cn/nukkit/command/defaults/BanListCommand.java index 0ca654c4e88..3da8e639593 100644 --- a/src/main/java/cn/nukkit/command/defaults/BanListCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/BanListCommand.java @@ -1,7 +1,6 @@ package cn.nukkit.command.defaults; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.permission.BanEntry; @@ -14,12 +13,13 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class BanListCommand extends VanillaCommand { + public BanListCommand(String name) { super(name, "%nukkit.command.banlist.description", "%commands.banlist.usage"); this.setPermission("nukkit.command.ban.list"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newEnum("type", true, new CommandEnum("BanListType", "ips", "players")) + new CommandParameter("ips|players", true) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/ClearCommand.java b/src/main/java/cn/nukkit/command/defaults/ClearCommand.java index fad7ba1074e..5a7cc71c5ca 100644 --- a/src/main/java/cn/nukkit/command/defaults/ClearCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/ClearCommand.java @@ -9,14 +9,14 @@ import cn.nukkit.lang.TranslationContainer; import cn.nukkit.utils.TextFormat; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.Collections; /** - * Created on 2024/26/2 by Sherko231. - * Package cn.nukkit.command.defaults in project Nukkit . + * @author Sherko231 */ public class ClearCommand extends VanillaCommand { + public ClearCommand(String name) { super(name, "%nukkit.command.clear.description", "%nukkit.command.clear.usage"); this.setPermission("nukkit.command.clear"); @@ -38,16 +38,14 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return false; } - List targets = new ArrayList<>(); + Collection targets; if (args[0].equals("@a")) { - targets.addAll(Server.getInstance().getOnlinePlayers().values()); - } - else { - Player target = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + targets = Server.getInstance().getOnlinePlayers().values(); + } else { + Player target = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (target != null) { - targets.add(target); - } - else { + targets = Collections.singletonList(target); + } else { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return false; } diff --git a/src/main/java/cn/nukkit/command/defaults/ConvertCommand.java b/src/main/java/cn/nukkit/command/defaults/ConvertCommand.java new file mode 100644 index 00000000000..6d01514de3e --- /dev/null +++ b/src/main/java/cn/nukkit/command/defaults/ConvertCommand.java @@ -0,0 +1,80 @@ +package cn.nukkit.command.defaults; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandParamType; +import cn.nukkit.command.data.CommandParameter; +import cn.nukkit.lang.TranslationContainer; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.generic.Anvil2LevelDBConverter; +import cn.nukkit.level.format.leveldb.LevelDBProvider; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class ConvertCommand extends VanillaCommand { + + private static final Set conversionInProgress = ConcurrentHashMap.newKeySet(); + + public ConvertCommand(String name) { + super(name, "%nukkit.command.world.convert.description", "%nukkit.command.world.convert.usage"); + this.setPermission("nukkit.command.world.convert"); + this.commandParameters.clear(); + this.commandParameters.put("default", new CommandParameter[]{ + new CommandParameter("world", CommandParamType.STRING, false) + }); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!this.testPermission(sender)) { + return true; + } + + if (sender instanceof Player) { + sender.sendMessage("§cThis command can be used only via console"); + return true; + } + + if (args.length < 1) { + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); + return false; + } + + String worldName = String.join(" ", args); + Level level = Server.getInstance().getLevelByName(worldName); + if (level == null) { + sender.sendMessage("Unknown level: " + worldName); + return true; + } + + if (level.getProvider() instanceof LevelDBProvider) { + sender.sendMessage(worldName + " is already in LevelDB format"); + return true; + } + + if (!level.getPlayers().isEmpty()) { + sender.sendMessage(worldName + " has players in it! Make sure the world is not used while it's being converted"); + return true; + } + + if (conversionInProgress.contains(worldName)) { + sender.sendMessage(worldName + " is already being converted"); + return true; + } + + conversionInProgress.add(worldName); + + Anvil2LevelDBConverter converter = new Anvil2LevelDBConverter(level); + converter.convert().whenComplete((ignore, error) -> { + if (error != null) { + sender.sendMessage("Error during conversion!"); + Server.getInstance().getLogger().logException(error); + } + + conversionInProgress.remove(worldName); + }); + return true; + } +} diff --git a/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java b/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java deleted file mode 100644 index 57e765d08bb..00000000000 --- a/src/main/java/cn/nukkit/command/defaults/DebugPasteCommand.java +++ /dev/null @@ -1,90 +0,0 @@ -package cn.nukkit.command.defaults; - -import cn.nukkit.Server; -import cn.nukkit.command.CommandSender; -import cn.nukkit.network.protocol.ProtocolInfo; -import cn.nukkit.plugin.Plugin; -import cn.nukkit.plugin.PluginDescription; -import cn.nukkit.scheduler.AsyncTask; -import cn.nukkit.utils.HastebinUtility; -import cn.nukkit.utils.MainLogger; -import cn.nukkit.utils.Utils; -import java.io.File; -import java.io.IOException; -import java.lang.management.ManagementFactory; - -public class DebugPasteCommand extends VanillaCommand { - - public DebugPasteCommand(String name) { - super(name, "%nukkit.command.debug.description", "%nukkit.command.debug.usage"); - this.setPermission("nukkit.command.debug.perform"); - this.commandParameters.clear(); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (!this.testPermission(sender)) { - return true; - } - Server server = Server.getInstance(); - server.getScheduler().scheduleAsyncTask(new AsyncTask() { - @Override - public void onRun() { - try { - new StatusCommand("status").execute(server.getConsoleSender(), "status", new String[]{}); - String dataPath = server.getDataPath(); - String nukkitYML = HastebinUtility.upload(new File(dataPath, "nukkit.yml")); - String serverProperties = HastebinUtility.upload(new File(dataPath, "server.properties")); - String latestLog = HastebinUtility.upload(new File(dataPath, "/logs/server.log")); - String threadDump = HastebinUtility.upload(Utils.getAllThreadDumps()); - - StringBuilder b = new StringBuilder(); - b.append("# Files\n"); - b.append("links.nukkit_yml: ").append(nukkitYML).append('\n'); - b.append("links.server_properties: ").append(serverProperties).append('\n'); - b.append("links.server_log: ").append(latestLog).append('\n'); - b.append("links.thread_dump: ").append(threadDump).append('\n'); - b.append("\n# Server Information\n"); - - b.append("version.api: ").append(server.getApiVersion()).append('\n'); - b.append("version.nukkit: ").append(server.getNukkitVersion()).append('\n'); - b.append("version.minecraft: ").append(server.getVersion()).append('\n'); - b.append("version.protocol: ").append(ProtocolInfo.CURRENT_PROTOCOL).append('\n'); - b.append("plugins:"); - for (Plugin plugin : server.getPluginManager().getPlugins().values()) { - boolean enabled = plugin.isEnabled(); - String name = plugin.getName(); - PluginDescription desc = plugin.getDescription(); - String version = desc.getVersion(); - b.append("\n ") - .append(name) - .append(":\n ") - .append("version: '") - .append(version) - .append('\'') - .append("\n enabled: ") - .append(enabled); - } - b.append("\n\n# Java Details\n"); - Runtime runtime = Runtime.getRuntime(); - b.append("memory.free: ").append(runtime.freeMemory()).append('\n'); - b.append("memory.max: ").append(runtime.maxMemory()).append('\n'); - b.append("cpu.runtime: ").append(ManagementFactory.getRuntimeMXBean().getUptime()).append('\n'); - b.append("cpu.processors: ").append(runtime.availableProcessors()).append('\n'); - b.append("java.specification.version: '").append(System.getProperty("java.specification.version")).append("'\n"); - b.append("java.vendor: '").append(System.getProperty("java.vendor")).append("'\n"); - b.append("java.version: '").append(System.getProperty("java.version")).append("'\n"); - b.append("os.arch: '").append(System.getProperty("os.arch")).append("'\n"); - b.append("os.name: '").append(System.getProperty("os.name")).append("'\n"); - b.append("os.version: '").append(System.getProperty("os.version")).append("'\n\n"); - b.append("\n# Create a ticket: https://github.com/NukkitX/Nukkit/issues/new"); - String link = HastebinUtility.upload(b.toString()); - sender.sendMessage(link); - } catch (IOException e) { - MainLogger.getLogger().logException(e); - } - } - }); - return true; - } -} diff --git a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java index 73604ce3cd3..a9940d858e6 100644 --- a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java @@ -1,11 +1,12 @@ package cn.nukkit.command.defaults; import cn.nukkit.Server; +import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; +import cn.nukkit.network.protocol.SetDefaultGameTypePacket; /** * Created on 2015/11/12 by xtypr. @@ -18,10 +19,11 @@ public DefaultGamemodeCommand(String name) { this.setPermission("nukkit.command.defaultgamemode"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("gameMode", CommandParamType.INT) + new CommandParameter("mode", CommandParamType.INT, false) }); this.commandParameters.put("byString", new CommandParameter[]{ - CommandParameter.newEnum("gameMode", CommandEnum.ENUM_GAMEMODE) + new CommandParameter("mode", new String[]{"survival", "creative", "s", "c", + "adventure", "a", "spectator", "view", "v"}) }); } @@ -31,15 +33,21 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } if (args.length == 0) { - sender.sendMessage(new TranslationContainer("commands.generic.usage", new String[]{this.usageMessage})); + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return false; } int gameMode = Server.getGamemodeFromString(args[0]); if (gameMode != -1) { sender.getServer().setPropertyInt("gamemode", gameMode); + sender.getServer().gamemode = gameMode; sender.sendMessage(new TranslationContainer("commands.defaultgamemode.success", new String[]{Server.getGamemodeString(gameMode)})); + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.defaultgamemode.success", new String[]{Server.getGamemodeString(sender.getServer().getDefaultGamemode())})); + + SetDefaultGameTypePacket gameTypePacket = new SetDefaultGameTypePacket(); + gameTypePacket.gamemode = sender.getServer().getDefaultGamemode(); + Server.broadcastPacket(sender.getServer().getOnlinePlayers().values(), gameTypePacket); } else { - sender.sendMessage("Unknown game mode"); // + sender.sendMessage("Unknown game mode"); } return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/DeopCommand.java b/src/main/java/cn/nukkit/command/defaults/DeopCommand.java index 08ba7b58c16..c3c7a6cd353 100644 --- a/src/main/java/cn/nukkit/command/defaults/DeopCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DeopCommand.java @@ -14,11 +14,13 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class DeopCommand extends VanillaCommand { + public DeopCommand(String name) { super(name, "%nukkit.command.deop.description", "%commands.deop.description"); this.setPermission("nukkit.command.op.take"); + this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, false) }); } @@ -34,16 +36,16 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return false; } - String playerName = args[0].replace("@s", sender.getName()); - IPlayer player = sender.getServer().getOfflinePlayer(playerName); - player.setOp(false); - + String name = args[0].replace("@s", sender.getName()); + IPlayer player = sender.getServer().getOfflinePlayer(name); if (player instanceof Player) { + player.setOp(false); ((Player) player).sendMessage(new TranslationContainer(TextFormat.GRAY + "%commands.deop.message")); + } else { + sender.getServer().removeOp(name); } Command.broadcastCommandMessage(sender, new TranslationContainer("commands.deop.success", new String[]{player.getName()})); - return true; } } diff --git a/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java b/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java index 1b0dcf97a53..4cb03129bb9 100644 --- a/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DifficultyCommand.java @@ -3,14 +3,11 @@ import cn.nukkit.Server; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.network.protocol.SetDifficultyPacket; -import java.util.ArrayList; - /** * Created on 2015/11/12 by xtypr. * Package cn.nukkit.command.defaults in project Nukkit . @@ -22,10 +19,11 @@ public DifficultyCommand(String name) { this.setPermission("nukkit.command.difficulty"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("difficulty", CommandParamType.INT) + new CommandParameter("difficulty", CommandParamType.INT, false) }); this.commandParameters.put("byString", new CommandParameter[]{ - CommandParameter.newEnum("difficulty", new CommandEnum("Difficulty", "peaceful", "p", "easy", "e", "normal", "n", "hard", "h")) + new CommandParameter("difficulty", new String[]{"peaceful", "p", "easy", "e", + "normal", "n", "hard", "h"}) }); } @@ -51,7 +49,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) SetDifficultyPacket pk = new SetDifficultyPacket(); pk.difficulty = sender.getServer().getDifficulty(); - Server.broadcastPacket(new ArrayList<>(sender.getServer().getOnlinePlayers().values()), pk); + Server.broadcastPacket(sender.getServer().getOnlinePlayers().values(), pk); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.difficulty.success", String.valueOf(difficulty))); } else { diff --git a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java index d60f51d49a2..e5365cf811e 100644 --- a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java @@ -12,38 +12,26 @@ import cn.nukkit.utils.ServerException; import cn.nukkit.utils.TextFormat; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.List; - /** * Created by Snake1999 and Pub4Game on 2016/1/23. * Package cn.nukkit.command.defaults in project nukkit. */ public class EffectCommand extends Command { + public EffectCommand(String name) { super(name, "%nukkit.command.effect.description", "%commands.effect.usage"); this.setPermission("nukkit.command.effect"); this.commandParameters.clear(); - - List effects = new ArrayList<>(); - for (Field field : Effect.class.getDeclaredFields()) { - if (field.getType() == int.class && field.getModifiers() == (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)) { - effects.add(field.getName().toLowerCase()); - } - } - this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("effect", new CommandEnum("Effect", effects)), - CommandParameter.newType("seconds", true, CommandParamType.INT), - CommandParameter.newType("amplifier", true, CommandParamType.INT), + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("effect", CommandParamType.STRING, false), //Do not use Enum here because of buggy behavior + new CommandParameter("seconds", CommandParamType.INT, true), + new CommandParameter("amplifier", true), CommandParameter.newEnum("hideParticle", true, CommandEnum.ENUM_BOOLEAN) }); this.commandParameters.put("clear", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("clear", new CommandEnum("ClearEffects", "clear")) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("clear", new String[]{"clear"}) }); } @@ -56,7 +44,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; } - Player player = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + Player player = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (player == null) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return true; @@ -79,7 +67,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } } - int duration = 300; + int duration = 600; int amplification = 0; if (args.length >= 3) { try { @@ -110,7 +98,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (duration == 0) { if (!player.hasEffect(effect.getId())) { - if (player.getEffects().size() == 0) { + if (player.getEffects().isEmpty()) { sender.sendMessage(new TranslationContainer("commands.effect.failure.notActive.all", player.getDisplayName())); } else { sender.sendMessage(new TranslationContainer("commands.effect.failure.notActive", effect.getName(), player.getDisplayName())); diff --git a/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java b/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java index 77228636b95..7886a5254bb 100644 --- a/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/EnchantCommand.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; @@ -21,19 +20,14 @@ public EnchantCommand(String name) { this.setPermission("nukkit.command.enchant"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("enchantmentId", CommandParamType.INT), - CommandParameter.newType("level", true, CommandParamType.INT) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("enchantment ID", CommandParamType.INT, false), + new CommandParameter("level", CommandParamType.INT, true) }); this.commandParameters.put("byName", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("enchantmentName", new CommandEnum("Enchant", - "protection", "fire_protection", "feather_falling", "blast_protection", "projectile_projection", "thorns", "respiration", - "aqua_affinity", "depth_strider", "sharpness", "smite", "bane_of_arthropods", "knockback", "fire_aspect", "looting", "efficiency", - "silk_touch", "durability", "fortune", "power", "punch", "flame", "infinity", "luck_of_the_sea", "lure", "frost_walker", "mending", - "binding_curse", "vanishing_curse", "impaling", "loyalty", "riptide", "channeling", "multishot", "piercing", "quick_charge", - "soul_speed")), - CommandParameter.newType("level", true, CommandParamType.INT) + new CommandParameter("player", CommandParameter.ARG_TYPE_TARGET, false), + new CommandParameter("id", false, CommandParameter.ENUM_TYPE_ENCHANTMENT_LIST), + new CommandParameter("level", CommandParamType.INT, true) }); } @@ -46,7 +40,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; } - Player player = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + Player player = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (player == null) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return true; @@ -73,7 +67,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } item.addEnchantment(enchantment); player.getInventory().setItemInHand(item); - Command.broadcastCommandMessage(sender, new TranslationContainer("%commands.enchant.success")); + Command.broadcastCommandMessage(sender, new TranslationContainer("%commands.enchant.success", player.getDisplayName())); return true; } @@ -88,7 +82,7 @@ public int getIdByName(String value) throws NumberFormatException { return 2; case "blast_protection": return 3; - case "projectile_projection": + case "projectile_protection": return 4; case "thorns": return 5; diff --git a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java index ef43d0476fb..89a6130f53a 100644 --- a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java @@ -27,11 +27,11 @@ public GamemodeCommand(String name) { this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ CommandParameter.newType("gameMode", CommandParamType.INT), - CommandParameter.newType("player", true, CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, true) }); this.commandParameters.put("byString", new CommandParameter[]{ CommandParameter.newEnum("gameMode", CommandEnum.ENUM_GAMEMODE), - CommandParameter.newType("player", true, CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, true) }); } @@ -51,7 +51,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) CommandSender target = sender; if (args.length > 1) { if (sender.hasPermission("nukkit.command.gamemode.other")) { - target = sender.getServer().getPlayer(args[1].replace("@s", sender.getName())); + target = sender.getServer().getPlayerExact(args[1].replace("@s", sender.getName())); if (target == null) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return true; @@ -79,7 +79,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (target.equals(sender)) { Command.broadcastCommandMessage(sender, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(gameMode))); } else { - target.sendMessage(new TranslationContainer("gameMode.changed", Server.getGamemodeString(gameMode))); + target.sendMessage(new TranslationContainer("gameMode.changed")); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.gamemode.success.other", target.getName(), Server.getGamemodeString(gameMode))); } } diff --git a/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java b/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java index 1015a4cc662..be736ce86f2 100644 --- a/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GameruleCommand.java @@ -9,16 +9,12 @@ import cn.nukkit.level.GameRule; import cn.nukkit.level.GameRules; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.StringJoiner; +import java.util.*; public class GameruleCommand extends VanillaCommand { public GameruleCommand(String name) { - super(name, "%nukkit.command.gamerule.description", "%nukkit.command.gamerule.usage"); + super(name, "%nukkit.command.gamerule.description", "%commands.gamerule.usage"); this.setPermission("nukkit.command.gamerule"); this.commandParameters.clear(); @@ -79,7 +75,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (!sender.isPlayer()) { - sender.sendMessage(new TranslationContainer("%commands.generic.ingame")); + sender.sendMessage(new TranslationContainer("commands.generic.ingame")); return true; } GameRules rules = ((Player) sender).getLevel().getGameRules(); @@ -99,14 +95,13 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - sender.sendMessage(gameRule.get().getName() .toLowerCase()+ " = " + rules.getString(gameRule.get())); + sender.sendMessage(gameRule.get().getName().toLowerCase() + " = " + rules.getString(gameRule.get())); return true; default: Optional optionalRule = GameRule.parseString(args[0]); if (!optionalRule.isPresent()) { - sender.sendMessage(new TranslationContainer("commands.generic.syntax", - "/gamerule ", args[0], " " + String.join(" ", Arrays.copyOfRange(args, 1, args.length)))); + sender.sendMessage(new TranslationContainer("commands.generic.syntax", "/gamerule ", args[0], ' ' + String.join(" ", Arrays.copyOfRange(args, 1, args.length)))); return true; } @@ -114,9 +109,9 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) rules.setGameRules(optionalRule.get(), args[1]); sender.sendMessage(new TranslationContainer("commands.gamerule.success", optionalRule.get().getName().toLowerCase(), args[1])); } catch (IllegalArgumentException e) { - sender.sendMessage(new TranslationContainer("commands.generic.syntax", "/gamerule " + args[0] + " ", args[1], " " + String.join(" ", Arrays.copyOfRange(args, 2, args.length)))); + sender.sendMessage(new TranslationContainer("commands.generic.syntax", "/gamerule " + args[0] + ' ', args[1], ' ' + String.join(" ", Arrays.copyOfRange(args, 2, args.length)))); } return true; } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/defaults/GarbageCollectorCommand.java b/src/main/java/cn/nukkit/command/defaults/GarbageCollectorCommand.java index 94cb5c126c9..adc74d229e5 100644 --- a/src/main/java/cn/nukkit/command/defaults/GarbageCollectorCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GarbageCollectorCommand.java @@ -30,12 +30,12 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) for (Level level : sender.getServer().getLevels().values()) { int chunksCount = level.getChunks().size(); - int entitiesCount = level.getEntities().length; + int entitiesCount = level.entities.size(); int tilesCount = level.getBlockEntities().size(); level.doChunkGarbageCollection(); level.unloadChunks(true); chunksCollected += chunksCount - level.getChunks().size(); - entitiesCollected += entitiesCount - level.getEntities().length; + entitiesCollected += entitiesCount - level.entities.size(); tilesCollected += tilesCount - level.getBlockEntities().size(); } diff --git a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java index 0c325aea344..e5fe2a5724f 100644 --- a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java @@ -4,83 +4,88 @@ import cn.nukkit.Server; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.utils.TextFormat; -import java.util.ArrayList; -import java.util.List; +import java.util.Collection; +import java.util.Collections; /** * Created on 2015/12/9 by xtypr. * Package cn.nukkit.command.defaults in project Nukkit . */ public class GiveCommand extends VanillaCommand { + public GiveCommand(String name) { super(name, "%nukkit.command.give.description", "%nukkit.command.give.usage"); this.setPermission("nukkit.command.give"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("itemName", CommandEnum.ENUM_ITEM), - CommandParameter.newType("amount", true, CommandParamType.INT), - CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("itemName", false, CommandParameter.ENUM_TYPE_ITEM_LIST), + new CommandParameter("amount", CommandParamType.INT, true), + new CommandParameter("meta", CommandParamType.INT, true), + new CommandParameter("tags...", CommandParamType.RAWTEXT, true) }); this.commandParameters.put("toPlayerById", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("itemId", CommandParamType.INT), - CommandParameter.newType("amount", true, CommandParamType.INT), - CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("item ID", CommandParamType.INT, false), + new CommandParameter("amount", CommandParamType.INT, true), + new CommandParameter("tags...", CommandParamType.RAWTEXT, true) }); this.commandParameters.put("toPlayerByIdMeta", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("itemAndData", CommandParamType.STRING), - CommandParameter.newType("amount", true, CommandParamType.INT), - CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("item ID:meta", CommandParamType.RAWTEXT, false), + new CommandParameter("amount", CommandParamType.INT, true), + new CommandParameter("tags...", CommandParamType.RAWTEXT, true) }); } @Override public boolean execute(CommandSender sender, String commandLabel, String[] args) { if (!this.testPermission(sender)) { - return false; + return true; } if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); - return false; + return true; } - List targets = new ArrayList<>(); + Collection targets; if (args[0].equals("@a")) { - targets.addAll(Server.getInstance().getOnlinePlayers().values()); - } - else { - Player target = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + targets = Server.getInstance().getOnlinePlayers().values(); + } else { + Player target = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (target != null) { - targets.add(target); - } - else { + targets = Collections.singletonList(target); + } else { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return false; } } Item item; + try { - item = Item.fromString(args[1]); + item = Item.fromString(args[1].replace("minecraft:", "")); } catch (Exception e) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); - return false; + return true; + } + + if (item.getDamage() < 0) { + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); + return true; } try { item.setCount(Integer.parseInt(args[2])); } catch (Exception e) { - item.setCount(item.getMaxStackSize()); + item.setCount(1); } if (item.getId() == 0) { diff --git a/src/main/java/cn/nukkit/command/defaults/HelpCommand.java b/src/main/java/cn/nukkit/command/defaults/HelpCommand.java index 124c35b46f1..206f31c0003 100644 --- a/src/main/java/cn/nukkit/command/defaults/HelpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/HelpCommand.java @@ -12,7 +12,7 @@ import java.util.TreeMap; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class HelpCommand extends VanillaCommand { @@ -22,7 +22,7 @@ public HelpCommand(String name) { this.setPermission("nukkit.command.help"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("page", true, CommandParamType.INT) + new CommandParameter("page", CommandParamType.INT, true) }); } @@ -50,16 +50,16 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) args = new String[0]; }*/ for (String arg : args) { - if (!command.toString().equals("")) { - command.append(" "); + if (command.length() > 0) { + command.append(' '); } command.append(arg); } } catch (NumberFormatException e) { pageNumber = 1; for (String arg : args) { - if (!command.toString().equals("")) { - command.append(" "); + if (command.length() > 0) { + command.append(' '); } command.append(arg); } @@ -70,7 +70,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) pageHeight = Integer.MAX_VALUE; } - if (command.toString().equals("")) { + if (command.length() == 0) { Map commands = new TreeMap<>(); for (Command cmd : sender.getServer().getCommandMap().getCommands().values()) { if (cmd.testPermissionSilent(sender)) { @@ -98,16 +98,16 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (cmd != null) { if (cmd.testPermissionSilent(sender)) { String message = TextFormat.YELLOW + "--------- " + TextFormat.WHITE + " Help: /" + cmd.getName() + TextFormat.YELLOW + " ---------\n"; - message += TextFormat.GOLD + "Description: " + TextFormat.WHITE + cmd.getDescription() + "\n"; + message += TextFormat.GOLD + "Description: " + TextFormat.WHITE + cmd.getDescription() + '\n'; StringBuilder usage = new StringBuilder(); String[] usages = cmd.getUsage().split("\n"); for (String u : usages) { - if (!usage.toString().equals("")) { + if (usage.length() > 0) { usage.append("\n" + TextFormat.WHITE); } usage.append(u); } - message += TextFormat.GOLD + "Usage: " + TextFormat.WHITE + usage + "\n"; + message += TextFormat.GOLD + "Usage: " + TextFormat.WHITE + usage + '\n'; sender.sendMessage(message); return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/KickCommand.java b/src/main/java/cn/nukkit/command/defaults/KickCommand.java index a73db169b5d..737ab8f1894 100644 --- a/src/main/java/cn/nukkit/command/defaults/KickCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/KickCommand.java @@ -20,8 +20,8 @@ public KickCommand(String name) { this.setPermission("nukkit.command.kick"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("reason", true, CommandParamType.MESSAGE) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("reason", CommandParamType.STRING, false), }); } @@ -39,16 +39,16 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) StringBuilder reason = new StringBuilder(); for (int i = 1; i < args.length; i++) { - reason.append(args[i]).append(" "); + reason.append(args[i]).append(' '); } if (reason.length() > 0) { reason = new StringBuilder(reason.substring(0, reason.length() - 1)); } - Player player = sender.getServer().getPlayer(name); + Player player = sender.getServer().getPlayerExact(name); if (player != null) { - player.kick(PlayerKickEvent.Reason.KICKED_BY_ADMIN, reason.toString()); + player.kick(PlayerKickEvent.Reason.KICKED_BY_ADMIN, reason.toString(), true); if (reason.length() >= 1) { Command.broadcastCommandMessage(sender, new TranslationContainer("commands.kick.success.reason", player.getName(), reason.toString()) ); diff --git a/src/main/java/cn/nukkit/command/defaults/KillCommand.java b/src/main/java/cn/nukkit/command/defaults/KillCommand.java index e51262dcec1..f44cb3cb690 100644 --- a/src/main/java/cn/nukkit/command/defaults/KillCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/KillCommand.java @@ -23,11 +23,10 @@ public class KillCommand extends VanillaCommand { public KillCommand(String name) { super(name, "%nukkit.command.kill.description", "%nukkit.command.kill.usage", new String[]{"suicide"}); - this.setPermission("nukkit.command.kill.self;" - + "nukkit.command.kill.other"); + this.setPermission("nukkit.command.kill.self;nukkit.command.kill.other"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", true, CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, true) }); } @@ -45,20 +44,15 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); return true; } - Player player = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + Player player = sender.getServer().getPlayerExact(args[0]); if (player != null) { if (player.isCreative() || player.isSpectator()) { sender.sendMessage(TextFormat.RED + "No targets matched selector"); return true; } - EntityDamageEvent ev = new EntityDamageEvent(player, DamageCause.SUICIDE, 1000); - sender.getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return true; + if (resetHealth(player)) { + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.kill.successful", player.getName())); } - player.setLastDamageCause(ev); - player.setHealth(0); - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.kill.successful", player.getName())); } else if (args[0].equals("@e")) { StringJoiner joiner = new StringJoiner(", "); for (Level level : Server.getInstance().getLevels().values()) { @@ -91,33 +85,22 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(TextFormat.RED + "No targets matched selector"); return true; } - EntityDamageEvent ev = new EntityDamageEvent(p, DamageCause.SUICIDE, 1000); - sender.getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return true; + if (resetHealth(p)) { + sender.sendMessage(new TranslationContainer("commands.kill.successful", sender.getName())); } - p.setLastDamageCause(ev); - p.setHealth(0); - sender.sendMessage(new TranslationContainer("commands.kill.successful", sender.getName())); } else if (args[0].equals("@a")) { if (!sender.hasPermission("nukkit.command.kill.other")) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); return true; } for (Level level : Server.getInstance().getLevels().values()) { - for (Entity entity : level.getEntities()) { + for (Entity entity : level.entities.values()) { if (entity instanceof Player) { Player p = (Player) entity; if (p.isCreative() || p.isSpectator()) { continue; } - EntityDamageEvent ev = new EntityDamageEvent(entity, DamageCause.SUICIDE, 1000); - sender.getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - continue; - } - entity.setLastDamageCause(ev); - entity.setHealth(0); + resetHealth(p); } } } @@ -126,8 +109,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); } return true; - } - if (sender instanceof Player) { + } else if (sender instanceof Player) { if (!sender.hasPermission("nukkit.command.kill.self")) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); return true; @@ -137,18 +119,24 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(TextFormat.RED + "No targets matched selector"); return true; } - EntityDamageEvent ev = new EntityDamageEvent(player, DamageCause.SUICIDE, 1000); - sender.getServer().getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return true; + if (resetHealth(player)) { + sender.sendMessage(new TranslationContainer("commands.kill.successful", sender.getName())); } - player.setLastDamageCause(ev); - player.setHealth(0); - sender.sendMessage(new TranslationContainer("commands.kill.successful", sender.getName())); } else { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); + } + return true; + } + + private boolean resetHealth(Entity entity) { + EntityDamageEvent ev = new EntityDamageEvent(entity, DamageCause.SUICIDE, 1000); + entity.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { return false; } + entity.setLastDamageCause(ev); + entity.removeAllEffects(); // Fix issue with absorption + entity.setHealth(0); return true; } } diff --git a/src/main/java/cn/nukkit/command/defaults/ListCommand.java b/src/main/java/cn/nukkit/command/defaults/ListCommand.java index 7417211d0c2..60a0ca31ad5 100644 --- a/src/main/java/cn/nukkit/command/defaults/ListCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/ListCommand.java @@ -34,8 +34,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) online = new StringBuilder(online.substring(0, online.length() - 2)); } - sender.sendMessage(new TranslationContainer("commands.players.list", - String.valueOf(onlineCount), String.valueOf(sender.getServer().getMaxPlayers()))); + sender.sendMessage(new TranslationContainer("commands.players.list", String.valueOf(onlineCount), String.valueOf(sender.getServer().getMaxPlayers()))); sender.sendMessage(online.toString()); return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/MeCommand.java b/src/main/java/cn/nukkit/command/defaults/MeCommand.java index 57a35cfc010..70f4f81c850 100644 --- a/src/main/java/cn/nukkit/command/defaults/MeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/MeCommand.java @@ -14,11 +14,11 @@ public class MeCommand extends VanillaCommand { public MeCommand(String name) { - super(name, "%nukkit.command.me.description", "%commands.me.usage"); + super(name, "%nukkit.command.me.description", "%nukkit.command.me.usage"); this.setPermission("nukkit.command.me"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("message", CommandParamType.MESSAGE) + new CommandParameter("action ...", CommandParamType.RAWTEXT, false) }); } @@ -43,7 +43,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) StringBuilder msg = new StringBuilder(); for (String arg : args) { - msg.append(arg).append(" "); + msg.append(arg).append(' '); } if (msg.length() > 0) { diff --git a/src/main/java/cn/nukkit/command/defaults/OpCommand.java b/src/main/java/cn/nukkit/command/defaults/OpCommand.java index 6afc9fdcc6c..1b46b1c5465 100644 --- a/src/main/java/cn/nukkit/command/defaults/OpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/OpCommand.java @@ -16,11 +16,11 @@ public class OpCommand extends VanillaCommand { public OpCommand(String name) { - super(name, "%nukkit.command.op.description", "%commands.op.description"); + super(name, "%nukkit.command.op.description", "%nukkit.command.op.usage"); this.setPermission("nukkit.command.op.give"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, false) }); } @@ -29,21 +29,22 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (!this.testPermission(sender)) { return true; } + if (args.length == 0) { - sender.sendMessage(new TranslationContainer("commands.op.usage", this.usageMessage)); + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return false; } String name = args[0]; IPlayer player = sender.getServer().getOfflinePlayer(name); - - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.op.success", player.getName())); if (player instanceof Player) { + player.setOp(true); ((Player) player).sendMessage(new TranslationContainer(TextFormat.GRAY + "%commands.op.message")); + } else { + sender.getServer().addOp(name); } - player.setOp(true); - + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.op.success", player.getName())); return true; } } diff --git a/src/main/java/cn/nukkit/command/defaults/PardonCommand.java b/src/main/java/cn/nukkit/command/defaults/PardonCommand.java index 0ffd5296b72..5f471969213 100644 --- a/src/main/java/cn/nukkit/command/defaults/PardonCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/PardonCommand.java @@ -7,7 +7,7 @@ import cn.nukkit.lang.TranslationContainer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PardonCommand extends VanillaCommand { @@ -18,7 +18,7 @@ public PardonCommand(String name) { this.setAliases(new String[]{"unban"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, false) }); } diff --git a/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java b/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java index d3ae0695a85..91c8a113a88 100644 --- a/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/PardonIpCommand.java @@ -2,7 +2,6 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -11,7 +10,7 @@ import java.util.regex.Pattern; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PardonIpCommand extends VanillaCommand { @@ -22,7 +21,7 @@ public PardonIpCommand(String name) { this.setAliases(new String[]{"unbanip", "unban-ip", "pardonip"}); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("ip", CommandParamType.STRING) + new CommandParameter("ip") }); } diff --git a/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java b/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java index aea1e78c51e..478593f7c09 100644 --- a/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/ParticleCommand.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.item.Item; @@ -12,7 +11,6 @@ import cn.nukkit.level.particle.*; import cn.nukkit.math.Vector3; -import java.util.Random; import java.util.concurrent.ThreadLocalRandom; /** @@ -20,19 +18,21 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class ParticleCommand extends VanillaCommand { - private static final String[] ENUM_VALUES = new String[]{"explode", "hugeexplosion", "hugeexplosionseed", "bubble" + + private static final String[] ENUM_VALUES = {"explode", "hugeexplosion", "hugeexplosionseed", "bubble" , "splash", "wake", "water", "crit", "smoke", "spell", "instantspell", "dripwater", "driplava", "townaura" , "spore", "portal", "flame", "lava", "reddust", "snowballpoof", "slime", "itembreak", "terrain", "heart" , "ink", "droplet", "enchantmenttable", "happyvillager", "angryvillager", "forcefield"}; + public ParticleCommand(String name) { super(name, "%nukkit.command.particle.description", "%nukkit.command.particle.usage"); this.setPermission("nukkit.command.particle"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newEnum("effect", new CommandEnum("Particle", ENUM_VALUES)), - CommandParameter.newType("position", CommandParamType.POSITION), - CommandParameter.newType("count", true, CommandParamType.INT), - CommandParameter.newType("data", true, CommandParamType.INT) + new CommandParameter("name", false, ENUM_VALUES), + new CommandParameter("position", CommandParamType.POSITION, false), + new CommandParameter("count", CommandParamType.INT, true), + new CommandParameter("data", true) }); } @@ -44,7 +44,6 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (args.length < 4) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); - return true; } @@ -75,23 +74,18 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) try { double c = Double.parseDouble(args[4]); count = (int) c; - } catch (Exception e) { - //ignore - } + } catch (Exception ignored) {} } count = Math.max(1, count); int data = -1; if (args.length > 5) { try { - double d = Double.parseDouble(args[5]); + double d = Double.parseDouble(args[8]); data = (int) d; - } catch (Exception e) { - //ignore - } + } catch (Exception ignored) {} } - - Particle particle = this.getParticle(name, position, data); + Particle particle = getParticle(name, position, data); if (particle == null) { position.level.addParticleEffect(position.asVector3f(), args[0], -1, position.level.getDimension()); @@ -100,7 +94,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.particle.success", name, String.valueOf(count))); - Random random = ThreadLocalRandom.current(); + ThreadLocalRandom random = ThreadLocalRandom.current(); for (int i = 0; i < count; i++) { particle.setComponents( @@ -114,7 +108,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - private Particle getParticle(String name, Vector3 pos, int data) { + private static Particle getParticle(String name, Vector3 pos, int data) { switch (name) { case "explode": return new ExplodeParticle(pos); @@ -202,7 +196,7 @@ private Particle getParticle(String name, Vector3 pos, int data) { return null; } - private static double getDouble(String arg, double defaultValue) throws Exception { + private static double getDouble(String arg, double defaultValue) { if (arg.startsWith("~")) { String relativePos = arg.substring(1); if (relativePos.isEmpty()) { diff --git a/src/main/java/cn/nukkit/command/defaults/PlaySoundCommand.java b/src/main/java/cn/nukkit/command/defaults/PlaySoundCommand.java new file mode 100644 index 00000000000..f5ad9567ab5 --- /dev/null +++ b/src/main/java/cn/nukkit/command/defaults/PlaySoundCommand.java @@ -0,0 +1,78 @@ +package cn.nukkit.command.defaults; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandParamType; +import cn.nukkit.command.data.CommandParameter; +import cn.nukkit.lang.TranslationContainer; + +public class PlaySoundCommand extends VanillaCommand { + + public PlaySoundCommand(String name) { + super(name, "%nukkit.command.playsound.description", "%commands.playsound.usage"); + this.setPermission("nukkit.command.playsound"); + this.commandParameters.clear(); + this.commandParameters.put("default", new CommandParameter[]{ + new CommandParameter("sound", CommandParamType.STRING, false), + new CommandParameter("player", CommandParamType.TARGET, true) + }); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!this.testPermission(sender)) { + return true; + } + + if (args.length == 0) { + sender.sendMessage(new TranslationContainer("%commands.playsound.usage", this.usageMessage)); + return false; + } + + if (args.length == 1) { + if (!(sender instanceof Player)) { + sender.sendMessage(new TranslationContainer("commands.generic.ingame")); + return true; + } + + Player p = (Player) sender; + + p.getLevel().addSound(p, args[0], p); + p.sendMessage(new TranslationContainer("commands.playsound.success", args[0], p.getName())); + + return true; + } + + if (args[1].equalsIgnoreCase("@a")) { + for (Player p : Server.getInstance().getOnlinePlayers().values()) { + p.getLevel().addSound(p, args[0], p); + } + + sender.sendMessage(new TranslationContainer("commands.playsound.success", args[0], "@a")); + + return true; + } + + if (args[1].equalsIgnoreCase("@s") && sender instanceof Player) { + Player p = (Player) sender; + + p.getLevel().addSound(p, args[0], p); + sender.sendMessage(new TranslationContainer("commands.playsound.success", args[0], p.getName())); + + return true; + } + + Player p = Server.getInstance().getPlayerExact(args[1]); + + if (p == null) { + sender.sendMessage(new TranslationContainer("commands.generic.player.notFound")); + return true; + } + + p.getLevel().addSound(p, args[0], p); + sender.sendMessage(new TranslationContainer("commands.playsound.success", args[0], p.getName())); + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/defaults/PluginsCommand.java b/src/main/java/cn/nukkit/command/defaults/PluginsCommand.java index b3377bfd251..52438e9dc02 100644 --- a/src/main/java/cn/nukkit/command/defaults/PluginsCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/PluginsCommand.java @@ -14,12 +14,9 @@ public class PluginsCommand extends VanillaCommand { public PluginsCommand(String name) { - super(name, - "%nukkit.command.plugins.description", - "%nukkit.command.plugins.usage", - new String[]{"pl"} - ); + super(name, "%nukkit.command.plugins.description", "%nukkit.command.plugins.usage"); this.setPermission("nukkit.command.plugins"); + this.setAliases(new String[]{"pl"}); this.commandParameters.clear(); } @@ -29,18 +26,18 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - this.sendPluginList(sender); + sendPluginList(sender); return true; } - private void sendPluginList(CommandSender sender) { + private static void sendPluginList(CommandSender sender) { StringBuilder list = new StringBuilder(); Map plugins = sender.getServer().getPluginManager().getPlugins(); for (Plugin plugin : plugins.values()) { if (list.length() > 0) { list.append(TextFormat.WHITE + ", "); } - list.append(plugin.isEnabled() ? TextFormat.GREEN : TextFormat.RED); + list.append(plugin.isEnabled() ? TextFormat.GREEN : TextFormat.RED + "*"); list.append(plugin.getDescription().getFullName()); } diff --git a/src/main/java/cn/nukkit/command/defaults/ReloadCommand.java b/src/main/java/cn/nukkit/command/defaults/ReloadCommand.java index 718c140b5b5..cf584839a73 100644 --- a/src/main/java/cn/nukkit/command/defaults/ReloadCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/ReloadCommand.java @@ -6,7 +6,7 @@ import cn.nukkit.utils.TextFormat; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ReloadCommand extends VanillaCommand { diff --git a/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java b/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java index 762cd3140bb..4967c22d8a0 100644 --- a/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java @@ -21,6 +21,11 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (!this.testPermission(sender)) { return true; } + if (args.length == 1 && args[0].equalsIgnoreCase("hold")) { + sender.getServer().holdWorldSave = true; + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-on")); + return true; + } sender.getServer().setAutoSave(false); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.disabled")); return true; diff --git a/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java b/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java index a3f6a5dfdd3..d9818a32cc3 100644 --- a/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java @@ -21,6 +21,11 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (!this.testPermission(sender)) { return true; } + if (args.length == 1 && args[0].equalsIgnoreCase("hold")) { + sender.getServer().holdWorldSave = false; + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-off")); + return true; + } sender.getServer().setAutoSave(true); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.enabled")); return true; diff --git a/src/main/java/cn/nukkit/command/defaults/SayCommand.java b/src/main/java/cn/nukkit/command/defaults/SayCommand.java index a55d3c459cf..ae234c5f085 100644 --- a/src/main/java/cn/nukkit/command/defaults/SayCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SayCommand.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.command.CommandSender; import cn.nukkit.command.ConsoleCommandSender; -import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.utils.TextFormat; @@ -19,7 +18,7 @@ public SayCommand(String name) { this.setPermission("nukkit.command.say"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("message", CommandParamType.MESSAGE) + new CommandParameter("message") }); } @@ -45,7 +44,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) StringBuilder msg = new StringBuilder(); for (String arg : args) { - msg.append(arg).append(" "); + msg.append(arg).append(' '); } if (msg.length() > 0) { msg = new StringBuilder(msg.substring(0, msg.length() - 1)); diff --git a/src/main/java/cn/nukkit/command/defaults/SeedCommand.java b/src/main/java/cn/nukkit/command/defaults/SeedCommand.java index 3c70e7dd5f1..b269df22c97 100644 --- a/src/main/java/cn/nukkit/command/defaults/SeedCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SeedCommand.java @@ -5,7 +5,7 @@ import cn.nukkit.lang.TranslationContainer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class SeedCommand extends VanillaCommand { diff --git a/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java b/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java index fcb93688eb2..2da5ba32e78 100644 --- a/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SetWorldSpawnCommand.java @@ -16,12 +16,13 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class SetWorldSpawnCommand extends VanillaCommand { + public SetWorldSpawnCommand(String name) { super(name, "%nukkit.command.setworldspawn.description", "%commands.setworldspawn.usage"); this.setPermission("nukkit.command.setworldspawn"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("spawnPoint", true, CommandParamType.POSITION) + new CommandParameter("blockPos", CommandParamType.POSITION, true) }); } @@ -54,9 +55,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } level.setSpawnLocation(pos); DecimalFormat round2 = new DecimalFormat("##0.00"); - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.setworldspawn.success", round2.format(pos.x), - round2.format(pos.y), - round2.format(pos.z))); + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.setworldspawn.success", round2.format(pos.x), round2.format(pos.y), round2.format(pos.z))); return true; } } diff --git a/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java b/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java index 1afd5d68e3d..3919114c731 100644 --- a/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SpawnpointCommand.java @@ -17,6 +17,7 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class SpawnpointCommand extends VanillaCommand { + public SpawnpointCommand(String name) { super(name, "%nukkit.command.spawnpoint.description", "%commands.spawnpoint.usage"); this.setPermission("nukkit.command.spawnpoint"); @@ -41,7 +42,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } } else { - target = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + target = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (target == null) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); return true; @@ -62,8 +63,8 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; } - if (y < 0) y = 0; - if (y > 256) y = 256; + if (y < target.getLevel().getMinBlockY()) y = target.getLevel().getMinBlockY(); + if (y > target.getLevel().getMaxBlockY()) y = target.getLevel().getMaxBlockY(); target.setSpawn(new Position(x, y, z, level)); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.spawnpoint.success", target.getName(), round2.format(x), @@ -88,4 +89,4 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/defaults/StatusCommand.java b/src/main/java/cn/nukkit/command/defaults/StatusCommand.java index 7dc9c054975..2c2c6498ea3 100644 --- a/src/main/java/cn/nukkit/command/defaults/StatusCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/StatusCommand.java @@ -15,6 +15,7 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class StatusCommand extends VanillaCommand { + private static final String UPTIME_FORMAT = TextFormat.RED + "%d" + TextFormat.GOLD + " days " + TextFormat.RED + "%d" + TextFormat.GOLD + " hours " + TextFormat.RED + "%d" + TextFormat.GOLD + " minutes " + @@ -47,13 +48,13 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) tpsColor = TextFormat.GOLD; } - sender.sendMessage(TextFormat.GOLD + "Current TPS: " + tpsColor + NukkitMath.round(tps, 2)); + sender.sendMessage(TextFormat.GOLD + "TPS / Average: " + tpsColor + NukkitMath.round(tps, 2) + " / " + NukkitMath.round(server.getTicksPerSecondAverage(), 2)); - sender.sendMessage(TextFormat.GOLD + "Load: " + tpsColor + server.getTickUsage() + "%"); + sender.sendMessage(TextFormat.GOLD + "Load / Average: " + tpsColor + server.getTickUsage() + "% / " + server.getTickUsageAverage() + '%'); - sender.sendMessage(TextFormat.GOLD + "Network upload: " + TextFormat.GREEN + NukkitMath.round((server.getNetwork().getUpload() / 1024 * 1000), 2) + " kB/s"); + //sender.sendMessage(TextFormat.GOLD + "Network upload: " + TextFormat.GREEN + NukkitMath.round((server.getNetwork().getUpload() / 1024 * 1000), 2) + " kB/s"); - sender.sendMessage(TextFormat.GOLD + "Network download: " + TextFormat.GREEN + NukkitMath.round((server.getNetwork().getDownload() / 1024 * 1000), 2) + " kB/s"); + //sender.sendMessage(TextFormat.GOLD + "Network download: " + TextFormat.GREEN + NukkitMath.round((server.getNetwork().getDownload() / 1024 * 1000), 2) + " kB/s"); sender.sendMessage(TextFormat.GOLD + "Thread count: " + TextFormat.GREEN + Thread.getAllStackTraces().size()); @@ -69,38 +70,40 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) usageColor = TextFormat.GOLD; } - sender.sendMessage(TextFormat.GOLD + "Used memory: " + usageColor + usedMB + " MB. (" + NukkitMath.round(usage, 2) + "%)"); + sender.sendMessage(TextFormat.GOLD + "Used memory: " + usageColor + usedMB + " MB (" + NukkitMath.round(usage, 2) + "%)"); - sender.sendMessage(TextFormat.GOLD + "Total memory: " + TextFormat.RED + totalMB + " MB."); + sender.sendMessage(TextFormat.GOLD + "Total memory: " + TextFormat.RED + totalMB + " MB"); - sender.sendMessage(TextFormat.GOLD + "Maximum VM memory: " + TextFormat.RED + maxMB + " MB."); + sender.sendMessage(TextFormat.GOLD + "Maximum VM memory: " + TextFormat.RED + maxMB + " MB"); sender.sendMessage(TextFormat.GOLD + "Available processors: " + TextFormat.GREEN + runtime.availableProcessors()); + int players = server.getOnlinePlayersCount(); + TextFormat playerColor = TextFormat.GREEN; - if (((float) server.getOnlinePlayers().size() / (float) server.getMaxPlayers()) > 0.85) { + if (((float) players / (float) server.getMaxPlayers()) > 0.85) { playerColor = TextFormat.GOLD; } - sender.sendMessage(TextFormat.GOLD + "Players: " + playerColor + server.getOnlinePlayers().size() + TextFormat.GREEN + " online, " + - TextFormat.RED + server.getMaxPlayers() + TextFormat.GREEN + " max. "); + sender.sendMessage(TextFormat.GOLD + "Players: " + playerColor + players + TextFormat.GREEN + " online, " + + TextFormat.RED + server.getMaxPlayers() + TextFormat.GREEN + " max"); for (Level level : server.getLevels().values()) { sender.sendMessage( - TextFormat.GOLD + "World \"" + level.getFolderName() + "\"" + (!Objects.equals(level.getFolderName(), level.getName()) ? " (" + level.getName() + ")" : "") + ": " + + TextFormat.GOLD + "World \"" + level.getFolderName() + '"' + (!Objects.equals(level.getFolderName(), level.getName()) ? " (" + level.getName() + ')' : "") + ": " + TextFormat.RED + level.getChunks().size() + TextFormat.GREEN + " chunks, " + - TextFormat.RED + level.getEntities().length + TextFormat.GREEN + " entities, " + - TextFormat.RED + level.getBlockEntities().size() + TextFormat.GREEN + " blockEntities." + + TextFormat.RED + level.entities.size() + TextFormat.GREEN + " entities, " + + TextFormat.RED + level.getBlockEntities().size() + TextFormat.GREEN + " block entities." + " Time " + ((level.getTickRate() > 1 || level.getTickRateTime() > 40) ? TextFormat.RED : TextFormat.YELLOW) + NukkitMath.round(level.getTickRateTime(), 2) + "ms" + - (level.getTickRate() > 1 ? " (tick rate " + level.getTickRate() + ")" : "") + (level.getTickRate() > 1 ? " (tick rate " + level.getTickRate() + ')' : "") ); } return true; } - private static String formatUptime(long uptime) { + public static String formatUptime(long uptime) { long days = TimeUnit.MILLISECONDS.toDays(uptime); uptime -= TimeUnit.DAYS.toMillis(days); long hours = TimeUnit.MILLISECONDS.toHours(uptime); @@ -110,4 +113,4 @@ private static String formatUptime(long uptime) { long seconds = TimeUnit.MILLISECONDS.toSeconds(uptime); return String.format(UPTIME_FORMAT, days, hours, minutes, seconds); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/defaults/StopCommand.java b/src/main/java/cn/nukkit/command/defaults/StopCommand.java index 0c89133e00f..e4c22b0601d 100644 --- a/src/main/java/cn/nukkit/command/defaults/StopCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/StopCommand.java @@ -5,7 +5,7 @@ import cn.nukkit.lang.TranslationContainer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class StopCommand extends VanillaCommand { diff --git a/src/main/java/cn/nukkit/command/defaults/SummonCommand.java b/src/main/java/cn/nukkit/command/defaults/SummonCommand.java new file mode 100644 index 00000000000..74744eeea64 --- /dev/null +++ b/src/main/java/cn/nukkit/command/defaults/SummonCommand.java @@ -0,0 +1,75 @@ +package cn.nukkit.command.defaults; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandSender; +import cn.nukkit.command.data.CommandParamType; +import cn.nukkit.command.data.CommandParameter; +import cn.nukkit.entity.Entity; +import cn.nukkit.level.Position; +import cn.nukkit.network.protocol.AddEntityPacket; +import cn.nukkit.utils.TextFormat; + +import java.util.ArrayList; +import java.util.List; + +public class SummonCommand extends Command { + + public SummonCommand(String name) { + super(name, "%nukkit.command.summon.description", "%nukkit.command.summon.usage"); + this.setPermission("nukkit.command.summon"); + this.commandParameters.clear(); + List entityNames = new ArrayList<>(); + for (String key : AddEntityPacket.LEGACY_IDS.values()) { + entityNames.add(key.substring(10)); + } + this.commandParameters.put("default", new CommandParameter[]{ + CommandParameter.newEnum("entityType", false, entityNames.toArray(new String[0])), + new CommandParameter("player", CommandParamType.TARGET, true) + }); + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!this.testPermission(sender)) { + return true; + } + + if (args.length == 0 || (args.length == 1 && !(sender instanceof Player))) { + return false; + } + + // Convert Minecraft format to the format what Nukkit uses + String mob = Character.toUpperCase(args[0].charAt(0)) + args[0].substring(1); + int max = mob.length() - 1; + for (int x = 2; x < max; x++) { + if (mob.charAt(x) == '_') { + mob = mob.substring(0, x) + Character.toUpperCase(mob.charAt(x + 1)) + mob.substring(x + 2); + } + } + + Player playerThatSpawns; + + if (args.length == 2) { + playerThatSpawns = Server.getInstance().getPlayerExact(args[1].replace("@s", sender.getName())); + } else { + playerThatSpawns = (Player) sender; + } + + if (playerThatSpawns != null) { + Position pos = playerThatSpawns.getPosition().floor().add(0.5, 0, 0.5); + Entity ent; + if ((ent = Entity.createEntity(mob, pos)) != null) { + ent.spawnToAll(); + sender.sendMessage("\u00A76Spawned " + mob + " to " + playerThatSpawns.getName()); + } else { + sender.sendMessage(TextFormat.RED + "Unable to spawn " + mob); + } + } else { + sender.sendMessage(TextFormat.RED + "Unknown player " + (args.length == 2 ? args[1] : sender.getName())); + } + + return true; + } +} diff --git a/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java b/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java index e69aa948808..f025e56fa5d 100644 --- a/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TeleportCommand.java @@ -16,27 +16,24 @@ * Package cn.nukkit.command.defaults in project Nukkit . */ public class TeleportCommand extends VanillaCommand { + public TeleportCommand(String name) { super(name, "%nukkit.command.tp.description", "%commands.tp.usage"); this.setPermission("nukkit.command.teleport"); this.commandParameters.clear(); this.commandParameters.put("->Player", new CommandParameter[]{ - CommandParameter.newType("destination", CommandParamType.TARGET), + new CommandParameter("player", CommandParamType.TARGET, false), }); this.commandParameters.put("Player->Player", new CommandParameter[]{ - CommandParameter.newType("victim", CommandParamType.TARGET), - CommandParameter.newType("destination", CommandParamType.TARGET) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("target", CommandParamType.TARGET, false), }); this.commandParameters.put("Player->Pos", new CommandParameter[]{ - CommandParameter.newType("victim", CommandParamType.TARGET), - CommandParameter.newType("destination", CommandParamType.POSITION), - CommandParameter.newType("yRot", true, CommandParamType.VALUE), - CommandParameter.newType("xRot", true, CommandParamType.VALUE) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("blockPos", CommandParamType.POSITION, false), }); this.commandParameters.put("->Pos", new CommandParameter[]{ - CommandParameter.newType("destination", CommandParamType.POSITION), - CommandParameter.newType("yRot", true, CommandParamType.VALUE), - CommandParameter.newType("xRot", true, CommandParamType.VALUE) + new CommandParameter("blockPos", CommandParamType.POSITION, false), }); } @@ -59,21 +56,21 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } if (args.length == 1) { - target = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + target = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (target == null) { sender.sendMessage(TextFormat.RED + "Can't find player " + args[0]); return true; } } } else { - target = sender.getServer().getPlayer(args[0].replace("@s", sender.getName())); + target = sender.getServer().getPlayerExact(args[0].replace("@s", sender.getName())); if (target == null) { sender.sendMessage(TextFormat.RED + "Can't find player " + args[0]); return true; } if (args.length == 2) { origin = target; - target = sender.getServer().getPlayer(args[1].replace("@s", sender.getName())); + target = sender.getServer().getPlayerExact(args[1].replace("@s", sender.getName())); if (target == null) { sender.sendMessage(TextFormat.RED + "Can't find player " + args[1]); return true; @@ -106,11 +103,17 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; } - if (y < 0) y = 0; - if (y > 256) y = 256; + + if (x < -30000000) x = -30000000; + if (x > 30000000) x = 30000000; + if (y < -30000000) y = -30000000; + if (y > 30000000) y = 30000000; + if (z < -30000000) z = -30000000; + if (z > 30000000) z = 30000000; + if (args.length == 6 || (args.length == 5 && pos == 3)) { yaw = Integer.parseInt(args[pos++]); - pitch = Integer.parseInt(args[pos++]); + pitch = Integer.parseInt(args[pos]); } ((Player) target).teleport(new Location(x, y, z, yaw, pitch, ((Player) target).getLevel()), PlayerTeleportEvent.TeleportCause.COMMAND); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.tp.success.coordinates", target.getName(), String.valueOf(NukkitMath.round(x, 2)), String.valueOf(NukkitMath.round(y, 2)), String.valueOf(NukkitMath.round(z, 2)))); diff --git a/src/main/java/cn/nukkit/command/defaults/TellCommand.java b/src/main/java/cn/nukkit/command/defaults/TellCommand.java index 58cf3004cf4..383205c202e 100644 --- a/src/main/java/cn/nukkit/command/defaults/TellCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TellCommand.java @@ -20,8 +20,8 @@ public TellCommand(String name) { this.setPermission("nukkit.command.tell"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newType("message", CommandParamType.MESSAGE) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("message", CommandParamType.TEXT, false) }); } @@ -33,13 +33,12 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (args.length < 2) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); - return false; } String name = args[0].replace("@s", sender.getName()); - Player player = sender.getServer().getPlayer(name); + Player player = sender.getServer().getPlayerExact(name); if (player == null) { sender.sendMessage(new TranslationContainer("commands.generic.player.notFound")); return true; @@ -52,16 +51,22 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) StringBuilder msg = new StringBuilder(); for (int i = 1; i < args.length; i++) { - msg.append(args[i]).append(" "); + msg.append(args[i]).append(' '); } - if (msg.length() > 0) { + if (msg.length() > 512) { + sender.sendMessage(TextFormat.RED + "The message is too long"); + return true; + } else if (msg.length() > 0) { msg = new StringBuilder(msg.substring(0, msg.length() - 1)); } String displayName = (sender instanceof Player ? ((Player) sender).getDisplayName() : sender.getName()); - sender.sendMessage("[" + sender.getName() + " -> " + player.getDisplayName() + "] " + msg); - player.sendMessage("[" + displayName + " -> " + player.getName() + "] " + msg); + sender.sendMessage('[' + sender.getName() + " -> " + player.getDisplayName() + "] " + msg); + player.sendMessage('[' + displayName + " -> " + player.getName() + "] " + msg); + if (sender instanceof Player) { + sender.getServer().getLogger().info('[' + sender.getName() + " -> " + player.getDisplayName() + "] " + msg); + } return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/TimeCommand.java b/src/main/java/cn/nukkit/command/defaults/TimeCommand.java index 5d6f39ad95d..b180aa6bf27 100644 --- a/src/main/java/cn/nukkit/command/defaults/TimeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TimeCommand.java @@ -18,10 +18,7 @@ public class TimeCommand extends VanillaCommand { public TimeCommand(String name) { super(name, "%nukkit.command.time.description", "%nukkit.command.time.usage"); - this.setPermission("nukkit.command.time.add;" + - "nukkit.command.time.set;" + - "nukkit.command.time.start;" + - "nukkit.command.time.stop"); + this.setPermission("nukkit.command.time.add;nukkit.command.time.set;nukkit.command.time.start;nukkit.command.time.stop"); this.commandParameters.clear(); this.commandParameters.put("1arg", new CommandParameter[]{ CommandParameter.newEnum("mode", new CommandEnum("TimeMode", "query", "start", "stop")) diff --git a/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java b/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java deleted file mode 100644 index beb9354e2ba..00000000000 --- a/src/main/java/cn/nukkit/command/defaults/TimingsCommand.java +++ /dev/null @@ -1,74 +0,0 @@ -package cn.nukkit.command.defaults; - -import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; -import cn.nukkit.command.data.CommandParameter; -import cn.nukkit.lang.TranslationContainer; -import co.aikar.timings.Timings; -import co.aikar.timings.TimingsExport; - -/** - * @author fromgate - * @author Pub4Game - */ -public class TimingsCommand extends VanillaCommand { - - public TimingsCommand(String name) { - super(name, "%nukkit.command.timings.description", "%nukkit.command.timings.usage"); - this.setPermission("nukkit.command.timings"); - this.commandParameters.clear(); - this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newEnum("action", new CommandEnum("TimingsAction", "on", "off", "paste", "verbon", "verboff", "reset", "report")) - }); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (!this.testPermission(sender)) { - return true; - } - - if (args.length != 1) { - sender.sendMessage(new TranslationContainer("commands.generic.usage", usageMessage)); - return true; - } - - String mode = args[0].toLowerCase(); - - if (mode.equals("on")) { - Timings.setTimingsEnabled(true); - Timings.reset(); - sender.sendMessage(new TranslationContainer("nukkit.command.timings.enable")); - return true; - } else if (mode.equals("off")) { - Timings.setTimingsEnabled(false); - sender.sendMessage(new TranslationContainer("nukkit.command.timings.disable")); - return true; - } - - if (!Timings.isTimingsEnabled()) { - sender.sendMessage(new TranslationContainer("nukkit.command.timings.timingsDisabled")); - return true; - } - - switch (mode) { - case "verbon": - sender.sendMessage(new TranslationContainer("nukkit.command.timings.verboseEnable")); - Timings.setVerboseEnabled(true); - break; - case "verboff": - sender.sendMessage(new TranslationContainer("nukkit.command.timings.verboseDisable")); - Timings.setVerboseEnabled(true); - break; - case "reset": - Timings.reset(); - sender.sendMessage(new TranslationContainer("nukkit.command.timings.reset")); - break; - case "report": - case "paste": - TimingsExport.reportTimings(sender); - break; - } - return true; - } -} diff --git a/src/main/java/cn/nukkit/command/defaults/TitleCommand.java b/src/main/java/cn/nukkit/command/defaults/TitleCommand.java index b7be1a97d31..cc6f2085bb0 100644 --- a/src/main/java/cn/nukkit/command/defaults/TitleCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/TitleCommand.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.command.CommandSender; -import cn.nukkit.command.data.CommandEnum; import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; @@ -13,30 +12,41 @@ * @author Tee7even */ public class TitleCommand extends VanillaCommand { + public TitleCommand(String name) { super(name, "%nukkit.command.title.description", "%nukkit.command.title.usage"); this.setPermission("nukkit.command.title"); this.commandParameters.clear(); this.commandParameters.put("clear", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("clear", new CommandEnum("TitleClear", "clear")) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("clear", new String[]{"clear"}) }); this.commandParameters.put("reset", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("reset", new CommandEnum("TitleReset", "reset")) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("reset", new String[]{"reset"}) + }); + this.commandParameters.put("title", new CommandParameter[]{ + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("title", new String[]{"title"}), + new CommandParameter("titleText", CommandParamType.STRING, false) }); - this.commandParameters.put("set", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("titleLocation", new CommandEnum("TitleSet", "title", "subtitle", "actionbar")), - CommandParameter.newType("titleText", CommandParamType.MESSAGE) + this.commandParameters.put("subtitle", new CommandParameter[]{ + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("subtitle", new String[]{"subtitle"}), + new CommandParameter("titleText", CommandParamType.STRING, false) + }); + this.commandParameters.put("actionbar", new CommandParameter[]{ + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("actionbar", new String[]{"actionbar"}), + new CommandParameter("titleText", CommandParamType.STRING, false) }); this.commandParameters.put("times", new CommandParameter[]{ - CommandParameter.newType("player", CommandParamType.TARGET), - CommandParameter.newEnum("times", new CommandEnum("TitleTimes", "times")), - CommandParameter.newType("fadeIn", CommandParamType.INT), - CommandParameter.newType("stay", CommandParamType.INT), - CommandParameter.newType("fadeOut", CommandParamType.INT) + new CommandParameter("player", CommandParamType.TARGET, false), + new CommandParameter("times", new String[]{"times"}), + new CommandParameter("fadeIn", CommandParamType.INT, false), + new CommandParameter("stay", CommandParamType.INT, false), + new CommandParameter("fadeOut", CommandParamType.INT, false) }); } @@ -81,10 +91,10 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) player.setSubtitle(args[2]); sender.sendMessage(new TranslationContainer("nukkit.command.title.subtitle", TextFormat.clean(args[2]), player.getName())); break; - /*case "actionbar": - player.sendActionBarTitle(args[2]); - sender.sendMessage(new TranslationContainer("nukkit.command.title.actionbar", new String[]{TextFormat.clean(args[2]), player.getName()})); - break;*/ + case "actionbar": + player.sendActionBar(args[2]); + sender.sendMessage(new TranslationContainer("nukkit.command.title.actionbar", TextFormat.clean(args[2]), player.getName())); + break; default: sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return false; @@ -111,4 +121,4 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/command/defaults/VanillaCommand.java b/src/main/java/cn/nukkit/command/defaults/VanillaCommand.java index 10d4276fd58..ee701172696 100644 --- a/src/main/java/cn/nukkit/command/defaults/VanillaCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/VanillaCommand.java @@ -3,7 +3,7 @@ import cn.nukkit.command.Command; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class VanillaCommand extends Command { diff --git a/src/main/java/cn/nukkit/command/defaults/VersionCommand.java b/src/main/java/cn/nukkit/command/defaults/VersionCommand.java index 5c4e9234aa1..73896c5059b 100644 --- a/src/main/java/cn/nukkit/command/defaults/VersionCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/VersionCommand.java @@ -26,7 +26,7 @@ public VersionCommand(String name) { this.setPermission("nukkit.command.version"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("pluginName", true, CommandParamType.STRING) + new CommandParameter("pluginName", CommandParamType.STRING, true) }); } @@ -35,7 +35,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) if (!this.testPermission(sender)) { return true; } - if (args.length == 0) { + if (args.length == 0 || !sender.hasPermission("nukkit.command.version.plugins")) { sender.sendMessage(new TranslationContainer("nukkit.server.info.extended", sender.getServer().getName(), sender.getServer().getNukkitVersion(), sender.getServer().getCodename(), @@ -44,7 +44,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) String.valueOf(ProtocolInfo.CURRENT_PROTOCOL))); } else { StringBuilder pluginName = new StringBuilder(); - for (String arg : args) pluginName.append(arg).append(" "); + for (String arg : args) pluginName.append(arg).append(' '); pluginName = new StringBuilder(pluginName.toString().trim()); final boolean[] found = {false}; final Plugin[] exactPlugin = {sender.getServer().getPluginManager().getPlugin(pluginName.toString())}; diff --git a/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java b/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java index f85de3c6d19..b65753f6b85 100644 --- a/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/WeatherCommand.java @@ -10,7 +10,7 @@ import cn.nukkit.level.Level; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class WeatherCommand extends VanillaCommand { @@ -81,6 +81,5 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.weather.usage", this.usageMessage)); return false; } - } } diff --git a/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java b/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java index 39b3a487e21..45a5ec175a3 100644 --- a/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/WhitelistCommand.java @@ -34,6 +34,7 @@ public WhitelistCommand(String name) { }); } + @Override public boolean execute(CommandSender sender, String commandLabel, String[] args) { if (!this.testPermission(sender)) { @@ -46,24 +47,23 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (args.length == 1) { - if (this.badPerm(sender, args[0].toLowerCase())) { + if (badPerm(sender, args[0].toLowerCase())) { return false; } switch (args[0].toLowerCase()) { case "reload": sender.getServer().reloadWhitelist(); Command.broadcastCommandMessage(sender, new TranslationContainer("commands.whitelist.reloaded")); - return true; case "on": sender.getServer().setPropertyBoolean("white-list", true); + sender.getServer().whitelistEnabled = true; Command.broadcastCommandMessage(sender, new TranslationContainer("commands.whitelist.enabled")); - return true; case "off": sender.getServer().setPropertyBoolean("white-list", false); + sender.getServer().whitelistEnabled = false; Command.broadcastCommandMessage(sender, new TranslationContainer("commands.whitelist.disabled")); - return true; case "list": StringBuilder result = new StringBuilder(); @@ -85,8 +85,8 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(new TranslationContainer("commands.generic.usage", "%commands.whitelist.remove.usage")); return true; } - } else if (args.length == 2) { - if (this.badPerm(sender, args[0].toLowerCase())) { + } else { + if (badPerm(sender, args[0].toLowerCase())) { return false; } switch (args[0].toLowerCase()) { @@ -106,7 +106,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - private boolean badPerm(CommandSender sender, String perm) { + private static boolean badPerm(CommandSender sender, String perm) { if (!sender.hasPermission("nukkit.command.whitelist." + perm)) { sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.permission")); diff --git a/src/main/java/cn/nukkit/command/defaults/XpCommand.java b/src/main/java/cn/nukkit/command/defaults/XpCommand.java index 9ca01f11b92..c5038a77cb3 100644 --- a/src/main/java/cn/nukkit/command/defaults/XpCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/XpCommand.java @@ -13,17 +13,14 @@ * Package cn.nukkit.command.defaults in project nukkit. */ public class XpCommand extends Command { + public XpCommand(String name) { super(name, "%nukkit.command.xp.description", "%commands.xp.usage"); this.setPermission("nukkit.command.xp"); this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ - CommandParameter.newType("amount", CommandParamType.INT), - CommandParameter.newType("player", true, CommandParamType.TARGET) - }); - this.commandParameters.put("level", new CommandParameter[]{ - CommandParameter.newPostfix("amount", "l"), - CommandParameter.newType("player", true, CommandParamType.TARGET) + new CommandParameter("amount|level", CommandParamType.INT, false), + new CommandParameter("player", CommandParamType.TARGET, true) }); } @@ -45,7 +42,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } amountString = args[0]; playerName = args[1]; - player = sender.getServer().getPlayer(playerName); + player = sender.getServer().getPlayerExact(playerName); } else { if (args.length == 1) { amountString = args[0]; @@ -53,7 +50,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } else if (args.length == 2) { amountString = args[0]; playerName = args[1].replace("@s", sender.getName()); - player = sender.getServer().getPlayer(playerName); + player = sender.getServer().getPlayerExact(playerName); } else { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return true; diff --git a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java index 6fae8ab119f..d111773dcd0 100644 --- a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java +++ b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java @@ -12,8 +12,8 @@ * @author Tee7even */ public class SimpleCommand extends Command { - private Object object; - private Method method; + private final Object object; + private final Method method; private boolean forbidConsole; private int maxArgs; private int minArgs; @@ -37,7 +37,7 @@ public void setMinArgs(int minArgs) { } public void sendUsageMessage(CommandSender sender) { - if (!this.usageMessage.equals("")) { + if (!this.usageMessage.isEmpty()) { sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); } } diff --git a/src/main/java/cn/nukkit/console/NukkitConsole.java b/src/main/java/cn/nukkit/console/NukkitConsole.java index a83a589b770..41e005d89a7 100644 --- a/src/main/java/cn/nukkit/console/NukkitConsole.java +++ b/src/main/java/cn/nukkit/console/NukkitConsole.java @@ -2,7 +2,6 @@ import cn.nukkit.Server; import cn.nukkit.event.server.ServerCommandEvent; -import co.aikar.timings.Timings; import lombok.RequiredArgsConstructor; import net.minecrell.terminalconsole.SimpleTerminalConsole; import org.jline.reader.LineReader; @@ -14,27 +13,25 @@ @RequiredArgsConstructor public class NukkitConsole extends SimpleTerminalConsole { - private final Server server; + private final BlockingQueue consoleQueue = new LinkedBlockingQueue<>(); - private AtomicBoolean executingCommands = new AtomicBoolean(false); + private final AtomicBoolean executingCommands = new AtomicBoolean(false); @Override protected boolean isRunning() { - return server.isRunning(); + return Server.getInstance().isRunning(); } @Override protected void runCommand(String command) { if (executingCommands.get()) { - Timings.serverCommandTimer.startTiming(); - ServerCommandEvent event = new ServerCommandEvent(server.getConsoleSender(), command); - if (server.getPluginManager() != null) { - server.getPluginManager().callEvent(event); + ServerCommandEvent event = new ServerCommandEvent(Server.getInstance().getConsoleSender(), command); + if (Server.getInstance().getPluginManager() != null) { + Server.getInstance().getPluginManager().callEvent(event); } if (!event.isCancelled()) { - Server.getInstance().getScheduler().scheduleTask(() -> server.dispatchCommand(event.getSender(), event.getCommand())); + Server.getInstance().getScheduler().scheduleTask(() -> Server.getInstance().dispatchCommand(event.getSender(), event.getCommand())); } - Timings.serverCommandTimer.stopTiming(); } else { consoleQueue.add(command); } @@ -50,12 +47,12 @@ public String readLine() { @Override protected void shutdown() { - server.shutdown(); + Server.getInstance().shutdown(); } @Override protected LineReader buildReader(LineReaderBuilder builder) { - builder.completer(new NukkitConsoleCompleter(server)); + builder.completer(new NukkitConsoleCompleter()); builder.appName("Nukkit"); builder.option(LineReader.Option.HISTORY_BEEP, false); builder.option(LineReader.Option.HISTORY_IGNORE_DUPS, true); diff --git a/src/main/java/cn/nukkit/console/NukkitConsoleCompleter.java b/src/main/java/cn/nukkit/console/NukkitConsoleCompleter.java index 60d4943ed46..e898cea8cc7 100644 --- a/src/main/java/cn/nukkit/console/NukkitConsoleCompleter.java +++ b/src/main/java/cn/nukkit/console/NukkitConsoleCompleter.java @@ -14,7 +14,6 @@ @RequiredArgsConstructor public class NukkitConsoleCompleter implements Completer { - private final Server server; @Override public void complete(LineReader lineReader, ParsedLine parsedLine, List candidates) { @@ -35,7 +34,7 @@ public void complete(LineReader lineReader, ParsedLine parsedLine, List 0 && !parsedLine.word().isEmpty()) { String word = parsedLine.word(); SortedSet names = new TreeSet<>(); - server.getOnlinePlayers().values().forEach((p) -> names.add(p.getName())); + Server.getInstance().getOnlinePlayers().values().forEach((p) -> names.add(p.getName())); for (String match : names) { if (!match.toLowerCase().startsWith(word.toLowerCase())) { continue; @@ -46,8 +45,8 @@ public void complete(LineReader lineReader, ParsedLine parsedLine, List commandConsumer) { - for (String command : server.getCommandMap().getCommands().keySet()) { + private static void addCandidates(Consumer commandConsumer) { + for (String command : Server.getInstance().getCommandMap().getCommands().keySet()) { if (!command.contains(":")) { commandConsumer.accept(command); } diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java b/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java new file mode 100644 index 00000000000..c3727230159 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java @@ -0,0 +1,13 @@ +package cn.nukkit.customblock; + +import cn.nukkit.customblock.container.BlockContainer; +import com.nukkitx.nbt.NbtMap; +import lombok.Data; + +@Data +public class CustomBlockDefinition { + private final String identifier; + private final NbtMap networkData; + private final int legacyId; + private final Class typeOf; +} diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockManager.java b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java new file mode 100644 index 00000000000..cae9d1c47b2 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java @@ -0,0 +1,316 @@ +package cn.nukkit.customblock; + +import cn.nukkit.Server; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.customblock.comparator.HashedPaletteComparator; +import cn.nukkit.customblock.container.BlockContainer; +import cn.nukkit.customblock.container.BlockContainerFactory; +import cn.nukkit.customblock.container.BlockStorageContainer; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.customblock.properties.BlockProperty; +import cn.nukkit.customblock.properties.EnumBlockProperty; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.item.RuntimeItems; +import cn.nukkit.level.BlockPalette; +import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.level.format.leveldb.BlockStateMapping; +import cn.nukkit.level.format.leveldb.LevelDBConstants; +import cn.nukkit.level.format.leveldb.NukkitLegacyMapper; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import com.nukkitx.nbt.*; +import it.unimi.dsi.fastutil.ints.*; +import it.unimi.dsi.fastutil.objects.*; +import lombok.extern.log4j.Log4j2; + +import java.io.*; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +@Log4j2 +public class CustomBlockManager { + + public static final Path BIN_PATH = Paths.get("bin/"); + public static final int LOWEST_CUSTOM_BLOCK_ID = 5000; + + private static CustomBlockManager instance; + + public static CustomBlockManager init(Server server) { + if (instance == null) { + return instance = new CustomBlockManager(server); + } + throw new IllegalStateException("CustomBlockManager was already initialized!"); + } + + public static CustomBlockManager get() { + return instance; + } + + private final Server server; + + private final Int2ObjectMap blockDefinitions = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap legacy2CustomState = new Int2ObjectOpenHashMap<>(); + + private volatile boolean closed = false; + + private CustomBlockManager(Server server) { + this.server = server; + + Path filesPath = this.getBinPath(); + if (!Files.isDirectory(filesPath)) { + try { + Files.createDirectories(filesPath); + } catch (IOException e) { + throw new IllegalStateException("Failed to create BIN_DIRECTORY", e); + } + } + } + + public void registerCustomBlock(String identifier, int nukkitId, Supplier factory) { + this.registerCustomBlock(identifier, nukkitId, NbtMap.EMPTY, factory); + } + + public void registerCustomBlock(String identifier, int nukkitId, NbtMap networkData, Supplier factory) { + this.registerCustomBlock(identifier, nukkitId, null, networkData, meta -> factory.get()); + } + + public void registerCustomBlock(String identifier, int nukkitId, BlockProperties properties, NbtMap networkData, BlockContainerFactory factory) { + if (this.closed) { + throw new IllegalStateException("Block registry was already closed"); + } + + if (nukkitId < LOWEST_CUSTOM_BLOCK_ID) { + throw new IllegalArgumentException("Block ID can not be lower than " + LOWEST_CUSTOM_BLOCK_ID); + } + + BlockContainer blockSample = factory.create(0); + if (blockSample instanceof BlockStorageContainer && properties == null) { + properties = ((BlockStorageContainer) blockSample).getBlockProperties(); + log.warn("Custom block {} was registered using wrong method! Trying to use sample properties!", identifier); + } + + if (properties != null && networkData.isEmpty()) { + throw new IllegalArgumentException("Block network data can not be empty for block with more permutations: " + identifier); + } + + CustomBlockState defaultState = this.createBlockState(identifier, nukkitId << Block.DATA_BITS, properties, factory); + this.legacy2CustomState.put(defaultState.getLegacyId(), defaultState); + + // TODO: unsure if this is per state or not + CustomBlockDefinition definition = new CustomBlockDefinition(identifier, networkData, defaultState.getLegacyId(), blockSample.getClass()); + this.blockDefinitions.put(defaultState.getLegacyId(), definition); + + int itemId = 255 - nukkitId; + RuntimeItems.getMapping().registerItem(identifier, nukkitId, itemId, 0); + + if (properties != null) { + for (int meta = 1; meta < (1 << Block.DATA_BITS); meta++) { + CustomBlockState state; + try { + state = this.createBlockState(identifier, (nukkitId << Block.DATA_BITS) | meta, properties, factory); + } catch (InvalidBlockPropertyMetaException e) { + break; // Nukkit has more states than our block + } + this.legacy2CustomState.put(state.getLegacyId(), state); + } + } + } + + private CustomBlockState createBlockState(String identifier, int legacyId, BlockProperties properties, BlockContainerFactory factory) { + int meta = legacyId & Block.DATA_MASK; + + NbtMapBuilder statesBuilder = NbtMap.builder(); + if (properties != null) { + for (String propertyName : properties.getNames()) { + BlockProperty property = properties.getBlockProperty(propertyName); + if (property instanceof EnumBlockProperty) { + statesBuilder.put(property.getPersistenceName(), properties.getPersistenceValue(meta, propertyName)); + } else { + statesBuilder.put(property.getPersistenceName(), properties.getValue(meta, propertyName)); + } + } + } + + NbtMap state = NbtMap.builder() + .putString("name", identifier) + .putCompound("states", statesBuilder.build()) + .putInt("version", LevelDBConstants.STATE_VERSION) + .build(); + return new CustomBlockState(identifier, legacyId, state, factory); + } + + public boolean closeRegistry() throws IOException { + if (this.closed) { + throw new IllegalStateException("Block registry was already closed"); + } + + this.closed = true; + if (this.legacy2CustomState.isEmpty()) { + return false; + } + + long startTime = System.currentTimeMillis(); + + BlockPalette storagePalette = GlobalBlockPalette.getLeveldbBlockPalette(); + BlockPalette palette = GlobalBlockPalette.getCurrentBlockPalette(); + if (palette.getProtocol() == storagePalette.getProtocol()) { + this.recreateBlockPalette(palette, new ObjectArrayList<>(NukkitLegacyMapper.loadBlockPalette())); + } else { + Path path = this.getVanillaPalettePath(palette.getProtocol()); + if (!Files.exists(path)) { + log.warn("No vanilla palette found for"); + return false; + } + this.recreateBlockPalette(palette); + } + + log.info("Custom block registry closed in {}ms", (System.currentTimeMillis() - startTime)); + return true; + } + + private void recreateBlockPalette(BlockPalette palette) throws IOException { + List vanillaPalette = new ObjectArrayList<>(this.loadVanillaPalette(palette.getProtocol())); + this.recreateBlockPalette(palette, vanillaPalette); + } + + private void recreateBlockPalette(BlockPalette palette, List vanillaPalette) { + Object2ObjectMap state2Legacy = new Object2ObjectLinkedOpenHashMap<>(); + + int paletteVersion = vanillaPalette.get(0).getInt("version"); + + for (Int2IntMap.Entry entry : palette.getLegacyToRuntimeIdMap().int2IntEntrySet()) { + int runtimeId = entry.getIntValue(); + NbtMap state = vanillaPalette.get(runtimeId); + if (state == null) { + log.info("Unknown runtime ID {}! protocol={}", runtimeId, palette.getProtocol()); + continue; + } + IntSet legacyIds = state2Legacy.computeIfAbsent(state, s -> new IntOpenHashSet()); + legacyIds.add(entry.getIntKey()); + } + + for (CustomBlockState definition : this.legacy2CustomState.values()) { + NbtMap state = definition.getBlockState(); + if (state.getInt("version") != paletteVersion) { + state = state.toBuilder().putInt("version", paletteVersion).build(); + } + vanillaPalette.add(state); + state2Legacy.computeIfAbsent(state, s -> new IntOpenHashSet()).add(legacyToFullId(definition.getLegacyId())); + } + + vanillaPalette.sort(HashedPaletteComparator.INSTANCE); + + palette.clearStates(); + boolean levelDb = palette.getProtocol() == GlobalBlockPalette.getLeveldbBlockPalette().getProtocol(); + if (levelDb) { + BlockStateMapping.get().clearMapping(); + } + + for (int runtimeId = 0; runtimeId < vanillaPalette.size(); runtimeId++) { + NbtMap state = vanillaPalette.get(runtimeId); + if (levelDb) { + BlockStateMapping.get().registerState(runtimeId, state); + } + + IntSet legacyIds = state2Legacy.get(state); + if (legacyIds == null) { + continue; + } + + CompoundTag nukkitState = convertNbtMap(state); + for (Integer fullId : legacyIds) { + palette.registerState(fullId >> 6, (fullId & 0xf), runtimeId, nukkitState); + } + } + } + + private List loadVanillaPalette(int version) throws FileNotFoundException { + Path path = this.getVanillaPalettePath(version); + if (!Files.exists(path)) { + throw new FileNotFoundException("Missing vanilla palette for version " + version); + } + + try (InputStream stream = Files.newInputStream(path)) { + return ((NbtMap) NbtUtils.createGZIPReader(stream).readTag()).getList("blocks", NbtType.COMPOUND); + } catch (Exception e) { + throw new AssertionError("Error loading block palette leveldb_palette.nbt", e); + } + } + + private Path getVanillaPalettePath(int version) { + return this.getBinPath().resolve("vanilla_palette_" + version + ".nbt"); + } + + public Block getBlock(int legacyId) { + CustomBlockState state = this.legacy2CustomState.get(legacyId); + if (state == null) { + return Block.get(BlockID.INFO_UPDATE); + } + + BlockContainer block = state.getFactory().create(legacyId & Block.DATA_MASK); + if (block instanceof Block) { + return (Block) block; + } + return null; + } + + public Block getBlock(int id, int meta) { + int legacyId = id << Block.DATA_BITS | meta; + CustomBlockState state = this.legacy2CustomState.get(legacyId); + if (state == null) { + state = this.legacy2CustomState.get(id << Block.DATA_BITS); + if (state == null) { + return Block.get(BlockID.INFO_UPDATE); + } + } + + BlockContainer block = state.getFactory().create(meta); + if (block instanceof Block) { + return (Block) block; + } + return null; + } + + public Class getClassType(int blockId) { + CustomBlockDefinition definition = this.blockDefinitions.get(blockId << Block.DATA_BITS); + if (definition == null) { + return null; + } + return definition.getTypeOf(); + } + + private Path getBinPath() { + return Paths.get(this.server.getDataPath()).resolve(BIN_PATH); + } + + public Collection getBlockDefinitions() { + return Collections.unmodifiableCollection(this.blockDefinitions.values()); + } + + private static int legacyToFullId(int legacyId) { + int blockId = legacyId >> Block.DATA_BITS; + int meta = legacyId & Block.DATA_MASK; + return (blockId << 6) | meta; + } + + public static CompoundTag convertNbtMap(NbtMap nbt) { + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try (NBTOutputStream nbtOutputStream = NbtUtils.createWriter(stream)) { + nbtOutputStream.writeTag(nbt); + } finally { + stream.close(); + } + return NBTIO.read(stream.toByteArray(), ByteOrder.BIG_ENDIAN, false); + } catch (IOException e) { + throw new IllegalStateException("Failed to convert NbtMap: " + nbt, e); + } + } +} diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockState.java b/src/main/java/cn/nukkit/customblock/CustomBlockState.java new file mode 100644 index 00000000000..626d6c38537 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/CustomBlockState.java @@ -0,0 +1,22 @@ +package cn.nukkit.customblock; + +import cn.nukkit.customblock.container.BlockContainerFactory; +import cn.nukkit.nbt.tag.CompoundTag; +import com.nukkitx.nbt.NbtMap; +import lombok.Data; + +@Data +public class CustomBlockState { + private final String identifier; + private final int legacyId; + private final NbtMap blockState; + private final BlockContainerFactory factory; + private CompoundTag nukkitBlockState; + + public CompoundTag getNukkitBlockState() { + if (this.nukkitBlockState == null) { + this.nukkitBlockState = CustomBlockManager.convertNbtMap(this.blockState); + } + return this.nukkitBlockState; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java b/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java new file mode 100644 index 00000000000..5f1420f87e9 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java @@ -0,0 +1,186 @@ +package cn.nukkit.customblock; + +import com.google.gson.*; +import com.google.gson.internal.LazilyParsedNumber; +import com.nukkitx.nbt.NbtList; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.lang.reflect.Type; +import java.util.List; + +public class GsonNBTMapper { + + public static final Gson GSON; + + static { + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(NbtMap.class, new NbtMapDeserializer()); + builder.registerTypeAdapter(NbtList.class, new NbtArrayDeserializer()); + GSON = builder.create(); + } + + public static NbtMap objectToNbtMap(Object object) { + JsonObject json = GSON.toJsonTree(object).getAsJsonObject(); + return GSON.fromJson(json, NbtMap.class); + } + + public static class NbtMapDeserializer implements JsonDeserializer { + @Override + public NbtMap deserialize(JsonElement json, Type typeOf, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonObject()) { + throw new IllegalStateException("Expected JsonObject but got: " + json.getClass().getSimpleName()); + } + + JsonObject jsonObject = json.getAsJsonObject(); + NbtMapBuilder builder = NbtMap.builder(); + + for (String key : jsonObject.keySet()) { + JsonElement element = jsonObject.get(key); + if (element.isJsonObject()) { + builder.putCompound(key, context.deserialize(element, NbtMap.class)); + } else if (element.isJsonArray()) { + NbtList list = context.deserialize(element, NbtList.class); + builder.putList(key, list.getType(), list); + } else if (element.isJsonPrimitive()) { + builder.put(key, getPrimitiveObject(element)); + } else if (element.isJsonNull()) { + builder.putCompound(key, NbtMap.builder().build()); + } + } + return builder.build(); + } + } + + public static class NbtArrayDeserializer implements JsonDeserializer> { + @Override + public NbtList deserialize(JsonElement json, Type typeOf, JsonDeserializationContext context) throws JsonParseException { + if (!json.isJsonArray()) { + throw new IllegalStateException("Expected JsonObject but got: " + json.getClass().getSimpleName()); + } + + JsonArray jsonArray = json.getAsJsonArray(); + NbtType type = getListType(jsonArray); + return new NbtList<>(type, getList(jsonArray, type, context)); + } + } + + public static NbtType getListType(JsonArray array) { + NbtType type = null; + for (JsonElement element : array) { + NbtType elementType; + if (element.isJsonObject()) { + elementType = NbtType.COMPOUND; + } else if (element.isJsonArray()) { + elementType = NbtType.LIST; + } else { + elementType = getPrimitiveType(element); + } + + if (type != null && elementType != type) { + throw new IllegalArgumentException("Can not create array of mixed types"); + } else { + type = elementType; + } + } + return type; + } + + public static List getList(JsonArray array, NbtType type, JsonDeserializationContext context) { + List list = new ObjectArrayList<>(); + for (JsonElement element : array) { + if (type == NbtType.COMPOUND) { + list.add(context.deserialize(element, NbtMap.class)); + } else if (type == NbtType.LIST) { + list.add(context.deserialize(element, NbtList.class)); + } else { + list.add((T) getPrimitiveObject(element)); + } + } + return list; + } + + public static Object getPrimitiveObject(JsonElement element) { + if (element.getAsJsonPrimitive().isBoolean()) { + return element.getAsBoolean(); + } else if (element.getAsJsonPrimitive().isNumber()) { + Number number = element.getAsNumber(); + if (number instanceof Byte) { + return number.byteValue(); + } else if (number instanceof Short) { + return number.shortValue(); + } else if (number instanceof Integer) { + return number.intValue(); + } else if (number instanceof Long) { + return number.longValue(); + } else if (number instanceof Float) { + return number.floatValue(); + } else if (number instanceof Double) { + return number.doubleValue(); + } else { + String str = number.toString(); + try { + return Integer.parseInt(str); + } catch(NumberFormatException e) { + try { + return Long.parseLong(str); + } catch(NumberFormatException e1) { + try { + return Float.parseFloat(str); + } catch(NumberFormatException e2) { + return Double.parseDouble(str); + } + } + } + } + } else if (element.getAsJsonPrimitive().isString()) { + return element.getAsString(); + } + throw new IllegalArgumentException("Unknown type of primitive: " + element); + } + + public static NbtType getPrimitiveType(JsonElement element) { + if (element.getAsJsonPrimitive().isBoolean()) { + return NbtType.BYTE; + } else if (element.getAsJsonPrimitive().isNumber()) { + Number number = element.getAsNumber(); + if (number instanceof Byte) { + return NbtType.BYTE; + } else if (number instanceof Short) { + return NbtType.SHORT; + } else if (number instanceof Integer) { + return NbtType.INT; + } else if (number instanceof Long) { + return NbtType.LONG; + } else if (number instanceof Float) { + return NbtType.FLOAT; + } else if (number instanceof Double) { + return NbtType.DOUBLE; + } else if (number instanceof LazilyParsedNumber) { + String str = number.toString(); + try { + Integer.parseInt(str); + return NbtType.INT; + } catch(NumberFormatException e) { + try { + Long.parseLong(str); + return NbtType.LONG; + } catch(NumberFormatException e1) { + try { + Float.parseFloat(str); + return NbtType.FLOAT; + } catch(NumberFormatException e2) { + Double.parseDouble(str); + return NbtType.DOUBLE; + } + } + } + } + } else if (element.getAsJsonPrimitive().isString()) { + return NbtType.STRING; + } + throw new IllegalArgumentException("Unknown type of primitive: " + element); + } +} diff --git a/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java b/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java new file mode 100644 index 00000000000..a0513aef90a --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java @@ -0,0 +1,18 @@ +package cn.nukkit.customblock.comparator; + +import com.nukkitx.nbt.NbtMap; + +import java.util.Comparator; + +public class AlphabetPaletteComparator implements Comparator { + public static final AlphabetPaletteComparator INSTANCE = new AlphabetPaletteComparator(); + + @Override + public int compare(NbtMap o1, NbtMap o2) { + return getIdentifier(o1).compareToIgnoreCase(getIdentifier(o2)); + } + + private String getIdentifier(NbtMap state) { + return state.getString("name"); + } +} diff --git a/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java b/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java new file mode 100644 index 00000000000..ec75d6bbf58 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java @@ -0,0 +1,37 @@ +package cn.nukkit.customblock.comparator; + + +import com.nukkitx.nbt.NbtMap; + +import java.nio.charset.StandardCharsets; +import java.util.Comparator; + +public class HashedPaletteComparator implements Comparator { + public static final HashedPaletteComparator INSTANCE = new HashedPaletteComparator(); + + private static final long FNV1_64_INIT = 0xcbf29ce484222325L; + private static final long FNV1_PRIME_64 = 1099511628211L; + + @Override + public int compare(NbtMap o1, NbtMap o2) { + byte[] b1 = getIdentifier(o1).getBytes(StandardCharsets.UTF_8); + byte[] b2 = getIdentifier(o2).getBytes(StandardCharsets.UTF_8); + long hash1 = fnv164(b1); + long hash2 = fnv164(b2); + return Long.compareUnsigned(hash1, hash2); + } + + private String getIdentifier(NbtMap state) { + return state.getString("name"); + } + + public static long fnv164(byte[] data) { + long hash = FNV1_64_INIT; + for (byte datum : data) { + hash *= FNV1_PRIME_64; + hash ^= (datum & 0xff); + } + + return hash; + } +} diff --git a/src/main/java/cn/nukkit/customblock/container/BlockContainer.java b/src/main/java/cn/nukkit/customblock/container/BlockContainer.java new file mode 100644 index 00000000000..676637b2315 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/container/BlockContainer.java @@ -0,0 +1,16 @@ +package cn.nukkit.customblock.container; + +import cn.nukkit.level.GlobalBlockPalette; + +public interface BlockContainer { + + int getNukkitId(); + + default int getNukkitDamage() { + return 0; + } + + default int getRuntimeId() { + return GlobalBlockPalette.getOrCreateRuntimeId(this.getNukkitId(), this.getNukkitDamage()); + } +} diff --git a/src/main/java/cn/nukkit/customblock/container/BlockContainerFactory.java b/src/main/java/cn/nukkit/customblock/container/BlockContainerFactory.java new file mode 100644 index 00000000000..da6f0763e63 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/container/BlockContainerFactory.java @@ -0,0 +1,16 @@ +package cn.nukkit.customblock.container; + +import cn.nukkit.customblock.properties.BlockProperties; + +public interface BlockContainerFactory { + + BlockContainer create(int meta); + + static BlockContainer createSimple(String blockName, int blockId) { + return new CustomBlock(blockName, blockId); + } + + static BlockContainer createMeta(String blockName, int blockId, int meta, BlockProperties properties) { + return new CustomBlockMeta(blockName, blockId, properties, meta); + } +} diff --git a/src/main/java/cn/nukkit/customblock/container/BlockStorageContainer.java b/src/main/java/cn/nukkit/customblock/container/BlockStorageContainer.java new file mode 100644 index 00000000000..74afdd042ce --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/container/BlockStorageContainer.java @@ -0,0 +1,78 @@ +package cn.nukkit.customblock.container; + +import cn.nukkit.block.Block; +import cn.nukkit.customblock.properties.BlockProperties; +import cn.nukkit.customblock.properties.BlockProperty; + +import java.io.Serializable; + +public interface BlockStorageContainer extends BlockContainer { + + int getStorage(); + void setStorage(int damage); + + BlockProperties getBlockProperties(); + + default int getNukkitDamage() { + return getStorage() & Block.DATA_MASK; + } + + default void setBooleanValue(BlockProperty property, boolean value) { + this.setBooleanValue(property.getName(), value); + } + + default void setBooleanValue(String propertyName, boolean value) { + this.setStorage(this.getBlockProperties().setBooleanValue(this.getStorage(), propertyName, value)); + } + + default void setPropertyValue(String propertyName, Serializable value) { + this.setStorage(this.getBlockProperties().setValue(this.getStorage(), propertyName, value)); + } + + default void setPropertyValue(BlockProperty property, T value) { + this.setPropertyValue(property.getName(), value); + } + + default void setIntValue(String propertyName, int value) { + this.setStorage(this.getBlockProperties().setIntValue(this.getStorage(), propertyName, value)); + } + + default Serializable getPropertyValue(String propertyName) { + return this.getBlockProperties().getValue(this.getStorage(), propertyName); + } + + default V getPropertyValue(BlockProperty property) { + return this.getCheckedPropertyValue(property.getName(), property.getValueClass()); + } + + default T getCheckedPropertyValue(String propertyName, Class tClass) { + return tClass.cast(this.getPropertyValue(propertyName)); + } + + default int getIntValue(String propertyName) { + return this.getBlockProperties().getIntValue(this.getStorage(), propertyName); + } + + default boolean getBooleanValue(BlockProperty property) { + return this.getBooleanValue(property.getName()); + } + + default boolean getBooleanValue(String propertyName) { + return this.getBlockProperties().getBooleanValue(this.getStorage(), propertyName); + } + + default void setStorageFromItem(int itemMeta) { + BlockProperties properties = this.getBlockProperties(); + BlockProperties itemProperties = properties.getItemBlockProperties(); + if (itemProperties.equals(properties)) { + this.setStorage(itemMeta); + return; + } + + int damage = 0; + for (String propertyName : itemProperties.getItemPropertyNames()) { + damage = properties.setValue(damage, propertyName, itemProperties.getValue(itemMeta, propertyName)); + } + this.setStorage(damage); + } +} diff --git a/src/main/java/cn/nukkit/customblock/container/CustomBlock.java b/src/main/java/cn/nukkit/customblock/container/CustomBlock.java new file mode 100644 index 00000000000..364a726e706 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/container/CustomBlock.java @@ -0,0 +1,29 @@ +package cn.nukkit.customblock.container; + +import cn.nukkit.block.Block; + +public class CustomBlock extends Block implements BlockContainer { + + private final String blockName; + private final int blockId; + + public CustomBlock(String blockName, int blockId) { + this.blockName = blockName; + this.blockId = blockId; + } + + @Override + public int getId() { + return this.blockId; + } + + @Override + public int getNukkitId() { + return this.blockId; + } + + @Override + public String getName() { + return this.blockName; + } +} diff --git a/src/main/java/cn/nukkit/customblock/container/CustomBlockMeta.java b/src/main/java/cn/nukkit/customblock/container/CustomBlockMeta.java new file mode 100644 index 00000000000..f1952861642 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/container/CustomBlockMeta.java @@ -0,0 +1,57 @@ +package cn.nukkit.customblock.container; + +import cn.nukkit.block.BlockMeta; +import cn.nukkit.customblock.properties.BlockProperties; + +public class CustomBlockMeta extends BlockMeta implements BlockStorageContainer { + + private final String blockName; + private final int blockId; + private final BlockProperties properties; + + public CustomBlockMeta(String blockName, int blockId, BlockProperties properties) { + this(blockName, blockId, properties, 0); + } + + public CustomBlockMeta(String blockName, int blockId, BlockProperties properties, int meta) { + super(meta); + this.blockName = blockName; + this.blockId = blockId; + this.properties = properties; + } + + @Override + public int getId() { + return this.blockId; + } + + @Override + public int getNukkitId() { + return this.blockId; + } + + @Override + public String getName() { + return this.blockName; + } + + @Override + public int getStorage() { + return this.getDamage(); + } + + @Override + public void setStorage(int damage) { + this.setDamage(damage); + } + + @Override + public void setDamage(int meta) { + super.setDamage(meta); + } + + @Override + public BlockProperties getBlockProperties() { + return this.properties; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java b/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java new file mode 100644 index 00000000000..0c825a0621f --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java @@ -0,0 +1,420 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.BlockPropertyNotFoundException; +import cn.nukkit.utils.functional.ToIntTriFunctionTwoInts; +import cn.nukkit.utils.functional.ToLongTriFunctionOneIntOneLong; +import com.nukkitx.network.util.Preconditions; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import net.daporkchop.lib.common.function.plain.TriFunction; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; + +public class BlockProperties { + private final Map byName; + private final int bitSize; + private final BlockProperties itemBlockProperties; + + public BlockProperties(BlockProperty... properties) { + this(null, properties); + } + + public BlockProperties(BlockProperties itemBlockProperties, BlockProperty... properties) { + if (itemBlockProperties == null) { + this.itemBlockProperties = this; + } else { + this.itemBlockProperties = itemBlockProperties; + } + + Map registry = new Object2ObjectLinkedOpenHashMap<>(properties.length); + Map byPersistenceName = new Object2ObjectLinkedOpenHashMap<>(properties.length); + + int offset = 0; + boolean allowItemExport = true; + for (BlockProperty property : properties) { + Preconditions.checkArgument(property != null, "The properties can not contains null values"); + if (property.isExportedToItem()) { + Preconditions.checkArgument(allowItemExport, "Cannot export a property to item if the previous property does not export"); + Preconditions.checkArgument(offset <= 6); // Only 6 bits of data can be stored in item blocks, client side limitation. + } else { + allowItemExport = false; + } + + RegisteredBlockProperty register = new RegisteredBlockProperty(property, offset); + offset += property.getBitSize(); + + Preconditions.checkArgument(registry.put(property.getName(), register) == null, "The property %s is duplicated by it's normal name", property.getName()); + Preconditions.checkArgument(byPersistenceName.put(property.getPersistenceName(), register) == null, "The property %s is duplicated by it's persistence name", property.getPersistenceName()); + } + + this.byName = Collections.unmodifiableMap(registry); + bitSize = offset; + } + + public BlockProperties getItemBlockProperties() { + return this.itemBlockProperties; + } + + @SuppressWarnings("unchecked") + public int setValue(int currentMeta, String propertyName, Serializable value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public long setValue(long currentMeta, String propertyName, Serializable value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public int setBooleanValue(int currentMeta, String propertyName, boolean value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (BooleanBlockProperty.class == property.getClass()) { + return ((BooleanBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public long setBooleanValue(long currentMeta, String propertyName, boolean value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (BooleanBlockProperty.class == property.getClass()) { + return ((BooleanBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public BigInteger setBooleanValue(BigInteger currentMeta, String propertyName, boolean value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (BooleanBlockProperty.class == property.getClass()) { + return ((BooleanBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public int setIntValue(int currentMeta, String propertyName, int value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (IntBlockProperty.class == property.getClass()) { + return ((IntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } else if (UnsignedIntBlockProperty.class == property.getClass()) { + return ((UnsignedIntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public long setIntValue(long currentMeta, String propertyName, int value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (IntBlockProperty.class == property.getClass()) { + return ((IntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } else if (UnsignedIntBlockProperty.class == property.getClass()) { + return ((UnsignedIntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public BigInteger setIntValue(BigInteger currentMeta, String propertyName, int value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + if (IntBlockProperty.class == property.getClass()) { + return ((IntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } else if (UnsignedIntBlockProperty.class == property.getClass()) { + return ((UnsignedIntBlockProperty) property).setValue(currentMeta, registry.getOffset(), value); + } + + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public int setPersistenceValue(int currentMeta, String propertyName, String persistenceValue) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + int meta = property.getMetaForPersistenceValue(persistenceValue); + Serializable value = property.getValueForMeta(meta); + return property.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public long setPersistenceValue(long currentMeta, String propertyName, String persistenceValue) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + int meta = property.getMetaForPersistenceValue(persistenceValue); + Serializable value = property.getValueForMeta(meta); + return property.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public BigInteger setPersistenceValue(BigInteger currentMeta, String propertyName, String persistenceValue) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty property = registry.getProperty(); + int meta = property.getMetaForPersistenceValue(persistenceValue); + Serializable value = property.getValueForMeta(meta); + return property.setValue(currentMeta, registry.getOffset(), value); + } + + @SuppressWarnings("unchecked") + public BigInteger setValue(BigInteger currentMeta, String propertyName, Serializable value) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + BlockProperty unchecked = registry.getProperty(); + return unchecked.setValue(currentMeta, registry.getOffset(), value); + } + + public Serializable getValue(int currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public Serializable getValue(long currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public Serializable getValue(BigInteger currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public T getCheckedValue(int currentMeta, String propertyName, Class clazz) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return clazz.cast(registry.getProperty().getValue(currentMeta, registry.getOffset())); + } + + public T getCheckedValue(long currentMeta, String propertyName, Class clazz) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return clazz.cast(registry.getProperty().getValue(currentMeta, registry.getOffset())); + } + + public T getCheckedValue(BigInteger currentMeta, String propertyName, Class clazz) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return clazz.cast(registry.getProperty().getValue(currentMeta, registry.getOffset())); + } + + @SuppressWarnings("unchecked") + public T getUncheckedValue(int currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return (T) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + @SuppressWarnings("unchecked") + public T getUncheckedValue(long currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return (T) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + @SuppressWarnings("unchecked") + public T getUncheckedValue(BigInteger currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return (T) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public int getIntValue(int currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getIntValue(currentMeta, registry.getOffset()); + } + + public int getIntValue(long currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getIntValue(currentMeta, registry.getOffset()); + } + + public int getIntValue(BigInteger currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getIntValue(currentMeta, registry.getOffset()); + } + + public Serializable getPersistenceValue(int currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getPersistenceValue(currentMeta, registry.getOffset()); + } + + public Serializable getPersistenceValue(long currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getPersistenceValue(currentMeta, registry.getOffset()); + } + + public Serializable getPersistenceValue(BigInteger currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + return registry.getProperty().getPersistenceValue(currentMeta, registry.getOffset()); + } + + public boolean getBooleanValue(int currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + if (registry.getProperty() instanceof BooleanBlockProperty) { + return ((BooleanBlockProperty) registry.getProperty()).getBooleanValue(currentMeta, registry.getOffset()); + } + + return (Boolean) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public boolean getBooleanValue(long currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + if (registry.getProperty() instanceof BooleanBlockProperty) { + return ((BooleanBlockProperty) registry.getProperty()).getBooleanValue(currentMeta, registry.getOffset()); + } + + return (Boolean) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public boolean getBooleanValue(BigInteger currentMeta, String propertyName) { + RegisteredBlockProperty registry = requireRegisteredProperty(propertyName); + if (registry.getProperty() instanceof BooleanBlockProperty) { + return ((BooleanBlockProperty) registry.getProperty()).getBooleanValue(currentMeta, registry.getOffset()); + } + + return (Boolean) registry.getProperty().getValue(currentMeta, registry.getOffset()); + } + + public R reduce(R identity, TriFunction, Integer, R, R> accumulator) { + R result = identity; + for (RegisteredBlockProperty registry : byName.values()) { + result = accumulator.apply(registry.getProperty(), registry.getOffset(), result); + } + return result; + } + + public int reduceInt(int identity, ToIntTriFunctionTwoInts> accumulator) { + int result = identity; + for (RegisteredBlockProperty registry : byName.values()) { + result = accumulator.apply(registry.getProperty(), registry.getOffset(), result); + } + return result; + } + + public long reduceLong(long identity, ToLongTriFunctionOneIntOneLong> accumulator) { + long result = identity; + for (RegisteredBlockProperty registry : byName.values()) { + result = accumulator.apply(registry.getProperty(), registry.getOffset(), result); + } + return result; + } + + public boolean isDefaultValue(String propertyName, Serializable value) { + BlockProperty blockProperty = getBlockProperty(propertyName); + return blockProperty.isDefaultValue(value); + } + + public boolean isDefaultValue(BlockProperty property, T value) { + return isDefaultValue(property.getName(), value); + } + + public boolean isDefaultIntValue(String propertyName, int value) { + BlockProperty blockProperty = getBlockProperty(propertyName); + return blockProperty.isDefaultIntValue(value); + } + + public boolean isDefaultIntValue(BlockProperty property, int value) { + return isDefaultIntValue(property.getName(), value); + } + + public boolean isDefaultBooleanValue(String propertyName, boolean value) { + BlockProperty blockProperty = getBlockProperty(propertyName); + return blockProperty.isDefaultBooleanValue(value); + } + + public boolean isDefaultBooleanValue(BlockProperty property, boolean value) { + return isDefaultBooleanValue(property.getName(), value); + } + + public int getOffset(String propertyName) { + return this.requireRegisteredProperty(propertyName).getOffset(); + } + + public boolean contains(String propertyName) { + return this.byName.containsKey(propertyName); + } + + public boolean contains(BlockProperty property) { + RegisteredBlockProperty registry = this.byName.get(property.getName()); + if (registry == null) { + return false; + } + return registry.getProperty().getValueClass().equals(property.getValueClass()); + } + + @SuppressWarnings("java:S1452") + public BlockProperty getBlockProperty(String propertyName) { + return this.requireRegisteredProperty(propertyName).getProperty(); + } + + public > T getBlockProperty(String propertyName, Class clazz) { + return clazz.cast(this.requireRegisteredProperty(propertyName).getProperty()); + } + + public RegisteredBlockProperty requireRegisteredProperty(String propertyName) { + RegisteredBlockProperty registry = this.byName.get(propertyName); + if (registry == null) { + throw new BlockPropertyNotFoundException(propertyName, this); + } + return registry; + } + + public Set getNames() { + return this.byName.keySet(); + } + + public Collection getAllProperties() { + return this.byName.values(); + } + + public int getBitSize() { + return this.bitSize; + } + + public List getItemPropertyNames() { + List itemProperties = new ArrayList<>(this.byName.size()); + for (RegisteredBlockProperty registry : this.byName.values()) { + if (registry.getProperty().isExportedToItem()) { + itemProperties.add(registry.getProperty().getName()); + } else { + break; + } + } + return itemProperties; + } + + public void forEach(ObjIntConsumer> consumer) { + for (RegisteredBlockProperty registry : this.byName.values()) { + consumer.accept(registry.getProperty(), registry.getOffset()); + } + } + + public void forEach(Consumer> consumer) { + for (RegisteredBlockProperty registry : this.byName.values()) { + consumer.accept(registry.getProperty()); + } + } + + @Override + public String toString() { + return "BlockProperties{" + + "bitSize=" + bitSize + + ", properties=" + byName.values() + + '}'; + } + +} diff --git a/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java new file mode 100644 index 00000000000..7d0e4ad57e1 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java @@ -0,0 +1,333 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; +import com.nukkitx.network.util.Preconditions; + +import java.io.Serializable; +import java.math.BigInteger; + +public abstract class BlockProperty implements Serializable { + private static final long serialVersionUID = -2594821043880025191L; + + private final String name; + private final boolean exportedToItem; + private final String persistenceName; + private final int bitSize; + + public BlockProperty(String name, boolean exportedToItem, String persistenceName, int bitSize) { + Preconditions.checkArgument(bitSize > 0, "Bit size (%s) must be positive", bitSize); + this.name = name; + this.exportedToItem = exportedToItem; + this.persistenceName = persistenceName; + this.bitSize = bitSize; + } + + public abstract int getMetaForValue(T value); + public abstract T getValueForMeta(int meta); + public abstract int getIntValueForMeta(int meta); + + public abstract Serializable getPersistenceValueForMeta(int meta); + public abstract int getMetaForPersistenceValue(String persistenceValue); + + public abstract Class getValueClass(); + + public abstract boolean isDefaultValue(T value); + public abstract T getDefaultValue(); + public abstract BlockProperty exportingToItems(boolean exportedToItem); + + public int setValue(int currentMeta, int bitOffset, T newValue) { + int mask = computeValueMask(bitOffset); + try { + int value = getMetaForValue(newValue) << bitOffset; + + if ((value & ~mask) != 0) { + throw new IllegalStateException("Attempted to set a value which overflows the size of " + bitSize + " bits. Current:" + currentMeta + ", offset:" + bitOffset + ", meta:" + value + ", value:" + newValue); + } + + return currentMeta & ~mask | (value & mask); + } catch (Exception e) { + T oldValue = null; + InvalidBlockPropertyMetaException suppressed = null; + try { + oldValue = getValue(currentMeta, bitOffset); + } catch (Exception e2) { + suppressed = new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & mask, e2); + } + InvalidBlockPropertyValueException toThrow = new InvalidBlockPropertyValueException(this, oldValue, newValue, e); + if (suppressed != null) { + toThrow.addSuppressed(suppressed); + } + throw toThrow; + } + } + + public long setValue(long currentBigMeta, int bitOffset, T newValue) { + long mask = computeBigValueMask(bitOffset); + try { + long value = getMetaForValue(newValue) << bitOffset; + + if ((value & ~mask) != 0L) { + throw new IllegalStateException("Attempted to set a value which overflows the size of " + bitSize + " bits. Current:" + currentBigMeta + ", offset:" + bitOffset + ", meta:" + value + ", value:" + newValue); + } + + return currentBigMeta & ~mask | (value & mask); + } catch (Exception e) { + T oldValue = null; + InvalidBlockPropertyMetaException suppressed = null; + try { + oldValue = getValue(currentBigMeta, bitOffset); + } catch (Exception e2) { + suppressed = new InvalidBlockPropertyMetaException(this, currentBigMeta, currentBigMeta & mask, e2); + } + InvalidBlockPropertyValueException toThrow = new InvalidBlockPropertyValueException(this, oldValue, newValue, e); + if (suppressed != null) { + toThrow.addSuppressed(suppressed); + } + throw toThrow; + } + } + + public BigInteger setValue(BigInteger currentHugeMeta, int bitOffset, T newValue) { + BigInteger mask = computeHugeValueMask(bitOffset); + try { + BigInteger value = BigInteger.valueOf(getMetaForValue(newValue)).shiftLeft(bitOffset); + + if (!value.andNot(mask).equals(BigInteger.ZERO)) { + throw new IllegalStateException("Attempted to set a value which overflows the size of " + bitSize + " bits. Current:" + currentHugeMeta + ", offset:" + bitOffset + ", meta:" + value + ", value:" + newValue); + } + + return currentHugeMeta.andNot(mask).or(value.and(mask)); + } catch (Exception e) { + T oldValue = null; + InvalidBlockPropertyMetaException suppressed = null; + try { + oldValue = getValue(currentHugeMeta, bitOffset); + } catch (Exception e2) { + suppressed = new InvalidBlockPropertyMetaException(this, currentHugeMeta, currentHugeMeta.and(mask), e2); + } + InvalidBlockPropertyValueException toThrow = new InvalidBlockPropertyValueException(this, oldValue, newValue, e); + if (suppressed != null) { + toThrow.addSuppressed(suppressed); + } + throw toThrow; + } + } + + public T getValue(int currentMeta, int bitOffset) { + int meta = getMetaFromInt(currentMeta, bitOffset); + try { + return getValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & computeValueMask(bitOffset), e); + } + } + + public T getValue(long currentBigMeta, int bitOffset) { + int meta = getMetaFromLong(currentBigMeta, bitOffset); + try { + return getValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentBigMeta, currentBigMeta & computeBigValueMask(bitOffset), e); + } + } + + public T getValue(BigInteger currentHugeMeta, int bitOffset) { + int meta = getMetaFromBigInt(currentHugeMeta, bitOffset); + try { + return getValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentHugeMeta, currentHugeMeta.and(computeHugeValueMask(bitOffset)), e); + } + } + + public int getIntValue(int currentMeta, int bitOffset) { + int meta = getMetaFromInt(currentMeta, bitOffset); + try { + return getIntValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & computeValueMask(bitOffset), e); + } + } + + public int getIntValue(long currentMeta, int bitOffset) { + int meta = getMetaFromLong(currentMeta, bitOffset); + try { + return getIntValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & computeBigValueMask(bitOffset), e); + } + } + + public int getIntValue(BigInteger currentMeta, int bitOffset) { + int meta = getMetaFromBigInt(currentMeta, bitOffset); + try { + return getIntValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta.and(computeHugeValueMask(bitOffset)), e); + } + } + + public Serializable getPersistenceValue(int currentMeta, int bitOffset) { + int meta = getMetaFromInt(currentMeta, bitOffset); + try { + return this.getPersistenceValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & computeValueMask(bitOffset), e); + } + } + + public Serializable getPersistenceValue(long currentMeta, int bitOffset) { + int meta = getMetaFromLong(currentMeta, bitOffset); + try { + return this.getPersistenceValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta & computeBigValueMask(bitOffset), e); + } + } + + public Serializable getPersistenceValue(BigInteger currentMeta, int bitOffset) { + int meta = getMetaFromBigInt(currentMeta, bitOffset); + try { + return this.getPersistenceValueForMeta(meta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, currentMeta, currentMeta.and(computeHugeValueMask(bitOffset)), e); + } + } + + + private int computeRightMask(int bitOffset) { + return bitOffset == 0? 0 : -1 >>> (32 - bitOffset); + } + + private long computeBigRightMask(int bitOffset) { + return bitOffset == 0L? 0L : -1L >>> (64 - bitOffset); + } + + private BigInteger computeHugeRightMask(int bitOffset) { + return BigInteger.ONE.shiftLeft(bitOffset).subtract(BigInteger.ONE); + } + + private int computeValueMask(int bitOffset) { + Preconditions.checkArgument(bitOffset >= 0, "Bit offset can not be negative. Got %s", bitOffset); + + int maskBits = bitSize + bitOffset; + Preconditions.checkArgument(0 < maskBits && maskBits <= 32, "The bit offset %s plus the bit size %s causes memory overflow (32 bits)", bitOffset, bitSize); + + int rightMask = computeRightMask(bitOffset); + int leftMask = -1 << maskBits; + return ~rightMask & ~leftMask; + } + + private long computeBigValueMask(int bitOffset) { + Preconditions.checkArgument(bitOffset >= 0, "Bit offset can not be negative. Got %s", bitOffset); + + int maskBits = bitSize + bitOffset; + Preconditions.checkArgument(0 < maskBits && maskBits <= 64, "The bit offset %s plus the bit size %s causes memory overflow (64 bits)", bitOffset, bitSize); + + long rightMask = computeBigRightMask(bitOffset); + long leftMask = -1L << maskBits; + return ~rightMask & ~leftMask; + } + + private BigInteger computeHugeValueMask(int bitOffset) { + Preconditions.checkArgument(bitOffset >= 0, "Bit offset can not be negative. Got %s", bitOffset); + + int maskBits = bitSize + bitOffset; + Preconditions.checkArgument(0 < maskBits, "The bit offset %s plus the bit size %s causes memory overflow (huge)", bitOffset, bitSize); + + BigInteger rightMask = computeHugeRightMask(bitOffset); + BigInteger leftMask = BigInteger.valueOf(-1).shiftLeft(maskBits); + + return rightMask.not().andNot(leftMask); + } + + public final int getMetaFromInt(int currentMeta, int bitOffset) { + return (currentMeta & computeValueMask(bitOffset)) >>> bitOffset; + } + + public final int getMetaFromLong(long currentMeta, int bitOffset) { + return (int) ((currentMeta & computeBigValueMask(bitOffset)) >>> bitOffset); + } + + public final int getMetaFromBigInt(BigInteger currentMeta, int bitOffset) { + return currentMeta.and(computeHugeValueMask(bitOffset)).shiftRight(bitOffset).intValue(); + } + + protected void validateDirectly(T value) { + // Does nothing by default + } + + protected abstract void validateMetaDirectly(int meta); + + public final void validateMeta(int meta, int offset) { + int propMeta = getMetaFromInt(meta, offset); + try { + validateMetaDirectly(propMeta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta & computeValueMask(offset), e); + } + } + + public final void validateMeta(long meta, int offset) { + int propMeta = getMetaFromLong(meta, offset); + try { + validateMetaDirectly(propMeta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta & computeBigValueMask(offset), e); + } + } + + public final void validateMeta(BigInteger meta, int offset) { + int propMeta = getMetaFromBigInt(meta, offset); + try { + validateMetaDirectly(propMeta); + } catch (Exception e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta.and(computeHugeRightMask(offset)), e); + } + } + + public boolean isDefaultIntValue(int value) { + return value == getDefaultIntValue(); + } + + public boolean isDefaultBooleanValue(boolean value) { + return value == getDefaultBooleanValue(); + } + + public int getDefaultIntValue() { + return 0; + } + + public boolean getDefaultBooleanValue() { + return false; + } + + public int getBitSize() { + return this.bitSize; + } + + public String getName() { + return this.name; + } + + public String getPersistenceName() { + return this.persistenceName; + } + + public boolean isExportedToItem() { + return this.exportedToItem; + } + + public abstract BlockProperty copy(); + + @Override + public String toString() { + return getClass().getSimpleName()+"{" + + "name='" + name + '\'' + + ", bitSize=" + bitSize + + ", exportedToItem=" + exportedToItem + + ", persistenceName='" + persistenceName + '\'' + + '}'; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/BlockPropertyUtils.java b/src/main/java/cn/nukkit/customblock/properties/BlockPropertyUtils.java new file mode 100644 index 00000000000..687781dddd8 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/BlockPropertyUtils.java @@ -0,0 +1,68 @@ +package cn.nukkit.customblock.properties; + +import java.math.BigInteger; + +public class BlockPropertyUtils { + + public static int bitLength(byte data) { + if (data < 0) { + return 32; + } + + if (data == 0) { + return 1; + } + + int bits = 0; + while (data != 0) { + data >>>= 1; + bits++; + } + + return bits; + } + + public static int bitLength(int data) { + if (data < 0) { + return 32; + } + + if (data == 0) { + return 1; + } + + int bits = 0; + while (data != 0) { + data >>>= 1; + bits++; + } + + return bits; + } + + public static int bitLength(long data) { + if (data < 0) { + return 64; + } + + if (data == 0) { + return 1; + } + + int bits = 0; + while (data != 0) { + data >>>= 1; + bits++; + } + + return bits; + } + + public static int bitLength(BigInteger data) { + if (data.compareTo(BigInteger.ZERO) < 0) { + throw new UnsupportedOperationException("Negative BigIntegers are not supported (nearly infinite bits)"); + } + + return data.bitLength(); + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java new file mode 100644 index 00000000000..a3fdfaf6cf3 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java @@ -0,0 +1,147 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; +import com.nukkitx.network.util.Preconditions; + +import java.io.Serializable; +import java.math.BigInteger; + +public class BooleanBlockProperty extends BlockProperty { + private static final long serialVersionUID = 8249827149092664486L; + + public BooleanBlockProperty(String name, boolean exportedToItem, String persistenceName) { + super(name, exportedToItem, persistenceName, 1); + } + + public BooleanBlockProperty(String name, boolean exportedToItem) { + super(name, exportedToItem, name, 1); + } + + @Override + public int setValue(int currentMeta, int bitOffset, Boolean newValue) { + boolean value = newValue != null && newValue; + return this.setValue(currentMeta, bitOffset, value); + } + + @Override + public long setValue(long currentBigMeta, int bitOffset, Boolean newValue) { + boolean value = newValue != null && newValue; + return this.setValue(currentBigMeta, bitOffset, value); + } + + public int setValue(int currentMeta, int bitOffset, boolean newValue) { + int mask = 1 << bitOffset; + return newValue ? (currentMeta | mask) : (currentMeta & ~mask); + } + + @Override + public Boolean getValue(int currentMeta, int bitOffset) { + return this.getBooleanValue(currentMeta, bitOffset); + } + + @Override + public Boolean getValue(long currentBigMeta, int bitOffset) { + return this.getBooleanValue(currentBigMeta, bitOffset); + } + + public boolean getBooleanValue(int currentMeta, int bitOffset) { + int mask = 1 << bitOffset; + return (currentMeta & mask) == mask; + } + + public boolean getBooleanValue(long currentBigMeta, int bitOffset) { + long mask = 1L << bitOffset; + return (currentBigMeta & mask) == mask; + } + + public boolean getBooleanValue(BigInteger currentHugeData, int bitOffset) { + BigInteger mask = BigInteger.ONE.shiftLeft(bitOffset); + return mask.equals(currentHugeData.and(mask)); + } + + @Override + public int getIntValue(int currentMeta, int bitOffset) { + return this.getBooleanValue(currentMeta, bitOffset)? 1 : 0; + } + + @Override + public int getIntValueForMeta(int meta) { + if (meta == 1 || meta == 0) { + return meta; + } + throw new InvalidBlockPropertyMetaException(this, meta, meta, "Only 1 or 0 was expected"); + } + + @Override + public int getMetaForValue(Boolean value) { + return Boolean.TRUE.equals(value)? 1 : 0; + } + + @Override + public Boolean getValueForMeta(int meta) { + return this.getBooleanValueForMeta(meta); + } + + public boolean getBooleanValueForMeta(int meta) { + if (meta == 0) { + return false; + } else if (meta == 1) { + return true; + } else { + throw new InvalidBlockPropertyMetaException(this, meta, meta, "Only 1 or 0 was expected"); + } + } + + @Override + public Serializable getPersistenceValueForMeta(int meta) { + if (meta == 1) { + return true; + } else if (meta == 0) { + return false; + } else { + throw new InvalidBlockPropertyMetaException(this, meta, meta, "Only 1 or 0 was expected"); + } + } + + @Override + public int getMetaForPersistenceValue(String persistenceValue) { + if ("1".equals(persistenceValue)) { + return 1; + } else if ("0".equals(persistenceValue)) { + return 0; + } else { + throw new InvalidBlockPropertyPersistenceValueException(this, null, persistenceValue, "Only 1 or 0 was expected"); + } + } + + @Override + public Boolean getDefaultValue() { + return Boolean.FALSE; + } + + @Override + public boolean isDefaultValue(Boolean value) { + return value == null || Boolean.FALSE.equals(value); + } + + @Override + protected void validateMetaDirectly(int meta) { + Preconditions.checkArgument(meta == 1 || meta == 0, "Must be 1 or 0"); + } + + @Override + public Class getValueClass() { + return Boolean.class; + } + + @Override + public BooleanBlockProperty exportingToItems(boolean exportedToItem) { + return new BooleanBlockProperty(this.getName(), exportedToItem, this.getPersistenceName()); + } + + @Override + public BooleanBlockProperty copy() { + return new BooleanBlockProperty(this.getName(), this.isExportedToItem(), this.getPersistenceName()); + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/EnumBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/EnumBlockProperty.java new file mode 100644 index 00000000000..9587e28398a --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/EnumBlockProperty.java @@ -0,0 +1,201 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; + +public class EnumBlockProperty extends BlockProperty { + private static final long serialVersionUID = 507174531989068430L; + + private final E[] values; + // This is null if ordinal = true + private final String[] persistenceNames; + private final Class typeOf; + private final boolean ordinal; + + + public EnumBlockProperty(String name, boolean exportedToItem, E[] values, int bitSize, String persistenceName) { + this(name, exportedToItem, values, bitSize, persistenceName, false); + } + + public EnumBlockProperty(String name, boolean exportedToItem, E[] values, int bitSize, String persistenceName, boolean ordinal) { + this(name, exportedToItem, values, bitSize, persistenceName, ordinal, ordinal ? null : + Arrays.stream(values).map(Objects::toString).map(String::toLowerCase).toArray(String[]::new)); + } + + public EnumBlockProperty(String name, boolean exportedToItem, E[] values, int bitSize) { + this(name, exportedToItem, values, bitSize, name); + } + + public EnumBlockProperty(String name, boolean exportedToItem, E[] values) { + this(name, exportedToItem, checkUniverseLength(values), BlockPropertyUtils.bitLength(values.length - 1)); + } + + public EnumBlockProperty(String name, boolean exportedToItem, Class enumClass) { + this(name, exportedToItem, enumClass.getEnumConstants()); + } + + public EnumBlockProperty(String name, boolean exportedToItem, E[] values, int bitSize, String persistenceName, boolean ordinal, String[] persistenceNames) { + super(name, exportedToItem, persistenceName, bitSize); + checkUniverseLength(values); + + if (ordinal) { + this.persistenceNames = null; + } else { + Preconditions.checkArgument(persistenceNames != null, "persistenceNames can't be null when ordinal is false"); + Preconditions.checkArgument(persistenceNames.length == values.length, "persistenceNames and universe must have the same length when ordinal is false"); + this.persistenceNames = persistenceNames.clone(); + } + + this.ordinal = ordinal; + this.values = values.clone(); + this.typeOf = (Class) values.getClass().getComponentType(); + + Set elements = new ObjectOpenHashSet<>(); + Set persistenceNamesCheck = new ObjectOpenHashSet<>(); + + for (int i = 0; i < this.values.length; i++) { + E element = this.values[i]; + Preconditions.checkNotNull(element, "Value can not be null"); + Preconditions.checkArgument(elements.add(element), "Duplicates are not allowed"); + if (!ordinal) { + String elementName = this.persistenceNames[i]; + Preconditions.checkNotNull(elementName, "The persistenceNames can not contain null values"); + Preconditions.checkArgument(persistenceNamesCheck.add(elementName), "The persistenceNames can not have duplicated elements"); + } + } + } + + public EnumBlockProperty ordinal(boolean ordinal) { + if (ordinal == this.ordinal) { + return this; + } + return new EnumBlockProperty<>(this.getName(), this.isExportedToItem(), this.values, this.getBitSize(), this.getPersistenceName(), ordinal); + } + + @Override + public int getMetaForValue( E value) { + if (value == null) { + return 0; + } + for (int i = 0; i < this.values.length; i++) { + if (this.values[i].equals(value)) { + return i; + } + } + throw new InvalidBlockPropertyValueException(this, null, value, "Element is not part of this property"); + } + + @Override + public E getValueForMeta(int meta) { + return this.values[meta]; + } + + @Override + public int getIntValueForMeta(int meta) { + try { + this.validateMetaDirectly(meta); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta, e); + } + return meta; + } + + @Override + protected void validateDirectly( E value) { + for (E object : this.values) { + if (object == value) { + return; + } + } + throw new IllegalArgumentException(value+" is not valid for this property"); + } + + @Override + protected void validateMetaDirectly(int meta) { + Preconditions.checkElementIndex(meta, this.values.length); + } + + @Override + public Serializable getPersistenceValueForMeta(int meta) { + try { + this.validateMetaDirectly(meta); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta, e); + } + + if (this.isOrdinal()) { + return meta; + } + return persistenceNames[meta]; + } + + @Override + public int getMetaForPersistenceValue(String persistenceValue) { + int meta; + if (this.isOrdinal()) { + try { + meta = Integer.parseInt(persistenceValue); + this.validateMetaDirectly(meta); + } catch (IndexOutOfBoundsException|IllegalArgumentException e) { + throw new InvalidBlockPropertyPersistenceValueException(this, null, persistenceValue, + "Expected a number from 0 to " + (this.values.length - 1), e); + } + return meta; + } + + for (int index = 0; index < persistenceNames.length; index++) { + if (persistenceNames[index].equals(persistenceValue)) { + return index; + } + } + + throw new InvalidBlockPropertyPersistenceValueException(this, null, persistenceValue, "The value does not exists in this property."); + } + + public E[] getValues() { + return this.values.clone(); + } + + public boolean isOrdinal() { + return this.ordinal; + } + + @Override + public E getDefaultValue() { + return this.values[0]; + } + + @Override + public boolean isDefaultValue( E value) { + return value == null || this.values[0].equals(value); + } + + @Override + public Class getValueClass() { + return this.typeOf; + } + + @Override + public EnumBlockProperty copy() { + return new EnumBlockProperty<>(this.getName(), this.isExportedToItem(), values, this.getBitSize(), this.getPersistenceName(), this.isOrdinal(), persistenceNames); + } + + @Override + public EnumBlockProperty exportingToItems(boolean exportedToItem) { + return new EnumBlockProperty<>(this.getName(), exportedToItem, values, this.getBitSize(), this.getPersistenceName(), this.isOrdinal(), persistenceNames); + } + + private static E[] checkUniverseLength(E[] universe) { + Preconditions.checkNotNull(universe, "universe can't be null"); + Preconditions.checkArgument(universe.length > 0, "The universe can't be empty"); + return universe; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java new file mode 100644 index 00000000000..507b6fb5fee --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java @@ -0,0 +1,154 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; +import cn.nukkit.math.NukkitMath; +import com.nukkitx.network.util.Preconditions; + +import java.io.Serializable; + +public class IntBlockProperty extends BlockProperty { + private static final long serialVersionUID = -2239010977496415152L; + + private final int minValue; + private final int maxValue; + + public IntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue, int bitSize) { + this(name, exportedToItem, maxValue, minValue, bitSize, name); + } + + public IntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue) { + this(name, exportedToItem, maxValue, minValue, BlockPropertyUtils.bitLength(maxValue - minValue)); + } + + public IntBlockProperty(String name, boolean exportedToItem, int maxValue) { + this(name, exportedToItem, maxValue, 0); + } + + public IntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue, int bitSize, String persistenceName) { + super(name, exportedToItem, persistenceName, bitSize); + int delta = maxValue - minValue; + Preconditions.checkArgument(delta > 0, "maxValue must be higher than minValue. Got min:%s and max:%s", minValue, maxValue); + + int mask = -1 >>> (32 - bitSize); + Preconditions.checkArgument(delta <= mask, "The data range from %s to %s can't be stored in %s bits", minValue, maxValue, bitSize); + + this.minValue = minValue; + this.maxValue = maxValue; + } + + @Override + public int getMetaForValue(Integer value) { + if (value == null) { + return 0; + } + return this.getMetaForValue(value.intValue()); + } + + public int getMetaForValue(int value) { + try { + this.validateDirectly(value); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyValueException(this, null, value, e); + } + return value - minValue; + } + + @Override + public Integer getValueForMeta(int meta) { + return this.getIntValueForMeta(meta); + } + + @Override + public int getIntValueForMeta(int meta) { + try { + this.validateMetaDirectly(meta); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta, e); + } + return minValue + meta; + } + + @Override + protected void validateDirectly(Integer value) { + if (value == null) { + return; + } + this.validateDirectly(value.intValue()); + } + + private void validateDirectly(int newValue) { + Preconditions.checkArgument(newValue >= minValue, "New value (%s) must be higher or equals to %s", newValue, minValue); + Preconditions.checkArgument(maxValue >= newValue, "New value (%s) must be less or equals to %s", newValue, maxValue); + } + + + @Override + protected void validateMetaDirectly(int meta) { + int max = maxValue - minValue; + Preconditions.checkArgument(0 <= meta && meta <= max, "The meta %s is outside the range of 0 .. ", meta, max); + } + + @Override + public Serializable getPersistenceValueForMeta(int meta) { + return this.getIntValueForMeta(meta); + } + + @Override + public int getMetaForPersistenceValue(String persistenceValue) { + try { + return this.getMetaForValue(Integer.parseInt(persistenceValue)); + } catch (NumberFormatException | InvalidBlockPropertyValueException e) { + throw new InvalidBlockPropertyPersistenceValueException(this, null, persistenceValue, e); + } + } + + public int clamp(int value) { + return NukkitMath.clamp(value, this.getMinValue(), this.getMaxValue()); + } + + + public int getMaxValue() { + return this.maxValue; + } + + public int getMinValue() { + return this.minValue; + } + + @Override + public Integer getDefaultValue() { + return this.minValue; + } + + @Override + public boolean isDefaultIntValue(int value) { + return this.minValue == value; + } + + @Override + public int getDefaultIntValue() { + return this.minValue; + } + + @Override + public boolean isDefaultValue(Integer value) { + return value == null || this.minValue == value; + } + + @Override + public Class getValueClass() { + return Integer.class; + } + + @Override + public IntBlockProperty exportingToItems(boolean exportedToItem) { + return new IntBlockProperty(this.getName(), exportedToItem, this.getMaxValue(), this.getMinValue(), this.getBitSize(), this.getPersistenceName()); + } + + @Override + public IntBlockProperty copy() { + return new IntBlockProperty(this.getName(), this.isExportedToItem(), this.getMaxValue(), this.getMinValue(), this.getBitSize(), this.getPersistenceName()); + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/RegisteredBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/RegisteredBlockProperty.java new file mode 100644 index 00000000000..00d1e9d6a8c --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/RegisteredBlockProperty.java @@ -0,0 +1,28 @@ +package cn.nukkit.customblock.properties; + +import lombok.Value; + +import java.math.BigInteger; + +@Value +public class RegisteredBlockProperty { + BlockProperty property; + int offset; + + public void validateMeta(int meta) { + this.property.validateMeta(meta, this.offset); + } + + public void validateMeta(long meta) { + this.property.validateMeta(meta, this.offset); + } + + public void validateMeta(BigInteger meta) { + this.property.validateMeta(meta, this.offset); + } + + @Override + public String toString() { + return this.offset + "-" + (this.offset + this.property.getBitSize()) + ":" + this.property.getName(); + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java new file mode 100644 index 00000000000..6f40613b019 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java @@ -0,0 +1,155 @@ +package cn.nukkit.customblock.properties; + +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; +import com.nukkitx.network.util.Preconditions; + +import java.io.Serializable; + +public class UnsignedIntBlockProperty extends BlockProperty { + private static final long serialVersionUID = 7896101036099245755L; + + private final long minValue; + private final long maxValue; + + public UnsignedIntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue, int bitSize) { + this(name, exportedToItem, maxValue, minValue, bitSize, name); + } + + public UnsignedIntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue) { + this(name, exportedToItem, maxValue, minValue, BlockPropertyUtils.bitLength(maxValue - minValue)); + } + + public UnsignedIntBlockProperty(String name, boolean exportedToItem, int maxValue) { + this(name, exportedToItem, maxValue, 0); + } + + public UnsignedIntBlockProperty(String name, boolean exportedToItem, int maxValue, int minValue, int bitSize, String persistenceName) { + super(name, exportedToItem, persistenceName, bitSize); + long unsignedMinValue = removeSign(minValue); + long unsignedMaxValue = removeSign(maxValue); + long delta = unsignedMaxValue - unsignedMinValue; + Preconditions.checkArgument(delta > 0, "maxValue must be higher than minValue. Got min:%s and max:%s", unsignedMinValue, unsignedMaxValue); + + long mask = removeSign(-1 >>> (32 - bitSize)); + Preconditions.checkArgument(delta <= mask, "The data range from %s to %s can't be stored in %s bits", unsignedMinValue, unsignedMaxValue, bitSize); + + this.minValue = unsignedMinValue; + this.maxValue = unsignedMaxValue; + } + + @Override + public int getMetaForValue(Integer value) { + if (value == null) { + return 0; + } + + long unsigned = removeSign(value); + try { + this.validateDirectly(unsigned); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyValueException(this, null, value, e); + } + return (int) (unsigned - minValue); + } + + @Override + public Integer getValueForMeta(int meta) { + return this.getIntValueForMeta(meta); + } + + @Override + public int getIntValueForMeta(int meta) { + try { + this.validateMetaDirectly(meta); + } catch (IllegalArgumentException e) { + throw new InvalidBlockPropertyMetaException(this, meta, meta, e); + } + return (int) (this.minValue + meta); + } + + @Override + protected void validateDirectly(Integer value) { + if (value == null) { + return; + } + this.validateDirectly(removeSign(value)); + } + + private void validateDirectly(long unsigned) { + Preconditions.checkArgument(unsigned >= this.minValue, "New value (%s) must be higher or equals to %s", unsigned, this.minValue); + Preconditions.checkArgument(this.maxValue >= unsigned, "New value (%s) must be less or equals to %s", unsigned, this.maxValue); + } + + @Override + protected void validateMetaDirectly(int meta) { + long max = this.maxValue - this.minValue; + Preconditions.checkArgument(0 <= meta && meta <= max, "The meta %s is outside the range of 0 .. ", meta, max); + } + + public long getMaxValue() { + return this.maxValue; + } + + public long getMinValue() { + return this.minValue; + } + + @Override + public Integer getDefaultValue() { + return (int) this.minValue; + } + + @Override + public boolean isDefaultValue(Integer value) { + return value == null || removeSign(value) == this.minValue; + } + + @Override + public boolean isDefaultIntValue(int value) { + return removeSign(value) == this.minValue; + } + + @Override + public int getDefaultIntValue() { + return (int) this.minValue; + } + + @Override + public Serializable getPersistenceValueForMeta(int meta) { + return removeSign(this.getIntValueForMeta(meta)); + } + + @Override + public int getMetaForPersistenceValue(String persistenceValue) { + try { + return this.getMetaForValue(addSign(Long.parseLong(persistenceValue))); + } catch (NumberFormatException | InvalidBlockPropertyValueException e) { + throw new InvalidBlockPropertyPersistenceValueException(this, null, persistenceValue, e); + } + } + + @Override + public UnsignedIntBlockProperty copy() { + return new UnsignedIntBlockProperty(this.getName(), this.isExportedToItem(), (int) this.getMaxValue(), (int) this.getMinValue(), this.getBitSize(), this.getPersistenceName()); + } + + @Override + public UnsignedIntBlockProperty exportingToItems(boolean exportedToItem) { + return new UnsignedIntBlockProperty(this.getName(), exportedToItem, (int) this.getMaxValue(), (int) this.getMinValue(), this.getBitSize(), this.getPersistenceName()); + } + + @Override + public Class getValueClass() { + return Integer.class; + } + + private static long removeSign(int value) { + return (long) value & 0xFFFFFFFFL; + } + + private static int addSign(long value) { + return (int) (value & 0xFFFFFFFFL); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/customblock/properties/exception/BlockPropertyNotFoundException.java b/src/main/java/cn/nukkit/customblock/properties/exception/BlockPropertyNotFoundException.java new file mode 100644 index 00000000000..24578312bd1 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/exception/BlockPropertyNotFoundException.java @@ -0,0 +1,28 @@ +package cn.nukkit.customblock.properties.exception; + +import cn.nukkit.customblock.properties.BlockProperties; + +import java.util.NoSuchElementException; + +public class BlockPropertyNotFoundException extends NoSuchElementException { + private final String propertyName; + + public BlockPropertyNotFoundException(String propertyName) { + super("The property \""+propertyName+"\" was not found."); + this.propertyName = propertyName; + } + + public BlockPropertyNotFoundException(String propertyName, String details) { + super(propertyName+": " + details); + this.propertyName = propertyName; + } + + public BlockPropertyNotFoundException(String propertyName, BlockProperties properties) { + super("The property " + propertyName + " was not found. Valid properties: " + properties.getNames()); + this.propertyName = propertyName; + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyException.java b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyException.java new file mode 100644 index 00000000000..544f2db2cd6 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyException.java @@ -0,0 +1,37 @@ +package cn.nukkit.customblock.properties.exception; + +import cn.nukkit.customblock.properties.BlockProperty; + +public class InvalidBlockPropertyException extends IllegalArgumentException { + private static final long serialVersionUID = -6934630506175381230L; + + private final BlockProperty property; + + public InvalidBlockPropertyException(BlockProperty property) { + super(buildMessage(property)); + this.property = property; + } + + public InvalidBlockPropertyException(BlockProperty property, String message) { + super(buildMessage(property) + ". "+message); + this.property = property; + } + + public InvalidBlockPropertyException(BlockProperty property, String message, Throwable cause) { + super(buildMessage(property) + ". "+message, cause); + this.property = property; + } + + public InvalidBlockPropertyException(BlockProperty property, Throwable cause) { + super(buildMessage(property), cause); + this.property = property; + } + + private static String buildMessage(BlockProperty property) { + return "Property: " + property.getName(); + } + + public BlockProperty getProperty() { + return property; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyMetaException.java b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyMetaException.java new file mode 100644 index 00000000000..82745ae0915 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyMetaException.java @@ -0,0 +1,46 @@ +package cn.nukkit.customblock.properties.exception; + +import cn.nukkit.customblock.properties.BlockProperty; + +public class InvalidBlockPropertyMetaException extends InvalidBlockPropertyException { + private static final long serialVersionUID = -8493494844859767053L; + + private final Number currentMeta; + private final Number invalidMeta; + + public InvalidBlockPropertyMetaException(BlockProperty property, Number currentMeta, Number invalidMeta) { + super(property, buildMessage(currentMeta, invalidMeta)); + this.currentMeta = currentMeta; + this.invalidMeta = invalidMeta; + } + + public InvalidBlockPropertyMetaException(BlockProperty property, Number currentMeta, Number invalidMeta, String message) { + super(property, buildMessage(currentMeta, invalidMeta)+". "+message); + this.currentMeta = currentMeta; + this.invalidMeta = invalidMeta; + } + + public InvalidBlockPropertyMetaException(BlockProperty property, Number currentMeta, Number invalidMeta, String message, Throwable cause) { + super(property, buildMessage(currentMeta, invalidMeta)+". "+message, cause); + this.currentMeta = currentMeta; + this.invalidMeta = invalidMeta; + } + + public InvalidBlockPropertyMetaException(BlockProperty property, Number currentMeta, Number invalidMeta, Throwable cause) { + super(property, buildMessage(currentMeta, invalidMeta), cause); + this.currentMeta = currentMeta; + this.invalidMeta = invalidMeta; + } + + private static String buildMessage(Object currentValue, Object invalidValue) { + return "Current Meta: "+currentValue+", Invalid Meta: "+invalidValue; + } + + public Number getCurrentMeta() { + return this.currentMeta; + } + + public Number getInvalidMeta() { + return this.invalidMeta; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyPersistenceValueException.java b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyPersistenceValueException.java new file mode 100644 index 00000000000..1fe2b806df6 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyPersistenceValueException.java @@ -0,0 +1,47 @@ +package cn.nukkit.customblock.properties.exception; + +import cn.nukkit.customblock.properties.BlockProperty; + +public class InvalidBlockPropertyPersistenceValueException extends InvalidBlockPropertyException { + private static final long serialVersionUID = 1L; + + private final String currentValue; + private final String invalidValue; + + public InvalidBlockPropertyPersistenceValueException(BlockProperty property, String currentValue, String invalidValue) { + super(property, buildMessage(currentValue, invalidValue)); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + + public InvalidBlockPropertyPersistenceValueException(BlockProperty property, String currentValue, String invalidValue, String message) { + super(property, buildMessage(currentValue, invalidValue)+". "+message); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + public InvalidBlockPropertyPersistenceValueException(BlockProperty property, String currentValue, String invalidValue, String message, Throwable cause) { + super(property, buildMessage(currentValue, invalidValue)+". "+message, cause); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + public InvalidBlockPropertyPersistenceValueException(BlockProperty property, String currentValue, String invalidValue, Throwable cause) { + super(property, buildMessage(currentValue, invalidValue), cause); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + private static String buildMessage(Object currentValue, Object invalidValue) { + return "Current Value: "+currentValue+", Invalid Value: "+invalidValue; + } + + public String getCurrentValue() { + return this.currentValue; + } + + public String getInvalidValue() { + return this.invalidValue; + } +} diff --git a/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyValueException.java b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyValueException.java new file mode 100644 index 00000000000..9017959e7b8 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/properties/exception/InvalidBlockPropertyValueException.java @@ -0,0 +1,49 @@ +package cn.nukkit.customblock.properties.exception; + +import cn.nukkit.customblock.properties.BlockProperty; + +import java.io.Serializable; + +public class InvalidBlockPropertyValueException extends InvalidBlockPropertyException { + private static final long serialVersionUID = -1087431932428639175L; + + private final Serializable currentValue; + private final Serializable invalidValue; + + public InvalidBlockPropertyValueException(BlockProperty property, Serializable currentValue, Serializable invalidValue) { + super(property, buildMessage(currentValue, invalidValue)); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + public InvalidBlockPropertyValueException(BlockProperty property, Serializable currentValue, Serializable invalidValue, String message) { + super(property, buildMessage(currentValue, invalidValue)+". "+message); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + public InvalidBlockPropertyValueException(BlockProperty property, Serializable currentValue, Serializable invalidValue, String message, Throwable cause) { + super(property, buildMessage(currentValue, invalidValue)+". "+message, cause); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + public InvalidBlockPropertyValueException(BlockProperty property, Serializable currentValue, Serializable invalidValue, Throwable cause) { + super(property, buildMessage(currentValue, invalidValue), cause); + this.currentValue = currentValue; + this.invalidValue = invalidValue; + } + + private static String buildMessage(Object currentValue, Object invalidValue) { + return "Current Value: "+currentValue+", Invalid Value: "+invalidValue; + } + + + public Serializable getCurrentValue() { + return this.currentValue; + } + + public Serializable getInvalidValue() { + return this.invalidValue; + } +} diff --git a/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java b/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java new file mode 100644 index 00000000000..f0798aa9875 --- /dev/null +++ b/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java @@ -0,0 +1,79 @@ +package cn.nukkit.customblock.util; + +import cn.nukkit.block.*; +import cn.nukkit.customblock.properties.*; +import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class BlockPropertyDumper { + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final BlockProperties PROPERTIES = null; // new BlockProperties(BlockDripleafBig.TILT_PROPERTY, BlockDripleafBig.HEAD_PROPERTY, VanillaProperties.DIRECTION); + + public static void main(String[] args) { + JsonArray blockStates = new JsonArray(); + long maxStates = getBlockStatesCount(PROPERTIES); + + + for (int meta = 0; meta < (1 << Block.DATA_BITS); meta++) { + if (meta >= maxStates) { + log.info("Max states reached: {}", maxStates); + break; + } + + try { + blockStates.add(createJsonBlockState(PROPERTIES, meta)); + } catch (InvalidBlockPropertyMetaException e) { + break; // Nukkit has more states than our block + } + } + + JsonObject blockDefinition = new JsonObject(); + blockDefinition.add("minecraft:block", blockStates); + + + System.out.println(GSON.toJson(blockDefinition)); + log.info("Created {} block states", blockStates.size()); + } + + private static JsonObject createJsonBlockState(BlockProperties properties, int meta) { + JsonObject json = new JsonObject(); + if (properties != null) { + for (String propertyName : properties.getNames()) { + BlockProperty property = properties.getBlockProperty(propertyName); + if (property instanceof EnumBlockProperty) { + json.add(property.getPersistenceName(), GSON.toJsonTree(properties.getPersistenceValue(meta, propertyName))); + } else if (property instanceof BooleanBlockProperty) { + json.addProperty(property.getPersistenceName(), properties.getBooleanValue(meta, propertyName)); + } else { + json.add(property.getPersistenceName(), GSON.toJsonTree(properties.getValue(meta, propertyName))); + } + } + } + return json; + } + + private static long getBlockStatesCount(BlockProperties properties) { + long maxStates = 1; + for (String propertyName : properties.getNames()) { + BlockProperty property = properties.getBlockProperty(propertyName); + if (property instanceof BooleanBlockProperty) { + maxStates *= 2; + } else if (property instanceof IntBlockProperty) { + int values = ((IntBlockProperty) property).getMaxValue() + 1 - ((IntBlockProperty) property).getMinValue(); + maxStates *= values; + } else if (property instanceof UnsignedIntBlockProperty) { + long values = ((UnsignedIntBlockProperty) property).getMaxValue() + 1 - ((UnsignedIntBlockProperty) property).getMinValue(); + maxStates *= values; + } else if (property instanceof EnumBlockProperty) { + maxStates *= ((EnumBlockProperty) property).getValues().length; + } + } + return maxStates; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java new file mode 100644 index 00000000000..f95106cae85 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/BoatDispenseBehavior.java @@ -0,0 +1,36 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityBoat; +import cn.nukkit.item.Item; +import cn.nukkit.level.Location; +import cn.nukkit.math.BlockFace; + +public class BoatDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (!(target instanceof BlockWater)) { + if (target.getId() != BlockID.AIR || !(target.down() instanceof BlockWater)) { + return super.dispense(block, face, item); + } + } + + Location pos = target.getLocation().setYaw(face.getHorizontalAngle()); + + EntityBoat boat = new EntityBoat(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos) + .putByte("woodID", item.getDamage()) + ); + + boat.spawnToAll(); + + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java index 73cf483bd85..3bebefe02bb 100644 --- a/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/BucketDispenseBehavior.java @@ -1,15 +1,42 @@ package cn.nukkit.dispenser; +import cn.nukkit.block.Block; import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockLiquid; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBucket; +import cn.nukkit.level.Level; +import cn.nukkit.level.particle.SmokeParticle; +import cn.nukkit.math.BlockFace; /** * @author CreeperFace */ -public class BucketDispenseBehavior implements DispenseBehavior { +public class BucketDispenseBehavior extends DefaultDispenseBehavior { @Override - public void dispense(BlockDispenser block, Item stack) { + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + if (item.getDamage() > 0) { + if (target.canBeFlowedInto()) { + Block replace = Block.get(ItemBucket.getDamageByTarget(item.getDamage())); + + if (replace instanceof BlockLiquid) { + if (block.level.getDimension() == Level.DIMENSION_NETHER) { + replace = Block.get(Block.AIR); + block.level.addParticle(new SmokeParticle(target.add(0.5, 0.5, 0.5)), null, 4); + } + block.level.setBlock(target, replace); + return Item.get(Item.BUCKET); + } + } + } else if (target instanceof BlockLiquid && target.getDamage() == 0) { + target.level.setBlock(target, Block.get(BlockID.AIR)); + return new ItemBucket(ItemBucket.getDamageByTarget(target.getId())); + } + + return super.dispense(block, face, item); } } diff --git a/src/main/java/cn/nukkit/dispenser/ChestBoatDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/ChestBoatDispenseBehavior.java new file mode 100644 index 00000000000..bd2603843d3 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/ChestBoatDispenseBehavior.java @@ -0,0 +1,56 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityBoat; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.Location; +import cn.nukkit.math.BlockFace; + +public class ChestBoatDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (!(target instanceof BlockWater)) { + if (target.getId() != BlockID.AIR || !(target.down() instanceof BlockWater)) { + return super.dispense(block, face, item); + } + } + + Location pos = target.getLocation().setYaw(face.getHorizontalAngle()); + + EntityBoat boat = new EntityChestBoat(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos) + .putByte("Variant", getBoatId(item)) + ); + + boat.spawnToAll(); + + return null; + } + + private static int getBoatId(Item item) { + switch (item.getId()) { + case ItemID.SPRUCE_CHEST_BOAT: + return 1; + case ItemID.BIRCH_CHEST_BOAT: + return 2; + case ItemID.JUNGLE_CHEST_BOAT: + return 3; + case ItemID.ACACIA_CHEST_BOAT: + return 4; + case ItemID.DARK_OAK_CHEST_BOAT: + return 5; + case ItemID.MANGROVE_CHEST_BOAT: + return 6; + } + return 0; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java index c13002af48e..0e5054ff3a6 100644 --- a/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/DefaultDispenseBehavior.java @@ -3,6 +3,10 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.item.Item; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.BlockFace.Axis; +import cn.nukkit.math.Vector3; + +import java.util.concurrent.ThreadLocalRandom; /** * @author CreeperFace @@ -10,11 +14,31 @@ public class DefaultDispenseBehavior implements DispenseBehavior { @Override - public void dispense(BlockDispenser block, Item stack) { + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 dispensePos = block.getDispensePosition(); - } + if (face.getAxis() == Axis.Y) { + dispensePos.y -= 0.125; + } else { + dispensePos.y -= 0.15625; + } + + ThreadLocalRandom rand = ThreadLocalRandom.current(); + Vector3 motion = new Vector3(); + + double offset = rand.nextDouble() * 0.1 + 0.2; + + motion.x = face.getXOffset() * offset; + motion.y = 0.20000000298023224; + motion.z = face.getZOffset() * offset; + + motion.x += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.y += rand.nextGaussian() * 0.007499999832361937 * 6; + motion.z += rand.nextGaussian() * 0.007499999832361937 * 6; - private int getParticleMetadataForFace(BlockFace face) { - return face.getXOffset() + 1 + (face.getZOffset() + 1) * 3; + Item i = item.clone(); + i.setCount(1); + block.level.dropItem(dispensePos, i, motion); + return null; } } diff --git a/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java index 9019b20ff16..54d2905a56d 100644 --- a/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/DispenseBehavior.java @@ -2,12 +2,12 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; /** * @author CreeperFace */ public interface DispenseBehavior { - void dispense(BlockDispenser block, Item item); - + Item dispense(BlockDispenser block, BlockFace face, Item item); } diff --git a/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java b/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java index 9cf02b8558b..78e3ae5a143 100644 --- a/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java +++ b/src/main/java/cn/nukkit/dispenser/DispenseBehaviorRegister.java @@ -1,15 +1,17 @@ package cn.nukkit.dispenser; -import java.util.HashMap; -import java.util.Map; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.ItemID; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; /** * @author CreeperFace */ -public class DispenseBehaviorRegister { +public final class DispenseBehaviorRegister { - private static final Map behaviors = new HashMap<>(); - private static DispenseBehavior defaultBehavior = new DefaultDispenseBehavior(); + private static final Int2ObjectMap behaviors = new Int2ObjectOpenHashMap<>(); + private static final DispenseBehavior defaultBehavior = new DefaultDispenseBehavior(); public static void registerBehavior(int itemId, DispenseBehavior behavior) { behaviors.put(itemId, behavior); @@ -22,4 +24,85 @@ public static DispenseBehavior getBehavior(int id) { public static void removeDispenseBehavior(int id) { behaviors.remove(id); } + + public static void init() { + registerBehavior(ItemID.BOAT, new BoatDispenseBehavior()); + registerBehavior(ItemID.BUCKET, new BucketDispenseBehavior()); + registerBehavior(ItemID.DYE, new DyeDispenseBehavior()); + registerBehavior(ItemID.FIREWORKS, new FireworksDispenseBehavior()); + registerBehavior(ItemID.FLINT_AND_STEEL, new FlintAndSteelDispenseBehavior()); + registerBehavior(BlockID.SHULKER_BOX, new ShulkerBoxDispenseBehavior()); + registerBehavior(BlockID.UNDYED_SHULKER_BOX, new UndyedShulkerBoxDispenseBehavior()); + registerBehavior(ItemID.SPAWN_EGG, new SpawnEggDispenseBehavior()); + registerBehavior(BlockID.TNT, new TNTDispenseBehavior()); + registerBehavior(ItemID.FIRE_CHARGE, new FireChargeDispenseBehavior()); + registerBehavior(ItemID.SHEARS, new ShearsDispenseBehaviour()); + registerBehavior(ItemID.ARROW, new ProjectileDispenseBehavior("Arrow") { + @Override + protected double getMotion() { + return super.getMotion() * 1.5; + } + }); + //TODO: tipped arrow + //TODO: spectral arrow + registerBehavior(ItemID.EGG, new ProjectileDispenseBehavior("Egg")); + registerBehavior(ItemID.SNOWBALL, new ProjectileDispenseBehavior("Snowball")); + registerBehavior(ItemID.EXPERIENCE_BOTTLE, new ProjectileDispenseBehavior("ThrownExpBottle") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + registerBehavior(ItemID.SPLASH_POTION, new ProjectileDispenseBehavior("ThrownPotion") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + registerBehavior(ItemID.LINGERING_POTION, new ProjectileDispenseBehavior("LingeringPotion") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + registerBehavior(ItemID.TRIDENT, new ProjectileDispenseBehavior("ThrownTrident") { + @Override + protected float getAccuracy() { + return super.getAccuracy() * 0.5f; + } + + @Override + protected double getMotion() { + return super.getMotion() * 1.25; + } + }); + + registerBehavior(ItemID.ACACIA_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.DARK_OAK_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.BIRCH_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.JUNGLE_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.MANGROVE_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.SPRUCE_CHEST_BOAT, new ChestBoatDispenseBehavior()); + registerBehavior(ItemID.OAK_CHEST_BOAT, new ChestBoatDispenseBehavior()); + + registerBehavior(ItemID.MINECART, new MinecartDispenseBehavior()); + registerBehavior(ItemID.MINECART_WITH_CHEST, new MinecartDispenseBehavior()); + registerBehavior(ItemID.MINECART_WITH_HOPPER, new MinecartDispenseBehavior()); + registerBehavior(ItemID.MINECART_WITH_TNT, new MinecartDispenseBehavior()); + } } diff --git a/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java new file mode 100644 index 00000000000..e226d6886ab --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/DyeDispenseBehavior.java @@ -0,0 +1,26 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.*; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.DyeColor; + +public class DyeDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (DyeColor.getByDyeData(item.getDamage()) == DyeColor.WHITE) { + if (target instanceof BlockCrops || target instanceof BlockSapling || target instanceof BlockTallGrass + || target instanceof BlockDoublePlant || target instanceof BlockMushroom) { + target.onActivate(item); + + } + + return null; + } + + return super.dispense(block, face, item); + } +} diff --git a/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java deleted file mode 100644 index f01f4e1619f..00000000000 --- a/src/main/java/cn/nukkit/dispenser/EmptyBucketDispenseBehavior.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.dispenser; - -import cn.nukkit.block.BlockDispenser; -import cn.nukkit.item.Item; - -/** - * @author CreeperFace - */ -public class EmptyBucketDispenseBehavior implements DispenseBehavior { - - @Override - public void dispense(BlockDispenser block, Item item) { - - } -} diff --git a/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java new file mode 100644 index 00000000000..c67bbc1463d --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FireChargeDispenseBehavior.java @@ -0,0 +1,29 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityProjectile; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class FireChargeDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 dispensePos = block.getDispensePosition(); + + Entity projectile = Entity.createEntity("GhastFireBall", block.level.getChunk(dispensePos.getChunkX(), dispensePos.getChunkZ()), Entity.getDefaultNBT(dispensePos)); + + if (!(projectile instanceof EntityProjectile)) { + return super.dispense(block, face, item); + } + + projectile.setMotion(new Vector3(face.getXOffset(), face.getYOffset() + 0.1f, face.getZOffset()).normalize().multiply(1.3)); + ((EntityProjectile) projectile).inaccurate(6f); + ((EntityProjectile) projectile).updateRotation(); + + projectile.spawnToAll(); + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java new file mode 100644 index 00000000000..3943b2f08fc --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FireworksDispenseBehavior.java @@ -0,0 +1,23 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityFirework; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; + +public class FireworksDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + BlockFace opposite = face.getOpposite(); + Vector3 pos = block.getSide(face).add(0.5 + opposite.getXOffset() * 0.2, 0.5 + opposite.getYOffset() * 0.2, 0.5 + opposite.getZOffset() * 0.2); + CompoundTag nbt = Entity.getDefaultNBT(pos); + nbt.putCompound("FireworkItem", NBTIO.putItemHelper(item)); + new EntityFirework(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), nbt).spawnToAll(); + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java new file mode 100644 index 00000000000..9281153a8d2 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java @@ -0,0 +1,29 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; + +public class FlintAndSteelDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (target.getId() == BlockID.AIR) { + Block down = target.down(); + if (down.getId() != BlockID.OBSIDIAN || !down.level.createPortal(down, false)) { + boolean soulFire = down.getId() == Block.SOUL_SAND || down.getId() == Block.SOUL_SOIL; + block.level.setBlock(target, Block.get(soulFire ? BlockID.SOUL_FIRE : BlockID.FIRE)); + } + item.useOn(target); + } else if (target.getId() == BlockID.TNT) { + target.onActivate(item); + item.useOn(target); + } + + return item; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/MinecartDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/MinecartDispenseBehavior.java new file mode 100644 index 00000000000..ef681d626c7 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/MinecartDispenseBehavior.java @@ -0,0 +1,62 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockRail; +import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.Rail; + +public class MinecartDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (Rail.isRailBlock(target)) { + Rail.Orientation type = ((BlockRail) target).getOrientation(); + double adjacent = 0.0D; + if (type.isAscending()) { + adjacent = 0.5D; + } + + Entity minecart = Entity.createEntity(getMinecartId(item), + block.level.getChunk(target.getChunkX(), target.getChunkZ()), new CompoundTag("") + .putList(new ListTag<>("Pos") + .add(new DoubleTag("", target.getX() + 0.5)) + .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) + .add(new DoubleTag("", target.getZ() + 0.5))) + .putList(new ListTag<>("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag<>("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0))) + ); + + minecart.spawnToAll(); + return null; + } + + return super.dispense(block, face, item); + } + + private String getMinecartId(Item item) { + switch (item.getId()) { + case ItemID.MINECART_WITH_CHEST: + return "MinecartChest"; + case ItemID.MINECART_WITH_HOPPER: + return "MinecartHopper"; + case ItemID.MINECART_WITH_TNT: + return "MinecartTnt"; + } + return "MinecartRideable"; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java index 52c2006408c..19993682f54 100644 --- a/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/ProjectileDispenseBehavior.java @@ -2,8 +2,9 @@ import cn.nukkit.block.BlockDispenser; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.item.Item; -import cn.nukkit.level.Position; +import cn.nukkit.item.ItemID; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; @@ -11,45 +12,61 @@ /** * @author CreeperFace */ -public class ProjectileDispenseBehavior implements DispenseBehavior { +public class ProjectileDispenseBehavior extends DefaultDispenseBehavior { - private String entityType; - - public ProjectileDispenseBehavior() { - - } + private final String entityType; public ProjectileDispenseBehavior(String entity) { this.entityType = entity; } @Override - public void dispense(BlockDispenser source, Item item) { - Position dispensePos = Position.fromObject(source.getDispensePosition(), source.getLevel()); + public Item dispense(BlockDispenser source, BlockFace face, Item item) { + Vector3 dispensePos = source.getDispensePosition(); + CompoundTag nbt = Entity.getDefaultNBT(dispensePos); - this.correctNBT(nbt); + this.correctNBT(nbt, item); - BlockFace face = source.getFacing(); + Entity projectile = Entity.createEntity(entityType, source.level.getChunk(dispensePos.getChunkX(), dispensePos.getChunkZ()), nbt); - Entity projectile = Entity.createEntity(getEntityType(), dispensePos.getLevel().getChunk(dispensePos.getFloorX(), dispensePos.getFloorZ()), nbt); - if (projectile == null) { - return; + if (!(projectile instanceof EntityProjectile)) { + return super.dispense(source, face, item); } - projectile.setMotion(new Vector3(face.getXOffset(), face.getYOffset() + 0.1f, face.getZOffset()).multiply(6)); + Vector3 motion = new Vector3(face.getXOffset(), face.getYOffset() + 0.1f, face.getZOffset()) + .normalize(); + + projectile.setMotion(motion); + ((EntityProjectile) projectile).inaccurate(getAccuracy()); + projectile.setMotion(projectile.getMotion().multiply(getMotion())); + + ((EntityProjectile) projectile).updateRotation(); + projectile.spawnToAll(); + return null; + } + + protected double getMotion() { + return 1.1; } - protected String getEntityType() { - return this.entityType; + protected float getAccuracy() { + return 6; } /** - * you can add extra data of projectile here + * You can add extra data of projectile here * * @param nbt tag + * @param item item */ - protected void correctNBT(CompoundTag nbt) { - + protected void correctNBT(CompoundTag nbt, Item item) { + if (item != null) { + if (item.getId() == ItemID.SPLASH_POTION || item.getId() == ItemID.LINGERING_POTION) { + nbt.putInt("PotionId", item.getDamage()); + } else if (item.getId() == ItemID.ARROW && item.getDamage() > 0) { + nbt.putByte("arrowData", item.getDamage()); + } + } } } diff --git a/src/main/java/cn/nukkit/dispenser/ShearsDispenseBehaviour.java b/src/main/java/cn/nukkit/dispenser/ShearsDispenseBehaviour.java new file mode 100644 index 00000000000..e0f805a11a1 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/ShearsDispenseBehaviour.java @@ -0,0 +1,35 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.passive.EntitySheep; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.SimpleAxisAlignedBB; + +public class ShearsDispenseBehaviour extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + item = item.clone(); + for (Entity entity : block.getLevel().getNearbyEntities(new SimpleAxisAlignedBB( + target.x, + target.y, + target.z, + target.x + 1, + target.y + 1, + target.z + 1 + ))) { + if (entity instanceof EntitySheep) { + if (!((EntitySheep) entity).isSheared()) { + ((EntitySheep) entity).shear(true); + item.useOn(entity); + return item.getDamage() >= item.getMaxDurability() ? null : item; + } + } + } + return item; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java new file mode 100644 index 00000000000..aa72281a048 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/ShulkerBoxDispenseBehavior.java @@ -0,0 +1,40 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; + +public class ShulkerBoxDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (target.getId() == Block.AIR) { + CompoundTag nbt = BlockEntity.getDefaultCompound(target, BlockEntity.SHULKER_BOX); + nbt.putByte("facing", BlockFace.UP.getIndex()); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + CompoundTag tag = item.getNamedTag(); + + if (tag != null) { + if (tag.contains("Items")) { + nbt.putList(tag.getList("Items")); + } + } + + block.level.setBlock(target, Block.get(BlockID.SHULKER_BOX, item.getDamage()), true); + BlockEntity.createBlockEntity(BlockEntity.SHULKER_BOX, block.level.getChunk(target.getChunkX(), target.getChunkZ()), nbt); + return null; + } + + return item; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java new file mode 100644 index 00000000000..7d495adeb6f --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/SpawnEggDispenseBehavior.java @@ -0,0 +1,30 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityLiving; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class SpawnEggDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).add(0.5, 0.7, 0.5); + + Entity entity = Entity.createEntity(item.getDamage(), block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos)); + + if (entity != null) { + if (item.hasCustomName() && entity instanceof EntityLiving) { + entity.setNameTag(item.getCustomName()); + } + + entity.spawnToAll(); + return null; + } + + return super.dispense(block, face, item); + } +} diff --git a/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java new file mode 100644 index 00000000000..806adae7831 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/TNTDispenseBehavior.java @@ -0,0 +1,22 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityPrimedTNT; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + +public class TNTDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Vector3 pos = block.getSide(face).add(0.5, 0, 0.5); + + EntityPrimedTNT tnt = new EntityPrimedTNT(block.level.getChunk(pos.getChunkX(), pos.getChunkZ()), + Entity.getDefaultNBT(pos)); + tnt.spawnToAll(); + + return null; + } +} diff --git a/src/main/java/cn/nukkit/dispenser/UndyedShulkerBoxDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/UndyedShulkerBoxDispenseBehavior.java new file mode 100644 index 00000000000..b14129f4134 --- /dev/null +++ b/src/main/java/cn/nukkit/dispenser/UndyedShulkerBoxDispenseBehavior.java @@ -0,0 +1,40 @@ +package cn.nukkit.dispenser; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockDispenser; +import cn.nukkit.block.BlockID; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.item.Item; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; + +public class UndyedShulkerBoxDispenseBehavior extends DefaultDispenseBehavior { + + @Override + public Item dispense(BlockDispenser block, BlockFace face, Item item) { + Block target = block.getSide(face); + + if (target.getId() == Block.AIR) { + CompoundTag nbt = BlockEntity.getDefaultCompound(target, BlockEntity.SHULKER_BOX); + nbt.putByte("facing", BlockFace.UP.getIndex()); + + if (item.hasCustomName()) { + nbt.putString("CustomName", item.getCustomName()); + } + + CompoundTag tag = item.getNamedTag(); + + if (tag != null) { + if (tag.contains("Items")) { + nbt.putList(tag.getList("Items")); + } + } + + block.level.setBlock(target, Block.get(BlockID.UNDYED_SHULKER_BOX, 0), true); + BlockEntity.createBlockEntity(BlockEntity.SHULKER_BOX, block.level.getChunk(target.getChunkX(), target.getChunkZ()), nbt); + return null; + } + + return item; + } +} diff --git a/src/main/java/cn/nukkit/entity/Attribute.java b/src/main/java/cn/nukkit/entity/Attribute.java index 68faacc5653..bd7566e1744 100644 --- a/src/main/java/cn/nukkit/entity/Attribute.java +++ b/src/main/java/cn/nukkit/entity/Attribute.java @@ -1,17 +1,16 @@ package cn.nukkit.entity; -/** - * Attribute - * - * @author Box, MagicDroidX(code), PeratX @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 - */ import cn.nukkit.utils.ServerException; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; +/** + * Attribute + * + * @author Box, MagicDroidX(code), PeratX @ Nukkit Project + */ public class Attribute implements Cloneable { public static final int ABSORPTION = 0; @@ -21,14 +20,18 @@ public class Attribute implements Cloneable { public static final int MAX_HEALTH = 4; public static final int MOVEMENT_SPEED = 5; public static final int FOLLOW_RANGE = 6; - public static final int MAX_HUNGER = 7; - public static final int FOOD = 7; + public static final int FOOD = 7, MAX_HUNGER = FOOD; public static final int ATTACK_DAMAGE = 8; public static final int EXPERIENCE_LEVEL = 9; public static final int EXPERIENCE = 10; - public static final int LUCK = 11; + public static final int UNDERWATER_MOVEMENT = 11; + public static final int LUCK = 12; + public static final int FALL_DAMAGE = 13; + public static final int HORSE_JUMP_STRENGTH = 14; + public static final int ZOMBIE_SPAWN_REINFORCEMENTS = 15; + public static final int LAVA_MOVEMENT = 16; - protected static Map attributes = new HashMap<>(); + protected static Int2ObjectMap attributes = new Int2ObjectOpenHashMap<>(); protected float minValue; protected float maxValue; @@ -36,7 +39,7 @@ public class Attribute implements Cloneable { protected float currentValue; protected String name; protected boolean shouldSend; - private int id; + private final int id; private Attribute(int id, String name, float minValue, float maxValue, float defaultValue, boolean shouldSend) { this.id = id; @@ -51,7 +54,7 @@ private Attribute(int id, String name, float minValue, float maxValue, float def public static void init() { addAttribute(ABSORPTION, "minecraft:absorption", 0.00f, 340282346638528859811704183484516925440.00f, 0.00f); addAttribute(SATURATION, "minecraft:player.saturation", 0.00f, 20.00f, 5.00f); - addAttribute(EXHAUSTION, "minecraft:player.exhaustion", 0.00f, 5.00f, 0.41f); + addAttribute(EXHAUSTION, "minecraft:player.exhaustion", 0.00f, 5.00f, 0.00f, false); addAttribute(KNOCKBACK_RESISTANCE, "minecraft:knockback_resistance", 0.00f, 1.00f, 0.00f); addAttribute(MAX_HEALTH, "minecraft:health", 0.00f, 20.00f, 20.00f); addAttribute(MOVEMENT_SPEED, "minecraft:movement", 0.00f, 340282346638528859811704183484516925440.00f, 0.10f); @@ -60,7 +63,12 @@ public static void init() { addAttribute(ATTACK_DAMAGE, "minecraft:attack_damage", 0.00f, 340282346638528859811704183484516925440.00f, 1.00f, false); addAttribute(EXPERIENCE_LEVEL, "minecraft:player.level", 0.00f, 24791.00f, 0.00f); addAttribute(EXPERIENCE, "minecraft:player.experience", 0.00f, 1.00f, 0.00f); - addAttribute(LUCK, "minecraft:luck", -1024, 1024, 0); + addAttribute(UNDERWATER_MOVEMENT, "minecraft:underwater_movement", 0.0f, 340282346638528859811704183484516925440.0f, 0.02f); + addAttribute(LUCK, "minecraft:luck", -1024.0f, 1024.0f, 0.0f); + addAttribute(FALL_DAMAGE, "minecraft:fall_damage", 0.0f, 340282346638528859811704183484516925440.0f, 1.0f); + addAttribute(HORSE_JUMP_STRENGTH, "minecraft:horse.jump_strength", 0.0f, 2.0f, 0.7f); + addAttribute(ZOMBIE_SPAWN_REINFORCEMENTS, "minecraft:zombie.spawn_reinforcements", 0.0f, 1.0f, 0.0f); + addAttribute(LAVA_MOVEMENT, "minecraft:lava_movement", 0.00f, 340282346638528859811704183484516925440.00f, 0.02f); } public static Attribute addAttribute(int id, String name, float minValue, float maxValue, float defaultValue) { @@ -88,7 +96,7 @@ public static Attribute getAttribute(int id) { */ public static Attribute getAttributeByName(String name) { for (Attribute a : attributes.values()) { - if (Objects.equals(a.getName(), name)) { + if (Objects.equals(a.name, name)) { return a.clone(); } } @@ -100,7 +108,7 @@ public float getMinValue() { } public Attribute setMinValue(float minValue) { - if (minValue > this.getMaxValue()) { + if (minValue > this.maxValue) { throw new IllegalArgumentException("Value " + minValue + " is bigger than the maxValue!"); } this.minValue = minValue; @@ -112,7 +120,7 @@ public float getMaxValue() { } public Attribute setMaxValue(float maxValue) { - if (maxValue < this.getMinValue()) { + if (maxValue < this.minValue) { throw new IllegalArgumentException("Value " + maxValue + " is bigger than the minValue!"); } this.maxValue = maxValue; @@ -124,7 +132,7 @@ public float getDefaultValue() { } public Attribute setDefaultValue(float defaultValue) { - if (defaultValue > this.getMaxValue() || defaultValue < this.getMinValue()) { + if (defaultValue > this.maxValue || defaultValue < this.minValue) { throw new IllegalArgumentException("Value " + defaultValue + " exceeds the range!"); } this.defaultValue = defaultValue; @@ -140,11 +148,11 @@ public Attribute setValue(float value) { } public Attribute setValue(float value, boolean fit) { - if (value > this.getMaxValue() || value < this.getMinValue()) { + if (value > this.maxValue || value < this.minValue) { if (!fit) { throw new IllegalArgumentException("Value " + value + " exceeds the range!"); } - value = Math.min(Math.max(value, this.getMinValue()), this.getMaxValue()); + value = Math.min(Math.max(value, this.minValue), this.maxValue); } this.currentValue = value; return this; diff --git a/src/main/java/cn/nukkit/entity/BaseEntity.java b/src/main/java/cn/nukkit/entity/BaseEntity.java new file mode 100644 index 00000000000..6ff8280e1e8 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/BaseEntity.java @@ -0,0 +1,99 @@ +package cn.nukkit.entity; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.entity.mob.EntityEnderDragon; +import cn.nukkit.item.Item; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +/** + * The base class of all mobs + */ +public abstract class BaseEntity extends EntityCreature implements EntityAgeable { + + public BaseEntity(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + public abstract int getKillExperience(); + + @Override + protected boolean applyNameTag(Player player, Item item) { + if (item.getId() == Item.NAME_TAG) { + if (item.hasCustomName() && !(this instanceof EntityEnderDragon)) { + String name = item.getCustomName(); + this.namedTag.putString("CustomName", name); + this.namedTag.putBoolean("CustomNameVisible", true); + this.setNameTag(name); + this.setNameTagVisible(true); + return true; // onInteract: true = decrease count + } + } + + return false; + } + + @Override + protected void checkGroundState(double movX, double movY, double movZ, double dx, double dy, double dz) { + if (onGround && movX == 0 && movY == 0 && movZ == 0 && dx == 0 && dy == 0 && dz == 0) { + return; + } + this.isCollidedVertically = movY != dy; + this.isCollidedHorizontally = (movX != dx || movZ != dz); + this.isCollided = (this.isCollidedHorizontally || this.isCollidedVertically); + this.onGround = (movY != dy && movY < 0); + } + + @Override + protected void checkBlockCollision() { + for (Block block : this.getCollisionBlocks()) { + block.onEntityCollide(this); + } + + // TODO: portals + } + + @Override + public boolean entityBaseTick(int tickDiff) { + if (!this.closed) { + if (!this.isAlive()) { + this.despawnFromAll(); + this.close(); + return false; + } + + if (this.age % 10 == 0) { + this.blocksAround = null; + this.collisionBlocks = null; + if (this.onGround) { + if (level.getBlock(chunk, getFloorX(), getFloorY() - 1, getFloorZ(), false).canPassThrough()) { + this.onGround = false; + } + } + } + + if (!this.onGround || Math.abs(this.motionX) > 0.1 || Math.abs(this.motionY) > 0.1 || Math.abs(this.motionZ) > 0.1) { + this.motionY -= 0.08f; + this.move(this.motionX, this.motionY, this.motionZ); + } + + this.motionX *= 0.9; + this.motionY *= 0.9; + this.motionZ *= 0.9; + super.entityBaseTick(tickDiff); // updateMovement is called at the end of onUpdate after entityBaseTick is called + return true; + } + return false; + } + + @Override + public boolean isBaby() { + return false; + } + + @Override + public void setBaby(boolean baby) { + //TODO + } +} diff --git a/src/main/java/cn/nukkit/entity/Entity.java b/src/main/java/cn/nukkit/entity/Entity.java index 5b8dd04de39..75010e63ce7 100644 --- a/src/main/java/cn/nukkit/entity/Entity.java +++ b/src/main/java/cn/nukkit/entity/Entity.java @@ -2,20 +2,33 @@ import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockFire; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockSlab; +import cn.nukkit.entity.custom.CustomEntity; +import cn.nukkit.entity.custom.EntityDefinition; +import cn.nukkit.entity.custom.EntityManager; import cn.nukkit.entity.data.*; +import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.entity.item.EntityMinecartEmpty; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Event; import cn.nukkit.event.entity.*; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; -import cn.nukkit.event.entity.EntityPortalEnterEvent.PortalType; import cn.nukkit.event.player.PlayerInteractEvent; import cn.nukkit.event.player.PlayerInteractEvent.Action; import cn.nukkit.event.player.PlayerTeleportEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemID; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.level.*; +import cn.nukkit.level.GameRule; +import cn.nukkit.level.Level; +import cn.nukkit.level.Location; +import cn.nukkit.level.Position; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.level.persistence.impl.PersistentDataContainerEntityWrapper; import cn.nukkit.math.*; import cn.nukkit.metadata.MetadataValue; import cn.nukkit.metadata.Metadatable; @@ -27,17 +40,15 @@ import cn.nukkit.network.protocol.types.EntityLink; import cn.nukkit.plugin.Plugin; import cn.nukkit.potion.Effect; -import cn.nukkit.scheduler.Task; import cn.nukkit.utils.ChunkException; import cn.nukkit.utils.MainLogger; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; -import co.aikar.timings.TimingsHistory; +import cn.nukkit.utils.Utils; import com.google.common.collect.Iterables; import java.lang.reflect.Constructor; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; import static cn.nukkit.network.protocol.SetEntityLinkPacket.*; @@ -63,7 +74,7 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_FLAGS = 0; public static final int DATA_HEALTH = 1; //int (minecart/boat) public static final int DATA_VARIANT = 2; //int - public static final int DATA_COLOR = 3, DATA_COLOUR = 3; //byte + public static final int DATA_COLOR = 3, DATA_COLOUR = DATA_COLOR; //byte public static final int DATA_NAMETAG = 4; //string public static final int DATA_OWNER_EID = 5; //long public static final int DATA_TARGET_EID = 6; //long @@ -83,7 +94,7 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_OLD_SWELL = 20; public static final int DATA_SWELL_DIR = 21; public static final int DATA_CHARGE_AMOUNT = 22; - public static final int DATA_ENDERMAN_HELD_RUNTIME_ID = 23; //short + public static final int DATA_ENDERMAN_HELD_RUNTIME_ID = 23; //int (block runtime id) public static final int DATA_ENTITY_AGE = 24; //short public static final int DATA_PLAYER_FLAGS = 26; //byte public static final int DATA_PLAYER_INDEX = 27; @@ -151,7 +162,7 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_SITTING_AMOUNT = 89; public static final int DATA_SITTING_AMOUNT_PREVIOUS = 90; public static final int DATA_EATING_COUNTER = 91; - public static final int DATA_FLAGS_EXTENDED = 92; + public static final int DATA_FLAGS_EXTENDED = 92; //long (extended data flags) public static final int DATA_LAYING_AMOUNT = 93; public static final int DATA_LAYING_AMOUNT_PREVIOUS = 94; public static final int DATA_DURATION = 95; @@ -163,10 +174,10 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_TRADE_TIER = 101; public static final int DATA_MAX_TRADE_TIER = 102; public static final int DATA_TRADE_EXPERIENCE = 103; - public static final int DATA_SKIN_ID = 104; // int ??? + public static final int DATA_SKIN_ID = 104; // int public static final int DATA_SPAWNING_FRAMES = 105; - public static final int DATA_COMMAND_BLOCK_TICK_DELAY = 106; - public static final int DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 107; + public static final int DATA_COMMAND_BLOCK_TICK_DELAY = 106; //int + public static final int DATA_COMMAND_BLOCK_EXECUTE_ON_FIRST_TICK = 107; //byte public static final int DATA_AMBIENT_SOUND_INTERVAL = 108; public static final int DATA_AMBIENT_SOUND_INTERVAL_RANGE = 109; public static final int DATA_AMBIENT_SOUND_EVENT_NAME = 110; @@ -207,7 +218,7 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_FLAG_CRITICAL = 13; public static final int DATA_FLAG_CAN_SHOW_NAMETAG = 14; public static final int DATA_FLAG_ALWAYS_SHOW_NAMETAG = 15; - public static final int DATA_FLAG_IMMOBILE = 16, DATA_FLAG_NO_AI = 16; + public static final int DATA_FLAG_IMMOBILE = 16, DATA_FLAG_NO_AI = DATA_FLAG_IMMOBILE; public static final int DATA_FLAG_SILENT = 17; public static final int DATA_FLAG_WALLCLIMBING = 18; public static final int DATA_FLAG_CAN_CLIMB = 19; @@ -307,12 +318,14 @@ public abstract class Entity extends Location implements Metadatable { public static final int DATA_FLAG_SEARCHING = 113; public static final int DATA_FLAG_CRAWLING = 114; + public static final double STEP_CLIP_MULTIPLIER = 0.4; + public static long entityCount = 1; private static final Map> knownEntities = new HashMap<>(); private static final Map shortNames = new HashMap<>(); - protected final Map hasSpawned = new HashMap<>(); + public final Map hasSpawned = new HashMap<>(); protected final Map effects = new ConcurrentHashMap<>(); @@ -322,18 +335,18 @@ public abstract class Entity extends Location implements Metadatable { .putLong(DATA_FLAGS, 0) .putByte(DATA_COLOR, 0) .putShort(DATA_AIR, 400) - .putShort(DATA_MAX_AIR, 400) + .putShort(DATA_MAX_AIR, 400) // Should be 300? .putString(DATA_NAMETAG, "") .putLong(DATA_LEAD_HOLDER_EID, -1) .putFloat(DATA_SCALE, 1f); public final List passengers = new ArrayList<>(); - public Entity riding = null; + public Entity riding; public FullChunk chunk; - protected EntityDamageEvent lastDamageCause = null; + protected EntityDamageEvent lastDamageCause; public List blocksAround = new ArrayList<>(); public List collisionBlocks = new ArrayList<>(); @@ -353,64 +366,75 @@ public abstract class Entity extends Location implements Metadatable { public double lastMotionY; public double lastMotionZ; - public double lastPitch; public double lastYaw; + public double lastPitch; public double lastHeadYaw; - public double pitchDelta; - public double yawDelta; - public double headYawDelta; - public double entityCollisionReduction = 0; // Higher than 0.9 will result a fast collisions public AxisAlignedBB boundingBox; public boolean onGround; - public boolean inBlock = false; - public boolean positionChanged; - public boolean motionChanged; public int deadTicks = 0; - protected int age = 0; + public int age = 0; + public int ticksLived = 0; + protected int airTicks = 400; + protected boolean noFallDamage; protected float health = 20; - private int maxHealth = 20; + protected int maxHealth = 20; protected float absorption = 0; protected float ySize = 0; - public boolean keepMovement = false; + public boolean keepMovement; public float fallDistance = 0; - public int ticksLived = 0; public int lastUpdate; - public int maxFireTicks; public int fireTicks = 0; public int inPortalTicks = 0; - + public int inEndPortalTicks = 0; + protected Position portalPos; + public boolean noClip; public float scale = 1; public CompoundTag namedTag; - protected boolean isStatic = false; - - public boolean isCollided = false; - public boolean isCollidedHorizontally = false; - public boolean isCollidedVertically = false; + public boolean isCollided; + public boolean isCollidedHorizontally; + public boolean isCollidedVertically; public int noDamageTicks; public boolean justCreated; public boolean fireProof; public boolean invulnerable; + // Allow certain entities to be ticked only when a nearby block is updated or the entity is moving already + // 1 = has pending update, 3 = update on block update only, 5 = update on block update + schedule occasional updates + // updateMode % 2: 1 = has update mode, updateMode % 3: 1 = nearby update (only), 2 = occasional update + public int updateMode; + + private boolean gliding; + private boolean immobile; + private boolean sprinting; + private boolean swimming; + private boolean sneaking; + private boolean crawling; + protected Server server; public double highestPosition; - public boolean closed = false; + public boolean closed; + + public final boolean isPlayer; - protected Timing timing; + private volatile boolean init; + private volatile boolean initEntity; - protected boolean isPlayer = this instanceof Player; + private volatile boolean saveToStorage = true; - private volatile boolean initialized; + private PersistentDataContainer persistentContainer; + + private Vector3 enterMinecartPos; // Used to check the minecart achievement public float getHeight() { return 0; @@ -449,14 +473,19 @@ protected float getBaseOffset() { } public Entity(FullChunk chunk, CompoundTag nbt) { - if (this instanceof Player) { - return; + this.isPlayer = this instanceof Player; + if (!this.isPlayer) { + this.init(chunk, nbt); } - - this.init(chunk, nbt); } protected void initEntity() { + if (this.initEntity) { + throw new RuntimeException("Entity is already initialized: " + this.getName() + " (" + this.id + ')'); + } + + this.initEntity = true; + if (this.namedTag.contains("ActiveEffects")) { ListTag effects = this.namedTag.getList("ActiveEffects", CompoundTag.class); for (CompoundTag e : effects.getAll()) { @@ -473,18 +502,20 @@ protected void initEntity() { if (this.namedTag.contains("CustomName")) { this.setNameTag(this.namedTag.getString("CustomName")); + if (this.namedTag.contains("CustomNameVisible")) { this.setNameTagVisible(this.namedTag.getBoolean("CustomNameVisible")); } - if(this.namedTag.contains("CustomNameAlwaysVisible")){ + + if (this.namedTag.contains("CustomNameAlwaysVisible")) { this.setNameTagAlwaysVisible(this.namedTag.getBoolean("CustomNameAlwaysVisible")); } } - this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, true); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_HAS_COLLISION, true, false); this.dataProperties.putFloat(DATA_BOUNDING_BOX_HEIGHT, this.getHeight()); this.dataProperties.putFloat(DATA_BOUNDING_BOX_WIDTH, this.getWidth()); - this.dataProperties.putInt(DATA_HEALTH, (int) this.getHealth()); + this.dataProperties.putInt(DATA_HEALTH, (int) this.health); this.scheduleUpdate(); } @@ -494,17 +525,15 @@ protected final void init(FullChunk chunk, CompoundTag nbt) { throw new ChunkException("Invalid garbage Chunk given to Entity"); } - if (this.initialized) { - // We've already initialized this entity - return; + if (this.init) { + throw new RuntimeException("Entity is already initialized: " + this.getName() + " (" + this.id + ')'); } - this.initialized = true; - this.timing = Timings.getEntityTiming(this); + this.init = true; this.temporalVector = new Vector3(); - this.id = Entity.entityCount++; + this.id = entityCount++; this.justCreated = true; this.namedTag = nbt; @@ -512,19 +541,25 @@ protected final void init(FullChunk chunk, CompoundTag nbt) { this.setLevel(chunk.getProvider().getLevel()); this.server = chunk.getProvider().getLevel().getServer(); + if (!this.server.isPrimaryThread() && !this.level.isBeingConverted) { + this.server.getLogger().warning("Entity initialized asynchronously: " + this.getClass().getSimpleName()); + } + this.boundingBox = new SimpleAxisAlignedBB(0, 0, 0, 0, 0, 0); ListTag posList = this.namedTag.getList("Pos", DoubleTag.class); ListTag rotationList = this.namedTag.getList("Rotation", FloatTag.class); ListTag motionList = this.namedTag.getList("Motion", DoubleTag.class); + float correctedYaw = rotationList.get(0).data; + if (!(correctedYaw >= 0 && correctedYaw <= 360)) correctedYaw = 0; + float correctedPitch = rotationList.get(1).data; + if (!(correctedPitch >= 0 && correctedPitch <= 360)) correctedPitch = 0; this.setPositionAndRotation( this.temporalVector.setComponents( posList.get(0).data, posList.get(1).data, posList.get(2).data - ), - rotationList.get(0).data, - rotationList.get(1).data + ), correctedYaw, correctedPitch ); this.setMotion(this.temporalVector.setComponents( @@ -545,7 +580,7 @@ protected final void init(FullChunk chunk, CompoundTag nbt) { this.fireTicks = this.namedTag.getShort("Fire"); if (!this.namedTag.contains("Air")) { - this.namedTag.putShort("Air", 300); + this.namedTag.putShort("Air", 400); } this.setDataProperty(new ShortEntityData(DATA_AIR, this.namedTag.getShort("Air")), false); @@ -562,6 +597,7 @@ protected final void init(FullChunk chunk, CompoundTag nbt) { if (!this.namedTag.contains("Scale")) { this.namedTag.putFloat("Scale", 1); } + this.scale = this.namedTag.getFloat("Scale"); this.setDataProperty(new FloatEntityData(DATA_SCALE, scale), false); @@ -621,7 +657,7 @@ public String getScoreTag() { } public boolean isSneaking() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_SNEAKING); + return this.sneaking; } public void setSneaking() { @@ -629,11 +665,15 @@ public void setSneaking() { } public void setSneaking(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SNEAKING, value); + if (this.sneaking != value) { + this.sneaking = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SNEAKING, value); + this.recalculateBoundingBox(true); + } } public boolean isSwimming() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_SWIMMING); + return this.swimming; } public void setSwimming() { @@ -641,11 +681,15 @@ public void setSwimming() { } public void setSwimming(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SWIMMING, value); + if (this.swimming != value) { + this.swimming = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SWIMMING, value); + this.recalculateBoundingBox(true); + } } public boolean isSprinting() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_SPRINTING); + return this.sprinting; } public void setSprinting() { @@ -653,11 +697,14 @@ public void setSprinting() { } public void setSprinting(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SPRINTING, value); + if (this.sprinting != value) { + this.sprinting = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SPRINTING, value); + } } public boolean isGliding() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_GLIDING); + return this.gliding; } public void setGliding() { @@ -665,11 +712,31 @@ public void setGliding() { } public void setGliding(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_GLIDING, value); + if (this.gliding != value) { + this.gliding = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_GLIDING, value); + this.recalculateBoundingBox(true); + } + } + + public boolean isCrawling() { + return this.crawling; + } + + public void setCrawling() { + this.setCrawling(true); + } + + public void setCrawling(boolean value) { + if (this.crawling != value) { + this.crawling = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_CRAWLING, value); + this.recalculateBoundingBox(true); + } } public boolean isImmobile() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_IMMOBILE); + return this.immobile; } public void setImmobile() { @@ -677,6 +744,7 @@ public void setImmobile() { } public void setImmobile(boolean value) { + this.immobile = value; this.setDataFlag(DATA_FLAGS, DATA_FLAG_IMMOBILE, value); } @@ -705,9 +773,11 @@ public void setCanClimbWalls(boolean value) { } public void setScale(float scale) { - this.scale = scale; - this.setDataProperty(new FloatEntityData(DATA_SCALE, this.scale)); - this.recalculateBoundingBox(); + if (this.scale != scale) { + this.scale = scale; + this.setDataProperty(new FloatEntityData(DATA_SCALE, this.scale)); + this.recalculateBoundingBox(true); + } } public float getScale() { @@ -753,7 +823,6 @@ public void removeEffect(int effectId) { Effect effect = this.effects.get(effectId); this.effects.remove(effectId); effect.remove(this); - this.recalculateEffectColor(); } } @@ -778,25 +847,52 @@ public void addEffect(Effect effect) { this.recalculateEffectColor(); if (effect.getId() == Effect.HEALTH_BOOST) { - this.setHealth(this.getHealth() + 4 * (effect.getAmplifier() + 1)); + this.setHealth(this.health + ((effect.getAmplifier() + 1) << 2)); } + } + protected static void addEffectFromTippedArrow(Entity entity, Effect effect, float damage) { + switch (effect.getId()) { + case Effect.HEALING: + if (entity.isAlive()) { // Avoid dupes + entity.heal(new EntityRegainHealthEvent(entity, 4 * (effect.getAmplifier() + 1), EntityRegainHealthEvent.CAUSE_MAGIC)); + } + break; + case Effect.HARMING: + float attackDamage = (effect.getAmplifier() < 1 ? 6 : 12) - damage; + if (attackDamage > 0) { + EntityDamageEvent ev = new EntityDamageEvent(entity, DamageCause.MAGIC, attackDamage); + entity.attack(ev); + } + break; + default: + effect.add(entity); + entity.effects.put(effect.getId(), effect); + entity.recalculateEffectColor(); + } } public void recalculateBoundingBox() { - this.recalculateBoundingBox(true); + this.recalculateBoundingBox(false); } public void recalculateBoundingBox(boolean send) { - float height = this.getHeight() * this.scale; + float height = this.getHeight(); double radius = (this.getWidth() * this.scale) / 2d; - this.boundingBox.setBounds(x - radius, y, z - radius, x + radius, y + height, z + radius); + this.boundingBox.setBounds( + this.x - radius, + this.y + this.ySize, + z - radius, + x + radius, + y + height * this.scale + this.ySize, + z + radius + ); - FloatEntityData bbH = new FloatEntityData(DATA_BOUNDING_BOX_HEIGHT, this.getHeight()); - FloatEntityData bbW = new FloatEntityData(DATA_BOUNDING_BOX_WIDTH, this.getWidth()); - this.dataProperties.put(bbH); - this.dataProperties.put(bbW); if (send) { + FloatEntityData bbH = new FloatEntityData(DATA_BOUNDING_BOX_HEIGHT, height); + FloatEntityData bbW = new FloatEntityData(DATA_BOUNDING_BOX_WIDTH, this.getWidth()); + this.dataProperties.put(bbH); + this.dataProperties.put(bbW); sendData(this.hasSpawned.values().toArray(new Player[0]), new EntityMetadata().put(bbH).put(bbW)); } } @@ -840,50 +936,62 @@ public static Entity createEntity(int type, Position pos, Object... args) { } public static Entity createEntity(String name, FullChunk chunk, CompoundTag nbt, Object... args) { - Entity entity = null; - - if (knownEntities.containsKey(name)) { - Class clazz = knownEntities.get(name); - - if (clazz == null) { + Class clazz = knownEntities.get(name); + if (clazz == null) { + EntityDefinition definition = EntityManager.get().getDefinition(name); + if (definition == null) { return null; } + clazz = definition.getImplementation(); + } + return createEntity0(clazz, chunk, nbt, args); + } - for (Constructor constructor : clazz.getConstructors()) { - if (entity != null) { - break; - } + public static Entity createEntity(int type, FullChunk chunk, CompoundTag nbt, Object... args) { + String identifier = String.valueOf(type); + if (knownEntities.containsKey(identifier)) { + return createEntity(identifier, chunk, nbt, args); + } - if (constructor.getParameterCount() != (args == null ? 2 : args.length + 2)) { - continue; - } + EntityDefinition definition = EntityManager.get().getDefinition(type); + if (definition != null) { + return createEntity0(definition.getImplementation(), chunk, nbt, args); + } + return null; + } - try { - if (args == null || args.length == 0) { - entity = (Entity) constructor.newInstance(chunk, nbt); - } else { - Object[] objects = new Object[args.length + 2]; + private static Entity createEntity0(Class clazz, FullChunk chunk, CompoundTag nbt, Object... args) { + Entity entity = null; - objects[0] = chunk; - objects[1] = nbt; - System.arraycopy(args, 0, objects, 2, args.length); - entity = (Entity) constructor.newInstance(objects); + for (Constructor constructor : clazz.getConstructors()) { + if (entity != null) { + break; + } - } - } catch (Exception e) { - MainLogger.getLogger().logException(e); - } + if (constructor.getParameterCount() != (args == null ? 2 : args.length + 2)) { + continue; + } + + try { + if (args == null || args.length == 0) { + entity = (Entity) constructor.newInstance(chunk, nbt); + } else { + Object[] objects = new Object[args.length + 2]; + objects[0] = chunk; + objects[1] = nbt; + System.arraycopy(args, 0, objects, 2, args.length); + entity = (Entity) constructor.newInstance(objects); + + } + } catch (Exception e) { + MainLogger.getLogger().error("Failed to create an instance of " + clazz.getName(), e); } } return entity; } - public static Entity createEntity(int type, FullChunk chunk, CompoundTag nbt, Object... args) { - return createEntity(String.valueOf(type), chunk, nbt, args); - } - public static boolean registerEntity(String name, Class clazz) { return registerEntity(name, clazz, false); } @@ -938,8 +1046,9 @@ public static CompoundTag getDefaultNBT(Vector3 pos, Vector3 motion, float yaw, public void saveNBT() { if (!(this instanceof Player)) { this.namedTag.putString("id", this.getSaveId()); - if (!this.getNameTag().equals("")) { - this.namedTag.putString("CustomName", this.getNameTag()); + String customName = this.getNameTag(); + if (!customName.isEmpty()) { + this.namedTag.putString("CustomName", customName); this.namedTag.putBoolean("CustomNameVisible", this.isNameTagVisible()); this.namedTag.putBoolean("CustomNameAlwaysVisible", this.isNameTagAlwaysVisible()); } else { @@ -968,7 +1077,7 @@ public void saveNBT() { this.namedTag.putFloat("FallDistance", this.fallDistance); this.namedTag.putShort("Fire", this.fireTicks); - this.namedTag.putShort("Air", this.getDataPropertyShort(DATA_AIR)); + this.namedTag.putShort("Air", this.airTicks); this.namedTag.putBoolean("OnGround", this.onGround); this.namedTag.putBoolean("Invulnerable", this.invulnerable); this.namedTag.putFloat("Scale", this.scale); @@ -1000,34 +1109,44 @@ public String getName() { } public final String getSaveId() { + if (this instanceof CustomEntity) { + EntityDefinition definition = ((CustomEntity) this).getEntityDefinition(); + return definition == null ? "" : definition.getIdentifier(); + } return shortNames.getOrDefault(this.getClass().getSimpleName(), ""); } public void spawnTo(Player player) { - - if (!this.hasSpawned.containsKey(player.getLoaderId()) && this.chunk != null && player.usedChunks.containsKey(Level.chunkHash(this.chunk.getX(), this.chunk.getZ()))) { - this.hasSpawned.put(player.getLoaderId(), player); - player.dataPacket(createAddEntityPacket()); + if (!this.init || !this.initEntity) { + this.server.getLogger().warning("Spawned an entity that is not initialized yet: " + this.getClass().getName() + " (" + this.id + ')'); } - if (this.riding != null) { - this.riding.spawnTo(player); + if (!this.hasSpawned.containsKey(player.getLoaderId())) { + Boolean hasChunk = player.usedChunks.get(Level.chunkHash(this.chunk.getX(), this.chunk.getZ())); + if (hasChunk != null && hasChunk) { + player.dataPacket(createAddEntityPacket()); + this.hasSpawned.put(player.getLoaderId(), player); + + if (this.riding != null) { + this.riding.spawnTo(player); - SetEntityLinkPacket pkk = new SetEntityLinkPacket(); - pkk.vehicleUniqueId = this.riding.getId(); - pkk.riderUniqueId = this.getId(); - pkk.type = 1; - pkk.immediate = 1; + SetEntityLinkPacket pkk = new SetEntityLinkPacket(); + pkk.vehicleUniqueId = this.riding.id; + pkk.riderUniqueId = this.id; + pkk.type = 1; + pkk.immediate = 1; - player.dataPacket(pkk); + player.dataPacket(pkk); + } + } } } protected DataPacket createAddEntityPacket() { AddEntityPacket addEntity = new AddEntityPacket(); addEntity.type = this.getNetworkId(); - addEntity.entityUniqueId = this.getId(); - addEntity.entityRuntimeId = this.getId(); + addEntity.entityUniqueId = this.id; + addEntity.entityRuntimeId = this.id; addEntity.yaw = (float) this.yaw; addEntity.headYaw = (float) this.yaw; addEntity.pitch = (float) this.pitch; @@ -1037,11 +1156,11 @@ protected DataPacket createAddEntityPacket() { addEntity.speedX = (float) this.motionX; addEntity.speedY = (float) this.motionY; addEntity.speedZ = (float) this.motionZ; - addEntity.metadata = this.dataProperties; + addEntity.metadata = this.dataProperties.clone(); addEntity.links = new EntityLink[this.passengers.size()]; for (int i = 0; i < addEntity.links.length; i++) { - addEntity.links[i] = new EntityLink(this.getId(), this.passengers.get(i).getId(), i == 0 ? EntityLink.TYPE_RIDER : TYPE_PASSENGER, false, false); + addEntity.links[i] = new EntityLink(this.id, this.passengers.get(i).id, i == 0 ? EntityLink.TYPE_RIDER : TYPE_PASSENGER, false, false); } return addEntity; @@ -1054,7 +1173,7 @@ public Map getViewers() { public void sendPotionEffects(Player player) { for (Effect effect : this.effects.values()) { MobEffectPacket pk = new MobEffectPacket(); - pk.eid = this.getId(); + pk.eid = this.id; pk.effectId = effect.getId(); pk.amplifier = effect.getAmplifier(); pk.particles = effect.isVisible(); @@ -1071,9 +1190,8 @@ public void sendData(Player player) { public void sendData(Player player, EntityMetadata data) { SetEntityDataPacket pk = new SetEntityDataPacket(); - pk.eid = this.getId(); - pk.metadata = data == null ? this.dataProperties : data; - + pk.eid = this.id; + pk.metadata = data == null ? this.dataProperties.clone() : data; player.dataPacket(pk); } @@ -1083,29 +1201,41 @@ public void sendData(Player[] players) { public void sendData(Player[] players, EntityMetadata data) { SetEntityDataPacket pk = new SetEntityDataPacket(); - pk.eid = this.getId(); - pk.metadata = data == null ? this.dataProperties : data; + pk.eid = this.id; + //pk.metadata = data == null ? this.dataProperties : data; for (Player player : players) { if (player == this) { continue; } - player.dataPacket(pk.clone()); + pk.metadata = data == null ? this.dataProperties.clone() : data; + player.dataPacket(pk); } if (this instanceof Player) { + pk.metadata = data == null ? this.dataProperties.clone() : data; ((Player) this).dataPacket(pk); } } public void despawnFrom(Player player) { - if (this.hasSpawned.containsKey(player.getLoaderId())) { + if (this.hasSpawned.remove(player.getLoaderId()) != null) { RemoveEntityPacket pk = new RemoveEntityPacket(); - pk.eid = this.getId(); + pk.eid = this.id; player.dataPacket(pk); - this.hasSpawned.remove(player.getLoaderId()); } } + /** + * Check if player's hit should be critical + * @param player player + * @return can make a critical hit + */ + private static boolean canCriticalHit(Player player) { + if (player.isOnGround() || player.riding != null || player.speed == null || player.speed.y <= 0 || player.hasEffect(Effect.BLINDNESS)) return false; + int b = player.getLevel().getBlockIdAt(player.chunk, player.getFloorX(), player.getFloorY(), player.getFloorZ()); + return b != Block.LADDER && b != Block.VINES && !Block.hasWater(b); + } + public boolean attack(EntityDamageEvent source) { if (hasEffect(Effect.FIRE_RESISTANCE) && (source.getCause() == DamageCause.FIRE @@ -1114,13 +1244,29 @@ public boolean attack(EntityDamageEvent source) { return false; } - getServer().getPluginManager().callEvent(source); + if (this instanceof EntityLiving && source instanceof EntityDamageByEntityEvent && !(source instanceof EntityDamageByChildEntityEvent)) { + Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); + if (damager instanceof Player && canCriticalHit((Player) damager)) { + source.setDamage(source.getFinalDamage() * 0.5f, EntityDamageEvent.DamageModifier.CRITICAL); + } + } + + server.getPluginManager().callEvent(source); if (source.isCancelled()) { return false; } - // Make fire aspect to set the target in fire before dealing any damage so the target is in fire on death even if killed by the first hit + if (source.isApplicable(EntityDamageEvent.DamageModifier.CRITICAL)) { + AnimatePacket animate = new AnimatePacket(); + animate.action = AnimatePacket.Action.CRITICAL_HIT; + animate.eid = this.getId(); + + this.getLevel().addChunkPacket(this.getChunkX(), this.getChunkZ(), animate); + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_STRONG); + } + if (source instanceof EntityDamageByEntityEvent) { + // Make fire aspect to set the target in fire before dealing any damage so the target is in fire on death even if killed by the first hit Enchantment[] enchantments = ((EntityDamageByEntityEvent) source).getWeaponEnchantments(); if (enchantments != null) { for (Enchantment enchantment : enchantments) { @@ -1129,21 +1275,38 @@ public boolean attack(EntityDamageEvent source) { } } - if (this.absorption > 0) { // Damage Absorption - this.setAbsorption(Math.max(0, this.getAbsorption() + source.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION))); + float finalDamage = source.getFinalDamage(); + float a = this.absorption; + + if (a > 0) { // Damage Absorption + //this.setAbsorption(Math.max(0, this.absorption + source.getDamage(EntityDamageEvent.DamageModifier.ABSORPTION))); + this.setAbsorption(Math.max(0, a - finalDamage)); + finalDamage = Math.max(0, finalDamage - a); } - setLastDamageCause(source); - float newHealth = getHealth() - source.getFinalDamage(); + setLastDamageCause(source); + float newHealth = this.health - finalDamage; if (newHealth < 1 && this instanceof Player) { if (source.getCause() != DamageCause.VOID && source.getCause() != DamageCause.SUICIDE) { Player p = (Player) this; boolean totem = false; - boolean isOffhand = false; - if (p.getOffhandInventory().getItem(0).getId() == ItemID.TOTEM) { + if (p.getOffhandInventory().getItemFast(0).getId() == ItemID.TOTEM) { + // Must be sent before removing the item + EntityEventPacket pk = new EntityEventPacket(); + pk.eid = this.getId(); + pk.event = EntityEventPacket.CONSUME_TOTEM; + p.dataPacket(pk); + + p.getOffhandInventory().clear(0); totem = true; - isOffhand = true; - } else if (p.getInventory().getItemInHand().getId() == ItemID.TOTEM) { + } else if (p.getInventory().getItemInHandFast().getId() == ItemID.TOTEM) { + // Must be sent before removing the item + EntityEventPacket pk = new EntityEventPacket(); + pk.eid = this.getId(); + pk.event = EntityEventPacket.CONSUME_TOTEM; + p.dataPacket(pk); + + p.getInventory().clear(p.getInventory().getHeldItemIndex()); totem = true; } if (totem) { @@ -1157,24 +1320,17 @@ public boolean attack(EntityDamageEvent source) { this.addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(800)); this.addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(100).setAmplifier(1)); - EntityEventPacket pk = new EntityEventPacket(); - pk.eid = this.getId(); - pk.event = EntityEventPacket.CONSUME_TOTEM; - p.dataPacket(pk); - - if (isOffhand) { - p.getOffhandInventory().clear(0); - } else { - p.getInventory().clear(p.getInventory().getHeldItemIndex()); - } - - source.setCancelled(true); return false; } } } - setHealth(newHealth); + if (finalDamage >= 18 && source instanceof EntityDamageByEntityEvent && source.getCause() == DamageCause.ENTITY_ATTACK) { + Entity p = ((EntityDamageByEntityEvent) source).getDamager(); + if (p instanceof Player) { + ((Player) p).awardAchievement("overkill"); + } + } return true; } @@ -1187,7 +1343,7 @@ public void heal(EntityRegainHealthEvent source) { if (source.isCancelled()) { return; } - this.setHealth(this.getHealth() + source.getAmount()); + this.setHealth(this.health + source.getAmount()); } public void heal(float amount) { @@ -1199,7 +1355,7 @@ public float getHealth() { } public boolean isAlive() { - return this.health > 0; + return this.health >= 1; } public boolean isClosed() { @@ -1221,7 +1377,12 @@ public void setHealth(float health) { this.health = this.getMaxHealth(); } - setDataProperty(new IntEntityData(DATA_HEALTH, (int) this.health)); + // Sending health updates to viewers is not necessary in most cases + /*if (this instanceof Player) { + setDataPropertyAndSendOnlyToSelf(new IntEntityData(DATA_HEALTH, (int) this.health)); + } else { + setDataProperty(new IntEntityData(DATA_HEALTH, (int) this.health), this instanceof EntityRideable || this instanceof EntityBoss); + }*/ } public void setLastDamageCause(EntityDamageEvent type) { @@ -1232,8 +1393,20 @@ public EntityDamageEvent getLastDamageCause() { return lastDamageCause; } + /** + * Get maximum health including health from health boost effect + * @return current max health + */ public int getMaxHealth() { - return maxHealth + (this.hasEffect(Effect.HEALTH_BOOST) ? 4 * (this.getEffect(Effect.HEALTH_BOOST).getAmplifier() + 1) : 0); + return maxHealth + (this.hasEffect(Effect.HEALTH_BOOST) ? (this.getEffect(Effect.HEALTH_BOOST).getAmplifier() + 1) << 2 : 0); + } + + /** + * Get normal maximum health excluding health from effects + * @return real max health + */ + public int getRealMaxHealth() { + return maxHealth; } public void setMaxHealth(int maxHealth) { @@ -1241,11 +1414,11 @@ public void setMaxHealth(int maxHealth) { } public boolean canCollideWith(Entity entity) { - return !this.justCreated && this != entity; + return entity != null && !this.justCreated && this != entity && !entity.noClip && !this.noClip; } protected boolean checkObstruction(double x, double y, double z) { - if (this.level.getCollisionCubes(this, this.getBoundingBox(), false).length == 0) { + if (this.noClip || this.level.getCollisionCubes(this, this.boundingBox, false).length == 0) { return false; } @@ -1257,13 +1430,13 @@ protected boolean checkObstruction(double x, double y, double z) { double diffY = y - j; double diffZ = z - k; - if (!Block.transparent[this.level.getBlockIdAt(i, j, k)]) { - boolean flag = Block.transparent[this.level.getBlockIdAt(i - 1, j, k)]; - boolean flag1 = Block.transparent[this.level.getBlockIdAt(i + 1, j, k)]; - boolean flag2 = Block.transparent[this.level.getBlockIdAt(i, j - 1, k)]; - boolean flag3 = Block.transparent[this.level.getBlockIdAt(i, j + 1, k)]; - boolean flag4 = Block.transparent[this.level.getBlockIdAt(i, j, k - 1)]; - boolean flag5 = Block.transparent[this.level.getBlockIdAt(i, j, k + 1)]; + if (!Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i, j, k))) { + boolean flag = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i - 1, j, k)); + boolean flag1 = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i + 1, j, k)); + boolean flag2 = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i, j - 1, k)); + boolean flag3 = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i, j + 1, k)); + boolean flag4 = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i, j, k - 1)); + boolean flag5 = Block.isBlockTransparentById(this.level.getBlockIdAt(this.chunk, i, j, k + 1)); int direction = -1; double limit = 9999; @@ -1297,7 +1470,7 @@ protected boolean checkObstruction(double x, double y, double z) { direction = 5; } - double force = new Random().nextDouble() * 0.2 + 0.1; + double force = Utils.random.nextDouble() * 0.2 + 0.1; if (direction == 0) { this.motionX = -force; @@ -1344,26 +1517,24 @@ public boolean entityBaseTick() { } public boolean entityBaseTick(int tickDiff) { - Timings.entityBaseTickTimer.startTiming(); - - if (!this.isPlayer) { - this.blocksAround = null; + if (!(this instanceof Player)) { + //this.blocksAround = null; // Use only when entity moves for better performance this.collisionBlocks = null; } + this.justCreated = false; if (!this.isAlive()) { - this.removeAllEffects(); + //this.removeAllEffects(); // Why to remove them if the entity is dead anyways? this.despawnFromAll(); - if (!this.isPlayer) { + if (!(this instanceof Player)) { this.close(); } - Timings.entityBaseTickTimer.stopTiming(); return false; } - if (riding != null && !riding.isAlive() && riding instanceof EntityRideable) { - ((EntityRideable) riding).dismountEntity(this); - } + /*if (riding != null && !riding.isAlive() && riding instanceof EntityRideable) { + ((EntityRideable) riding).mountEntity(this); + }*/ updatePassengers(); @@ -1384,10 +1555,9 @@ public boolean entityBaseTick(int tickDiff) { this.checkBlockCollision(); - if (this.y <= -16 && this.isAlive()) { + if (this.y <= (this.level.getMinBlockY() - 16) && this.isAlive()) { if (this instanceof Player) { - Player player = (Player) this; - if (!player.isCreative()) this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); + if (((Player) this).getGamemode() != Player.CREATIVE) this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); } else { this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); hasUpdate = true; @@ -1396,19 +1566,19 @@ public boolean entityBaseTick(int tickDiff) { if (this.fireTicks > 0) { if (this.fireProof) { - this.fireTicks -= 4 * tickDiff; + this.fireTicks -= tickDiff << 2; if (this.fireTicks < 0) { this.fireTicks = 0; } } else { - if (!this.hasEffect(Effect.FIRE_RESISTANCE) && ((this.fireTicks % 20) == 0 || tickDiff > 20)) { + if (((this.fireTicks % 20) == 0 || tickDiff > 20) && !this.hasEffect(Effect.FIRE_RESISTANCE)) { this.attack(new EntityDamageEvent(this, DamageCause.FIRE_TICK, 1)); } this.fireTicks -= tickDiff; } if (this.fireTicks <= 0) { this.extinguish(); - } else if (!this.fireProof && (!(this instanceof Player) || !((Player) this).isSpectator())) { + } else if (!this.fireProof && (!(this instanceof Player) || !((Player) this).isSpectator()) && !(this instanceof EntityBoss)) { this.setDataFlag(DATA_FLAGS, DATA_FLAG_ONFIRE, true); hasUpdate = true; } @@ -1421,45 +1591,34 @@ public boolean entityBaseTick(int tickDiff) { } } - if (this.inPortalTicks == 80) { - EntityPortalEnterEvent ev = new EntityPortalEnterEvent(this, PortalType.NETHER); - getServer().getPluginManager().callEvent(ev); + if (this.inPortalTicks == 80 && Server.getInstance().isNetherAllowed() && this instanceof BaseEntity) { + EntityPortalEnterEvent ev = new EntityPortalEnterEvent(this, EntityPortalEnterEvent.PortalType.NETHER); + this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { - Position newPos = EnumLevel.moveToNether(this); - if (newPos != null) { - for (int x = -1; x < 2; x++) { - for (int z = -1; z < 2; z++) { - int chunkX = (newPos.getFloorX() >> 4) + x, chunkZ = (newPos.getFloorZ() >> 4) + z; - FullChunk chunk = newPos.level.getChunk(chunkX, chunkZ, false); - if (chunk == null || !(chunk.isGenerated() || chunk.isPopulated())) { - newPos.level.generateChunk(chunkX, chunkZ, true); - } - } - } - this.teleport(newPos.add(1.5, 1, 0.5)); - server.getScheduler().scheduleDelayedTask(new Task() { - @Override - public void onRun(int currentTick) { - // dirty hack to make sure chunks are loaded and generated before spawning - // player - teleport(newPos.add(1.5, 1, 0.5)); - BlockNetherPortal.spawnPortal(newPos); - } - }, 20); + if (this.getLevel().getDimension() == Level.DIMENSION_NETHER) { + this.switchLevel(server.getDefaultLevel()); + } else { + this.switchLevel(server.getLevelByName("nether")); } } } this.age += tickDiff; this.ticksLived += tickDiff; - TimingsHistory.activatedEntityTicks++; - - Timings.entityBaseTickTimer.stopTiming(); return hasUpdate; } public void updateMovement() { + // Reset motion when it approaches 0 to avoid unnecessary processing of move() + if (Math.abs(this.motionX) < 0.00001) { + this.motionX = 0; + } + + if (Math.abs(this.motionZ) < 0.00001) { + this.motionZ = 0; + } + double diffPosition = (this.x - this.lastX) * (this.x - this.lastX) + (this.y - this.lastY) * (this.y - this.lastY) + (this.z - this.lastZ) * (this.z - this.lastZ); double diffRotation = (this.yaw - this.lastYaw) * (this.yaw - this.lastYaw) + (this.pitch - this.lastPitch) * (this.pitch - this.lastPitch); @@ -1470,12 +1629,11 @@ public void updateMovement() { this.lastY = this.y; this.lastZ = this.z; - this.lastPitch = this.pitch; this.lastYaw = this.yaw; + this.lastPitch = this.pitch; this.lastHeadYaw = this.headYaw; - // If you want to achieve headYaw in movement. You can override it by yourself. Changing would break some mob plugins. - this.addMovement(this.x, this.isPlayer ? this.y : this.y + this.getBaseOffset(), this.z, this.yaw, this.pitch, this.yaw); + this.addMovement(this.x, this instanceof Player ? this.y : this.y + this.getBaseOffset(), this.z, this.yaw, this.pitch, this.headYaw == 0 || this instanceof Player ? this.yaw : this.headYaw); } if (diffMotion > 0.0025 || (diffMotion > 0.0001 && this.getMotion().lengthSquared() <= 0.0001)) { //0.05 ** 2 @@ -1492,22 +1650,19 @@ public void addMovement(double x, double y, double z, double yaw, double pitch, } public void addMotion(double motionX, double motionY, double motionZ) { + if (this instanceof EntityItem) return; // Seems to be unnecessary SetEntityMotionPacket pk = new SetEntityMotionPacket(); pk.eid = this.id; pk.motionX = (float) motionX; pk.motionY = (float) motionY; pk.motionZ = (float) motionZ; - - Server.broadcastPacket(this.hasSpawned.values(), pk); - } - - public Vector3 getDirectionVector() { - Vector3 vector = super.getDirectionVector(); - return this.temporalVector.setComponents(vector.x, vector.y, vector.z); + for (Player p : this.hasSpawned.values()) { + p.dataPacket(pk); + } } public Vector2 getDirectionPlane() { - return (new Vector2((float) (-Math.cos(Math.toRadians(this.yaw) - Math.PI / 2)), (float) (-Math.sin(Math.toRadians(this.yaw) - Math.PI / 2)))).normalize(); + return (new Vector2((float) (-Math.cos(Math.toRadians(this.yaw) - 1.5707963267948966)), (float) (-Math.sin(Math.toRadians(this.yaw) - 1.5707963267948966)))).normalize(); } public BlockFace getHorizontalFacing() { @@ -1523,7 +1678,7 @@ public boolean onUpdate(int currentTick) { ++this.deadTicks; if (this.deadTicks >= 10) { this.despawnFromAll(); - if (!this.isPlayer) { + if (!(this instanceof Player)) { this.close(); } } @@ -1531,7 +1686,6 @@ public boolean onUpdate(int currentTick) { } int tickDiff = currentTick - this.lastUpdate; - if (tickDiff <= 0) { return false; } @@ -1550,7 +1704,7 @@ public boolean mountEntity(Entity entity) { } /** - * Mount an Entity from a/into vehicle + * Mount an Entity into a vehicle * * @param entity The target Entity * @return {@code true} if the mounting successful @@ -1558,17 +1712,20 @@ public boolean mountEntity(Entity entity) { public boolean mountEntity(Entity entity, byte mode) { Objects.requireNonNull(entity, "The target of the mounting entity can't be null"); + if (entity instanceof Player && ((Player) entity).isSleeping()) { + return false; + } + if (isPassenger(entity) || entity.riding != null && !entity.riding.dismountEntity(entity, false)) { return false; } // Entity entering a vehicle - EntityVehicleEnterEvent ev = new EntityVehicleEnterEvent(entity, this); + EntityVehicleEnterEvent ev = new EntityVehicleEnterEvent(entity, (EntityVehicle) this); server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } - broadcastLinkPacket(entity, mode); // Add variables to entity @@ -1576,6 +1733,10 @@ public boolean mountEntity(Entity entity, byte mode) { entity.setDataFlag(DATA_FLAGS, DATA_FLAG_RIDING, true); passengers.add(entity); + if (this instanceof EntityMinecartEmpty) { + entity.enterMinecartPos = new Vector3(entity.x, entity.y, entity.z); + } + entity.setSeatPosition(getMountedOffset(entity)); updatePassengerPosition(entity); return true; @@ -1586,17 +1747,19 @@ public boolean dismountEntity(Entity entity) { } public boolean dismountEntity(Entity entity, boolean sendLinks) { - // Run the events - EntityVehicleExitEvent ev = new EntityVehicleExitEvent(entity, this); - server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - int seatIndex = this.passengers.indexOf(entity); - if (seatIndex == 0) { - this.broadcastLinkPacket(entity, TYPE_RIDE); - } else if (seatIndex != -1) { - this.broadcastLinkPacket(entity, TYPE_PASSENGER); + if (this instanceof EntityVehicle) { + // Run the events + EntityVehicleExitEvent ev = new EntityVehicleExitEvent(entity, (EntityVehicle) this); + server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + int seatIndex = this.passengers.indexOf(entity); + if (seatIndex == 0) { + this.broadcastLinkPacket(entity, TYPE_RIDE); + } else if (seatIndex != -1) { + this.broadcastLinkPacket(entity, TYPE_PASSENGER); + } + return false; } - return false; } if (sendLinks) { @@ -1611,13 +1774,22 @@ public boolean dismountEntity(Entity entity, boolean sendLinks) { entity.setSeatPosition(new Vector3f()); updatePassengerPosition(entity); + // Avoid issues with anti fly + entity.resetFallDistance(); + + if (this instanceof EntityMinecartEmpty) { + if (entity instanceof Player && entity.enterMinecartPos != null && this.distanceSquared(entity.enterMinecartPos) >= 1000000) { // 1000 blocks + ((Player) entity).awardAchievement("onARail"); + } + entity.enterMinecartPos = null; + } return true; } protected void broadcastLinkPacket(Entity rider, byte type) { SetEntityLinkPacket pk = new SetEntityLinkPacket(); - pk.vehicleUniqueId = getId(); // To the? - pk.riderUniqueId = rider.getId(); // From who? + pk.vehicleUniqueId = id; // To the? + pk.riderUniqueId = rider.id; // From who? pk.type = type; Server.broadcastPacket(this.hasSpawned.values(), pk); @@ -1655,7 +1827,9 @@ public Vector3f getMountedOffset(Entity entity) { } public final void scheduleUpdate() { - this.level.updateEntities.put(this.id, this); + if (!this.closed && !this.level.isBeingConverted) { + this.level.updateEntities.put(this.id, this); + } } public boolean isOnFire() { @@ -1676,8 +1850,7 @@ public float getAbsorption() { public void setAbsorption(float absorption) { if (absorption != this.absorption) { this.absorption = absorption; - if (this instanceof Player) - ((Player) this).setAttribute(Attribute.getAttribute(Attribute.ABSORPTION).setValue(absorption)); + if (this instanceof Player) ((Player) this).setAttribute(Attribute.getAttribute(Attribute.ABSORPTION).setValue(absorption)); } } @@ -1695,6 +1868,7 @@ public BlockFace getDirection() { } else if (225 <= rotation && rotation < 315) { return BlockFace.EAST; } else { + this.server.getLogger().debug("Failed to getDirection for yaw=" + this.yaw); return null; } } @@ -1709,7 +1883,7 @@ public boolean canTriggerWalking() { } public void resetFallDistance() { - this.highestPosition = 0; + this.highestPosition = this.y; } protected void updateFallState(boolean onGround) { @@ -1717,9 +1891,8 @@ protected void updateFallState(boolean onGround) { fallDistance = (float) (this.highestPosition - this.y); if (fallDistance > 0) { - // check if we fell into at least 1 block of water - if (this instanceof EntityLiving && !(this.getLevelBlock() instanceof BlockWater)) { - this.fall(fallDistance); + if (this instanceof EntityLiving && !(this instanceof EntityFlying)) { + this.fall(fallDistance); // NOTE -- if you override fall(): water check was moved INSIDE fall() } this.resetFallDistance(); } @@ -1731,48 +1904,54 @@ public AxisAlignedBB getBoundingBox() { } public void fall(float fallDistance) { - if (this.hasEffect(Effect.SLOW_FALLING)) { - return; - } - - Block down = this.level.getBlock(this.floor().down()); - - if (!this.isPlayer || level.getGameRules().getBoolean(GameRule.FALL_DAMAGE)) { - float damage = (float) Math.floor(fallDistance - 3 - (this.hasEffect(Effect.JUMP) ? this.getEffect(Effect.JUMP).getAmplifier() + 1 : 0)); - - if (down instanceof BlockHayBale) { - damage -= (damage * 0.8f); - } - - if (damage > 0) { - this.attack(new EntityDamageEvent(this, DamageCause.FALL, damage)); - } - } - if (fallDistance > 0.75) { + int block = this.level.getBlockIdAt(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ()); + if (Block.hasWater(block)) { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_SPLASH, ThreadLocalRandom.current().nextInt(600000, 800000), "minecraft:player", false, false); + return; // TODO: Some waterlogged blocks prevent fall damage + } + if (!this.hasEffect(Effect.SLOW_FALLING)) { + Block down = this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY() - 1, this.getFloorZ(), true); + int floor = down.getId(); + + if (!this.noFallDamage) { + float damage = (float) Math.floor(fallDistance - 3 - (this.hasEffect(Effect.JUMP) ? this.getEffect(Effect.JUMP).getAmplifier() + 1 : 0)); + + if (floor == BlockID.HAY_BALE || block == BlockID.HAY_BALE) { + damage -= (damage * 0.8f); + } else if (floor == BlockID.BED_BLOCK || block == BlockID.BED_BLOCK) { + damage -= (damage * 0.5f); + } else if (floor == BlockID.SWEET_BERRY_BUSH || floor == BlockID.SCAFFOLDING || block == BlockID.SWEET_BERRY_BUSH || block == BlockID.SCAFFOLDING) { + damage = 0; + } else if ((floor == BlockID.SLIME_BLOCK || floor == BlockID.COBWEB || block == BlockID.SLIME_BLOCK || block == BlockID.COBWEB) && !this.isSneaking()) { + damage = 0; + } - if (down.getId() == Item.FARMLAND) { - Event ev; - - if (this instanceof Player) { - ev = new PlayerInteractEvent((Player) this, null, down, null, Action.PHYSICAL); - } else { - ev = new EntityInteractEvent(this, down); + if (damage > 0) { + if (!(this instanceof Player) || level.getGameRules().getBoolean(GameRule.FALL_DAMAGE)) { + this.attack(new EntityDamageEvent(this, DamageCause.FALL, damage)); + } + } } - this.server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return; + if (floor == BlockID.FARMLAND) { + Event ev; + if (this instanceof Player) { + ev = new PlayerInteractEvent((Player) this, null, down, null, Action.PHYSICAL); + } else { + ev = new EntityInteractEvent(this, down); + } + + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } + this.level.setBlock(down, Block.get(BlockID.DIRT), true, true); } - this.level.setBlock(down, Block.get(BlockID.DIRT), false, true); } } } - public void handleLavaMovement() { - //todo - } - public void moveFlying(float strafe, float forward, float friction) { // This is special for Nukkit! :) float speed = strafe * strafe + forward * forward; @@ -1791,10 +1970,6 @@ public void moveFlying(float strafe, float forward, float friction) { } } - public void onCollideWithPlayer(EntityHuman entityPlayer) { - - } - public void applyEntityCollision(Entity entity) { if (entity.riding != this && !entity.passengers.contains(this)) { double dx = entity.x - this.x; @@ -1816,7 +1991,6 @@ public void applyEntityCollision(Entity entity) { dx *= 0.05000000074505806; dy *= 0.05000000074505806; dx *= 1F + entityCollisionReduction; - if (this.riding == null) { motionX -= dx; motionZ -= dy; @@ -1825,9 +1999,9 @@ public void applyEntityCollision(Entity entity) { } } - public void onStruckByLightning(Entity entity) { - if (this.attack(new EntityDamageByEntityEvent(entity, this, DamageCause.LIGHTNING, 5))) { - if (this.fireTicks < 8 * 20) { + public void onStruckByLightning(Entity lightning) { + if (this.attack(new EntityDamageByEntityEvent(lightning, this, DamageCause.LIGHTNING, 5))) { + if (this.fireTicks < 160) { this.setOnFire(8); } } @@ -1858,15 +2032,26 @@ protected boolean switchLevel(Level targetLevel) { this.chunk.removeEntity(this); } this.despawnFromAll(); + + this.preSwitchLevel(); } this.setLevel(targetLevel); this.level.addEntity(this); this.chunk = null; + this.afterSwitchLevel(); return true; } + protected void preSwitchLevel() { + // Override in Player + } + + protected void afterSwitchLevel() { + // Override in Player + } + public Position getPosition() { return new Position(this.x, this.y, this.z, this.level); } @@ -1875,31 +2060,36 @@ public Location getLocation() { return new Location(this.x, this.y, this.z, this.yaw, this.pitch, this.headYaw, this.level); } - public boolean isInsideOfWater() { - double y = this.y + this.getEyeHeight(); - Block block = this.level.getBlock(this.temporalVector.setComponents(NukkitMath.floorDouble(this.x), NukkitMath.floorDouble(y), NukkitMath.floorDouble(this.z))); + public boolean isSubmerged() { + int fx = this.getFloorX(); + int fy = NukkitMath.floorDouble(this.y + this.getEyeHeight()); + int fz = this.getFloorZ(); + int bid = level.getBlockIdAt(this.chunk, fx, fy, fz); - if (block instanceof BlockWater) { - double f = (block.y + 1) - (((BlockWater) block).getFluidHeightPercent() - 0.1111111); - return y < f; - } + return bid != Block.BUBBLE_COLUMN && + (Block.hasWater(bid) || this.level.isBlockWaterloggedAt(this.chunk, fx, fy, fz)); + } - return false; + public boolean isInsideOfWater() { + int fx = this.getFloorX(); + int fy = this.getFloorY(); + int fz = this.getFloorZ(); + int bid = level.getBlockIdAt(this.chunk, fx, fy, fz); + + return Block.hasWater(bid) || this.level.isBlockWaterloggedAt(this.chunk, fx, fy, fz); } public boolean isInsideOfSolid() { double y = this.y + this.getEyeHeight(); - Block block = this.level.getBlock( - this.temporalVector.setComponents( + Block block = this.level.getBlock(this.chunk, NukkitMath.floorDouble(this.x), NukkitMath.floorDouble(y), - NukkitMath.floorDouble(this.z)) + NukkitMath.floorDouble(this.z), true ); AxisAlignedBB bb = block.getBoundingBox(); - return bb != null && block.isSolid() && !block.isTransparent() && bb.intersectsWith(this.getBoundingBox()); - + return bb != null && block.isSolid() && !block.isTransparent() && bb.intersectsWith(this.boundingBox) && !(block instanceof BlockSlab); // The instanceof BlockSlab check is a hack to fix issues with the solid slab hack } public boolean isInsideOfFire() { @@ -1912,22 +2102,18 @@ public boolean isInsideOfFire() { return false; } - public boolean isOnLadder() { - Block b = this.getLevelBlock(); - - return b.getId() == Block.LADDER; - } - public boolean fastMove(double dx, double dy, double dz) { if (dx == 0 && dy == 0 && dz == 0) { return true; } - Timings.entityMoveTimer.startTiming(); + if (!(this instanceof Player)) { + this.blocksAround = null; + } AxisAlignedBB newBB = this.boundingBox.getOffsetBoundingBox(dx, dy, dz); - if (server.getAllowFlight() || !this.level.hasCollision(this, newBB, false)) { + if (server.getAllowFlight() || (this instanceof Player && ((Player) this).isSpectator()) || !this.level.hasCollision(this, this.getStepHeight() == 0 ? newBB : newBB.shrink(0, this.getStepHeight(), 0), false)) { this.boundingBox = newBB; } @@ -1937,33 +2123,34 @@ public boolean fastMove(double dx, double dy, double dz) { this.checkChunks(); - if (!this.onGround || dy != 0) { + if (!this.noClip && (!this.onGround || dy != 0) && (!(this instanceof Player) || !((Player) this).isSpectator())) { AxisAlignedBB bb = this.boundingBox.clone(); bb.setMinY(bb.getMinY() - 0.75); - this.onGround = this.level.getCollisionBlocks(bb).length > 0; + this.onGround = this.level.hasCollisionBlocks(this, bb); } + this.isCollided = this.onGround; this.updateFallState(this.onGround); - Timings.entityMoveTimer.stopTiming(); return true; } public boolean move(double dx, double dy, double dz) { if (dx == 0 && dz == 0 && dy == 0) { - return true; + return false; + } + + if (!(this instanceof Player)) { + this.blocksAround = null; } if (this.keepMovement) { this.boundingBox.offset(dx, dy, dz); this.setPosition(this.temporalVector.setComponents((this.boundingBox.getMinX() + this.boundingBox.getMaxX()) / 2, this.boundingBox.getMinY(), (this.boundingBox.getMinZ() + this.boundingBox.getMaxZ()) / 2)); - this.onGround = this.isPlayer; + this.onGround = this instanceof Player; // This should be false? Maybe use noClip instead? return true; } else { - - Timings.entityMoveTimer.startTiming(); - - this.ySize *= 0.4; + this.ySize *= STEP_CLIP_MULTIPLIER; double movX = dx; double movY = dy; @@ -1971,7 +2158,7 @@ public boolean move(double dx, double dy, double dz) { AxisAlignedBB axisalignedbb = this.boundingBox.clone(); - AxisAlignedBB[] list = this.level.getCollisionCubes(this, this.boundingBox.addCoord(dx, dy, dz), false); + AxisAlignedBB[] list = this.noClip ? new AxisAlignedBB[0] : this.level.getCollisionCubes(this, this.boundingBox.addCoord(dx, dy, dz), false); for (AxisAlignedBB bb : list) { dy = bb.calculateYOffset(this.boundingBox, dy); @@ -1993,7 +2180,7 @@ public boolean move(double dx, double dy, double dz) { this.boundingBox.offset(0, 0, dz); - if (this.getStepHeight() > 0 && fallingFlag && this.ySize < 0.05 && (movX != dx || movZ != dz)) { + if (this.getStepHeight() > 0 && fallingFlag && (movX != dx || movZ != dz)) { double cx = dx; double cy = dy; double cz = dz; @@ -2025,7 +2212,12 @@ public boolean move(double dx, double dy, double dz) { this.boundingBox.offset(0, 0, dz); - this.boundingBox.offset(0, 0, dz); + double reverseDY = -dy; + for (AxisAlignedBB bb : list) { + reverseDY = bb.calculateYOffset(this.boundingBox, reverseDY); + } + dy += reverseDY; + this.boundingBox.offset(0, reverseDY, 0); if ((cx * cx + cz * cz) >= (dx * dx + dz * dz)) { dx = cx; @@ -2033,9 +2225,8 @@ public boolean move(double dx, double dy, double dz) { dz = cz; this.boundingBox.setBB(axisalignedbb1); } else { - this.ySize += 0.5; + this.ySize += dy; } - } this.x = (this.boundingBox.getMinX() + this.boundingBox.getMaxX()) / 2; @@ -2058,18 +2249,22 @@ public boolean move(double dx, double dy, double dz) { if (movZ != dz) { this.motionZ = 0; } - - //TODO: vehicle collision events (first we need to spawn them!) - Timings.entityMoveTimer.stopTiming(); return true; } } protected void checkGroundState(double movX, double movY, double movZ, double dx, double dy, double dz) { - this.isCollidedVertically = movY != dy; - this.isCollidedHorizontally = (movX != dx || movZ != dz); - this.isCollided = (this.isCollidedHorizontally || this.isCollidedVertically); - this.onGround = (movY != dy && movY < 0); + if (this.noClip) { + this.isCollidedVertically = false; + this.isCollidedHorizontally = false; + this.isCollided = false; + this.onGround = false; + } else { + this.isCollidedVertically = movY != dy; + this.isCollidedHorizontally = (movX != dx || movZ != dz); + this.isCollided = (this.isCollidedHorizontally || this.isCollidedVertically); + this.onGround = (movY != dy && movY < 0); + } } public List getBlocksAround() { @@ -2086,7 +2281,7 @@ public List getBlocksAround() { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.level.getBlock(this.temporalVector.setComponents(x, y, z)); + Block block = this.level.getBlock(chunk, x, y, z, false); this.blocksAround.add(block); } } @@ -2100,8 +2295,9 @@ public List getCollisionBlocks() { if (this.collisionBlocks == null) { this.collisionBlocks = new ArrayList<>(); - for (Block b : getBlocksAround()) { - if (b.collidesWithBB(this.getBoundingBox(), true)) { + List bl = this.getBlocksAround(); + for (Block b : bl) { + if (b.collidesWithBB(this.boundingBox, true)) { this.collisionBlocks.add(b); } } @@ -2120,6 +2316,8 @@ public boolean canBeMovedByCurrents() { } protected void checkBlockCollision() { + if (this.noClip) return; + Vector3 vector = new Vector3(0, 0, 0); boolean portal = false; @@ -2134,11 +2332,7 @@ protected void checkBlockCollision() { } if (portal) { - if (this.inPortalTicks < 80) { - this.inPortalTicks = 80; - } else { - this.inPortalTicks++; - } + inPortalTicks++; } else { this.inPortalTicks = 0; } @@ -2153,12 +2347,7 @@ protected void checkBlockCollision() { } public boolean setPositionAndRotation(Vector3 pos, double yaw, double pitch) { - if (this.setPosition(pos)) { - this.setRotation(yaw, pitch); - return true; - } - - return false; + return this.setPositionAndRotation(pos, yaw, pitch, yaw); } public boolean setPositionAndRotation(Vector3 pos, double yaw, double pitch, double headYaw) { @@ -2171,9 +2360,7 @@ public boolean setPositionAndRotation(Vector3 pos, double yaw, double pitch, dou } public void setRotation(double yaw, double pitch) { - this.yaw = yaw; - this.pitch = pitch; - this.scheduleUpdate(); + this.setRotation(yaw, pitch, yaw); } public void setRotation(double yaw, double pitch, double headYaw) { @@ -2184,8 +2371,7 @@ public void setRotation(double yaw, double pitch, double headYaw) { } /** - * Whether the entity can active pressure plates. - * Used for {@link cn.nukkit.entity.passive.EntityBat}s only. + * Whether the entity can activate pressure plates. * * @return triggers pressure plate */ @@ -2198,14 +2384,16 @@ public boolean canPassThrough() { } protected void checkChunks() { - if (this.chunk == null || (this.chunk.getX() != ((int) this.x >> 4)) || this.chunk.getZ() != ((int) this.z >> 4)) { + int cx = this.getChunkX(); + int cz = this.getChunkZ(); + if (this.chunk == null || (this.chunk.getX() != cx) || this.chunk.getZ() != cz) { if (this.chunk != null) { this.chunk.removeEntity(this); } - this.chunk = this.level.getChunk((int) this.x >> 4, (int) this.z >> 4, true); + this.chunk = this.level.getChunk(cx, cz, true); if (!this.justCreated) { - Map newChunk = this.level.getChunkPlayers((int) this.x >> 4, (int) this.z >> 4); + Map newChunk = this.level.getChunkPlayers(cx, cz); for (Player player : new ArrayList<>(this.hasSpawned.values())) { if (!newChunk.containsKey(player.getLoaderId())) { this.despawnFrom(player); @@ -2232,17 +2420,39 @@ public boolean setPosition(Vector3 pos) { return false; } - if (pos instanceof Position && ((Position) pos).level != null && ((Position) pos).level != this.level) { - if (!this.switchLevel(((Position) pos).getLevel())) { - return false; + if (pos instanceof Position) { + Level oldLevel = this.level; + Level newLevel = ((Position) pos).level; + + if (newLevel != null && newLevel != oldLevel) { + if (!this.switchLevel(newLevel)) { + return false; + } + + this.x = pos.x; + this.y = pos.y; + this.z = pos.z; + + // Dimension change + if (this instanceof Player && newLevel.getDimension() != oldLevel.getDimension()) { + ((Player) this).setDimension(newLevel.getDimension()); + } + } else { + this.x = pos.x; + this.y = pos.y; + this.z = pos.z; } + } else { + this.x = pos.x; + this.y = pos.y; + this.z = pos.z; } - this.x = pos.x; - this.y = pos.y; - this.z = pos.z; + this.recalculateBoundingBox(); - this.recalculateBoundingBox(false); // Don't need to send BB height/width to client on position change + if (!(this instanceof Player)) { + this.blocksAround = null; + } this.checkChunks(); @@ -2281,8 +2491,11 @@ public void kill() { this.health = 0; this.scheduleUpdate(); - for (Entity passenger : new ArrayList<>(this.passengers)) { - dismountEntity(passenger); + if (!passengers.isEmpty()) { + for (Entity passenger : new ArrayList<>(passengers)) { + dismountEntity(passenger); + passenger.riding = null; // Make sure it's really removed even if a plugin tries to cancel it + } } } @@ -2310,9 +2523,9 @@ public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cau double yaw = location.yaw; double pitch = location.pitch; - Location from = this.getLocation(); Location to = location; if (cause != null) { + Location from = this.getLocation(); EntityTeleportEvent ev = new EntityTeleportEvent(this, from, to, cause); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -2327,11 +2540,13 @@ public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cau this.ySize = 0; - this.setMotion(this.temporalVector.setComponents(0, 0, 0)); + if (cause != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + this.setMotion(this.temporalVector.setComponents(0, 0, 0)); + } if (this.setPositionAndRotation(to, yaw, pitch)) { this.resetFallDistance(); - this.onGround = true; + this.onGround = !this.noClip; this.updateMovement(); @@ -2375,6 +2590,11 @@ public void despawnFromAll() { public void close() { if (!this.closed) { this.closed = true; + + if (!this.server.isPrimaryThread() && !this.level.isBeingConverted) { + this.server.getLogger().warning("Entity closed asynchronously: " + this.getClass().getSimpleName()); + } + this.server.getPluginManager().callEvent(new EntityDespawnEvent(this)); this.despawnFromAll(); if (this.chunk != null) { @@ -2388,24 +2608,42 @@ public void close() { } public boolean setDataProperty(EntityData data) { - return setDataProperty(data, true); + return this.setDataProperty(data, true); } public boolean setDataProperty(EntityData data, boolean send) { - if (Objects.equals(data, this.dataProperties.get(data.getId()))) { - return false; + if (!Objects.equals(data, this.dataProperties.get(data.getId()))) { + this.dataProperties.put(data); + if (send) { + EntityMetadata metadata = new EntityMetadata(); + metadata.put(this.dataProperties.get(data.getId())); + if (data.getId() == DATA_FLAGS_EXTENDED) { + metadata.put(this.dataProperties.get(DATA_FLAGS)); + } + this.sendData(this.hasSpawned.values().toArray(new Player[0]), metadata); + } + return true; } + return false; + } - this.dataProperties.put(data); - if (send) { - EntityMetadata metadata = new EntityMetadata(); - metadata.put(this.dataProperties.get(data.getId())); - if (data.getId() == DATA_FLAGS_EXTENDED) { - metadata.put(this.dataProperties.get(DATA_FLAGS)); + protected boolean setDataPropertyAndSendOnlyToSelf(EntityData data) { + if (data == null) { + throw new IllegalArgumentException("setDataPropertyAndSendOnlyToSelf: EntityData must not be null"); + } + if (!Objects.equals(data, this.dataProperties.get(data.getId()))) { + this.dataProperties.put(data); + if (this instanceof Player) { + EntityMetadata metadata = new EntityMetadata(); + metadata.put(this.dataProperties.get(data.getId())); + SetEntityDataPacket pk = new SetEntityDataPacket(); + pk.eid = this.id; + pk.metadata = metadata; + ((Player) this).dataPacket(pk); } - this.sendData(this.hasSpawned.values().toArray(new Player[0]), metadata); + return true; } - return true; + return false; } public EntityMetadata getDataProperties() { @@ -2413,51 +2651,51 @@ public EntityMetadata getDataProperties() { } public EntityData getDataProperty(int id) { - return this.getDataProperties().get(id); + return this.dataProperties.get(id); } public int getDataPropertyInt(int id) { - return this.getDataProperties().getInt(id); + return this.dataProperties.getInt(id); } public int getDataPropertyShort(int id) { - return this.getDataProperties().getShort(id); + return this.dataProperties.getShort(id); } public int getDataPropertyByte(int id) { - return this.getDataProperties().getByte(id); + return this.dataProperties.getByte(id); } public boolean getDataPropertyBoolean(int id) { - return this.getDataProperties().getBoolean(id); + return this.dataProperties.getBoolean(id); } public long getDataPropertyLong(int id) { - return this.getDataProperties().getLong(id); + return this.dataProperties.getLong(id); } public String getDataPropertyString(int id) { - return this.getDataProperties().getString(id); + return this.dataProperties.getString(id); } public float getDataPropertyFloat(int id) { - return this.getDataProperties().getFloat(id); + return this.dataProperties.getFloat(id); } public CompoundTag getDataPropertyNBT(int id) { - return this.getDataProperties().getNBT(id); + return this.dataProperties.getNBT(id); } public Vector3 getDataPropertyPos(int id) { - return this.getDataProperties().getPosition(id); + return this.dataProperties.getPosition(id); } public Vector3f getDataPropertyVector3f(int id) { - return this.getDataProperties().getFloatPosition(id); + return this.dataProperties.getFloatPosition(id); } public int getDataPropertyType(int id) { - return this.getDataProperties().exists(id) ? this.getDataProperty(id).getType() : -1; + return this.dataProperties.exists(id) ? this.getDataProperty(id).getType() : -1; } public void setDataFlag(int propertyId, int id) { @@ -2465,17 +2703,36 @@ public void setDataFlag(int propertyId, int id) { } public void setDataFlag(int propertyId, int id, boolean value) { + this.setDataFlag(propertyId, id, value, true); + } + + public void setDataFlag(int propertyId, int id, boolean value, boolean send) { if (this.getDataFlag(propertyId, id) != value) { if (propertyId == EntityHuman.DATA_PLAYER_FLAGS) { byte flags = (byte) this.getDataPropertyByte(propertyId); flags ^= 1 << id; - this.setDataProperty(new ByteEntityData(propertyId, flags)); + this.setDataProperty(new ByteEntityData(propertyId, flags), send); } else { - long flags = this.getDataPropertyLong(propertyId); - flags ^= 1L << id; - this.setDataProperty(new LongEntityData(propertyId, flags)); + LongEntityData oldData = (LongEntityData) this.dataProperties.getOrDefault(propertyId, new LongEntityData(propertyId, 0)); + long flags = oldData.getData() ^ 1L << id; + LongEntityData newData = new LongEntityData(propertyId, flags); + this.setDataProperty(newData, send); } + } + } + protected void setDataFlagSelfOnly(int propertyId, int id, boolean value) { + if (this.getDataFlag(propertyId, id) != value) { + if (propertyId == EntityHuman.DATA_PLAYER_FLAGS) { + byte flags = (byte) this.getDataPropertyByte(propertyId); + flags ^= 1 << id; + this.setDataPropertyAndSendOnlyToSelf(new ByteEntityData(propertyId, flags)); + } else { + LongEntityData oldData = (LongEntityData) this.dataProperties.getOrDefault(propertyId, new LongEntityData(propertyId, 0)); + long flags = oldData.getData() ^ 1L << id; + LongEntityData newData = new LongEntityData(propertyId, flags); + this.setDataPropertyAndSendOnlyToSelf(newData); + } } } @@ -2483,6 +2740,14 @@ public boolean getDataFlag(int propertyId, int id) { return (((propertyId == EntityHuman.DATA_PLAYER_FLAGS ? this.getDataPropertyByte(propertyId) & 0xff : this.getDataPropertyLong(propertyId))) & (1L << id)) > 0; } + public void setGenericFlag(int propertyId, boolean value) { + this.setDataFlag(propertyId >= 64 ? DATA_FLAGS_EXTENDED : DATA_FLAGS, propertyId % 64, value); + } + + public boolean getGenericFlag(int propertyId) { + return this.getDataFlag(propertyId >= 64 ? DATA_FLAGS_EXTENDED : DATA_FLAGS, propertyId % 64); + } + @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { this.server.getEntityMetadata().setMetadata(this, metadataKey, newMetadataValue); @@ -2516,14 +2781,85 @@ public boolean equals(Object obj) { return false; } Entity other = (Entity) obj; - return this.getId() == other.getId(); + return this.id == other.id; } @Override public int hashCode() { - int hash = 7; - hash = (int) (29 * hash + this.getId()); - return hash; + return (int) (203 + this.id); + } + + public static Entity create(Object type, Position source, Object... args) { + FullChunk chunk = source.getLevel().getChunk((int) source.x >> 4, (int) source.z >> 4, true); + if (!chunk.isGenerated()) { + chunk.setGenerated(); + } + if (!chunk.isPopulated()) { + chunk.setPopulated(); + } + + CompoundTag nbt = new CompoundTag().putList(new ListTag("Pos").add(new DoubleTag("", source.x)).add(new DoubleTag("", source.y)).add(new DoubleTag("", source.z))) + .putList(new ListTag("Motion").add(new DoubleTag("", 0)).add(new DoubleTag("", 0)).add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation").add(new FloatTag("", source instanceof Location ? (float) ((Location) source).yaw : 0)) + .add(new FloatTag("", source instanceof Location ? (float) ((Location) source).pitch : 0))); + + return Entity.createEntity(type.toString(), chunk, nbt, args); + } + + public boolean isOnLadder() { + int b = this.level.getBlockIdAt(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ()); + return b == Block.LADDER || b == Block.VINES || b == Block.TWISTING_VINES || b == Block.WEEPING_VINES; + } + + /** + * Check whether there is blocks above the entity + * + * @return no blocks above + */ + public boolean canSeeSky() { + int px = this.getFloorX(); + int py = this.getFloorY(); + int pz = this.getFloorZ(); + for (int i = level.getMaxBlockY(); i >= py; i--) { + if (level.getBlockIdAt(chunk, px, i, pz) != 0) { + return false; + } + } + return true; + } + + public void setSaveToStorage(boolean saveToStorage) { + this.saveToStorage = saveToStorage; + } + + public boolean canSaveToStorage() { + return this.saveToStorage; + } + + public PersistentDataContainer getPersistentDataContainer() { + if (this.persistentContainer == null) { + this.persistentContainer = new PersistentDataContainerEntityWrapper(this); + } + return this.persistentContainer; + } + + public boolean hasPersistentDataContainer() { + return !this.getPersistentDataContainer().isEmpty(); + } + + protected void minimalEntityTick(int currentTick, int tickDiff) { + this.justCreated = false; + this.lastUpdate = currentTick; + + this.age += tickDiff; + this.ticksLived += tickDiff; + + if (this.noDamageTicks > 0) { + this.noDamageTicks -= tickDiff; + if (this.noDamageTicks < 0) { + this.noDamageTicks = 0; + } + } } public void playAnimation(String animation) { diff --git a/src/main/java/cn/nukkit/entity/EntityAgeable.java b/src/main/java/cn/nukkit/entity/EntityAgeable.java index 9cb95096fc5..297d0ec5665 100644 --- a/src/main/java/cn/nukkit/entity/EntityAgeable.java +++ b/src/main/java/cn/nukkit/entity/EntityAgeable.java @@ -1,9 +1,14 @@ package cn.nukkit.entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface EntityAgeable { + boolean isBaby(); + + default void setBaby(boolean baby) { + + } } diff --git a/src/main/java/cn/nukkit/entity/EntityArthropod.java b/src/main/java/cn/nukkit/entity/EntityArthropod.java index 1a99378394d..35ea6532d55 100644 --- a/src/main/java/cn/nukkit/entity/EntityArthropod.java +++ b/src/main/java/cn/nukkit/entity/EntityArthropod.java @@ -1,7 +1,7 @@ package cn.nukkit.entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface EntityArthropod { diff --git a/src/main/java/cn/nukkit/entity/EntityBoss.java b/src/main/java/cn/nukkit/entity/EntityBoss.java new file mode 100644 index 00000000000..9c2127c0852 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityBoss.java @@ -0,0 +1,4 @@ +package cn.nukkit.entity; + +public interface EntityBoss { +} diff --git a/src/main/java/cn/nukkit/entity/EntityControllable.java b/src/main/java/cn/nukkit/entity/EntityControllable.java new file mode 100644 index 00000000000..de9ba1e516a --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityControllable.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity; + +import cn.nukkit.Player; + +public interface EntityControllable { + + void onPlayerInput(Player player, double strafe, double forward); + + default void onJump(Player player, int duration) { + } +} diff --git a/src/main/java/cn/nukkit/entity/EntityCreature.java b/src/main/java/cn/nukkit/entity/EntityCreature.java index 17937e6e679..a0be7473b67 100644 --- a/src/main/java/cn/nukkit/entity/EntityCreature.java +++ b/src/main/java/cn/nukkit/entity/EntityCreature.java @@ -7,15 +7,15 @@ import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityCreature extends EntityLiving { + public EntityCreature(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - // Armor stands, when implemented, should also check this. @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { if (item.getId() == Item.NAME_TAG && !player.isAdventure()) { @@ -24,14 +24,7 @@ public boolean onInteract(Player player, Item item, Vector3 clickedPos) { return false; } - // Structured like this so I can override nametags in player and dragon classes - // without overriding onInteract. protected boolean applyNameTag(Player player, Item item) { - if (item.hasCustomName()) { - this.setNameTag(item.getCustomName()); - this.setNameTagVisible(true); - return true; // onInteract: true = decrease count - } - return false; + return false; // Override in BaseEntity } } diff --git a/src/main/java/cn/nukkit/entity/EntityDamageable.java b/src/main/java/cn/nukkit/entity/EntityDamageable.java index 9b9785ce9ef..9f6bd252002 100644 --- a/src/main/java/cn/nukkit/entity/EntityDamageable.java +++ b/src/main/java/cn/nukkit/entity/EntityDamageable.java @@ -1,7 +1,7 @@ package cn.nukkit.entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface EntityDamageable { diff --git a/src/main/java/cn/nukkit/entity/EntityExplosive.java b/src/main/java/cn/nukkit/entity/EntityExplosive.java index b5df06eabe1..f69f05844e8 100644 --- a/src/main/java/cn/nukkit/entity/EntityExplosive.java +++ b/src/main/java/cn/nukkit/entity/EntityExplosive.java @@ -6,5 +6,4 @@ public interface EntityExplosive { void explode(); - } diff --git a/src/main/java/cn/nukkit/entity/EntityFlying.java b/src/main/java/cn/nukkit/entity/EntityFlying.java new file mode 100644 index 00000000000..f42b5715f46 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityFlying.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityFlying extends BaseEntity { + + public EntityFlying(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + this.noFallDamage = true; + } +} diff --git a/src/main/java/cn/nukkit/entity/EntityHanging.java b/src/main/java/cn/nukkit/entity/EntityHanging.java index 8fe70a69c18..f153b74d804 100644 --- a/src/main/java/cn/nukkit/entity/EntityHanging.java +++ b/src/main/java/cn/nukkit/entity/EntityHanging.java @@ -5,21 +5,23 @@ import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityHanging extends Entity { + protected int direction; public EntityHanging(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); + + this.updateMode = 3; } @Override protected void initEntity() { - super.initEntity(); - this.setMaxHealth(1); + super.initEntity(); this.setHealth(1); if (this.namedTag.contains("Direction")) { @@ -32,7 +34,6 @@ protected void initEntity() { this.direction = 2; } } - } @Override @@ -56,14 +57,30 @@ public boolean onUpdate(int currentTick) { return false; } + if (this.updateMode % 2 == 1) { + this.updateMode = 3; + } + + int tickDiff = currentTick - this.lastUpdate; + if (tickDiff <= 0 && !this.justCreated) { + return false; + } + + this.minimalEntityTick(currentTick, tickDiff); + if (!this.isAlive()) { + this.close(); + } - this.despawnFromAll(); - if (!this.isPlayer) { - this.close(); - } + if (!this.isAlive()) { + this.close(); + return false; + } - return true; + if (!this.isSurfaceValid()) { + this.dropItem(); + this.close(); + return false; } if (this.lastYaw != this.yaw || this.lastX != this.x || this.lastY != this.y || this.lastZ != this.z) { @@ -77,15 +94,17 @@ public boolean onUpdate(int currentTick) { this.lastZ = this.z; this.spawnToAll(); - - return true; + return false; } return false; } + protected void dropItem() { + + } + protected boolean isSurfaceValid() { return true; } - } diff --git a/src/main/java/cn/nukkit/entity/EntityHuman.java b/src/main/java/cn/nukkit/entity/EntityHuman.java index cb6881409cd..f6de2da1d52 100644 --- a/src/main/java/cn/nukkit/entity/EntityHuman.java +++ b/src/main/java/cn/nukkit/entity/EntityHuman.java @@ -3,12 +3,16 @@ import cn.nukkit.Player; import cn.nukkit.entity.data.IntPositionEntityData; import cn.nukkit.entity.data.Skin; +import cn.nukkit.event.entity.EntityDamageBlockedEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.StringTag; import cn.nukkit.network.protocol.AddPlayerPacket; -import cn.nukkit.network.protocol.RemoveEntityPacket; +import cn.nukkit.network.protocol.MobArmorEquipmentPacket; import cn.nukkit.network.protocol.SetEntityLinkPacket; import cn.nukkit.utils.*; @@ -18,7 +22,7 @@ import java.util.stream.Collectors; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityHuman extends EntityHumanType { @@ -27,13 +31,13 @@ public class EntityHuman extends EntityHumanType { public static final int DATA_PLAYER_FLAG_DEAD = 2; public static final int DATA_PLAYER_FLAGS = 26; - - public static final int DATA_PLAYER_BED_POSITION = 28; public static final int DATA_PLAYER_BUTTON_TEXT = 40; protected UUID uuid; protected byte[] rawUUID; + protected Skin skin; + @Override public float getWidth() { return 0.6f; @@ -46,21 +50,24 @@ public float getLength() { @Override public float getHeight() { - return 1.8f; + return isSwimming() || isGliding() || isCrawling() ? 0.6f : isSneaking() ? 1.5f : 1.8f; + } + + @Override + protected double getStepHeight() { + return 0.6; } @Override public float getEyeHeight() { - return 1.62f; + return isSwimming() || isGliding() || isCrawling() ? 0.42f : isSneaking() ? 1.26f : 1.62f; } @Override protected float getBaseOffset() { - return this.getEyeHeight(); + return 1.62f; } - protected Skin skin; - @Override public int getNetworkId() { return -1; @@ -88,9 +95,8 @@ public void setSkin(Skin skin) { @Override protected void initEntity() { - this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, false); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_GRAVITY); - + this.setDataFlag(DATA_PLAYER_FLAGS, DATA_PLAYER_FLAG_SLEEP, false, false); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_GRAVITY, true, false); this.setDataProperty(new IntPositionEntityData(DATA_PLAYER_BED_POSITION, 0, 0, 0), false); if (!(this instanceof Player)) { @@ -113,9 +119,7 @@ protected void initEntity() { if (skinTag.contains("Data")) { byte[] data = skinTag.getByteArray("Data"); if (skinTag.contains("SkinImageWidth") && skinTag.contains("SkinImageHeight")) { - int width = skinTag.getInt("SkinImageWidth"); - int height = skinTag.getInt("SkinImageHeight"); - newSkin.setSkinData(new SerializedImage(width, height, data)); + newSkin.setSkinData(new SerializedImage(skinTag.getInt("SkinImageWidth"), skinTag.getInt("SkinImageHeight"), data)); } else { newSkin.setSkinData(data); } @@ -126,9 +130,7 @@ protected void initEntity() { if (skinTag.contains("CapeData")) { byte[] data = skinTag.getByteArray("CapeData"); if (skinTag.contains("CapeImageWidth") && skinTag.contains("CapeImageHeight")) { - int width = skinTag.getInt("CapeImageWidth"); - int height = skinTag.getInt("CapeImageHeight"); - newSkin.setCapeData(new SerializedImage(width, height, data)); + newSkin.setCapeData(new SerializedImage(skinTag.getInt("CapeImageWidth"), skinTag.getInt("CapeImageHeight"), data)); } else { newSkin.setCapeData(data); } @@ -144,7 +146,7 @@ protected void initEntity() { } if (skinTag.contains("SkinAnimationData")) { newSkin.setAnimationData(new String(skinTag.getByteArray("SkinAnimationData"), StandardCharsets.UTF_8)); - } else if (skinTag.contains("AnimationData")) { // backwards compatible + } else if (skinTag.contains("AnimationData")) { // Backwards compatible newSkin.setAnimationData(new String(skinTag.getByteArray("AnimationData"), StandardCharsets.UTF_8)); } if (skinTag.contains("PremiumSkin")) { @@ -157,15 +159,8 @@ protected void initEntity() { newSkin.setCapeOnClassic(skinTag.getBoolean("CapeOnClassicSkin")); } if (skinTag.contains("AnimatedImageData")) { - ListTag list = skinTag.getList("AnimatedImageData", CompoundTag.class); - for (CompoundTag animationTag : list.getAll()) { - float frames = animationTag.getFloat("Frames"); - int type = animationTag.getInt("Type"); - byte[] image = animationTag.getByteArray("Image"); - int width = animationTag.getInt("ImageWidth"); - int height = animationTag.getInt("ImageHeight"); - int expression = animationTag.getInt("AnimationExpression"); - newSkin.getAnimations().add(new SkinAnimation(new SerializedImage(width, height, image), type, frames, expression)); + for (CompoundTag animationTag : skinTag.getList("AnimatedImageData", CompoundTag.class).getAll()) { + newSkin.getAnimations().add(new SkinAnimation(new SerializedImage(animationTag.getInt("ImageWidth"), animationTag.getInt("ImageHeight"), animationTag.getByteArray("Image")), animationTag.getInt("Type"), animationTag.getFloat("Frames"), animationTag.getInt("AnimationExpression"))); } } if (skinTag.contains("ArmSize")) { @@ -202,7 +197,7 @@ protected void initEntity() { this.setSkin(newSkin); } - this.uuid = Utils.dataToUUID(String.valueOf(this.getId()).getBytes(StandardCharsets.UTF_8), this.getSkin() + this.uuid = Utils.dataToUUID(String.valueOf(this.getId()).getBytes(StandardCharsets.UTF_8), this.skin .getSkinData().data, this.getNameTag().getBytes(StandardCharsets.UTF_8)); } @@ -223,13 +218,13 @@ public void saveNBT() { .putByteArray("Data", this.getSkin().getSkinData().data) .putInt("SkinImageWidth", this.getSkin().getSkinData().width) .putInt("SkinImageHeight", this.getSkin().getSkinData().height) - .putString("ModelId", this.getSkin().getSkinId()) + .putString("ModelId", this.skin.getSkinId()) .putString("CapeId", this.getSkin().getCapeId()) .putByteArray("CapeData", this.getSkin().getCapeData().data) .putInt("CapeImageWidth", this.getSkin().getCapeData().width) .putInt("CapeImageHeight", this.getSkin().getCapeData().height) .putByteArray("SkinResourcePatch", this.getSkin().getSkinResourcePatch().getBytes(StandardCharsets.UTF_8)) - .putByteArray("GeometryData", this.getSkin().getGeometryData().getBytes(StandardCharsets.UTF_8)) + .putByteArray("GeometryData", this.skin.getGeometryData().getBytes(StandardCharsets.UTF_8)) .putByteArray("SkinAnimationData", this.getSkin().getAnimationData().getBytes(StandardCharsets.UTF_8)) .putBoolean("PremiumSkin", this.getSkin().isPremium()) .putBoolean("PersonaSkin", this.getSkin().isPersona()) @@ -299,13 +294,14 @@ public void spawnTo(Player player) { throw new IllegalStateException(this.getClass().getSimpleName() + " must have a valid skin set"); } - if (this instanceof Player) - this.server.updatePlayerListData(this.getUniqueId(), this.getId(), ((Player) this).getDisplayName(), this.skin, ((Player) this).getLoginChainData().getXUID(), new Player[]{player}); - else - this.server.updatePlayerListData(this.getUniqueId(), this.getId(), this.getName(), this.skin, new Player[]{player}); + if (this instanceof Player) { + this.server.updatePlayerListData(this.uuid, this.getId(), ((Player) this).getDisplayName(), this.skin, ((Player) this).getLoginChainData().getXUID(), new Player[]{player}); + } else { + this.server.updatePlayerListData(this.uuid, this.getId(), this.getName(), this.skin, new Player[]{player}); + } AddPlayerPacket pk = new AddPlayerPacket(); - pk.uuid = this.getUniqueId(); + pk.uuid = this.uuid; pk.username = this.getName(); pk.entityUniqueId = this.getId(); pk.entityRuntimeId = this.getId(); @@ -318,10 +314,20 @@ public void spawnTo(Player player) { pk.yaw = (float) this.yaw; pk.pitch = (float) this.pitch; pk.item = this.getInventory().getItemInHand(); - pk.metadata = this.dataProperties; + pk.metadata = this.dataProperties.clone(); player.dataPacket(pk); - this.inventory.sendArmorContents(player); + if (this instanceof Player) { + this.inventory.sendArmorContents(player); + } else { + Item[] armor = this.inventory.getArmorContents(); + if (armor[0].getId() != 0 || armor[1].getId() != 0 || armor[2].getId() != 0 || armor[3].getId() != 0) { + MobArmorEquipmentPacket pk2 = new MobArmorEquipmentPacket(); + pk2.eid = this.getId(); + pk2.slots = armor; + player.dataPacket(pk2); + } + } this.offhandInventory.sendContents(player); if (this.riding != null) { @@ -330,27 +336,15 @@ public void spawnTo(Player player) { pkk.riderUniqueId = this.getId(); pkk.type = 1; pkk.immediate = 1; - player.dataPacket(pkk); } if (!(this instanceof Player)) { - this.server.removePlayerListData(this.getUniqueId(), player); + this.server.removePlayerListData(this.uuid, player); } } } - @Override - public void despawnFrom(Player player) { - if (this.hasSpawned.containsKey(player.getLoaderId())) { - - RemoveEntityPacket pk = new RemoveEntityPacket(); - pk.eid = this.getId(); - player.dataPacket(pk); - this.hasSpawned.remove(player.getLoaderId()); - } - } - @Override public void close() { if (!this.closed) { @@ -364,4 +358,17 @@ public void close() { } } + @Override + protected void onBlock(Entity damager, EntityDamageBlockedEvent event, EntityDamageEvent source) { + super.onBlock(damager, event, source); + Item shieldOffhand = getOffhandInventory().getItem(0); + if (shieldOffhand.getId() == ItemID.SHIELD) { + getOffhandInventory().setItem(0, damageArmor(shieldOffhand, damager, source.getDamage(), true, null)); + } else { + Item shield = getInventory().getItemInHand(); + if (shield.getId() == ItemID.SHIELD) { + getInventory().setItemInHand(damageArmor(shield, damager, source.getDamage(), true, null)); + } + } + } } diff --git a/src/main/java/cn/nukkit/entity/EntityHumanType.java b/src/main/java/cn/nukkit/entity/EntityHumanType.java index 592f31c4b50..11ac6faa706 100644 --- a/src/main/java/cn/nukkit/entity/EntityHumanType.java +++ b/src/main/java/cn/nukkit/entity/EntityHumanType.java @@ -1,18 +1,14 @@ package cn.nukkit.entity; -import cn.nukkit.Player; -import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; -import cn.nukkit.event.entity.EntityDamageEvent.DamageModifier; import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.inventory.PlayerEnderChestInventory; import cn.nukkit.inventory.PlayerInventory; import cn.nukkit.inventory.PlayerOffhandInventory; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemSkull; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.format.FullChunk; @@ -20,10 +16,10 @@ import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.Utils; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; public abstract class EntityHumanType extends EntityCreature implements InventoryHolder { @@ -57,8 +53,7 @@ protected void initEntity() { ListTag inventoryList = this.namedTag.getList("Inventory", CompoundTag.class); for (CompoundTag item : inventoryList.getAll()) { int slot = item.getByte("Slot"); - if (slot >= 0 && slot < 9) { //hotbar - //Old hotbar saving stuff, remove it (useless now) + if (slot >= 0 && slot < 9) { inventoryList.remove(item); } else if (slot >= 100 && slot < 104) { this.inventory.setItem(this.inventory.getSize() + slot - 100, NBTIO.getItemHelper(item)); @@ -101,7 +96,7 @@ public void saveNBT() { ); } - int slotCount = Player.SURVIVAL_SLOTS + 9; + int slotCount = 45; for (int slot = 9; slot < slotCount; ++slot) { Item item = this.inventory.getItem(slot - 9); inventoryTag.add(NBTIO.putItemHelper(item, slot)); @@ -149,51 +144,39 @@ public Item[] getDrops() { @Override public boolean attack(EntityDamageEvent source) { - if (this.isClosed() || !this.isAlive()) { + if (closed || !this.isAlive()) { return false; } - if (source.getCause() != DamageCause.VOID && source.getCause() != DamageCause.CUSTOM && source.getCause() != DamageCause.MAGIC && source.getCause() != DamageCause.HUNGER) { + if (source.getCause() != DamageCause.VOID && source.getCause() != DamageCause.CUSTOM && source.getCause() != DamageCause.HUNGER) { int armorPoints = 0; int epf = 0; - //int toughness = 0; for (Item armor : inventory.getArmorContents()) { armorPoints += armor.getArmorPoints(); epf += calculateEnchantmentProtectionFactor(armor, source); - //toughness += armor.getToughness(); } + //float originalDamage = source.getDamage(); + //float r = (source.getDamage(EntityDamageEvent.DamageModifier.ARMOR) - (originalDamage - originalDamage * (1 - Math.max(armorPoints / 5, armorPoints - originalDamage / 2) / 25))); + //originalDamage += r; + //epf = Math.min(20, epf); + //source.setDamage(r, EntityDamageEvent.DamageModifier.ARMOR); + //source.setDamage(source.getDamage(EntityDamageEvent.DamageModifier.ARMOR_ENCHANTMENTS) - (originalDamage - originalDamage * (1 - epf / 25f)), EntityDamageEvent.DamageModifier.ARMOR_ENCHANTMENTS); + if (source.canBeReducedByArmor()) { - source.setDamage(-source.getFinalDamage() * armorPoints * 0.04f, DamageModifier.ARMOR); + source.setDamage(-source.getFinalDamage() * armorPoints * 0.04f, EntityDamageEvent.DamageModifier.ARMOR); } - source.setDamage(-source.getFinalDamage() * Math.min(NukkitMath.ceilFloat(Math.min(epf, 25) * ((float) ThreadLocalRandom.current().nextInt(50, 100) / 100)), 20) * 0.04f, - DamageModifier.ARMOR_ENCHANTMENTS); - } - - source.setDamage(-Math.min(this.getAbsorption(), source.getFinalDamage()), DamageModifier.ABSORPTION); - - if (super.attack(source)) { - Entity damager = null; + source.setDamage(-source.getFinalDamage() * Math.min(NukkitMath.ceilFloat(Math.min(epf, 25) * ((float) Utils.random.nextInt(50, 100) / 100)), 20) * 0.04f, + EntityDamageEvent.DamageModifier.ARMOR_ENCHANTMENTS); - if (source instanceof EntityDamageByEntityEvent) { - damager = ((EntityDamageByEntityEvent) source).getDamager(); - } - - for (int slot = 0; slot < 4; slot++) { - Item armor = this.inventory.getArmorItem(slot); + //source.setDamage(-Math.min(this.getAbsorption(), source.getFinalDamage()), EntityDamageEvent.DamageModifier.ABSORPTION); - if (armor.hasEnchantments()) { - if (damager != null) { - for (Enchantment enchantment : armor.getEnchantments()) { - enchantment.doPostAttack(damager, this); - } - } - - Enchantment durability = armor.getEnchantment(Enchantment.ID_DURABILITY); - if (durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= ThreadLocalRandom.current().nextInt(100)) - continue; + if (super.attack(source)) { + Entity damager = null; + if (source instanceof EntityDamageByEntityEvent) { + damager = ((EntityDamageByEntityEvent) source).getDamager(); } if (source.getCause() != DamageCause.VOID && @@ -205,38 +188,75 @@ public boolean attack(EntityDamageEvent source) { source.getCause() != DamageCause.FIRE_TICK && source.getCause() != DamageCause.FALL) { // No armor damage - if (armor.isUnbreakable() || armor instanceof ItemSkull) { - continue; + for (int slot = 0; slot < 4; slot++) { + Item armor = damageArmor(this.inventory.getArmorItem(slot), damager, source.getDamage(), false, source.getCause()); + inventory.setArmorItem(slot, armor, armor.getId() != BlockID.AIR); + } + } else if (damager != null && source.getCause() != DamageCause.THORNS) { // Do post attack only + for (int slot = 0; slot < 4; slot++) { + Item armor = this.inventory.getArmorItem(slot); + if (armor.hasEnchantments()) { + for (Enchantment enchantment : armor.getEnchantments()) { + enchantment.doPostAttack(damager, this); + } + } } + } + return true; + } else { + return false; + } + } else { + return super.attack(source); + } + } - armor.setDamage(armor.getDamage() + 1); + protected Item damageArmor(Item armor, Entity damager, float damage, boolean shield, DamageCause cause) { + if (armor.isUnbreakable() || armor instanceof ItemSkull || armor.getId() == (255 - BlockID.CARVED_PUMPKIN)) { + return armor; + } - if (armor.getDamage() >= armor.getMaxDurability()) { - inventory.setArmorItem(slot, new ItemBlock(Block.get(BlockID.AIR))); - } else { - inventory.setArmorItem(slot, armor, true); - } + if (armor.hasEnchantments()) { + if (damager != null && cause != DamageCause.THORNS) { + for (Enchantment enchantment : armor.getEnchantments()) { + enchantment.doPostAttack(damager, this); } } - return true; + Enchantment durability = armor.getEnchantment(Enchantment.ID_DURABILITY); + if (durability != null + && durability.getLevel() > 0 + && (100 / (durability.getLevel() + 1)) <= Utils.random.nextInt(100)) { + return armor; + } + } + + if (shield) { + armor.setDamage(armor.getDamage() + (damage >= 4.0f ? ((int) damage) : 1)); } else { - return false; + armor.setDamage(armor.getDamage() + Math.max((int) (damage / 4), 1)); } + + if (armor.getDamage() >= armor.getMaxDurability()) { + return Item.get(BlockID.AIR, 0, 0); + } + + return armor; } + protected double calculateEnchantmentProtectionFactor(Item item, EntityDamageEvent source) { if (!item.hasEnchantments()) { return 0; } - double epf = 0; + double epf = 0; for (Enchantment ench : item.getEnchantments()) { - epf += ench.getProtectionFactor(source); + epf += ench.getProtectionFactor(source); } - return epf; + return epf ; } @Override @@ -255,9 +275,4 @@ public void setOnFire(int seconds) { super.setOnFire(seconds); } - - @Override - protected boolean applyNameTag(Player player, Item item) { - return false; - } } diff --git a/src/main/java/cn/nukkit/entity/EntityInteractable.java b/src/main/java/cn/nukkit/entity/EntityInteractable.java index aceb5f8f131..ec6cacfbc3b 100644 --- a/src/main/java/cn/nukkit/entity/EntityInteractable.java +++ b/src/main/java/cn/nukkit/entity/EntityInteractable.java @@ -9,11 +9,9 @@ public interface EntityInteractable { String getInteractButtonText(); - //TODO: Mob entity stuff (e.g. feed) default String getInteractButtonText(Player player) { return this.getInteractButtonText(); } boolean canDoInteraction(); - } diff --git a/src/main/java/cn/nukkit/entity/EntityJumping.java b/src/main/java/cn/nukkit/entity/EntityJumping.java new file mode 100644 index 00000000000..117c9847d11 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityJumping.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityJumping extends BaseEntity { + + public EntityJumping(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/EntityLiving.java b/src/main/java/cn/nukkit/entity/EntityLiving.java index 12d0d9d6e55..8e20923e9ac 100644 --- a/src/main/java/cn/nukkit/entity/EntityLiving.java +++ b/src/main/java/cn/nukkit/entity/EntityLiving.java @@ -3,15 +3,18 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockCactus; import cn.nukkit.block.BlockMagma; -import cn.nukkit.entity.data.ShortEntityData; -import cn.nukkit.entity.passive.EntityWaterAnimal; -import cn.nukkit.event.entity.EntityDamageByChildEntityEvent; -import cn.nukkit.event.entity.EntityDamageByEntityEvent; -import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.entity.mob.*; +import cn.nukkit.entity.passive.EntityIronGolem; +import cn.nukkit.entity.passive.EntitySkeletonHorse; +import cn.nukkit.entity.projectile.EntityProjectile; +import cn.nukkit.entity.weather.EntityWeather; +import cn.nukkit.event.entity.*; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; -import cn.nukkit.event.entity.EntityDeathEvent; +import cn.nukkit.inventory.PlayerInventory; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemArmor; import cn.nukkit.item.ItemTurtleShell; import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; @@ -19,12 +22,10 @@ import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.FloatTag; -import cn.nukkit.network.protocol.AnimatePacket; import cn.nukkit.network.protocol.EntityEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.potion.Effect; import cn.nukkit.utils.BlockIterator; -import co.aikar.timings.Timings; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +33,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityLiving extends Entity implements EntityDamageable { @@ -51,13 +52,13 @@ protected float getDrag() { return 0.02f; } - protected int attackTime = 0; - - protected boolean invisible = false; - + protected int attackTime; + protected int knockBackTime; + private float currentDamage; protected float movementSpeed = 0.1f; - - protected int turtleTicks = 0; + protected int turtleTicks; + private boolean blocking; + private boolean spinAttack; @Override protected void initEntity() { @@ -90,15 +91,15 @@ public void setHealth(float health) { @Override public void saveNBT() { super.saveNBT(); + this.namedTag.putFloat("Health", this.getHealth()); } public boolean hasLineOfSight(Entity entity) { - //todo return true; } - public void collidingWith(Entity ent) { // can override (IronGolem|Bats) + public void collidingWith(Entity ent) { ent.applyEntityCollision(this); } @@ -106,34 +107,35 @@ public void collidingWith(Entity ent) { // can override (IronGolem|Bats) public boolean attack(EntityDamageEvent source) { if (this.noDamageTicks > 0) { return false; - } else if (this.attackTime > 0) { - EntityDamageEvent lastCause = this.getLastDamageCause(); - if (lastCause != null && lastCause.getDamage() >= source.getDamage()) { + } + + float unmodifiedBaseDamage = source.getDamage(); + + if (this.attackTime > 0) { + if (unmodifiedBaseDamage > this.currentDamage) { + source.setDamage(Math.max(0, unmodifiedBaseDamage - this.currentDamage)); // https://minecraft.fandom.com/wiki/Damage#Immunity + } else { return false; } } - if (super.attack(source)) { + if (this.blockedByShield(source)) { + if (unmodifiedBaseDamage > this.currentDamage) { + this.currentDamage = unmodifiedBaseDamage; + } + return false; + } + + boolean attacked = super.attack(source); + if (attacked) { if (source instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); if (source instanceof EntityDamageByChildEntityEvent) { damager = ((EntityDamageByChildEntityEvent) source).getChild(); } - //Critical hit - if (damager instanceof Player && !damager.onGround) { - AnimatePacket animate = new AnimatePacket(); - animate.action = AnimatePacket.Action.CRITICAL_HIT; - animate.eid = getId(); - - this.getLevel().addChunkPacket(damager.getChunkX(), damager.getChunkZ(), animate); - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ATTACK_STRONG); - - source.setDamage(source.getDamage() * 1.5f); - } - if (damager.isOnFire() && !(damager instanceof Player)) { - this.setOnFire(2 * this.server.getDifficulty()); + this.setOnFire(this.server.getDifficulty() << 1); } double deltaX = this.x - damager.x; @@ -143,19 +145,71 @@ public boolean attack(EntityDamageEvent source) { EntityEventPacket pk = new EntityEventPacket(); pk.eid = this.getId(); - pk.event = this.getHealth() <= 0 ? EntityEventPacket.DEATH_ANIMATION : EntityEventPacket.HURT_ANIMATION; + pk.event = this.getHealth() < 1 ? EntityEventPacket.DEATH_ANIMATION : EntityEventPacket.HURT_ANIMATION; Server.broadcastPacket(this.hasSpawned.values(), pk); + } - this.attackTime = source.getAttackCooldown(); + if (!source.isCancelled()) { // attacked == false can also mean a totem was used + this.updateAttackTime(source); + if (unmodifiedBaseDamage > this.currentDamage) { + this.currentDamage = unmodifiedBaseDamage; + } + this.scheduleUpdate(); + } + + return attacked; + } + + protected boolean blockedByShield(EntityDamageEvent source) { + if (!this.isBlocking()) { + return false; + } + + Entity damager = source instanceof EntityDamageByChildEntityEvent ? ((EntityDamageByChildEntityEvent) source).getChild() : source instanceof EntityDamageByEntityEvent ? ((EntityDamageByEntityEvent) source).getDamager() : null; + if (damager == null || damager instanceof EntityWeather) { + return false; + } + + Vector3 entityPos = damager.getPosition(); + Vector3 direction = this.getDirectionVector(); + Vector3 normalizedVector = this.getPosition().subtract(entityPos).normalize(); + boolean blocked = (normalizedVector.x * direction.x) + (normalizedVector.z * direction.z) < 0.0; + boolean knockBack = !(damager instanceof EntityProjectile); + EntityDamageBlockedEvent event = new EntityDamageBlockedEvent(this, source, knockBack, true); + if (!blocked || !source.canBeReducedByArmor() || damager instanceof EntityProjectile && ((EntityProjectile) damager).piercing > 0) { + event.setCancelled(); + } - return true; - } else { + getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { return false; } + + if (event.getKnockBackAttacker() && damager instanceof EntityLiving) { + double deltaX = damager.getX() - this.getX(); + double deltaZ = damager.getZ() - this.getZ(); + this.updateAttackTime(source); + ((EntityLiving) damager).knockBack(this, 0, deltaX, deltaZ, 0.25); + } + + this.onBlock(damager, event, source); + return true; + } + + private void updateAttackTime(EntityDamageEvent source) { + if (this.attackTime < 1) { // Does not reset the immunity period + this.attackTime = source.getAttackCooldown(); + } + } + + protected void onBlock(Entity damager, EntityDamageBlockedEvent event, EntityDamageEvent source) { + if (event.getAnimation()) { + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_SHIELD_BLOCK); + } } public void knockBack(Entity attacker, double damage, double x, double z) { - this.knockBack(attacker, damage, x, z, 0.4); + this.knockBack(attacker, damage, x, z, 0.3); } public void knockBack(Entity attacker, double damage, double x, double z, double base) { @@ -164,6 +218,18 @@ public void knockBack(Entity attacker, double damage, double x, double z, double return; } + if (this instanceof Player) { + int netheritePieces = 0; + for (Item armor : ((Player) this).getInventory().getArmorContents()) { + if (armor.getTier() == ItemArmor.TIER_NETHERITE) { + netheritePieces++; + } + } + if (netheritePieces > 0) { + base *= 1 - 0.1 * netheritePieces; + } + } + f = 1 / f; Vector3 motion = new Vector3(this.motionX, this.motionY, this.motionZ); @@ -179,7 +245,11 @@ public void knockBack(Entity attacker, double damage, double x, double z, double motion.y = base; } + this.resetFallDistance(); + this.setMotion(motion); + + this.knockBackTime = 10; } @Override @@ -191,107 +261,149 @@ public void kill() { EntityDeathEvent ev = new EntityDeathEvent(this, this.getDrops()); this.server.getPluginManager().callEvent(ev); - if (this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - for (cn.nukkit.item.Item item : ev.getDrops()) { - this.getLevel().dropItem(this, item); + // Monster Hunter Achievement + int id = ev.getEntity().getNetworkId(); + if (id == EntityEnderman.NETWORK_ID || id == EntityZombiePigman.NETWORK_ID || id == EntitySpider.NETWORK_ID || id == EntityCaveSpider.NETWORK_ID) { + if (ev.getEntity().getLastDamageCause() instanceof EntityDamageByEntityEvent) { + Entity damager = ((EntityDamageByEntityEvent) ev.getEntity().getLastDamageCause()).getDamager(); + if (damager instanceof Player) { + ((Player) damager).awardAchievement("killEnemy"); + } } } - } - @Override - public boolean entityBaseTick() { - return this.entityBaseTick(1); + if (this.level.getGameRules().getBoolean(GameRule.DO_MOB_LOOT) && this.lastDamageCause != null && DamageCause.VOID != this.lastDamageCause.getCause()) { + if (ev.getEntity() instanceof BaseEntity) { + BaseEntity baseEntity = (BaseEntity) ev.getEntity(); + if (baseEntity.getLastDamageCause() instanceof EntityDamageByEntityEvent) { + Entity damager = ((EntityDamageByEntityEvent) baseEntity.getLastDamageCause()).getDamager(); + if (damager instanceof Player) { + this.getLevel().dropExpOrb(this, baseEntity.getKillExperience()); + + if (!this.dropsOnNaturalDeath()) { + for (cn.nukkit.item.Item item : ev.getDrops()) { + this.getLevel().dropItem(this, item); + } + } + } + } + } + + if (this.dropsOnNaturalDeath()) { + for (cn.nukkit.item.Item item : ev.getDrops()) { + this.getLevel().dropItem(this, item); + } + } + } } @Override public boolean entityBaseTick(int tickDiff) { - Timings.livingEntityBaseTickTimer.startTiming(); - boolean isBreathing = !this.isInsideOfWater(); + boolean inWater = this.isSubmerged(); - if (this instanceof Player) { - if (isBreathing && ((Player) this).getInventory().getHelmet() instanceof ItemTurtleShell) { + if (this instanceof Player && !this.closed) { + Player p = (Player) this; + boolean isBreathing = !inWater; + + PlayerInventory inv = p.getInventory(); + if (isBreathing && inv != null && inv.getHelmetFast() instanceof ItemTurtleShell) { turtleTicks = 200; } else if (turtleTicks > 0) { isBreathing = true; turtleTicks--; } - if ((((Player) this).isCreative() || ((Player) this).isSpectator())) { + if (p.isCreative() || p.isSpectator()) { isBreathing = true; } + + this.setDataFlagSelfOnly(DATA_FLAGS, DATA_FLAG_BREATHING, isBreathing); } - - this.setDataFlag(DATA_FLAGS, DATA_FLAG_BREATHING, isBreathing); boolean hasUpdate = super.entityBaseTick(tickDiff); if (this.isAlive()) { - if (this.isInsideOfSolid()) { hasUpdate = true; this.attack(new EntityDamageEvent(this, DamageCause.SUFFOCATION, 1)); } - if (this.isOnLadder() || this.hasEffect(Effect.LEVITATION) || this.hasEffect(Effect.SLOW_FALLING)) { + if (this.hasEffect(Effect.LEVITATION) || this.hasEffect(Effect.SLOW_FALLING)) { this.resetFallDistance(); } - if (!this.hasEffect(Effect.WATER_BREATHING) && this.isInsideOfWater()) { - if (this instanceof EntityWaterAnimal || (this instanceof Player && (((Player) this).isCreative() || ((Player) this).isSpectator()))) { + if (inWater && !this.hasEffect(Effect.WATER_BREATHING)) { + if (this instanceof EntitySwimming || this instanceof EntityDrowned || this instanceof EntitySkeletonHorse || this instanceof EntityIronGolem || + (this instanceof Player && (((Player) this).isCreative() || ((Player) this).isSpectator()))) { this.setAirTicks(400); } else { - if (turtleTicks == 0 || turtleTicks == 200) { + if (turtleTicks == 0) { hasUpdate = true; int airTicks = this.getAirTicks() - tickDiff; if (airTicks <= -20) { airTicks = 0; - this.attack(new EntityDamageEvent(this, DamageCause.DROWNING, 2)); + if (!(this instanceof Player) || level.getGameRules().getBoolean(GameRule.DROWNING_DAMAGE)) { + this.attack(new EntityDamageEvent(this, DamageCause.DROWNING, 2)); + } } - setAirTicks(airTicks); + this.setAirTicks(airTicks); } } } else { - if (this instanceof EntityWaterAnimal) { + if (this instanceof EntitySwimming) { hasUpdate = true; - int airTicks = getAirTicks() - tickDiff; + int airTicks = this.getAirTicks() - tickDiff; if (airTicks <= -20) { airTicks = 0; this.attack(new EntityDamageEvent(this, DamageCause.SUFFOCATION, 2)); } - setAirTicks(airTicks); + this.setAirTicks(airTicks); } else { int airTicks = getAirTicks(); - if (airTicks < 400) { setAirTicks(Math.min(400, airTicks + tickDiff * 5)); } } } - } - if (this.attackTime > 0) { - this.attackTime -= tickDiff; - } + // Check collisions with blocks + if ((this instanceof Player || this instanceof BaseEntity) && this.riding == null && this.age % (this instanceof Player ? 2 : 10) == 0) { + int floorY = NukkitMath.floorDouble(this.y - 0.25); + if (floorY != getFloorY()) { + Block block = this.level.getBlock(this.chunk, getFloorX(), floorY, getFloorZ(), false); + if (block instanceof BlockCactus) { + block.onEntityCollide(this); + } else if (block instanceof BlockMagma) { + block.onEntityCollide(this); + } + } + } - if (this.riding == null) { - for (Entity entity : level.getNearbyEntities(this.boundingBox.grow(0.20000000298023224, 0.0D, 0.20000000298023224), this)) { - if (entity instanceof EntityRideable) { - this.collidingWith(entity); + if (this.attackTime > 0) { + this.attackTime -= tickDiff; + if (this.attackTime < 1) { + this.currentDamage = 0; } + hasUpdate = true; } - } - // Check collision with magma blocks because Nukkit doesn't do collisions to full blocks below - if (this.isPlayer || this.age % 2 == 0) { - Block block = this.level.getBlock(getFloorX(), NukkitMath.floorDouble(this.y + 0.53) - 1, getFloorZ()); - if (block instanceof BlockMagma) block.onEntityCollide(this); - } + if (this.knockBackTime > 0) { + this.knockBackTime -= tickDiff; + } - Timings.livingEntityBaseTickTimer.stopTiming(); + if (this.riding == null && this.age % 2 == 1) { + Entity[] e = level.getNearbyEntities(this.boundingBox.grow(0.20000000298023224, 0.0D, 0.20000000298023224), this); + for (Entity entity : e) { + if (entity instanceof EntityRideable) { + this.collidingWith(entity); + } + } + } + } return hasUpdate; } @@ -372,9 +484,7 @@ public Block getTargetBlock(int maxDistance, Integer[] transparent) { return block; } } - } catch (Exception ignored) { - - } + } catch (Exception ignored) {} return null; } @@ -386,12 +496,38 @@ public void setMovementSpeed(float speed) { public float getMovementSpeed() { return this.movementSpeed; } - + public int getAirTicks() { - return this.getDataPropertyShort(DATA_AIR); + return this.airTicks; } public void setAirTicks(int ticks) { - this.setDataProperty(new ShortEntityData(DATA_AIR, ticks)); + this.airTicks = ticks; + } + + public boolean isBlocking() { + return this.blocking; + } + + public void setBlocking(boolean value) { + if (this.blocking != value) { + this.blocking = value; + this.setDataFlag(DATA_FLAGS_EXTENDED, DATA_FLAG_BLOCKING, value); + } + } + + public boolean dropsOnNaturalDeath() { + return true; + } + + public boolean isSpinAttack() { + return this.spinAttack; + } + + public void setSpinAttack(boolean value) { + if (this.spinAttack != value) { + this.spinAttack = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SPIN_ATTACK, value); + } } } diff --git a/src/main/java/cn/nukkit/entity/EntityOwnable.java b/src/main/java/cn/nukkit/entity/EntityOwnable.java index 31086b6b8b5..1408c976c3a 100644 --- a/src/main/java/cn/nukkit/entity/EntityOwnable.java +++ b/src/main/java/cn/nukkit/entity/EntityOwnable.java @@ -3,10 +3,11 @@ import cn.nukkit.Player; /** - * Author: BeYkeRYkt + * @author BeYkeRYkt * Nukkit Project */ public interface EntityOwnable { + String getOwnerName(); void setOwnerName(String playerName); diff --git a/src/main/java/cn/nukkit/entity/EntityRideable.java b/src/main/java/cn/nukkit/entity/EntityRideable.java index 9b5c42603bf..8375f0bc0a1 100644 --- a/src/main/java/cn/nukkit/entity/EntityRideable.java +++ b/src/main/java/cn/nukkit/entity/EntityRideable.java @@ -1,7 +1,7 @@ package cn.nukkit.entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface EntityRideable { diff --git a/src/main/java/cn/nukkit/entity/EntitySmite.java b/src/main/java/cn/nukkit/entity/EntitySmite.java index 90e33128ce0..e97c622ef5a 100644 --- a/src/main/java/cn/nukkit/entity/EntitySmite.java +++ b/src/main/java/cn/nukkit/entity/EntitySmite.java @@ -1,7 +1,7 @@ package cn.nukkit.entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface EntitySmite { diff --git a/src/main/java/cn/nukkit/entity/EntitySwimming.java b/src/main/java/cn/nukkit/entity/EntitySwimming.java new file mode 100644 index 00000000000..aad34c46d69 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntitySwimming.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntitySwimming extends BaseEntity { + + public EntitySwimming(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/EntityTameable.java b/src/main/java/cn/nukkit/entity/EntityTameable.java new file mode 100644 index 00000000000..0475c21b933 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityTameable.java @@ -0,0 +1,28 @@ +package cn.nukkit.entity; + +import cn.nukkit.Player; + +public interface EntityTameable { + + String NAMED_TAG_OWNER_UUID = "OwnerUUID"; + + String NAMED_TAG_SITTING = "Sitting"; + + Player getOwner(); + + boolean hasOwner(); + + void setOwner(Player player); + + String getOwnerUUID(); + + void setOwnerUUID(String uuid); + + boolean isSitting(); + + void setSitting(boolean sitting); + + default boolean isOwner(Entity entity) { + return entity instanceof Player && ((Player) entity).getUniqueId().toString().equals(this.getOwnerUUID()); + } +} diff --git a/src/main/java/cn/nukkit/entity/EntityWalking.java b/src/main/java/cn/nukkit/entity/EntityWalking.java new file mode 100644 index 00000000000..d7c3252307b --- /dev/null +++ b/src/main/java/cn/nukkit/entity/EntityWalking.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityWalking extends BaseEntity { + + public EntityWalking(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/custom/CustomEntity.java b/src/main/java/cn/nukkit/entity/custom/CustomEntity.java new file mode 100644 index 00000000000..509d12161be --- /dev/null +++ b/src/main/java/cn/nukkit/entity/custom/CustomEntity.java @@ -0,0 +1,14 @@ +package cn.nukkit.entity.custom; + +public interface CustomEntity { + + EntityDefinition getEntityDefinition(); + + default String getIdentifier() { + return this.getEntityDefinition().getIdentifier(); + } + + default int getNetworkId() { + return this.getEntityDefinition().getRuntimeId(); + } +} diff --git a/src/main/java/cn/nukkit/entity/custom/EntityDefinition.java b/src/main/java/cn/nukkit/entity/custom/EntityDefinition.java new file mode 100644 index 00000000000..45f3af424eb --- /dev/null +++ b/src/main/java/cn/nukkit/entity/custom/EntityDefinition.java @@ -0,0 +1,80 @@ +package cn.nukkit.entity.custom; + +import cn.nukkit.entity.Entity; +import cn.nukkit.nbt.tag.CompoundTag; +import lombok.Builder; +import lombok.Getter; + +import java.util.concurrent.atomic.AtomicInteger; + +@Getter +public class EntityDefinition { + + public static final AtomicInteger ID_ALLOCATOR = new AtomicInteger(10000); + + private final String identifier; + private final String parentEntity; + private final boolean spawnEgg; + + private final String alternateName; + private final Class implementation; + + private final boolean serverSideOnly; + private final int runtimeId; + + private CompoundTag networkTag; + private CompoundTag networkTagOld; // 1.16 and older + + @Builder + public EntityDefinition(String identifier, String parentEntity, boolean spawnEgg, String alternateName, Class implementation, boolean serverSideOnly) { + if (!CustomEntity.class.isAssignableFrom(implementation)) { + throw new IllegalArgumentException("Implementation class must implement CustomEntity interface"); + } + + if (serverSideOnly && parentEntity == null) { + throw new IllegalArgumentException("Server side entity must have parent entity set"); + } + + this.identifier = identifier; + this.parentEntity = parentEntity; + this.spawnEgg = spawnEgg; + this.alternateName = alternateName; + this.implementation = implementation; + this.serverSideOnly = serverSideOnly; + + if (this.serverSideOnly) { + int runtimeId = EntityManager.get().getRuntimeId(this.parentEntity); + if (runtimeId == 0) { + throw new IllegalArgumentException("Unknown entity type " + this.parentEntity); + } + this.runtimeId = runtimeId; + } else { + this.runtimeId = ID_ALLOCATOR.getAndIncrement(); + } + } + + private CompoundTag createNetworkTag() { + CompoundTag nbt = new CompoundTag(""); + nbt.putBoolean("hasspawnegg", this.spawnEgg); + nbt.putBoolean("summonable", true); + nbt.putString("id", this.identifier); + nbt.putString("bid", this.parentEntity == null ? "" : this.parentEntity); + nbt.putInt("rid", this.runtimeId); + return nbt; + } + + public CompoundTag getNetworkTag() { + if (this.networkTag == null) { + this.networkTag = this.createNetworkTag(); + } + return this.networkTag; + } + + public CompoundTag getNetworkTagOld() { + if (this.networkTagOld == null) { + this.networkTagOld = this.createNetworkTag(); + this.networkTagOld.putBoolean("experimental", false); + } + return this.networkTagOld; + } +} diff --git a/src/main/java/cn/nukkit/entity/custom/EntityManager.java b/src/main/java/cn/nukkit/entity/custom/EntityManager.java new file mode 100644 index 00000000000..11c5cab0938 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/custom/EntityManager.java @@ -0,0 +1,97 @@ +package cn.nukkit.entity.custom; + +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.network.protocol.AddEntityPacket; +import cn.nukkit.network.protocol.AvailableEntityIdentifiersPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class EntityManager { + + private static final EntityManager instance = new EntityManager(); + + public static EntityManager get() { + return instance; + } + + private final Map entityDefinitions = new HashMap<>(); + private final Map alternateNameDefinitions = new HashMap<>(); + private final Int2ObjectMap runtimeDefinitions = new Int2ObjectOpenHashMap<>(); + + private final Map vanillaEntitiesMap = new HashMap<>(); + + private byte[] networkTagCached; + + public EntityManager() { + AddEntityPacket.LEGACY_IDS.forEach((id, identifier) -> this.vanillaEntitiesMap.put(identifier, id)); + } + + public void registerDefinition(EntityDefinition definition) { + if (this.entityDefinitions.containsKey(definition.getIdentifier())) { + throw new IllegalArgumentException("Custom entity " + definition.getIdentifier() + " was already registered"); + } + this.entityDefinitions.put(definition.getIdentifier(), definition); + this.runtimeDefinitions.put(definition.getRuntimeId(), definition); + + if (definition.getAlternateName() != null) { + this.alternateNameDefinitions.put(definition.getAlternateName(), definition); + } + } + + public EntityDefinition getDefinition(String identifier) { + EntityDefinition definition = this.entityDefinitions.get(identifier); + if (definition == null) { + definition = this.alternateNameDefinitions.get(identifier); + } + return definition; + } + + public EntityDefinition getDefinition(int runtimeId) { + return this.runtimeDefinitions.get(runtimeId); + } + + public int getRuntimeId(String identifier) { + EntityDefinition definition = this.entityDefinitions.get(identifier); + if (definition == null) { + return this.vanillaEntitiesMap.getOrDefault(identifier, 0); + } + return definition.getRuntimeId(); + } + + private void createNetworkTag() { + try { + CompoundTag networkTag = (CompoundTag) NBTIO.readNetwork(new ByteArrayInputStream(AvailableEntityIdentifiersPacket.TAG)); + ListTag identifiers = networkTag.getList("idlist", CompoundTag.class); + + for (EntityDefinition definition : this.entityDefinitions.values()) { + if (!definition.isServerSideOnly()) { + CompoundTag nbt = definition.getNetworkTag(); + identifiers.add(nbt); + } + } + networkTag.putList(identifiers); + + this.networkTagCached = NBTIO.writeNetwork(networkTag); + } catch (IOException e) { + throw new RuntimeException("Unable to init entityIdentifiers", e); + } + } + + public byte[] getNetworkTagCached() { + if (this.networkTagCached == null) { + this.createNetworkTag(); + } + return this.networkTagCached; + } + + public boolean hasCustomEntities() { + return !this.entityDefinitions.isEmpty(); + } +} diff --git a/src/main/java/cn/nukkit/entity/data/ByteEntityData.java b/src/main/java/cn/nukkit/entity/data/ByteEntityData.java index 75ca4425762..79f03d2062e 100644 --- a/src/main/java/cn/nukkit/entity/data/ByteEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/ByteEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ByteEntityData extends EntityData { + public int data; public ByteEntityData(int id, int data) { diff --git a/src/main/java/cn/nukkit/entity/data/EntityData.java b/src/main/java/cn/nukkit/entity/data/EntityData.java index cd308b024a2..37552b1ee27 100644 --- a/src/main/java/cn/nukkit/entity/data/EntityData.java +++ b/src/main/java/cn/nukkit/entity/data/EntityData.java @@ -3,10 +3,11 @@ import java.util.Objects; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityData { + private int id; protected EntityData(int id) { @@ -30,6 +31,6 @@ public EntityData setId(int id) { @Override public boolean equals(Object obj) { - return obj instanceof EntityData && ((EntityData) obj).getId() == this.getId() && Objects.equals(((EntityData) obj).getData(), this.getData()); + return obj instanceof EntityData && ((EntityData) obj).id == this.id && Objects.equals(((EntityData) obj).getData(), this.getData()); } } diff --git a/src/main/java/cn/nukkit/entity/data/EntityMetadata.java b/src/main/java/cn/nukkit/entity/data/EntityMetadata.java index bd9351475ba..a228ca58857 100644 --- a/src/main/java/cn/nukkit/entity/data/EntityMetadata.java +++ b/src/main/java/cn/nukkit/entity/data/EntityMetadata.java @@ -4,19 +4,19 @@ import cn.nukkit.math.Vector3; import cn.nukkit.math.Vector3f; import cn.nukkit.nbt.tag.CompoundTag; -import lombok.extern.log4j.Log4j2; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -@Log4j2 public class EntityMetadata { - private final Map map = new HashMap<>(); + private Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); public EntityData get(int id) { return this.getOrDefault(id, null); @@ -39,7 +39,6 @@ public boolean exists(int id) { public EntityMetadata put(EntityData data) { this.map.put(data.getId(), data); - //log.info("Updated entity data {}", this::toString); return this; } @@ -121,11 +120,15 @@ public EntityMetadata putString(int id, String value) { } public Map getMap() { - return new HashMap<>(map); + return new TreeMap<>(this.map); // Ordered } - @Override - public String toString() { - return map.toString(); + private EntityMetadata replace(Int2ObjectMap map) { + this.map = map; + return this; + } + + public EntityMetadata clone() { + return new EntityMetadata().replace(new Int2ObjectOpenHashMap<>(this.map)); } } diff --git a/src/main/java/cn/nukkit/entity/data/FloatEntityData.java b/src/main/java/cn/nukkit/entity/data/FloatEntityData.java index 86ede31bca7..9f8bb3955c0 100644 --- a/src/main/java/cn/nukkit/entity/data/FloatEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/FloatEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FloatEntityData extends EntityData { + public float data; public FloatEntityData(int id, float data) { @@ -24,7 +25,6 @@ public void setData(Float data) { } else { this.data = data; } - } @Override diff --git a/src/main/java/cn/nukkit/entity/data/IntEntityData.java b/src/main/java/cn/nukkit/entity/data/IntEntityData.java index 51cf4a94888..e704efbbf67 100644 --- a/src/main/java/cn/nukkit/entity/data/IntEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/IntEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class IntEntityData extends EntityData { + public int data; public IntEntityData(int id, int data) { diff --git a/src/main/java/cn/nukkit/entity/data/IntPositionEntityData.java b/src/main/java/cn/nukkit/entity/data/IntPositionEntityData.java index f053d8f4be8..aeecebdc9cf 100644 --- a/src/main/java/cn/nukkit/entity/data/IntPositionEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/IntPositionEntityData.java @@ -5,10 +5,11 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class IntPositionEntityData extends EntityData { + public int x; public int y; public int z; diff --git a/src/main/java/cn/nukkit/entity/data/LongEntityData.java b/src/main/java/cn/nukkit/entity/data/LongEntityData.java index a51ae44aa6f..a9d19454330 100644 --- a/src/main/java/cn/nukkit/entity/data/LongEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/LongEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LongEntityData extends EntityData { + public long data; public LongEntityData(int id, long data) { diff --git a/src/main/java/cn/nukkit/entity/data/NBTEntityData.java b/src/main/java/cn/nukkit/entity/data/NBTEntityData.java index 9c6e1e0fd0c..bdf21631d6a 100644 --- a/src/main/java/cn/nukkit/entity/data/NBTEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/NBTEntityData.java @@ -1,20 +1,29 @@ package cn.nukkit.entity.data; import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class NBTEntityData extends EntityData { + public CompoundTag tag; + public Item item; public NBTEntityData(int id, CompoundTag tag) { super(id); this.tag = tag; } + public NBTEntityData(int id, Item item) { + super(id); + this.item = item; + this.tag = item.getNamedTag(); + } + @Override public CompoundTag getData() { return this.tag; diff --git a/src/main/java/cn/nukkit/entity/data/ShortEntityData.java b/src/main/java/cn/nukkit/entity/data/ShortEntityData.java index 1e9cf0a308c..7b775a8f6b8 100644 --- a/src/main/java/cn/nukkit/entity/data/ShortEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/ShortEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ShortEntityData extends EntityData { + public int data; public ShortEntityData(int id, int data) { diff --git a/src/main/java/cn/nukkit/entity/data/Skin.java b/src/main/java/cn/nukkit/entity/data/Skin.java index 4ea4dcc59a9..f833f449a99 100644 --- a/src/main/java/cn/nukkit/entity/data/Skin.java +++ b/src/main/java/cn/nukkit/entity/data/Skin.java @@ -1,5 +1,7 @@ package cn.nukkit.entity.data; +import cn.nukkit.Nukkit; +import cn.nukkit.Server; import cn.nukkit.nbt.stream.FastByteArrayOutputStream; import cn.nukkit.utils.*; import com.google.common.base.Preconditions; @@ -10,27 +12,29 @@ import java.awt.*; import java.awt.image.BufferedImage; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; -import java.util.UUID; +import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -@ToString(exclude = {"geometryData", "animationData"}) +@ToString public class Skin { + private static final int PIXEL_SIZE = 4; public static final int SINGLE_SKIN_SIZE = 64 * 32 * PIXEL_SIZE; public static final int DOUBLE_SKIN_SIZE = 64 * 64 * PIXEL_SIZE; public static final int SKIN_128_64_SIZE = 128 * 64 * PIXEL_SIZE; public static final int SKIN_128_128_SIZE = 128 * 128 * PIXEL_SIZE; + + private static final int MAX_DATA_SIZE = 262144; public static final String GEOMETRY_CUSTOM = convertLegacyGeometryName("geometry.humanoid.custom"); public static final String GEOMETRY_CUSTOM_SLIM = convertLegacyGeometryName("geometry.humanoid.customSlim"); + private boolean noPlayFab; // Don't attempt to generate missing play fab id multiple times private String fullSkinId; private String skinId; private String playFabId = ""; @@ -58,15 +62,24 @@ public boolean isValid() { } private boolean isValidSkin() { - return skinId != null && !skinId.trim().isEmpty() && skinId.length() < 100 && - skinData != null && skinData.width >= 64 && skinData.height >= 32 && - skinData.data.length >= SINGLE_SKIN_SIZE && - (playFabId == null || playFabId.length() < 100) && - (capeId == null || capeId.length() < 100) && - (skinColor == null || skinColor.length() < 100) && - (armSize == null || armSize.length() < 100) && - (fullSkinId == null || fullSkinId.length() < 200) && - (geometryDataEngineVersion == null || geometryDataEngineVersion.length() < 100); + try { + return (skinId != null && !skinId.trim().isEmpty() && skinId.length() < 100) && + (skinData != null && skinData.width >= 64 && skinData.height >= 32 && skinData.data.length >= SINGLE_SKIN_SIZE) && + (geometryData != null && !geometryData.isEmpty()) && + ((geometryData.getBytes().length <= MAX_DATA_SIZE && + skinData.data.length <= MAX_DATA_SIZE && + (capeData == null || capeData.data.length <= MAX_DATA_SIZE) && + (animationData == null || animationData.getBytes().length <= MAX_DATA_SIZE))) && + (playFabId == null || playFabId.length() < 100) && + (capeId == null || capeId.length() < 100) && + (skinColor == null || skinColor.length() < 100) && + (armSize == null || armSize.length() < 100) && + (fullSkinId == null || fullSkinId.length() < 200) && + (geometryDataEngineVersion == null || geometryDataEngineVersion.length() < 100); + } catch (Exception ex) { + if (Nukkit.DEBUG > 1) Server.getInstance().getLogger().logException(ex); + return false; + } } private boolean isValidResourcePatch() { @@ -74,8 +87,7 @@ private boolean isValidResourcePatch() { return false; } try { - JSONObject object = (JSONObject) JSONValue.parse(skinResourcePatch); - JSONObject geometry = (JSONObject) object.get("geometry"); + JSONObject geometry = (JSONObject) ((JSONObject) JSONValue.parse(skinResourcePatch)).get("geometry"); return geometry.containsKey("default") && geometry.get("default") instanceof String; } catch (ClassCastException | NullPointerException e) { return false; @@ -91,6 +103,7 @@ public SerializedImage getSkinData() { public String getSkinId() { if (this.skinId == null) { + Server.getInstance().getLogger().debug("Missing skin ID, generating new"); this.generateSkinId("Custom"); } return skinId; @@ -98,6 +111,7 @@ public String getSkinId() { public void setSkinId(String skinId) { if (skinId == null || skinId.trim().isEmpty()) { + Server.getInstance().getLogger().debug("Skin ID cannot be empty! ", new Throwable()); return; } this.skinId = skinId; @@ -123,14 +137,15 @@ public void setSkinData(SerializedImage skinData) { public void setSkinResourcePatch(String skinResourcePatch) { if (skinResourcePatch == null || skinResourcePatch.trim().isEmpty()) { - skinResourcePatch = GEOMETRY_CUSTOM; + this.skinResourcePatch = GEOMETRY_CUSTOM; + return; } this.skinResourcePatch = skinResourcePatch; } public void setGeometryName(String geometryName) { - if (geometryName == null || geometryName.trim().isEmpty()) { - skinResourcePatch = GEOMETRY_CUSTOM; + if (geometryName.trim().isEmpty()) { + this.skinResourcePatch = GEOMETRY_CUSTOM; return; } @@ -167,8 +182,9 @@ public void setCapeId(String capeId) { public void setCapeData(byte[] capeData) { Objects.requireNonNull(capeData, "capeData"); - Preconditions.checkArgument(capeData.length == SINGLE_SKIN_SIZE || capeData.length == 0, "Invalid legacy cape"); - setCapeData(new SerializedImage(64, 32, capeData)); + if (capeData.length == SINGLE_SKIN_SIZE) { + setCapeData(new SerializedImage(64, 32, capeData)); + } } public void setCapeData(BufferedImage image) { @@ -286,32 +302,47 @@ public void setArmSize(String armSize) { public void setFullSkinId(String fullSkinId) { this.fullSkinId = fullSkinId; + this.noPlayFab = false; // Allow another attempt to generate it using the new id } public String getFullSkinId() { if (this.fullSkinId == null) { this.fullSkinId = this.getSkinId() + this.getCapeId(); + this.noPlayFab = false; // Allow another attempt to generate it using the new id } return this.fullSkinId; } public void setPlayFabId(String playFabId) { this.playFabId = playFabId; + this.noPlayFab = false; } public String getPlayFabId() { - if (this.persona && (this.playFabId == null || this.playFabId.isEmpty())) { - try { - this.playFabId = this.skinId.split("-")[5]; - } catch (Exception e) { - this.playFabId = this.getFullSkinId().replace("-", "").substring(16); + if (this.noPlayFab) { + return ""; + } + if ((this.playFabId == null || this.playFabId.isEmpty())) { + String[] split = this.getFullSkinId().split("-", 6); + if (split.length > 5) { + this.playFabId = split[5]; + this.noPlayFab = false; + } else { + try { + this.playFabId = this.getFullSkinId().replace("-", "").substring(16); + this.noPlayFab = false; + } catch (Exception ignore) { + Server.getInstance().getLogger().debug("Couldn't generate Skin playFabId for " + this.getFullSkinId()); + this.playFabId = ""; + this.noPlayFab = true; + } } } return this.playFabId; } private static SerializedImage parseBufferedImage(BufferedImage image) { - FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream(); + FastByteArrayOutputStream outputStream = ThreadCache.fbaos.get().reset(); for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { Color color = new Color(image.getRGB(x, y), true); diff --git a/src/main/java/cn/nukkit/entity/data/StringEntityData.java b/src/main/java/cn/nukkit/entity/data/StringEntityData.java index 447b8226d7e..80771a36ad6 100644 --- a/src/main/java/cn/nukkit/entity/data/StringEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/StringEntityData.java @@ -3,10 +3,11 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class StringEntityData extends EntityData { + public String data; public StringEntityData(int id, String data) { diff --git a/src/main/java/cn/nukkit/entity/data/Vector3fEntityData.java b/src/main/java/cn/nukkit/entity/data/Vector3fEntityData.java index dbb37278fcb..3d8b9410b77 100644 --- a/src/main/java/cn/nukkit/entity/data/Vector3fEntityData.java +++ b/src/main/java/cn/nukkit/entity/data/Vector3fEntityData.java @@ -4,10 +4,11 @@ import cn.nukkit.math.Vector3f; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Vector3fEntityData extends EntityData { + public float x; public float y; public float z; diff --git a/src/main/java/cn/nukkit/entity/item/EntityAreaEffectCloud.java b/src/main/java/cn/nukkit/entity/item/EntityAreaEffectCloud.java new file mode 100644 index 00000000000..3ff0777e5ac --- /dev/null +++ b/src/main/java/cn/nukkit/entity/item/EntityAreaEffectCloud.java @@ -0,0 +1,407 @@ +package cn.nukkit.entity.item; + +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityBoss; +import cn.nukkit.entity.EntityLiving; +import cn.nukkit.entity.EntitySmite; +import cn.nukkit.entity.data.FloatEntityData; +import cn.nukkit.entity.data.IntEntityData; +import cn.nukkit.entity.data.LongEntityData; +import cn.nukkit.entity.data.ShortEntityData; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.event.entity.EntityRegainHealthEvent; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.potion.Effect; +import cn.nukkit.potion.InstantEffect; +import cn.nukkit.potion.Potion; + +import java.util.ArrayList; +import java.util.List; + +public class EntityAreaEffectCloud extends Entity { + + public static final int NETWORK_ID = 95; + + private int reapplicationDelay; + private int durationOnUse; + private float initialRadius; + private float radiusOnUse; + private int nextApply; + public List cloudEffects; + private int lastAge; + + public EntityAreaEffectCloud(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + public int getWaitTime() { + return this.getDataPropertyInt(DATA_AREA_EFFECT_CLOUD_WAITING); + } + + public void setWaitTime(int waitTime) { + setWaitTime(waitTime, true); + } + + public void setWaitTime(int waitTime, boolean send) { + this.setDataProperty(new IntEntityData(DATA_AREA_EFFECT_CLOUD_WAITING, waitTime), send); + } + + public int getPotionId() { + return this.getDataPropertyShort(DATA_POTION_AUX_VALUE); + } + + public void setPotionId(int potionId) { + setPotionId(potionId, true); + } + + public void setPotionId(int potionId, boolean send) { + this.setDataProperty(new ShortEntityData(DATA_POTION_AUX_VALUE, potionId & 0xFFFF), send); + } + + public void recalculatePotionColor() { + recalculatePotionColor(true); + } + + public void recalculatePotionColor(boolean send) { + int a; + int r; + int g; + int b; + + int color; + if (namedTag.contains("ParticleColor")) { + color = namedTag.getInt("ParticleColor"); + a = (color & 0xFF000000) >> 24; + r = (color & 0x00FF0000) >> 16; + g = (color & 0x0000FF00) >> 8; + b = color & 0x000000FF; + } else { + a = 255; + Effect effect = Potion.getEffect(getPotionId(), true); + if (effect == null) { + r = 40; + g = 40; + b = 255; + } else { + int[] colors = effect.getColor(); + r = colors[0]; + g = colors[1]; + b = colors[2]; + } + } + + setPotionColor(a, r, g, b, send); + } + + public int getPotionColor() { + return this.getDataPropertyInt(DATA_POTION_COLOR); + } + + public void setPotionColor(int alpha, int red, int green, int blue, boolean send) { + setPotionColor(((alpha & 0xff) << 24) | ((red & 0xff) << 16) | ((green & 0xff) << 8) | (blue & 0xff), send); + } + + public void setPotionColor(int argp) { + setPotionColor(argp, true); + } + + public void setPotionColor(int argp, boolean send) { + this.setDataProperty(new IntEntityData(DATA_POTION_COLOR, argp), send); + } + + public int getPickupCount() { + return this.getDataPropertyInt(DATA_PICKUP_COUNT); + } + + public void setPickupCount(int pickupCount) { + setPickupCount(pickupCount, true); + } + + public void setPickupCount(int pickupCount, boolean send) { + this.setDataProperty(new IntEntityData(DATA_PICKUP_COUNT, pickupCount), send); + } + + public float getRadiusChangeOnPickup() { + return this.getDataPropertyFloat(DATA_CHANGE_ON_PICKUP); + } + + public void setRadiusChangeOnPickup(float radiusChangeOnPickup) { + setRadiusChangeOnPickup(radiusChangeOnPickup, true); + } + + public void setRadiusChangeOnPickup(float radiusChangeOnPickup, boolean send) { + this.setDataProperty(new FloatEntityData(DATA_CHANGE_ON_PICKUP, radiusChangeOnPickup), send); + } + + public float getRadiusPerTick() { + return this.getDataPropertyFloat(DATA_CHANGE_RATE); + } + + public void setRadiusPerTick(float radiusPerTick) { + setRadiusPerTick(radiusPerTick, true); + } + + public void setRadiusPerTick(float radiusPerTick, boolean send) { + this.setDataProperty(new FloatEntityData(DATA_CHANGE_RATE, radiusPerTick), send); + } + + public long getSpawnTime() { + return this.getDataPropertyInt(DATA_SPAWN_TIME); + } + + public void setSpawnTime(long spawnTime) { + setSpawnTime(spawnTime, true); + } + + public void setSpawnTime(long spawnTime, boolean send) { + this.setDataProperty(new LongEntityData(DATA_SPAWN_TIME, spawnTime), send); + } + + public int getDuration() { + return this.getDataPropertyInt(DATA_DURATION); + } + + public void setDuration(int duration) { + setDuration(duration, true); + } + + public void setDuration(int duration, boolean send) { + this.setDataProperty(new IntEntityData(DATA_DURATION, duration), send); + } + + public float getRadius() { + return this.getDataPropertyFloat(DATA_AREA_EFFECT_CLOUD_RADIUS); + } + + public void setRadius(float radius) { + setRadius(radius, true); + } + + public void setRadius(float radius, boolean send) { + this.setDataProperty(new FloatEntityData(DATA_AREA_EFFECT_CLOUD_RADIUS, radius), send); + } + + public int getParticleId() { + return this.getDataPropertyInt(DATA_AREA_EFFECT_CLOUD_PARTICLE_ID); + } + + public void setParticleId(int particleId) { + setParticleId(particleId, true); + } + + public void setParticleId(int particleId, boolean send) { + this.setDataProperty(new IntEntityData(DATA_AREA_EFFECT_CLOUD_PARTICLE_ID, particleId), send); + } + + @Override + protected void initEntity() { + super.initEntity(); + this.invulnerable = true; + this.fireProof = true; + //this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); + //this.setDataFlag(DATA_FLAGS, DATA_FLAG_IMMOBILE, true); + this.setDataProperty(new ShortEntityData(DATA_AREA_EFFECT_CLOUD_PARTICLE_ID, 32), false); + this.setDataProperty(new LongEntityData(DATA_SPAWN_TIME, this.level.getCurrentTick()), false); + this.setDataProperty(new IntEntityData(DATA_PICKUP_COUNT, 0), false); + + cloudEffects = new ArrayList<>(1); + for (CompoundTag effectTag : namedTag.getList("mobEffects", CompoundTag.class).getAll()) { + Effect effect = Effect.getEffect(effectTag.getByte("Id")) + .setAmbient(effectTag.getBoolean("Ambient")) + .setAmplifier(effectTag.getByte("Amplifier")) + .setVisible(effectTag.getBoolean("DisplayOnScreenTextureAnimation")) + .setDuration(effectTag.getInt("Duration")); + cloudEffects.add(effect); + } + int displayedPotionId = namedTag.getShort("PotionId"); + setPotionId(displayedPotionId, false); + recalculatePotionColor(); + + if (namedTag.contains("Duration")) { + setDuration(namedTag.getInt("Duration"), false); + } else { + setDuration(600, false); + } + if (namedTag.contains("DurationOnUse")) { + durationOnUse = namedTag.getInt("DurationOnUse"); + } else { + durationOnUse = 0; + } + if (namedTag.contains("ReapplicationDelay")) { + reapplicationDelay = namedTag.getInt("ReapplicationDelay"); + } else { + reapplicationDelay = 0; + } + if (namedTag.contains("InitialRadius")) { + initialRadius = namedTag.getFloat("InitialRadius"); + } else { + initialRadius = 3.0F; + } + if (namedTag.contains("Radius")) { + setRadius(namedTag.getFloat("Radius"), false); + } else { + setRadius(initialRadius, false); + } + if (namedTag.contains("RadiusChangeOnPickup")) { + setRadiusChangeOnPickup(namedTag.getFloat("RadiusChangeOnPickup"), false); + } else { + setRadiusChangeOnPickup(-0.5F, false); + } + if (namedTag.contains("RadiusOnUse")) { + radiusOnUse = namedTag.getFloat("RadiusOnUse"); + } else { + radiusOnUse = -0.5F; + } + if (namedTag.contains("RadiusPerTick")) { + setRadiusPerTick(namedTag.getFloat("RadiusPerTick"), false); + } else { + setRadiusPerTick(-0.005F, false); + } + if (namedTag.contains("WaitTime")) { + setWaitTime(namedTag.getInt("WaitTime"), false); + } else { + setWaitTime(10, false); + } + } + + @Override + public boolean attack(EntityDamageEvent source) { + return false; + } + + @Override + public void saveNBT() { + super.saveNBT(); + ListTag effectsTag = new ListTag<>("mobEffects"); + for (Effect effect : cloudEffects) { + effectsTag.add(new CompoundTag().putByte("Id", effect.getId()) + .putBoolean("Ambient", effect.isAmbient()) + .putByte("Amplifier", effect.getAmplifier()) + .putBoolean("DisplayOnScreenTextureAnimation", effect.isVisible()) + .putInt("Duration", effect.getDuration()) + ); + } + namedTag.putList(effectsTag); + namedTag.putInt("ParticleColor", getPotionColor()); + namedTag.putShort("PotionId", getPotionId()); + namedTag.putInt("Duration", getDuration()); + namedTag.putInt("DurationOnUse", durationOnUse); + namedTag.putInt("ReapplicationDelay", reapplicationDelay); + namedTag.putFloat("Radius", getRadius()); + namedTag.putFloat("RadiusChangeOnPickup", getRadiusChangeOnPickup()); + namedTag.putFloat("RadiusOnUse", radiusOnUse); + namedTag.putFloat("RadiusPerTick", getRadiusPerTick()); + namedTag.putInt("WaitTime", getWaitTime()); + namedTag.putFloat("InitialRadius", initialRadius); + } + + @Override + public boolean onUpdate(int currentTick) { + if (this.closed) { + return false; + } + + super.onUpdate(currentTick); + + boolean sendRadius = age % 10 == 0; + + int age = this.age; + float radius = getRadius(); + int waitTime = getWaitTime(); + if (age < waitTime) { + radius = initialRadius; + } else if (age > waitTime + getDuration()) { + close(); + return false; + } else { + int tickDiff = age - lastAge; + radius += getRadiusPerTick() * tickDiff; + if ((nextApply -= tickDiff) <= 0) { + nextApply = reapplicationDelay + 10; + + Entity[] collidingEntities = level.getCollidingEntities(getBoundingBox()); + if (collidingEntities.length > 0) { + radius += radiusOnUse; + radiusOnUse /= 2; + + setDuration(getDuration() + durationOnUse); + + for (Entity collidingEntity : collidingEntities) { + if (collidingEntity == null || collidingEntity.closed || !collidingEntity.isAlive() || !(collidingEntity instanceof EntityLiving) || collidingEntity instanceof EntityBoss) continue; + + for (Effect effect : cloudEffects) { + if (effect instanceof InstantEffect) { + boolean damage = false; + if (effect.getId() == Effect.HARMING) damage = true; + if (collidingEntity instanceof EntitySmite) damage = !damage; // invert effect if undead + + if (damage) + collidingEntity.attack(new EntityDamageByEntityEvent(this, collidingEntity, EntityDamageEvent.DamageCause.MAGIC, (float) (0.5 * (double) (6 << (effect.getAmplifier() + 1))))); + else + collidingEntity.heal(new EntityRegainHealthEvent(collidingEntity, (float) (0.5 * (double) (4 << (effect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); + + continue; + } + + collidingEntity.addEffect(effect); + } + } + } + } + } + + this.lastAge = age; + + if (radius <= 1.5 && age >= waitTime) { + setRadius(radius, false); + close(); + return false; + } else { + setRadius(radius, sendRadius); + } + + float height = getHeight(); + boundingBox.setBounds(x - radius, y - height, z - radius, x + radius, y + height, z + radius); + this.setDataProperty(new FloatEntityData(DATA_BOUNDING_BOX_HEIGHT, height), false); + this.setDataProperty(new FloatEntityData(DATA_BOUNDING_BOX_WIDTH, radius), false); + return true; + } + + @Override + public boolean canCollideWith(Entity entity) { + return entity instanceof EntityLiving; + } + + @Override + public float getHeight() { + return 0.3F + (getRadius() / 2F); + } + + @Override + public float getWidth() { + return getRadius(); + } + + @Override + public float getLength() { + return getRadius(); + } + + @Override + protected float getGravity() { + return 0; + } + + @Override + protected float getDrag() { + return 0; + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } +} diff --git a/src/main/java/cn/nukkit/entity/item/EntityArmorStand.java b/src/main/java/cn/nukkit/entity/item/EntityArmorStand.java new file mode 100644 index 00000000000..ceee553cdcc --- /dev/null +++ b/src/main/java/cn/nukkit/entity/item/EntityArmorStand.java @@ -0,0 +1,378 @@ +package cn.nukkit.entity.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityInteractable; +import cn.nukkit.entity.data.StringEntityData; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.inventory.EntityArmorInventory; +import cn.nukkit.inventory.EntityEquipmentInventory; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemArmor; +import cn.nukkit.level.GameRule; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +import java.util.Collection; + +public class EntityArmorStand extends Entity implements InventoryHolder, EntityInteractable { + + public static final int NETWORK_ID = 61; + + public static final String TAG_MAINHAND = "Mainhand"; + public static final String TAG_OFFHAND = "Offhand"; + public static final String TAG_POSE_INDEX = "PoseIndex"; + public static final String TAG_ARMOR = "Armor"; + + private EntityEquipmentInventory equipmentInventory; + private EntityArmorInventory armorInventory; + + private int pose; + private String nameTag; + + public EntityArmorStand(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + + this.updateMode = 3; + + if (nbt.contains(TAG_POSE_INDEX)) { + this.setPose(nbt.getInt(TAG_POSE_INDEX)); + } + } + + private static int getArmorSlot(ItemArmor armorItem) { + if (armorItem.canBePutInHelmetSlot()) { + return 0; + } else if (armorItem.isChestplate()) { + return 1; + } else if (armorItem.isLeggings()) { + return 2; + } else { + return 3; + } + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } + + @Override + protected float getGravity() { + return 0.04f; + } + + @Override + public float getHeight() { + return 1.975f; + } + + @Override + public float getWidth() { + return 0.5f; + } + + @Override + protected void initEntity() { + this.setMaxHealth(6); + + super.initEntity(); + + this.setHealth(6); + this.setImmobile(true); + + this.equipmentInventory = new EntityEquipmentInventory(this); + this.armorInventory = new EntityArmorInventory(this); + + if (this.namedTag.contains(TAG_MAINHAND)) { + this.equipmentInventory.setItemInHand(NBTIO.getItemHelper(this.namedTag.getCompound(TAG_MAINHAND)), true); + } + + if (this.namedTag.contains(TAG_OFFHAND)) { + this.equipmentInventory.setOffhandItem(NBTIO.getItemHelper(this.namedTag.getCompound(TAG_OFFHAND)), true); + } + + if (this.namedTag.contains(TAG_ARMOR)) { + ListTag armorList = this.namedTag.getList(TAG_ARMOR, CompoundTag.class); + for (CompoundTag armorTag : armorList.getAll()) { + this.armorInventory.setItem(armorTag.getByte("Slot"), NBTIO.getItemHelper(armorTag)); + } + } + + if (this.namedTag.contains(TAG_POSE_INDEX)) { + this.setPose(this.namedTag.getInt(TAG_POSE_INDEX)); + } + } + + @Override + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { + if (!this.isAlive()) { + return false; + } + + if (item.getId() == Item.NAME_TAG && !player.isAdventure()) { + if (item.hasCustomName()) { + String name = item.getCustomName(); + this.namedTag.putString("CustomName", name); + this.namedTag.putBoolean("CustomNameVisible", true); + this.setNameTag(name); + this.setNameTagVisible(true); + return true; // onInteract: true = decrease count + } + } + + if (player.isSneaking()) { + if (this.getPose() >= 12) { + this.setPose(0); + } else { + this.setPose(this.getPose() + 1); + } + this.sendData(this.getViewers().values().toArray(new Player[0])); + return false; // do not consume item + } + + if (this.isValid() && !player.isSpectator()) { + int i = 0; + boolean flag = !item.isNull(); + boolean isArmorSlot = false; + + if (flag && item instanceof ItemArmor) { + ItemArmor itemArmor = (ItemArmor) item; + i = getArmorSlot(itemArmor); + isArmorSlot = true; + } + + if (flag && (item.getId() == Item.SKULL) || item.getId() == (255 - BlockID.CARVED_PUMPKIN)) { + i = 0; + isArmorSlot = true; + } + + int j = 0; + double d3 = clickedPos.y - this.y; + boolean flag2 = false; + + if (d3 >= 0.1 && d3 < 0.55 && !this.armorInventory.getItemFast(EntityArmorInventory.SLOT_FEET).isNull()) { + j = 3; + flag2 = isArmorSlot = true; + } else if (d3 >= 0.9 && d3 < 1.6 && !this.armorInventory.getItemFast(EntityArmorInventory.SLOT_CHEST).isNull()) { + j = 1; + flag2 = isArmorSlot = true; + } else if (d3 >= 0.4 && d3 < 1.2 && !this.armorInventory.getItemFast(EntityArmorInventory.SLOT_LEGS).isNull()) { + j = 2; + flag2 = isArmorSlot = true; + } else if (d3 >= 1.6 && !this.armorInventory.getItemFast(EntityArmorInventory.SLOT_HEAD).isNull()) { + flag2 = isArmorSlot = true; + } else if (!this.equipmentInventory.getItemFast(j).isNull()) { + flag2 = true; + } + + if (flag) { + this.tryChangeEquipment(player, item, i, isArmorSlot); + } else if (flag2) { + this.tryChangeEquipment(player, item, j, isArmorSlot); + } + return false; // Item set in tryChangeEquipment + } + return false; + } + + private void tryChangeEquipment(Player player, Item newItem, int slot, boolean isArmorSlot) { + Item currentItem = isArmorSlot ? this.armorInventory.getItem(slot) : this.equipmentInventory.getItem(slot); + + if (currentItem.equals(newItem)) { + return; + } + + if (newItem.isNull()) { + if (isArmorSlot) { + this.armorInventory.setItem(slot, Item.get(Item.AIR)); + } else { + this.equipmentInventory.setItem(slot, Item.get(Item.AIR)); + } + } else { + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + } + + Item itemToAdd = newItem.clone(); + itemToAdd.setCount(1); + if (isArmorSlot) { + this.armorInventory.setItem(slot, itemToAdd); + } else { + this.equipmentInventory.setItem(slot, itemToAdd); + } + } + + if (!currentItem.isNull()) { + player.getInventory().addItem(currentItem); + } + + Collection viewers = this.getViewers().values(); + this.equipmentInventory.sendContents(viewers); + this.armorInventory.sendContents(viewers); + } + + public int getPose() { + return this.pose; + } + + public void setPose(int pose) { + this.pose = pose; + this.dataProperties.putInt(Entity.DATA_ARMOR_STAND_POSE_INDEX, pose); + } + + @Override + public void saveNBT() { + super.saveNBT(); + + this.namedTag.put(TAG_MAINHAND, NBTIO.putItemHelper(this.equipmentInventory.getItemInHand())); + this.namedTag.put(TAG_OFFHAND, NBTIO.putItemHelper(this.equipmentInventory.getOffHandItem())); + + if (this.armorInventory != null) { + ListTag armorTag = new ListTag<>(TAG_ARMOR); + for (int i = 0; i < 4; i++) { + armorTag.add(NBTIO.putItemHelper(this.armorInventory.getItem(i), i)); + } + this.namedTag.putList(armorTag); + } + + this.namedTag.putInt(TAG_POSE_INDEX, this.getPose()); + } + + @Override + public void spawnTo(Player player) { + super.spawnTo(player); + this.equipmentInventory.sendContents(player); + this.armorInventory.sendContents(player); + } + + @Override + public boolean attack(EntityDamageEvent source) { + if (source.getCause() == EntityDamageEvent.DamageCause.CONTACT) { + source.setCancelled(true); + } + + if (!this.isAlive() || !super.attack(source)) { + return false; + } + + if (!source.isCancelled() && !this.closed) { + this.setGenericFlag(Entity.DATA_FLAG_VIBRATING, true); + this.level.addParticle(new DestroyBlockParticle(this, Block.get(Block.WOODEN_PLANKS))); + this.kill(); // Using close() here would not leave any time for the vibrating effect to display + if (source instanceof EntityDamageByEntityEvent) { + EntityDamageByEntityEvent event = (EntityDamageByEntityEvent) source; + if (event.getDamager() instanceof Player) { + Player player = (Player) event.getDamager(); + if (player.isCreative()) { + this.close(); + return true; + } + + boolean drop = this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS); + if (drop) { + this.level.dropItem(this, Item.get(Item.ARMOR_STAND)); + } + if (this.equipmentInventory != null) { + if (drop) { + this.equipmentInventory.getContents().values().forEach(items -> this.level.dropItem(this, items)); + } + this.equipmentInventory.clearAll(); + } + if (this.armorInventory != null) { + if (drop) { + this.armorInventory.getContents().values().forEach(items -> this.level.dropItem(this, items)); + } + this.armorInventory.clearAll(); + } + } + } + } + return true; + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Armor Stand"; + } + + public EntityEquipmentInventory getEquipmentInventory() { + return this.equipmentInventory; + } + + @Override + public EntityArmorInventory getInventory() { + return this.armorInventory; + } + + @Override + public boolean onUpdate(int currentTick) { + if (this.closed) { + return false; + } + + boolean hasUpdate = super.onUpdate(currentTick); + + if (this.updateMode % 2 == 1) { + this.updateMode = 3; + + if (this.onGround) { + if (level.getBlockIdAt(chunk, getFloorX(), getFloorY() - 1, getFloorZ()) == 0) { + this.onGround = false; + } + } + } + + if (!this.onGround) { + this.motionY -= getGravity(); + } else { + this.motionY = 0; + } + + this.move(this.motionX, this.motionY, this.motionZ); + + this.motionX *= 0.9; + this.motionY *= 0.9; + this.motionZ *= 0.9; + + this.updateMovement(); + + return hasUpdate || !(this.motionX == 0 && this.motionY == 0 && this.motionZ == 0); + } + + @Override + public String getInteractButtonText() { + return "action.interact.armorstand.equip"; + } + + @Override + public boolean canDoInteraction() { + return true; + } + + @Override + public void setNameTag(String name) { + this.nameTag = name; + if (this.namedTag.contains("CustomNameVisible") || this.namedTag.contains("CustomNameAlwaysVisible")) { // Hack: Vanilla: Disable client side name tag while keeping custom name in nbt + this.setDataProperty(new StringEntityData(DATA_NAMETAG, name)); + } + } + + @Override + public boolean hasCustomName() { + return this.nameTag != null; + } + + @Override + public String getNameTag() { + return this.nameTag == null ? "" : this.nameTag; + } +} diff --git a/src/main/java/cn/nukkit/entity/item/EntityBoat.java b/src/main/java/cn/nukkit/entity/item/EntityBoat.java index ff3a6fdf7ac..f063dd0f70e 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityBoat.java +++ b/src/main/java/cn/nukkit/entity/item/EntityBoat.java @@ -13,14 +13,10 @@ import cn.nukkit.event.vehicle.VehicleMoveEvent; import cn.nukkit.event.vehicle.VehicleUpdateEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBoat; import cn.nukkit.level.GameRule; import cn.nukkit.level.Location; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.AxisAlignedBB; -import cn.nukkit.math.NukkitMath; -import cn.nukkit.math.Vector3; -import cn.nukkit.math.Vector3f; +import cn.nukkit.math.*; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.AnimatePacket; import cn.nukkit.network.protocol.SetEntityLinkPacket; @@ -52,21 +48,21 @@ public class EntityBoat extends EntityVehicle { public EntityBoat(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); - - this.setMaxHealth(40); - this.setHealth(40); } @Override protected void initEntity() { + this.setMaxHealth(40); super.initEntity(); - this.dataProperties.putInt(DATA_VARIANT, woodID = this.namedTag.getByte("woodID")); - this.dataProperties.putByte(DATA_CONTROLLING_RIDER_SEAT_NUMBER, 0); + this.setHealth(40); + + if (this.namedTag.contains("Variant")) { + this.woodID = this.namedTag.getInt("Variant"); + } + this.dataProperties.putInt(DATA_VARIANT, this.woodID); this.dataProperties.putBoolean(DATA_IS_BUOYANT, true); this.dataProperties.putString(DATA_BUOYANCY_DATA, "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775,\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\",\"minecraft:flowing_water\"],\"simulate_waves\":true}"); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_GRAVITY, true); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_STACKABLE, true); } @Override @@ -86,12 +82,12 @@ protected float getDrag() { @Override protected float getGravity() { - return 0.03999999910593033F; + return 0.04f; } @Override public float getBaseOffset() { - return 0.375F; + return 0.375f; } @Override @@ -120,8 +116,11 @@ public boolean attack(EntityDamageEvent source) { public void close() { super.close(); - for (Entity linkedEntity : this.passengers) { - linkedEntity.riding = null; + if (!passengers.isEmpty()) { + for (Entity passenger : new ArrayList<>(passengers)) { + dismountEntity(passenger); + passenger.riding = null; // Make sure it's really removed even if a plugin tries to cancel it + } } } @@ -146,18 +145,16 @@ public boolean onUpdate(int currentTick) { double waterDiff = getWaterLevel(); if (!hasControllingPassenger()) { - if (waterDiff > SINKING_DEPTH && !sinking) { sinking = true; - } else if (waterDiff < -SINKING_DEPTH && sinking) { + } else if (waterDiff < -0.07 && sinking) { sinking = false; } - if (waterDiff < -SINKING_DEPTH) { + if (waterDiff < -0.07) { this.motionY = Math.min(0.05, this.motionY + 0.005); } else if (waterDiff < 0 || !sinking) { this.motionY = this.motionY > SINKING_MAX_SPEED ? Math.max(this.motionY - 0.02, SINKING_MAX_SPEED) : this.motionY + SINKING_SPEED; -// this.motionY = this.motionY + SINKING_SPEED > SINKING_MAX_SPEED ? this.motionY - SINKING_SPEED : this.motionY + SINKING_SPEED; } } @@ -165,19 +162,19 @@ public boolean onUpdate(int currentTick) { hasUpdate = true; } - this.move(this.motionX, this.motionY, this.motionZ); + //this.move(this.motionX, this.motionY, this.motionZ); double friction = 1 - this.getDrag(); if (this.onGround && (Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionZ) > 0.00001)) { - friction *= this.getLevel().getBlock(this.temporalVector.setComponents((int) Math.floor(this.x), (int) Math.floor(this.y - 1), (int) Math.floor(this.z) - 1)).getFrictionFactor(); + friction *= this.getLevel().getBlock(this.chunk, getFloorX(), getFloorY() - 1, getFloorZ(), false).getFrictionFactor(); } this.motionX *= friction; if (!hasControllingPassenger()) { if (waterDiff > SINKING_DEPTH || sinking) { - this.motionY = waterDiff > 0.5 ? this.motionY - this.getGravity() : (this.motionY - SINKING_SPEED < -SINKING_MAX_SPEED ? this.motionY : this.motionY - SINKING_SPEED); + this.motionY = waterDiff > 0.5 ? this.motionY - this.getGravity() : (this.motionY - SINKING_SPEED < -0.005 ? this.motionY : this.motionY - SINKING_SPEED); } } @@ -192,19 +189,39 @@ public boolean onUpdate(int currentTick) { this.getServer().getPluginManager().callEvent(new VehicleMoveEvent(this, from, to)); } - //TODO: lily pad collision + this.move(this.motionX, this.motionY, this.motionZ); + this.updateMovement(); - if (!this.isFull()) { - for (Entity entity : this.level.getCollidingEntities(this.boundingBox.grow(0.20000000298023224, 0.0D, 0.20000000298023224), this)) { - if (entity.riding != null || !(entity instanceof EntityLiving) || entity instanceof Player || entity instanceof EntityWaterAnimal || isPassenger(entity)) { - continue; + if (this.age % 5 == 0) { + int passengersCount = this.passengers.size(); + if (passengersCount > 0) { + Block[] blocks = this.level.getCollisionBlocks(this.getBoundingBox().grow(0.1, 0.3, 0.1)); + for (Block b : blocks) { + if (b.getId() == Block.LILY_PAD) { + this.level.setBlockAt((int) b.x, (int) b.y, (int) b.z, 0, 0); + this.level.dropItem(b, Item.get(Item.LILY_PAD, 0, 1)); + } } + } - this.mountEntity(entity); + if (passengersCount < 2) { + Entity[] e = this.level.getCollidingEntities(this.boundingBox.grow(0.20000000298023224, 0.0D, 0.20000000298023224), this); + for (Entity entity : e) { + boolean isPassenger = isPassenger(entity); + if (entity instanceof Player && !isPassenger) { + entity.resetFallDistance(); // Hack: Don't kick players standing on a boat for flying + } - if (this.isFull()) { - break; + if (entity.riding != null || !(entity instanceof EntityLiving) || entity instanceof Player || entity instanceof EntityWaterAnimal || isPassenger) { + continue; + } + + this.mountEntity(entity); + + if (this.isFull()) { + break; + } } } } @@ -231,19 +248,19 @@ public void updatePassengers(boolean sendLinks) { Entity ent; if (passengers.size() == 1) { - (ent = this.passengers.get(0)).setSeatPosition(getMountedOffset(ent)); + (ent = this.passengers.get(RIDER_INDEX)).setSeatPosition(getMountedOffset(ent)); super.updatePassengerPosition(ent); if (sendLinks) { broadcastLinkPacket(ent, SetEntityLinkPacket.TYPE_RIDE); } } else if (passengers.size() == 2) { - if (!((ent = passengers.get(0)) instanceof Player)) { //swap - Entity passenger2 = passengers.get(1); + if (!((ent = passengers.get(RIDER_INDEX)) instanceof Player)) { //swap + Entity passenger2 = passengers.get(PASSENGER_INDEX); if (passenger2 instanceof Player) { - this.passengers.set(0, passenger2); - this.passengers.set(1, ent); + this.passengers.set(RIDER_INDEX, passenger2); + this.passengers.set(PASSENGER_INDEX, ent); ent = passenger2; } @@ -255,7 +272,7 @@ public void updatePassengers(boolean sendLinks) { broadcastLinkPacket(ent, SetEntityLinkPacket.TYPE_RIDE); } - (ent = this.passengers.get(1)).setSeatPosition(getMountedOffset(ent).add(PASSENGER_OFFSET)); + (ent = this.passengers.get(PASSENGER_INDEX)).setSeatPosition(getMountedOffset(ent).add(PASSENGER_OFFSET)); super.updatePassengerPosition(ent); @@ -275,18 +292,15 @@ public void updatePassengers(boolean sendLinks) { public double getWaterLevel() { double maxY = this.boundingBox.getMinY() + getBaseOffset(); - AxisAlignedBB.BBConsumer consumer = new AxisAlignedBB.BBConsumer() { + AxisAlignedBB.BBConsumer consumer = new SimpleAxisAlignedBB.BBConsumer() { private double diffY = Double.MAX_VALUE; @Override public void accept(int x, int y, int z) { Block block = EntityBoat.this.level.getBlock(EntityBoat.this.temporalVector.setComponents(x, y, z)); - if (block instanceof BlockWater) { - double level = block.getMaxY(); - - diffY = Math.min(maxY - level, diffY); + diffY = Math.min(maxY - block.getMaxY(), diffY); } } @@ -302,23 +316,26 @@ public Double get() { } @Override - public boolean mountEntity(Entity entity) { - boolean player = this.passengers.size() >= 1 && this.passengers.get(0) instanceof Player; - byte mode = SetEntityLinkPacket.TYPE_PASSENGER; + public boolean mountEntity(Entity entity, byte mode) { + boolean player = this.passengers.size() >= 1 && this.passengers.get(RIDER_INDEX) instanceof Player; + mode = SetEntityLinkPacket.TYPE_PASSENGER; - if (!player && (entity instanceof Player || this.passengers.size() == 0)) { + if (!player && (entity instanceof Player || this.passengers.isEmpty())) { mode = SetEntityLinkPacket.TYPE_RIDE; } boolean r = super.mountEntity(entity, mode); - if (entity.riding != null) { + if (r) { updatePassengers(true); - entity.setDataProperty(new ByteEntityData(DATA_RIDER_ROTATION_LOCKED, 1)); - entity.setDataProperty(new FloatEntityData(DATA_RIDER_MAX_ROTATION, 90)); - entity.setDataProperty(new FloatEntityData(DATA_RIDER_MIN_ROTATION, 1)); - entity.setDataProperty(new FloatEntityData(DATA_RIDER_ROTATION_OFFSET, -90)); + entity.setDataProperty(new ByteEntityData(DATA_RIDER_ROTATION_LOCKED, 1), !(entity instanceof Player)); + if (entity instanceof Player) { + entity.setDataProperty(new FloatEntityData(DATA_RIDER_MAX_ROTATION, 90), false); + entity.setDataProperty(new FloatEntityData(DATA_RIDER_MIN_ROTATION, 1), false); + entity.setDataProperty(new FloatEntityData(DATA_RIDER_ROTATION_OFFSET, -90), false); + entity.sendData(((Player) entity)); + } } return r; @@ -333,20 +350,21 @@ protected void updatePassengerPosition(Entity passenger) { public boolean dismountEntity(Entity entity, boolean sendLinks) { boolean r = super.dismountEntity(entity, sendLinks); - updatePassengers(); - entity.setDataProperty(new ByteEntityData(DATA_RIDER_ROTATION_LOCKED, 0)); - + if (r) { + updatePassengers(); + entity.setDataProperty(new ByteEntityData(DATA_RIDER_ROTATION_LOCKED, 0), true); + } return r; } @Override public boolean isControlling(Entity entity) { - return entity instanceof Player && this.passengers.indexOf(entity) == 0; + return entity instanceof Player && this.passengers.indexOf(entity) == RIDER_INDEX; } @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - if (this.isFull()) { + if (this.isFull() || getWaterLevel() < -SINKING_DEPTH) { return false; } @@ -371,7 +389,7 @@ public void onPaddle(AnimatePacket.Action animation, float value) { public void applyEntityCollision(Entity entity) { if (this.riding == null && entity.riding != this && !entity.passengers.contains(this)) { if (!entity.boundingBox.intersectsWith(this.boundingBox.grow(0.20000000298023224, -0.1, 0.20000000298023224)) - || entity instanceof Player && ((Player) entity).isSpectator()) { + || entity instanceof Player && ((Player) entity).getGamemode() == Player.SPECTATOR) { return; } @@ -392,6 +410,7 @@ public void applyEntityCollision(Entity entity) { diffX *= 0.05000000074505806; diffZ *= 0.05000000074505806; diffX *= 1 + entityCollisionReduction; + diffZ *= 1 + entityCollisionReduction; if (this.riding == null) { motionX -= diffX; @@ -408,24 +427,36 @@ public boolean canPassThrough() { @Override public void kill() { + if (!this.isAlive()) { + return; + } + super.kill(); - if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { - Entity damager = ((EntityDamageByEntityEvent) this.lastDamageCause).getDamager(); - if (damager instanceof Player && ((Player) damager).isCreative()) { - return; - } - } if (level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - this.level.dropItem(this, new ItemBoat(this.woodID)); + if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { + Entity damager = ((EntityDamageByEntityEvent) this.lastDamageCause).getDamager(); + if (damager instanceof Player && ((Player) damager).isCreative()) { + return; + } + } + this.dropItem(); } } @Override public void saveNBT() { super.saveNBT(); + this.namedTag.putInt("Variant", this.woodID); + } - this.namedTag.putByte("woodID", this.woodID); + public int getVariant() { + return this.woodID; + } + + public void setVariant(int variant) { + this.woodID = variant; + this.dataProperties.putInt(DATA_VARIANT, variant); } public void onInput(double x, double y, double z, double yaw) { @@ -440,4 +471,8 @@ public boolean isFull() { public String getInteractButtonText() { return !this.isFull() ? "action.interact.ride.boat" : ""; } + + protected void dropItem() { + this.level.dropItem(this, Item.get(Item.BOAT, this.woodID)); + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityChestBoat.java b/src/main/java/cn/nukkit/entity/item/EntityChestBoat.java new file mode 100644 index 00000000000..e1ed04172d7 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/item/EntityChestBoat.java @@ -0,0 +1,143 @@ +package cn.nukkit.entity.item; + +import cn.nukkit.Player; +import cn.nukkit.inventory.*; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; + +public class EntityChestBoat extends EntityBoat implements InventoryHolder { + + public static final int NETWORK_ID = 218; + + protected ChestBoatInventory inventory; + + public EntityChestBoat(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } + + @Override + public String getName() { + return "Boat with Chest"; + } + + @Override + public String getInteractButtonText(Player player) { + if (player.isSneaking()) { + return "action.interact.opencontainer"; + } + return super.getInteractButtonText(); + } + + @Override + public boolean isFull() { + return this.passengers.size() >= 1; + } + + @Override + public ChestBoatInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } + return this.inventory; + } + + @Override + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { + if (player.isSneaking() && this.isAlive()) { + player.addWindow(this.getInventory()); + return false; + } + + return super.onInteract(player, item, clickedPos); + } + + @Override + public void initEntity() { + super.initEntity(); + + this.dataProperties + .putByte(DATA_CONTAINER_TYPE, InventoryType.CHEST_BOAT.getNetworkType()) + .putInt(DATA_CONTAINER_BASE_SIZE, 27) + .putInt(DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH, 0); + } + + private void initInventory() { + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new ChestBoatInventory(this); + + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } + } + } + + @Override + public void saveNBT() { + super.saveNBT(); + + if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); + for (int slot = 0; slot < 27; ++slot) { + Item item = this.inventory.getItem(slot); + if (item != null && item.getId() != Item.AIR) { + this.namedTag.getList("Items", CompoundTag.class) + .add(NBTIO.putItemHelper(item, slot)); + } + } + } + } + + @Override + protected void dropItem() { + switch (this.getVariant()) { + case 0: + this.level.dropItem(this, Item.get(ItemID.OAK_CHEST_BOAT)); + break; + case 1: + this.level.dropItem(this, Item.get(ItemID.SPRUCE_CHEST_BOAT)); + break; + case 2: + this.level.dropItem(this, Item.get(ItemID.BIRCH_CHEST_BOAT)); + break; + case 3: + this.level.dropItem(this, Item.get(ItemID.JUNGLE_CHEST_BOAT)); + break; + case 4: + this.level.dropItem(this, Item.get(ItemID.ACACIA_CHEST_BOAT)); + break; + case 5: + this.level.dropItem(this, Item.get(ItemID.DARK_OAK_CHEST_BOAT)); + break; + case 6: + this.level.dropItem(this, Item.get(ItemID.MANGROVE_CHEST_BOAT)); + break; + } + + if (this.inventory == null) { + this.initInventory(); + } + if (this.inventory != null) { + this.inventory.getViewers().clear(); + for (Item item : this.inventory.getContents().values()) { + this.level.dropItem(this, item); + } + this.inventory.clearAll(); + } + } +} diff --git a/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java b/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java index cd7d96dae02..3c75687ab2c 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java +++ b/src/main/java/cn/nukkit/entity/item/EntityEndCrystal.java @@ -1,7 +1,9 @@ package cn.nukkit.entity.item; +import cn.nukkit.block.Block; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityExplosive; +import cn.nukkit.entity.data.StringEntityData; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityExplosionPrimeEvent; import cn.nukkit.level.Explosion; @@ -9,14 +11,27 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * Created by PetteriM1 - */ public class EntityEndCrystal extends Entity implements EntityExplosive { public static final int NETWORK_ID = 71; - protected boolean detonated = false; + private boolean detonated = false; + private String nameTag; + + @Override + public float getLength() { + return 2f; + } + + @Override + public float getHeight() { + return 2f; + } + + @Override + public float getWidth() { + return 2f; + } @Override public int getNetworkId() { @@ -36,7 +51,6 @@ protected void initEntity() { } this.fireProof = true; - this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); } @Override @@ -46,35 +60,45 @@ public void saveNBT() { this.namedTag.putBoolean("ShowBottom", this.showBase()); } - @Override - public float getHeight() { - return 0.98f; - } - - @Override - public float getWidth() { - return 0.98f; - } - @Override public boolean attack(EntityDamageEvent source) { - if (source.getCause() == EntityDamageEvent.DamageCause.FIRE || source.getCause() == EntityDamageEvent.DamageCause.FIRE_TICK || source.getCause() == EntityDamageEvent.DamageCause.LAVA) { + if (this.closed) { return false; } - if (!super.attack(source)) { + if (source.getCause() == EntityDamageEvent.DamageCause.FIRE || source.getCause() == EntityDamageEvent.DamageCause.FIRE_TICK || source.getCause() == EntityDamageEvent.DamageCause.LAVA || + (source.getCause() == EntityDamageEvent.DamageCause.ENTITY_ATTACK && source.getFinalDamage() < 0.8)) { + source.setCancelled(true); + } + + this.getServer().getPluginManager().callEvent(source); + if (source.isCancelled()) { return false; } - explode(); + this.setLastDamageCause(source); + + this.explode(); return true; } + @Override + public boolean canCollideWith(Entity entity) { + return true; + } + + public boolean showBase() { + return this.getDataFlag(DATA_FLAGS, DATA_FLAG_SHOWBASE); + } + + public void setShowBase(boolean value) { + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHOWBASE, value); + } + @Override public void explode() { this.close(); - if (!this.detonated && this.level.getGameRules().getBoolean(GameRule.MOB_GRIEFING)) { EntityExplosionPrimeEvent ev = new EntityExplosionPrimeEvent(this, 6); this.server.getPluginManager().callEvent(ev); @@ -85,6 +109,13 @@ public void explode() { this.detonated = true; Explosion explosion = new Explosion(this, (float) ev.getForce(), this); + + int floor = this.getFloorY(); + int down = this.level.getBlockIdAt(this.chunk, this.getFloorX(), floor - 1, this.getFloorZ()); + if (down == Block.BEDROCK || down == Block.OBSIDIAN) { + explosion.setMinHeight(floor); + } + if (ev.isBlockBreaking()) { explosion.explodeA(); } @@ -93,15 +124,45 @@ public void explode() { } @Override - public boolean canCollideWith(Entity entity) { - return false; + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "End Crystal"; } - public boolean showBase() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_SHOWBASE); + @Override + public void setNameTag(String name) { + this.nameTag = name; + if (this.namedTag.contains("CustomNameVisible") || this.namedTag.contains("CustomNameAlwaysVisible")) { // Hack: Vanilla: Disable client side name tag while keeping custom name in nbt + this.setDataProperty(new StringEntityData(DATA_NAMETAG, name)); + } } - public void setShowBase(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHOWBASE, value); + @Override + public boolean hasCustomName() { + return this.nameTag != null; + } + + @Override + public String getNameTag() { + return this.nameTag == null ? "" : this.nameTag; + } + + @Override // Minimal + public boolean onUpdate(int currentTick) { + if (this.closed) { + return false; + } + + if (!this.isAlive() || this.y < (this.getLevel().getMinBlockY() - 16)) { + this.close(); + return false; + } + + int tickDiff = currentTick - this.lastUpdate; + if (tickDiff <= 0 && !this.justCreated) { + return false; + } + + this.minimalEntityTick(currentTick, tickDiff); + return false; } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityExpBottle.java b/src/main/java/cn/nukkit/entity/item/EntityExpBottle.java index 16963a25c9a..9ba9a4d6574 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityExpBottle.java +++ b/src/main/java/cn/nukkit/entity/item/EntityExpBottle.java @@ -3,12 +3,10 @@ import cn.nukkit.entity.Entity; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.particle.Particle; import cn.nukkit.level.particle.SpellParticle; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * @author xtypr @@ -57,42 +55,33 @@ public EntityExpBottle(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) @Override public boolean onUpdate(int currentTick) { - if (this.closed) { + if (this.age > 1200) { + this.close(); return false; } + boolean update = super.onUpdate(currentTick); - this.timing.startTiming(); - - boolean hasUpdate = super.onUpdate(currentTick); - - if (this.age > 1200) { - this.kill(); - hasUpdate = true; + if (this.closed) { + return false; } if (this.isCollided) { - this.kill(); + this.close(); this.dropXp(); - hasUpdate = true; + return false; } - - this.timing.stopTiming(); - - return hasUpdate; + return update; } @Override public void onCollideWithEntity(Entity entity) { - this.kill(); + this.close(); this.dropXp(); } public void dropXp() { - Particle particle2 = new SpellParticle(this, 0x00385dc6); - this.getLevel().addParticle(particle2); - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_GLASS); - - this.getLevel().dropExpOrb(this, ThreadLocalRandom.current().nextInt(3, 12)); + this.getLevel().addParticle(new SpellParticle(this, 0x00385dc6)); + this.getLevel().dropExpOrb(this, Utils.rand(3, 12)); } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java index 1d146ea97b0..417dfbbf090 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java @@ -1,5 +1,6 @@ package cn.nukkit.entity.item; +import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.block.BlockLiquid; @@ -13,9 +14,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.GameRule; import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.network.protocol.LevelEventPacket; /** @@ -90,13 +94,35 @@ protected void initEntity() { } this.fireProof = true; - this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); + } - setDataProperty(new IntEntityData(DATA_VARIANT, GlobalBlockPalette.getOrCreateRuntimeId(this.getBlock(), this.getDamage()))); + @Override + public void spawnTo(Player player) { + if (!this.hasSpawned.containsKey(player.getLoaderId())) { + Boolean hasChunk = player.usedChunks.get(Level.chunkHash(this.chunk.getX(), this.chunk.getZ())); + if (hasChunk != null && hasChunk) { + AddEntityPacket addEntity = new AddEntityPacket(); + addEntity.type = this.getNetworkId(); + addEntity.entityUniqueId = this.id; + addEntity.entityRuntimeId = this.id; + addEntity.yaw = (float) this.yaw; + addEntity.headYaw = (float) this.yaw; + addEntity.pitch = (float) this.pitch; + addEntity.x = (float) this.x; + addEntity.y = (float) this.y; + addEntity.z = (float) this.z; + addEntity.speedX = (float) this.motionX; + addEntity.speedY = (float) this.motionY; + addEntity.speedZ = (float) this.motionZ; + addEntity.metadata = this.dataProperties.clone().put(new IntEntityData(DATA_VARIANT, GlobalBlockPalette.getOrCreateRuntimeId(this.blockId, this.damage))); + player.dataPacket(addEntity); + this.hasSpawned.put(player.getLoaderId(), player); + } + } } public boolean canCollideWith(Entity entity) { - return blockId == BlockID.ANVIL; + return blockId == BlockID.ANVIL || blockId == BlockID.POINTED_DRIPSTONE; } @Override @@ -106,13 +132,10 @@ public boolean attack(EntityDamageEvent source) { @Override public boolean onUpdate(int currentTick) { - if (closed) { return false; } - this.timing.startTiming(); - int tickDiff = currentTick - lastUpdate; if (tickDiff <= 0 && !justCreated) { return true; @@ -133,26 +156,24 @@ public boolean onUpdate(int currentTick) { motionY *= 1 - getDrag(); motionZ *= friction; - Vector3 pos = (new Vector3(x - 0.5, y, z - 0.5)).round(); + Vector3 pos = new Vector3(x - 0.5, y, z - 0.5).round(); if (onGround) { close(); Block block = level.getBlock(pos); - - Vector3 floorPos = (new Vector3(x - 0.5, y, z - 0.5)).floor(); - Block floorBlock = this.level.getBlock(floorPos); + Block floorBlock = this.level.getBlock(pos); if (this.getBlock() == Block.SNOW_LAYER && floorBlock.getId() == Block.SNOW_LAYER && (floorBlock.getDamage() & 0x7) != 0x7) { int mergedHeight = (floorBlock.getDamage() & 0x7) + 1 + (this.getDamage() & 0x7) + 1; if (mergedHeight > 8) { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, 0x7)); this.server.getPluginManager().callEvent(event); if (!event.isCancelled()) { - this.level.setBlock(floorPos, event.getTo(), true); + this.level.setBlock(pos, event.getTo(), true); - Vector3 abovePos = floorPos.up(); + Vector3 abovePos = pos.getSideVec(BlockFace.UP); Block aboveBlock = this.level.getBlock(abovePos); if (aboveBlock.getId() == Block.AIR) { - EntityBlockChangeEvent event2 = new EntityBlockChangeEvent(this, aboveBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 8 - 1)); + EntityBlockChangeEvent event2 = new EntityBlockChangeEvent(this, aboveBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 9)); // -8-1 this.server.getPluginManager().callEvent(event2); if (!event2.isCancelled()) { this.level.setBlock(abovePos, event2.getTo(), true); @@ -163,21 +184,29 @@ public boolean onUpdate(int currentTick) { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 1)); this.server.getPluginManager().callEvent(event); if (!event.isCancelled()) { - this.level.setBlock(floorPos, event.getTo(), true); + this.level.setBlock(pos, event.getTo(), true); } } - } else if (block.getId() > 0 && block.isTransparent() && !block.canBeReplaced() || this.getBlock() == Block.SNOW_LAYER && block instanceof BlockLiquid) { + } else if ((block.isTransparent() && !block.canBeReplaced() || this.getBlock() == Block.SNOW_LAYER && block instanceof BlockLiquid)) { if (this.getBlock() != Block.SNOW_LAYER ? this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS) : this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { - getLevel().dropItem(this, Block.get(this.getBlock(), this.getDamage()).toItem()); + getLevel().dropItem(this, Item.get(this.blockId, this.damage, 1)); } } else { - EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, block, Block.get(getBlock(), getDamage())); + EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, block, Block.get(blockId, damage)); server.getPluginManager().callEvent(event); if (!event.isCancelled()) { - getLevel().setBlock(pos, event.getTo(), true); + int blockId = event.getTo().getId(); + if (blockId != Item.POINTED_DRIPSTONE) { + getLevel().setBlock(pos, event.getTo(), true, true); + getLevel().scheduleUpdate(getLevel().getBlock(pos), 1); + } - if (event.getTo().getId() == Item.ANVIL) { - getLevel().addLevelEvent(block, LevelEventPacket.EVENT_SOUND_ANVIL_FALL); + if (blockId == Item.ANVIL || blockId == Item.POINTED_DRIPSTONE) { + if (blockId == Item.ANVIL) { + this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_ANVIL_FALL); + } else { + this.getLevel().dropItem(this, Block.get(blockId, event.getTo().getDamage()).toItem()); + } Entity[] e = level.getCollidingEntities(this.getBoundingBox(), this); for (Entity entity : e) { @@ -194,8 +223,6 @@ public boolean onUpdate(int currentTick) { updateMovement(); } - this.timing.stopTiming(); - return hasUpdate || !onGround || Math.abs(motionX) > 0.00001 || Math.abs(motionY) > 0.00001 || Math.abs(motionZ) > 0.00001; } @@ -229,4 +256,4 @@ public void resetFallDistance() { this.highestPosition = this.y; } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/entity/item/EntityFirework.java b/src/main/java/cn/nukkit/entity/item/EntityFirework.java index 88a9aa74733..afad20bb0a7 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFirework.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFirework.java @@ -1,22 +1,21 @@ package cn.nukkit.entity.item; -import cn.nukkit.Server; import cn.nukkit.entity.Entity; -import cn.nukkit.entity.data.LongEntityData; import cn.nukkit.entity.data.NBTEntityData; -import cn.nukkit.entity.data.Vector3fEntityData; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; +import cn.nukkit.event.entity.EntityExplosionPrimeEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemFirework; +import cn.nukkit.level.Explosion; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.Vector3f; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.nbt.tag.Tag; import cn.nukkit.network.protocol.EntityEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; -import java.util.Random; import java.util.concurrent.ThreadLocalRandom; /** @@ -26,30 +25,35 @@ public class EntityFirework extends Entity { public static final int NETWORK_ID = 72; - private int fireworkAge; private int lifetime; private Item firework; public EntityFirework(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); + } + + @Override + public void initEntity() { + super.initEntity(); - this.fireworkAge = 0; - Random rand = ThreadLocalRandom.current(); - this.lifetime = 30 + rand.nextInt(12); + ThreadLocalRandom rand = ThreadLocalRandom.current(); this.motionX = rand.nextGaussian() * 0.001D; this.motionZ = rand.nextGaussian() * 0.001D; this.motionY = 0.05D; - if (nbt.contains("FireworkItem")) { - firework = NBTIO.getItemHelper(nbt.getCompound("FireworkItem")); - } else { - firework = new ItemFirework(); + if (namedTag.contains("FireworkItem")) { + this.setFirework(NBTIO.getItemHelper(namedTag.getCompound("FireworkItem"))); } + } + + @Override + public void saveNBT() { + super.saveNBT(); - this.setDataProperty(new NBTEntityData(Entity.DATA_DISPLAY_ITEM, firework.getNamedTag())); - this.setDataProperty(new Vector3fEntityData(Entity.DATA_DISPLAY_OFFSET, new Vector3f(0, 1, 0))); - this.setDataProperty(new LongEntityData(Entity.DATA_HAS_DISPLAY, -1)); + if (this.firework != null) { + this.namedTag.putCompound("FireworkItem", NBTIO.putItemHelper(this.firework)); + } } @Override @@ -61,6 +65,9 @@ public int getNetworkId() { public boolean onUpdate(int currentTick) { if (this.closed) { return false; + } else if (this.age > this.lifetime) { + this.close(); + return false; } int tickDiff = currentTick - this.lastUpdate; @@ -71,51 +78,60 @@ public boolean onUpdate(int currentTick) { this.lastUpdate = currentTick; - this.timing.startTiming(); - - boolean hasUpdate = this.entityBaseTick(tickDiff); if (this.isAlive()) { - this.motionX *= 1.15D; this.motionZ *= 1.15D; this.motionY += 0.04D; + this.move(this.motionX, this.motionY, this.motionZ); this.updateMovement(); - float f = (float) Math.sqrt(this.motionX * this.motionX + this.motionZ * this.motionZ); - this.yaw = (float) (Math.atan2(this.motionX, this.motionZ) * (180D / Math.PI)); - - this.pitch = (float) (Math.atan2(this.motionY, f) * (180D / Math.PI)); - + this.yaw = (float) (Math.atan2(this.motionX, this.motionZ) * (57.29577951308232)); + this.pitch = (float) (Math.atan2(this.motionY, f) * (57.29577951308232)); - if (this.fireworkAge == 0) { + if (this.age == 0) { this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_LAUNCH); } - this.fireworkAge++; - - hasUpdate = true; - if (this.fireworkAge >= this.lifetime) { + if (this.age >= this.lifetime) { EntityEventPacket pk = new EntityEventPacket(); - pk.data = 0; pk.event = EntityEventPacket.FIREWORK_EXPLOSION; pk.eid = this.getId(); + this.level.addChunkPacket(this.getChunkX(), this.getChunkZ(), pk); + + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_LARGE_BLAST, -1, NETWORK_ID); + + if (this.firework != null) { + Tag nbt = this.firework.getNamedTag(); + if (nbt != null) { + nbt = ((CompoundTag) nbt).get("Fireworks"); + if (nbt instanceof CompoundTag) { + nbt = ((CompoundTag) nbt).get("Explosions"); + if (nbt instanceof ListTag) { + if (!((ListTag) nbt).getAll().isEmpty()) { + EntityExplosionPrimeEvent ev = new EntityExplosionPrimeEvent(this, 2.5); + ev.setBlockBreaking(false); + server.getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { + Explosion explosion = new Explosion(this, ev.getForce(), this); + explosion.explodeEntity(); + } + } + } + } + } + } + + this.kill(); // Using close() here would remove the firework before the explosion is displayed - level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_LARGE_BLAST, -1, NETWORK_ID); - - Server.broadcastPacket(getViewers().values(), pk); - - this.kill(); hasUpdate = true; } } - this.timing.stopTiming(); - return hasUpdate || !this.onGround || Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionY) > 0.00001 || Math.abs(this.motionZ) > 0.00001; } @@ -130,7 +146,11 @@ public boolean attack(EntityDamageEvent source) { public void setFirework(Item item) { this.firework = item; - this.setDataProperty(new NBTEntityData(Entity.DATA_DISPLAY_ITEM, item.getNamedTag())); + this.setDataProperty(new NBTEntityData(Entity.DATA_DISPLAY_ITEM, this.firework)); + + int level = Math.max(1, this.firework instanceof ItemFirework ? ((ItemFirework) this.firework).getFlight() : 1); // Level 1 minimum + ThreadLocalRandom rand = ThreadLocalRandom.current(); + this.lifetime = 10 * (level + 1) + rand.nextInt(5) + rand.nextInt(6); // Wiki } @Override @@ -142,4 +162,4 @@ public float getWidth() { public float getHeight() { return 0.25f; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java b/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java index de9f2ab94fa..665f7d0e7c0 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFishingHook.java @@ -13,6 +13,9 @@ import cn.nukkit.event.entity.ProjectileHitEvent; import cn.nukkit.event.player.PlayerFishEvent; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemBookEnchanted; +import cn.nukkit.item.ItemFishingRod; +import cn.nukkit.item.ItemTool; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.item.randomitem.Fishing; import cn.nukkit.level.MovingObjectPosition; @@ -25,295 +28,324 @@ import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.protocol.EntityEventPacket; +import cn.nukkit.utils.Utils; import java.util.Collection; -import java.util.Random; import java.util.concurrent.ThreadLocalRandom; -/** - * Created by PetteriM1 - */ public class EntityFishingHook extends EntityProjectile { - public static final int NETWORK_ID = 77; - - public int waitChance = 120; - public int waitTimer = 240; - public boolean attracted = false; - public int attractTimer = 0; - public boolean caught = false; - public int caughtTimer = 0; - public boolean canCollide = true; - - public Vector3 fish = null; - - public Item rod = null; - - public EntityFishingHook(FullChunk chunk, CompoundTag nbt) { - this(chunk, nbt, null); - } - - public EntityFishingHook(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { - super(chunk, nbt, shootingEntity); - } - - @Override - protected void initEntity() { - super.initEntity(); - if (this.age > 0) { - this.close(); - } - } - - @Override - public int getNetworkId() { - return NETWORK_ID; - } - - @Override - public float getWidth() { - return 0.2f; - } - - @Override - public float getLength() { - return 0.2f; - } - - @Override - public float getHeight() { - return 0.2f; - } - - @Override - public float getGravity() { - return 0.07f; - } - - @Override - public float getDrag() { - return 0.05f; - } - - @Override - public boolean canCollide() { - return this.canCollide; - } - - @Override - public boolean onUpdate(int currentTick) { - boolean hasUpdate = super.onUpdate(currentTick); - - long target = this.getDataPropertyLong(DATA_TARGET_EID); - if (target != 0) { - Entity ent = level.getEntity(target); - if (ent == null || !ent.isAlive()) { - this.setTarget(0); - } else { - this.setPosition(new Vector3(ent.x, ent.y + (getHeight() * 0.75f), ent.z)); - } - hasUpdate = true; - } - - if (hasUpdate) { - return false; - } - - boolean inWater = this.isInsideOfWater(); - if (inWater) { - this.motionX = 0; - this.motionY -= getGravity() * -0.04; - this.motionZ = 0; - hasUpdate = true; - } else if (this.isCollided && this.keepMovement) { - this.motionX = 0; - this.motionY = 0; - this.motionZ = 0; - this.keepMovement = false; - hasUpdate = true; - } - - Random random = ThreadLocalRandom.current(); - - if (inWater) { - if (this.waitTimer == 240) { - this.waitTimer = this.waitChance << 1; - } else if (this.waitTimer == 360) { - this.waitTimer = this.waitChance * 3; - } - if (!this.attracted) { - if (this.waitTimer > 0) { - --this.waitTimer; - } - if (this.waitTimer == 0) { - if (random.nextInt(100) < 90) { - this.attractTimer = (random.nextInt(40) + 20); - this.spawnFish(); - this.caught = false; - this.attracted = true; - } else { - this.waitTimer = this.waitChance; - } - } - } else if (!this.caught) { - if (this.attractFish()) { - this.caughtTimer = (random.nextInt(20) + 30); - this.fishBites(); - this.caught = true; - } - } else { - if (this.caughtTimer > 0) { - --this.caughtTimer; - } - if (this.caughtTimer == 0) { - this.attracted = false; - this.caught = false; - this.waitTimer = this.waitChance * 3; - } - } - } - - return hasUpdate; - } - - public int getWaterHeight() { - for (int y = this.getFloorY(); y < 256; y++) { - int id = this.level.getBlockIdAt(this.getFloorX(), y, this.getFloorZ()); - if (id == Block.AIR) { - return y; - } - } - return this.getFloorY(); - } - - public void fishBites() { - Collection viewers = this.getViewers().values(); - - EntityEventPacket pk = new EntityEventPacket(); - pk.eid = this.getId(); - pk.event = EntityEventPacket.FISH_HOOK_HOOK; - Server.broadcastPacket(viewers, pk); - - EntityEventPacket bubblePk = new EntityEventPacket(); - bubblePk.eid = this.getId(); - bubblePk.event = EntityEventPacket.FISH_HOOK_BUBBLE; - Server.broadcastPacket(viewers, bubblePk); - - EntityEventPacket teasePk = new EntityEventPacket(); - teasePk.eid = this.getId(); - teasePk.event = EntityEventPacket.FISH_HOOK_TEASE; - Server.broadcastPacket(viewers, teasePk); - - Random random = ThreadLocalRandom.current(); - for (int i = 0; i < 5; i++) { - this.level.addParticle(new BubbleParticle(this.setComponents( - this.x + random.nextDouble() * 0.5 - 0.25, - this.getWaterHeight(), - this.z + random.nextDouble() * 0.5 - 0.25 - ))); - } - } - - public void spawnFish() { - Random random = ThreadLocalRandom.current(); - this.fish = new Vector3( - this.x + (random.nextDouble() * 1.2 + 1) * (random.nextBoolean() ? -1 : 1), - this.getWaterHeight(), - this.z + (random.nextDouble() * 1.2 + 1) * (random.nextBoolean() ? -1 : 1) - ); - } - - public boolean attractFish() { - double multiply = 0.1; - this.fish.setComponents( - this.fish.x + (this.x - this.fish.x) * multiply, - this.fish.y, - this.fish.z + (this.z - this.fish.z) * multiply - ); - if (ThreadLocalRandom.current().nextInt(100) < 85) { - this.level.addParticle(new WaterParticle(this.fish)); - } - double dist = Math.abs(Math.sqrt(this.x * this.x + this.z * this.z) - Math.sqrt(this.fish.x * this.fish.x + this.fish.z * this.fish.z)); - return dist < 0.15; - } - - public void reelLine() { - if (this.shootingEntity instanceof Player && this.caught) { - Player player = (Player) this.shootingEntity; - Item item = Fishing.getFishingResult(this.rod); - int experience = ThreadLocalRandom.current().nextInt(3) + 1; - Vector3 motion = player.subtract(this).multiply(0.1); - motion.y += Math.sqrt(player.distance(this)) * 0.08; - - PlayerFishEvent event = new PlayerFishEvent(player, this, item, experience, motion); - this.getServer().getPluginManager().callEvent(event); - - if (!event.isCancelled()) { - EntityItem itemEntity = new EntityItem( - this.level.getChunk((int) this.x >> 4, (int) this.z >> 4, true), - Entity.getDefaultNBT(new Vector3(this.x, this.getWaterHeight(), this.z), event.getMotion(), ThreadLocalRandom.current().nextFloat() * 360, 0).putShort("Health", 5).putCompound("Item", NBTIO.putItemHelper(event.getLoot())).putShort("PickupDelay", 1)); - - itemEntity.setOwner(player.getName()); - itemEntity.spawnToAll(); - - player.getLevel().dropExpOrb(player, event.getExperience()); - } - } - this.close(); - } - - @Override - protected DataPacket createAddEntityPacket() { - AddEntityPacket pk = new AddEntityPacket(); - pk.entityRuntimeId = this.getId(); - pk.entityUniqueId = this.getId(); - pk.type = NETWORK_ID; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.speedX = (float) this.motionX; - pk.speedY = (float) this.motionY; - pk.speedZ = (float) this.motionZ; - pk.yaw = (float) this.yaw; - pk.pitch = (float) this.pitch; - - long ownerId = -1; - if (this.shootingEntity != null) { - ownerId = this.shootingEntity.getId(); - } - pk.metadata = this.dataProperties.putLong(DATA_OWNER_EID, ownerId); - return pk; - } - - @Override - public void onCollideWithEntity(Entity entity) { - this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromEntity(entity))); - float damage = this.getResultDamage(); - - EntityDamageEvent ev; - if (this.shootingEntity == null) { - ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage); - } else { - ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage); - } - - if (entity.attack(ev)) { - this.setTarget(entity.getId()); - } - } - - public void checkLure() { - if (rod != null) { - Enchantment ench = rod.getEnchantment(Enchantment.ID_LURE); - if (ench != null) { - this.waitChance = 120 - (25 * ench.getLevel()); - } - } - } - - public void setTarget(long eid) { - this.setDataProperty(new LongEntityData(DATA_TARGET_EID, eid)); - this.canCollide = eid == 0; - } + public static final int NETWORK_ID = 77; + + public int waitChance = 120; + public int waitTimer = 240; + public boolean attracted = false; + public int attractTimer = 0; + public boolean caught = false; + public int caughtTimer = 0; + public boolean canCollide = true; + public Entity caughtEntity = null; + private long target = 0; + public Vector3 fish = null; + public Item rod = null; + + public EntityFishingHook(FullChunk chunk, CompoundTag nbt) { + this(chunk, nbt, null); + } + + public EntityFishingHook(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { + super(chunk, nbt, shootingEntity); + } + + @Override + protected void initEntity() { + super.initEntity(); + if (this.age > 0) { + this.close(); + } + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } + + @Override + public float getWidth() { + return 0.2f; + } + + @Override + public float getLength() { + return 0.2f; + } + + @Override + public float getHeight() { + return 0.2f; + } + + @Override + public float getGravity() { + return 0.07f; + } + + @Override + public float getDrag() { + return 0.05f; + } + + @Override + public boolean onUpdate(int currentTick) { + if (this.target != 0) { + Entity ent = this.level.getEntity(this.target); + if (ent == null || !ent.isAlive()) { + caughtEntity = null; + this.setTarget(0); + } else { + this.setPosition(ent.add(0, getHeight() * 0.75f, 0)); + } + return false; + } + + boolean hasUpdate = super.onUpdate(currentTick); + + boolean inWater = this.isInsideOfWater(); + if (inWater) { + this.motionX = 0; + this.motionY -= getGravity() * -0.04; + this.motionZ = 0; + hasUpdate = true; + } else if (this.isCollided && this.keepMovement) { + this.motionX = 0; + this.motionY = 0; + this.motionZ = 0; + this.keepMovement = false; + hasUpdate = true; + } + + if (inWater) { + if (this.waitTimer == 240) { + this.waitTimer = this.waitChance << 1; + } else if (this.waitTimer == 360) { + this.waitTimer = this.waitChance * 3; + } + if (!this.attracted) { + if (this.waitTimer > 0) { + --this.waitTimer; + } + if (this.waitTimer == 0) { + if (Utils.random.nextInt(100) < 90) { + this.attractTimer = (Utils.random.nextInt(40) + 20); + this.spawnFish(); + this.caught = false; + this.attracted = true; + } else { + this.waitTimer = this.waitChance; + } + } + } else if (!this.caught) { + if (this.attractFish()) { + this.caughtTimer = (Utils.random.nextInt(20) + 30); + this.fishBites(); + this.caught = true; + } + } else { + if (this.caughtTimer > 0) { + --this.caughtTimer; + } + if (this.caughtTimer == 0) { + this.attracted = false; + this.caught = false; + this.waitTimer = this.waitChance * 3; + } + } + } + + return hasUpdate; + } + + public int getWaterHeight() { + for (int y = this.getFloorY(); y < level.getMaxBlockY(); y++) { + int id = this.level.getBlockIdAt(chunk, this.getFloorX(), y, this.getFloorZ()); + if (id == Block.AIR) { + return y; + } + } + return this.getFloorY(); + } + + public void fishBites() { + Collection viewers = this.getViewers().values(); + + EntityEventPacket pk = new EntityEventPacket(); + pk.eid = this.getId(); + pk.event = EntityEventPacket.FISH_HOOK_HOOK; + Server.broadcastPacket(viewers, pk); + + EntityEventPacket bubblePk = new EntityEventPacket(); + bubblePk.eid = this.getId(); + bubblePk.event = EntityEventPacket.FISH_HOOK_BUBBLE; + Server.broadcastPacket(viewers, bubblePk); + + EntityEventPacket teasePk = new EntityEventPacket(); + teasePk.eid = this.getId(); + teasePk.event = EntityEventPacket.FISH_HOOK_TEASE; + Server.broadcastPacket(viewers, teasePk); + + this.level.addParticle(new BubbleParticle(this.setComponents( + this.x + Utils.random.nextDouble() * 0.5 - 0.25, + this.getWaterHeight(), + this.z + Utils.random.nextDouble() * 0.5 - 0.25 + )), null, 5); + } + + public void spawnFish() { + this.fish = new Vector3( + this.x + (Utils.random.nextDouble() * 1.2 + 1) * (Utils.rand() ? -1 : 1), + this.getWaterHeight(), + this.z + (Utils.random.nextDouble() * 1.2 + 1) * (Utils.rand() ? -1 : 1) + ); + } + + public boolean attractFish() { + double multiply = 0.1; + this.fish.setComponents( + this.fish.x + (this.x - this.fish.x) * multiply, + this.fish.y, + this.fish.z + (this.z - this.fish.z) * multiply + ); + if (Utils.random.nextInt(100) < 85) { + this.level.addParticle(new WaterParticle(this.fish)); + } + double dist = Math.abs(Math.sqrt(this.x * this.x + this.z * this.z) - Math.sqrt(this.fish.x * this.fish.x + this.fish.z * this.fish.z)); + return dist < 0.15; + } + + private static final int[] rodEnchantments = {Enchantment.ID_LURE, Enchantment.ID_FORTUNE_FISHING, Enchantment.ID_DURABILITY, Enchantment.ID_MENDING, Enchantment.ID_VANISHING_CURSE}; + + public void reelLine() { + if (this.shootingEntity instanceof Player) { + if (this.caught) { + Player player = (Player) this.shootingEntity; + Item item = Fishing.getFishingResult(this.rod); + if (item instanceof ItemTool) { + item.setDamage(Utils.rand(1, item.getMaxDurability() - 2)); + } + // TODO: improve loot selectors so this hack won't be needed + if (item instanceof ItemBookEnchanted) { + if (!item.hasEnchantments()) { + item = item.clone(); + Enchantment enchantment = Enchantment.getEnchantment(Utils.rand(0, 35)); + if (Utils.random.nextDouble() < 0.3) { + enchantment.setLevel(Utils.rand(1, enchantment.getMaxLevel())); + } + item.addEnchantment(enchantment); + } + } else if (item instanceof ItemFishingRod) { + if (Utils.rand(1, 3) == 2 && !item.hasEnchantments()) { + item = item.clone(); + Enchantment enchantment = Enchantment.getEnchantment(rodEnchantments[Utils.rand(0, 4)]); + if (Utils.random.nextDouble() < 0.3) { + enchantment.setLevel(Utils.rand(1, enchantment.getMaxLevel())); + } + item.addEnchantment(enchantment); + } + } + int experience = Utils.random.nextInt(3) + 1; + Vector3 motion = player.subtract(this).multiply(0.1); + motion.y += Math.sqrt(player.distance(this)) * 0.05; + + PlayerFishEvent event = new PlayerFishEvent(player, this, item, experience, motion); + this.getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + EntityItem itemEntity = new EntityItem( + this.level.getChunk(this.getChunkX(), this.getChunkZ(), true), + Entity.getDefaultNBT(new Vector3(this.x, this.getWaterHeight(), this.z), event.getMotion(), ThreadLocalRandom.current().nextFloat() * 360, 0).putShort("Health", 5).putCompound("Item", NBTIO.putItemHelper(event.getLoot())).putShort("PickupDelay", 1)); + + itemEntity.setOwner(player.getName()); + itemEntity.spawnToAll(); + + player.getLevel().dropExpOrb(player, event.getExperience()); + } + } + if (this.caughtEntity != null) { + Vector3 motion = this.shootingEntity.subtract(this).multiply(0.1); + motion.y += Math.sqrt(this.shootingEntity.distance(this)) * 0.08; + this.caughtEntity.setMotion(motion); + } + } + this.close(); + } + + @Override + protected DataPacket createAddEntityPacket() { + AddEntityPacket pk = new AddEntityPacket(); + pk.entityRuntimeId = this.getId(); + pk.entityUniqueId = this.getId(); + pk.type = NETWORK_ID; + pk.x = (float) this.x; + pk.y = (float) this.y; + pk.z = (float) this.z; + pk.speedX = (float) this.motionX; + pk.speedY = (float) this.motionY; + pk.speedZ = (float) this.motionZ; + pk.yaw = (float) this.yaw; + pk.pitch = (float) this.pitch; + + long ownerId = -1; + if (this.shootingEntity != null) { + ownerId = this.shootingEntity.getId(); + } + pk.metadata = this.dataProperties.putLong(DATA_OWNER_EID, ownerId).clone(); + return pk; + } + + @Override + public boolean canCollide() { + return this.canCollide; + } + + @Override + public void onCollideWithEntity(Entity entity) { + if (entity == this.shootingEntity) { + return; + } + + this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromEntity(entity))); + float damage = this.getResultDamage(); + + EntityDamageEvent ev; + if (this.shootingEntity == null) { + ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage, knockBack); + } else { + ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage, knockBack); + } + + if (entity.attack(ev)) { + this.caughtEntity = entity; + this.setTarget(entity.getId()); + } + } + + public void checkLure() { + if (rod != null) { + Enchantment ench = rod.getEnchantment(Enchantment.ID_LURE); + if (ench != null) { + this.waitChance = 120 - (25 * ench.getLevel()); + } + } + } + + public void setTarget(long eid) { + this.target = eid; + this.setDataProperty(new LongEntityData(DATA_TARGET_EID, eid)); + this.canCollide = eid == 0; + } + + public long getTarget() { + return this.target; + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityItem.java b/src/main/java/cn/nukkit/entity/item/EntityItem.java index 86d7f5257c4..c85c8b0f77a 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityItem.java +++ b/src/main/java/cn/nukkit/entity/item/EntityItem.java @@ -1,9 +1,11 @@ package cn.nukkit.entity.item; +import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.entity.Entity; +import cn.nukkit.event.entity.EntityDamageByBlockEvent; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.ItemDespawnEvent; @@ -24,9 +26,19 @@ public class EntityItem extends Entity { public static final int NETWORK_ID = 64; + protected String owner; + protected String thrower; + protected Item item; + protected int pickupDelay; + protected boolean floatsInLava; + public Player droppedBy; + + private boolean deadOnceAndForAll; public EntityItem(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); + + this.updateMode = 5; } @Override @@ -34,14 +46,6 @@ public int getNetworkId() { return NETWORK_ID; } - protected String owner; - protected String thrower; - - protected Item item; - - protected int pickupDelay; - protected boolean floatsInLava; - @Override public float getWidth() { return 0.25f; @@ -79,10 +83,15 @@ public boolean canCollide() { @Override protected void initEntity() { + this.setMaxHealth(5); + super.initEntity(); - this.setMaxHealth(5); - this.setHealth(this.namedTag.getShort("Health")); + if (namedTag.contains("Health")) { + this.setHealth(namedTag.getShort("Health")); + } else { + this.setHealth(5); + } if (this.namedTag.contains("Age")) { this.age = this.namedTag.getShort("Age"); @@ -106,7 +115,10 @@ protected void initEntity() { } this.item = NBTIO.getItemHelper(this.namedTag.getCompound("Item")); - this.setDataFlag(DATA_FLAGS, DATA_FLAG_GRAVITY, true); + + if (this.item == null) { + throw new NullPointerException("EntityItem with null item"); + } int id = this.item.getId(); if (id >= Item.NETHERITE_INGOT && id <= Item.NETHERITE_SCRAP) { @@ -123,9 +135,10 @@ public boolean attack(EntityDamageEvent source) { if ((cause == DamageCause.VOID || cause == DamageCause.CONTACT || cause == DamageCause.FIRE_TICK || (cause == DamageCause.ENTITY_EXPLOSION || cause == DamageCause.BLOCK_EXPLOSION) && !this.isInsideOfWater() && (this.item == null || this.item.getId() != Item.NETHER_STAR)) && super.attack(source)) { - if (this.item == null || this.isAlive()) { + if (this.item == null || this.isAlive() || this.deadOnceAndForAll) { return true; } + this.deadOnceAndForAll = true; int id = this.item.getId(); if (id != Item.SHULKER_BOX && id != Item.UNDYED_SHULKER_BOX) { return true; @@ -155,49 +168,22 @@ public boolean onUpdate(int currentTick) { } int tickDiff = currentTick - this.lastUpdate; - if (tickDiff <= 0 && !this.justCreated) { return true; } - this.lastUpdate = currentTick; - - this.timing.startTiming(); - - if (this.age % 60 == 0 && this.onGround && this.getItem() != null && this.isAlive()) { - if (this.getItem().getCount() < this.getItem().getMaxStackSize()) { - for (Entity entity : this.getLevel().getNearbyEntities(getBoundingBox().grow(1, 1, 1), this, false)) { - if (entity instanceof EntityItem) { - if (!entity.isAlive()) { - continue; - } - Item closeItem = ((EntityItem) entity).getItem(); - if (!closeItem.equals(getItem(), true, true)) { - continue; - } - if (!entity.isOnGround()) { - continue; - } - int newAmount = this.getItem().getCount() + closeItem.getCount(); - if (newAmount > this.getItem().getMaxStackSize()) { - continue; - } - entity.close(); - this.getItem().setCount(newAmount); - EntityEventPacket packet = new EntityEventPacket(); - packet.eid = getId(); - packet.data = newAmount; - packet.event = EntityEventPacket.MERGE_ITEMS; - Server.broadcastPacket(this.getViewers().values(), packet); - } - } - } - } + this.minimalEntityTick(currentTick, tickDiff); - boolean hasUpdate = this.entityBaseTick(tickDiff); + if (this.age > 6000) { + ItemDespawnEvent ev = new ItemDespawnEvent(this); + this.server.getPluginManager().callEvent(ev); - if (!this.fireProof && this.isInsideOfFire()) { - this.kill(); + if (ev.isCancelled()) { + this.age = 0; + } else { + this.close(); + return false; + } } if (this.isAlive()) { @@ -206,34 +192,111 @@ public boolean onUpdate(int currentTick) { if (this.pickupDelay < 0) { this.pickupDelay = 0; } - }/* else { // Done in Player#checkNearEntities - for (Entity entity : this.level.getNearbyEntities(this.boundingBox.grow(1, 0.5, 1), this)) { - if (entity instanceof Player) { - if (((Player) entity).pickupEntity(this, true)) { - return true; + } + + // updateMode % 3 or age % 20 basically means on every reduced update, don't use updateMode % 2 because that would include every update with default updateMode + if (this.onGround && this.item != null && (this.updateMode % 3 == 1 || this.age % 20 == 0)) { + if (this.item.getCount() < this.item.getMaxStackSize()) { + Entity[] e = this.getLevel().getNearbyEntities(getBoundingBox().grow(1, 1, 1), this); + + for (Entity entity : e) { + if (entity instanceof EntityItem) { + if (entity.closed || !entity.isAlive()) { + continue; + } + Item closeItem = ((EntityItem) entity).getItem(); + if (!closeItem.equals(item, true, true)) { + continue; + } + if (!entity.isOnGround()) { + continue; + } + int newAmount = this.item.getCount() + closeItem.getCount(); + if (newAmount > this.item.getMaxStackSize()) { + continue; + } + closeItem.setCount(0); + entity.close(); + this.item.setCount(newAmount); + EntityEventPacket packet = new EntityEventPacket(); + packet.eid = getId(); + packet.data = newAmount; + packet.event = EntityEventPacket.MERGE_ITEMS; + Server.broadcastPacket(this.getViewers().values(), packet); + } + if (this.item.getCount() >= this.item.getMaxStackSize()) { + break; } } } - }*/ + } - int bid = level.getBlock(this.getFloorX(), NukkitMath.floorDouble(this.y + 0.53), this.getFloorZ(), false).getId(); - if (bid == BlockID.WATER || bid == BlockID.STILL_WATER || - (this.floatsInLava && (bid == Block.LAVA || bid == Block.STILL_LAVA))) { + int blockId; + if (this.isOnGround()) { + this.motionY = 0; + } else if (Block.hasWater((blockId = level.getBlockIdAt(this.chunk, this.getFloorX(), NukkitMath.floorDouble(this.y + 0.53), this.getFloorZ()))) || + (this.floatsInLava && (blockId == Block.LAVA || blockId == Block.STILL_LAVA))) { this.motionY = this.getGravity() / 2; - } else if (!this.isOnGround()) { + } else { this.motionY -= this.getGravity(); } - if (this.checkObstruction(this.x, this.y, this.z)) { - hasUpdate = true; + this.checkBlockCollision(); + + this.checkObstruction(this.x, this.y, this.z); + + // Check nearby blocks changed + if (this.updateMode % 3 == 1) { + this.updateMode = 5; + + // Force check for nearby blocks changed even if not moving + if (this.motionY == 0) { + this.motionY = 0.00001; + } + + if (!this.fireProof) { // Fix missed collisions + Block block = level.getBlock(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ(), false); + int bid = block.getId(); + if (bid == BlockID.FIRE || bid == BlockID.SOUL_FIRE || bid == BlockID.LAVA || bid == BlockID.STILL_LAVA) { + this.attack(new EntityDamageByBlockEvent(block, this, DamageCause.FIRE, 1)); + } else if (bid == BlockID.CACTUS) { + this.attack(new EntityDamageByBlockEvent(block, this, DamageCause.CONTACT, 1)); + return true; + } + } + } + + // Flowing water + if (this.move(this.motionX, this.motionY, this.motionZ) && !(this.motionX == 0 && this.motionZ == 0)) { + this.collisionBlocks = null; } - this.move(this.motionX, this.motionY, this.motionZ); + if (this.y < (this.getLevel().getMinBlockY() - 16)) { + this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); + } - double friction = 1 - this.getDrag(); + if (this.fireTicks > 0) { + if (this.fireProof) { + this.fireTicks -= tickDiff << 2; + if (this.fireTicks < 0) { + this.fireTicks = 0; + } + } else { + if (((this.fireTicks % 20) == 0 || tickDiff > 20)) { + this.attack(new EntityDamageEvent(this, DamageCause.FIRE_TICK, 1)); + } + this.fireTicks -= tickDiff; + } + if (this.fireTicks <= 0) { + this.extinguish(); + } else if (!this.fireProof) { + this.setDataFlag(DATA_FLAGS, DATA_FLAG_ONFIRE, true); + } + } + double friction = 1 - this.getDrag(); if (this.onGround && (Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionZ) > 0.00001)) { - friction *= this.getLevel().getBlock(this.temporalVector.setComponents((int) Math.floor(this.x), (int) Math.floor(this.y - 1), (int) Math.floor(this.z) - 1)).getFrictionFactor(); + friction *= this.getLevel().getBlock(this.chunk, getFloorX(), getFloorY() - 1, getFloorZ(), false).getFrictionFactor(); } this.motionX *= friction; @@ -241,26 +304,18 @@ public boolean onUpdate(int currentTick) { this.motionZ *= friction; if (this.onGround) { - this.motionY *= -0.5; + this.motionY = 0; } this.updateMovement(); - - if (this.age > 6000) { - ItemDespawnEvent ev = new ItemDespawnEvent(this); - this.server.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - this.age = 0; - } else { - this.kill(); - hasUpdate = true; - } - } } - this.timing.stopTiming(); + if (!this.isAlive()) { + this.close(); + return false; + } - return hasUpdate || !this.onGround || Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionY) > 0.00001 || Math.abs(this.motionZ) > 0.00001; + return !(this.motionX == 0 && this.motionY == 0 && this.motionZ == 0); } @Override @@ -283,7 +338,7 @@ public void saveNBT() { @Override public String getName() { - return this.hasCustomName() ? this.getNameTag() : (this.item == null ? "" : this.item.hasCustomName() ? this.item.getCustomName() : this.item.getName()); + return this.hasCustomName() ? this.getNameTag() : (this.item.hasCustomName() ? this.item.getCustomName() : this.item.getName()); } public Item getItem() { @@ -330,13 +385,8 @@ public DataPacket createAddEntityPacket() { addEntity.speedX = (float) this.motionX; addEntity.speedY = (float) this.motionY; addEntity.speedZ = (float) this.motionZ; - addEntity.metadata = this.dataProperties; - addEntity.item = this.getItem(); + addEntity.metadata = this.dataProperties.clone(); + addEntity.item = this.item; return addEntity; } - - @Override - public boolean doesTriggerPressurePlate() { - return true; - } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java index dfeabbcf52d..6f48541db7d 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartAbstract.java @@ -1,15 +1,11 @@ package cn.nukkit.entity.item; import cn.nukkit.Player; -import cn.nukkit.api.API; -import cn.nukkit.api.API.Definition; -import cn.nukkit.api.API.Usage; -import cn.nukkit.block.Block; -import cn.nukkit.block.BlockRail; -import cn.nukkit.block.BlockRailActivator; -import cn.nukkit.block.BlockRailPowered; +import cn.nukkit.block.*; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityHopper; import cn.nukkit.entity.Entity; -import cn.nukkit.entity.EntityHuman; +import cn.nukkit.entity.EntityControllable; import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.data.ByteEntityData; import cn.nukkit.entity.data.IntEntityData; @@ -17,19 +13,18 @@ import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.vehicle.VehicleMoveEvent; import cn.nukkit.event.vehicle.VehicleUpdateEvent; +import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemMinecart; import cn.nukkit.level.GameRule; import cn.nukkit.level.Location; import cn.nukkit.level.format.FullChunk; -import cn.nukkit.math.MathHelper; -import cn.nukkit.math.NukkitMath; -import cn.nukkit.math.Vector3; +import cn.nukkit.math.*; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.utils.MinecartType; import cn.nukkit.utils.Rail; import cn.nukkit.utils.Rail.Orientation; +import java.util.ArrayList; import java.util.Iterator; import java.util.Objects; @@ -40,9 +35,10 @@ * Minecart and Riding Project, * Package cn.nukkit.entity.item in project Nukkit. */ -public abstract class EntityMinecartAbstract extends EntityVehicle { +public abstract class EntityMinecartAbstract extends EntityVehicle implements EntityControllable { - private static final int[][][] matrix = new int[][][]{ + private String entityName; + private static final int[][][] matrix = { {{0, 0, -1}, {0, 0, 1}}, {{-1, 0, 0}, {1, 0, 0}}, {{-1, -1, 0}, {1, 0, 0}}, @@ -65,7 +61,6 @@ public abstract class EntityMinecartAbstract extends EntityVehicle { private double flyingY = 0.95; private double flyingZ = 0.95; private double maxSpeed = 0.4D; - private final boolean devs = false; // Avoid maintained features into production public abstract MinecartType getType(); @@ -73,9 +68,6 @@ public abstract class EntityMinecartAbstract extends EntityVehicle { public EntityMinecartAbstract(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); - - setMaxHealth(40); - setHealth(40); } @Override @@ -93,21 +85,37 @@ protected float getDrag() { return 0.1F; } + public void setName(String name) { + entityName = name; + } + + @Override + public String getName() { + return entityName; + } + @Override public float getBaseOffset() { return 0.35F; } + @Override + public boolean hasCustomName() { + return entityName != null; + } + @Override public boolean canDoInteraction() { - return passengers.isEmpty() && this.getDisplayBlock() == null; + return passengers.isEmpty() && this.blockInside == null; } @Override public void initEntity() { + this.setMaxHealth(40); super.initEntity(); - prepareDataProperty(); + this.setHealth(40); + //this.prepareDataProperty(); // TODO: DATA_DISPLAY_ITEM NBTEntityData } @Override @@ -117,21 +125,19 @@ public boolean onUpdate(int currentTick) { } if (!this.isAlive()) { - ++this.deadTicks; - if (this.deadTicks >= 10) { - this.despawnFromAll(); - this.close(); - } - return this.deadTicks < 10; + this.despawnFromAll(); + this.close(); + return false; } int tickDiff = currentTick - this.lastUpdate; - - if (tickDiff <= 0) { - return false; + if (tickDiff <= 0 && !this.justCreated) { + return true; } - this.lastUpdate = currentTick; + boolean firstTick = this.justCreated; + + this.minimalEntityTick(currentTick, tickDiff); if (isAlive()) { super.onUpdate(currentTick); @@ -145,7 +151,7 @@ public boolean onUpdate(int currentTick) { lastX = x; lastY = y; lastZ = z; - motionY -= 0.03999999910593033D; + motionY -= 0.04; int dx = MathHelper.floor(x); int dy = MathHelper.floor(y); int dz = MathHelper.floor(z); @@ -155,7 +161,7 @@ public boolean onUpdate(int currentTick) { --dy; } - Block block = level.getBlock(new Vector3(dx, dy, dz)); + Block block = level.getBlock(chunk, dx, dy, dz, true); // Ensure that the block is a rail if (Rail.isRailBlock(block)) { @@ -196,9 +202,11 @@ public boolean onUpdate(int currentTick) { } // Collisions - for (cn.nukkit.entity.Entity entity : level.getNearbyEntities(boundingBox.grow(0.2D, 0, 0.2D), this)) { - if (!passengers.contains(entity) && entity instanceof EntityMinecartAbstract) { - entity.applyEntityCollision(this); + if (this instanceof InventoryHolder) { + for (cn.nukkit.entity.Entity entity : level.getNearbyEntities(boundingBox.grow(0.2D, 0, 0.2D), this)) { + if (entity instanceof EntityMinecartAbstract && !passengers.contains(entity)) { + entity.applyEntityCollision(this); + } } } @@ -216,8 +224,39 @@ public boolean onUpdate(int currentTick) { } } - // No need to onGround or Motion diff! This always have an update - return true; + if (this instanceof InventoryHolder) { + AxisAlignedBB pickupArea = new SimpleAxisAlignedBB(this.x, this.y - 1, this.z, this.x + 1, this.y, this.z + 1); + Block[] hopperPickupArray = this.level.getCollisionBlocks(this, pickupArea, false); + if (hopperPickupArray.length >= 1) { + Block hopper = hopperPickupArray[0]; + if (hopper instanceof BlockHopper) { + BlockEntity hopperBE = hopper.getLevel().getBlockEntityIfLoaded(hopper); + if (hopperBE instanceof BlockEntityHopper) { + ((BlockEntityHopper) hopperBE).setMinecartPickupInventory((InventoryHolder) this); + } + } + return true; + } + + if (!(this instanceof EntityMinecartHopper)) { + AxisAlignedBB pushArea = new SimpleAxisAlignedBB(this.x, this.y, this.z, this.x + 1, this.y + 2, this.z + 1); + Block[] hopperPushArray = this.level.getCollisionBlocks(this, pushArea, false); + if (hopperPushArray.length >= 1) { + Block hopper = hopperPushArray[0]; + if (hopper instanceof BlockHopper) { + BlockEntity hopperBE = hopper.getLevel().getBlockEntityIfLoaded(hopper); + if (hopperBE instanceof BlockEntityHopper) { + ((BlockEntityHopper) hopperBE).setMinecartPushInventory((InventoryHolder) this); + } + } + return true; + } + } + } + + this.updateMovement(); + + return firstTick || this.getRollingAmplitude() > 0 || !this.passengers.isEmpty() || !(this.motionX == 0 && this.motionY == 0 && this.motionZ == 0); } return false; @@ -247,15 +286,19 @@ public void dropItem() { return; } } - level.dropItem(this, new ItemMinecart()); + this.level.dropItem(this, Item.get(Item.MINECART)); } @Override public void kill() { + if (!this.isAlive()) { + return; + } + super.kill(); if (level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - dropItem(); + this.dropItem(); } } @@ -263,16 +306,18 @@ public void kill() { public void close() { super.close(); - for (cn.nukkit.entity.Entity entity : passengers) { - if (entity instanceof Player) { - entity.riding = null; - } + for (Entity passenger : new ArrayList<>(this.passengers)) { + dismountEntity(passenger); } } @Override public boolean onInteract(Player p, Item item, Vector3 clickedPos) { - if (!passengers.isEmpty() && isRideable()) { + if (!isRideable()) { + return false; + } + + if (!passengers.isEmpty()) { return false; } @@ -285,18 +330,7 @@ public boolean onInteract(Player p, Item item, Vector3 clickedPos) { @Override public void applyEntityCollision(cn.nukkit.entity.Entity entity) { - if (entity != riding && !(entity instanceof Player && ((Player) entity).isSpectator())) { - if (entity instanceof EntityLiving - && !(entity instanceof EntityHuman) - && motionX * motionX + motionZ * motionZ > 0.01D - && passengers.isEmpty() - && entity.riding == null - && blockInside == null) { - if (riding == null && devs) { - mountEntity(entity);// TODO: rewrite (weird riding) - } - } - + if (entity != riding && !(entity instanceof Player && ((Player) entity).getGamemode() == Player.SPECTATOR)) { double motiveX = entity.x - x; double motiveZ = entity.z - z; double square = motiveX * motiveX + motiveZ * motiveZ; @@ -385,8 +419,8 @@ protected void activate(int x, int y, int z, boolean flag) { private boolean hasUpdated = false; private void setFalling() { - motionX = NukkitMath.clamp(motionX, -getMaxSpeed(), getMaxSpeed()); - motionZ = NukkitMath.clamp(motionZ, -getMaxSpeed(), getMaxSpeed()); + motionX = NukkitMath.clamp(motionX, -maxSpeed, maxSpeed); + motionZ = NukkitMath.clamp(motionZ, -maxSpeed, maxSpeed); if (!hasUpdated) { for (cn.nukkit.entity.Entity linked : passengers) { @@ -523,7 +557,7 @@ private void processMovement(int dx, int dy, int dz, BlockRail block) { x = playerYawNeg + facing1 * expectedSpeed; z = playerYawPos + facing2 * expectedSpeed; - setPosition(new Vector3(x, y, z)); // Hehe, my minstake :3 + setPosition(new Vector3(x, y, z)); motX = motionX; motZ = motionZ; @@ -531,8 +565,8 @@ private void processMovement(int dx, int dy, int dz, BlockRail block) { motX *= 0.75D; motZ *= 0.75D; } - motX = NukkitMath.clamp(motX, -getMaxSpeed(), getMaxSpeed()); - motZ = NukkitMath.clamp(motZ, -getMaxSpeed(), getMaxSpeed()); + motX = NukkitMath.clamp(motX, -maxSpeed, maxSpeed); + motZ = NukkitMath.clamp(motZ, -maxSpeed, maxSpeed); move(motX, 0, motZ); if (facing[0][1] != 0 && MathHelper.floor(x) - dx == facing[0][0] && MathHelper.floor(z) - dz == facing[0][2]) { @@ -574,20 +608,19 @@ private void processMovement(int dx, int dy, int dz, BlockRail block) { motionX += motionX / newMovie * nextMovie; motionZ += motionZ / newMovie * nextMovie; } else if (block.getOrientation() == Orientation.STRAIGHT_NORTH_SOUTH) { - if (level.getBlock(new Vector3(dx - 1, dy, dz)).isNormalBlock()) { + if (level.getBlock(chunk, dx - 1, dy, dz, true).isNormalBlock()) { motionX = 0.02D; - } else if (level.getBlock(new Vector3(dx + 1, dy, dz)).isNormalBlock()) { - motionX = -0.02D; + } else if (level.getBlock(chunk, dx + 1, dy, dz, true).isNormalBlock()) { + motionX = -0.02; } } else if (block.getOrientation() == Orientation.STRAIGHT_EAST_WEST) { - if (level.getBlock(new Vector3(dx, dy, dz - 1)).isNormalBlock()) { + if (level.getBlock(chunk, dx, dy, dz - 1, true).isNormalBlock()) { motionZ = 0.02D; - } else if (level.getBlock(new Vector3(dx, dy, dz + 1)).isNormalBlock()) { - motionZ = -0.02D; + } else if (level.getBlock(chunk, dx, dy, dz + 1, true).isNormalBlock()) { + motionZ = -0.02; } } } - } private void applyDrag() { @@ -611,7 +644,7 @@ private Vector3 getNextRail(double dx, double dy, double dz) { --checkY; } - Block block = level.getBlock(new Vector3(checkX, checkY, checkZ)); + Block block = level.getBlock(chunk, checkX, checkY, checkZ, true); if (Rail.isRailBlock(block)) { int[][] facing = matrix[((BlockRail) block).getRealMeta()]; @@ -655,16 +688,21 @@ private Vector3 getNextRail(double dx, double dy, double dz) { } } + @Override + public void onPlayerInput(Player player, double strafe, double forward) { + this.setCurrentSpeed(forward); + } + /** * Used to multiply the minecart current speed * * @param speed The speed of the minecart that will be calculated */ public void setCurrentSpeed(double speed) { - currentSpeed = speed; + this.currentSpeed = speed; } - private void prepareDataProperty() { + /*private void prepareDataProperty() { setRollingAmplitude(0); setRollingDirection(1); if (namedTag.contains("CustomDisplayTile")) { @@ -687,17 +725,15 @@ private void prepareDataProperty() { setDataProperty(new IntEntityData(DATA_DISPLAY_ITEM, display)); setDataProperty(new IntEntityData(DATA_DISPLAY_OFFSET, 6)); } - } + }*/ private void saveEntityData() { - boolean hasDisplay = super.getDataPropertyByte(DATA_HAS_DISPLAY) == 1 - || blockInside != null; + boolean hasDisplay = super.getDataPropertyByte(DATA_HAS_DISPLAY) == 1 || blockInside != null; int display; int offSet; namedTag.putBoolean("CustomDisplayTile", hasDisplay); if (hasDisplay) { - display = blockInside.getId() - | blockInside.getDamage() << 16; + display = blockInside.getId() | blockInside.getDamage() << 16; offSet = getDataPropertyInt(DATA_DISPLAY_OFFSET); namedTag.putInt("DisplayTile", display); namedTag.putInt("DisplayOffset", offSet); @@ -710,7 +746,7 @@ private void saveEntityData() { * @param block The block that will changed. Set {@code null} for BlockAir * @return {@code true} if the block is normal block */ - public boolean setDisplayBlock(Block block){ + public boolean setDisplayBlock(Block block) { return setDisplayBlock(block, true); } @@ -721,9 +757,8 @@ public boolean setDisplayBlock(Block block){ * @param update Do update for the block. (This state changes if you want to show the block) * @return {@code true} if the block is normal block */ - @API(usage = Usage.MAINTAINED, definition = Definition.UNIVERSAL) public boolean setDisplayBlock(Block block, boolean update) { - if(!update){ + if (!update) { if (block.isNormalBlock()) { blockInside = block; } else { @@ -731,6 +766,7 @@ public boolean setDisplayBlock(Block block, boolean update) { } return true; } + //TODO: DATA_DISPLAY_ITEM NBTEntityData if (block != null) { if (block.isNormalBlock()) { blockInside = block; @@ -755,7 +791,6 @@ public boolean setDisplayBlock(Block block, boolean update) { * * @return Block of minecart display block */ - @API(usage = Usage.STABLE, definition = Definition.UNIVERSAL) public Block getDisplayBlock() { return blockInside; } @@ -765,7 +800,6 @@ public Block getDisplayBlock() { * * @param offset The offset */ - @API(usage = Usage.EXPERIMENTAL, definition = Definition.PLATFORM_NATIVE) public void setDisplayBlockOffset(int offset) { setDataProperty(new IntEntityData(DATA_DISPLAY_OFFSET, offset)); } @@ -775,7 +809,6 @@ public void setDisplayBlockOffset(int offset) { * * @return integer */ - @API(usage = Usage.EXPERIMENTAL, definition = Definition.UNIVERSAL) public int getDisplayBlockOffset() { return super.getDataPropertyInt(DATA_DISPLAY_OFFSET); } @@ -785,7 +818,6 @@ public int getDisplayBlockOffset() { * * @return boolean */ - @API(usage = Usage.EXPERIMENTAL, definition = Definition.UNIVERSAL) public boolean isSlowWhenEmpty() { return slowWhenEmpty; } @@ -795,7 +827,6 @@ public boolean isSlowWhenEmpty() { * * @param slow The slowdown flag */ - @API(usage = Usage.EXPERIMENTAL, definition = Definition.UNIVERSAL) public void setSlowWhenEmpty(boolean slow) { slowWhenEmpty = slow; } @@ -825,4 +856,9 @@ public void setDerailedVelocityMod(Vector3 derailed) { public void setMaximumSpeed(double speed) { maxSpeed = speed; } + + @Override + public String getInteractButtonText() { + return ""; + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartChest.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartChest.java index 8ce57da5390..f37b0e18853 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartChest.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartChest.java @@ -27,11 +27,7 @@ public class EntityMinecartChest extends EntityMinecartAbstract implements Inven public EntityMinecartChest(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); setDisplayBlock(Block.get(Block.CHEST), false); - } - - @Override - public String getName() { - return getType().getName(); + setName("Minecart with Chest"); } @Override @@ -40,7 +36,7 @@ public MinecartType getType() { } @Override - public boolean isRideable(){ + public boolean isRideable() { return false; } @@ -51,10 +47,6 @@ public int getNetworkId() { @Override public void dropItem() { - for (Item item : this.inventory.getContents().values()) { - this.level.dropItem(this, item); - } - if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) this.lastDamageCause).getDamager(); if (damager instanceof Player && ((Player) damager).isCreative()) { @@ -62,12 +54,17 @@ public void dropItem() { } } this.level.dropItem(this, Item.get(Item.CHEST_MINECART)); - } - @Override - public void kill() { - super.kill(); - this.inventory.clearAll(); + if (this.inventory == null) { + this.initInventory(); + } + if (this.inventory != null) { + this.inventory.getViewers().clear(); + for (Item item : this.inventory.getContents().values()) { + this.level.dropItem(this, item); + } + this.inventory.clearAll(); + } } @Override @@ -77,12 +74,17 @@ public boolean mountEntity(Entity entity, byte mode) { @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - player.addWindow(this.inventory); + if (this.isAlive()) { + player.addWindow(this.getInventory()); + } return false; // If true, the count of items player has in hand decreases } @Override public MinecartChestInventory getInventory() { + if (this.inventory == null) { + this.initInventory(); + } return inventory; } @@ -90,26 +92,34 @@ public MinecartChestInventory getInventory() { public void initEntity() { super.initEntity(); - this.inventory = new MinecartChestInventory(this); - if (this.namedTag.contains("Items") && this.namedTag.get("Items") instanceof ListTag) { - ListTag inventoryList = this.namedTag.getList("Items", CompoundTag.class); - for (CompoundTag item : inventoryList.getAll()) { - this.inventory.setItem(item.getByte("Slot"), NBTIO.getItemHelper(item)); - } - } - this.dataProperties .putByte(DATA_CONTAINER_TYPE, 10) - .putInt(DATA_CONTAINER_BASE_SIZE, this.inventory.getSize()) + .putInt(DATA_CONTAINER_BASE_SIZE, 27) .putInt(DATA_CONTAINER_EXTRA_SLOTS_PER_STRENGTH, 0); } + private void initInventory() { + if (!this.namedTag.contains("Items") || !(this.namedTag.get("Items") instanceof ListTag)) { + this.namedTag.putList(new ListTag("Items")); + } + ListTag list = (ListTag) this.namedTag.getList("Items"); + + this.inventory = new MinecartChestInventory(this); + + for (CompoundTag compound : list.getAll()) { + Item item = NBTIO.getItemHelper(compound); + if (item.getId() != 0 && item.getCount() > 0) { + this.inventory.slots.put(compound.getByte("Slot"), item); + } + } + } + @Override public void saveNBT() { super.saveNBT(); - this.namedTag.putList(new ListTag("Items")); if (this.inventory != null) { + this.namedTag.putList(new ListTag("Items")); for (int slot = 0; slot < 27; ++slot) { Item item = this.inventory.getItem(slot); if (item != null && item.getId() != Item.AIR) { diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartEmpty.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartEmpty.java index 997fdb8e6d5..6607a3294fa 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartEmpty.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartEmpty.java @@ -5,7 +5,7 @@ import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.passive.EntityWaterAnimal; import cn.nukkit.event.entity.EntityDamageByBlockEvent; -import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; +import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.utils.MinecartType; @@ -25,18 +25,14 @@ public int getNetworkId() { public EntityMinecartEmpty(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); - } - - @Override - public String getName() { - return getType().getName(); + setName("Minecart"); } @Override public MinecartType getType() { return MinecartType.valueOf(0); } - + @Override public boolean isRideable() { return true; @@ -45,7 +41,7 @@ public boolean isRideable() { @Override protected void activate(int x, int y, int z, boolean flag) { if (flag && this.getHealth() > 15 - && this.attack(new EntityDamageByBlockEvent(this.level.getBlock(x, y, z), this, DamageCause.CONTACT, 1)) + && this.attack(new EntityDamageByBlockEvent(this.level.getBlock(x, y, z), this, EntityDamageEvent.DamageCause.CONTACT, 1)) && !this.passengers.isEmpty()) { this.dismountEntity(this.getPassenger()); } @@ -66,7 +62,6 @@ public boolean onUpdate(int currentTick) { break; } } - return update; } diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartHopper.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartHopper.java index 0b3107e0de9..664078062b1 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartHopper.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartHopper.java @@ -2,12 +2,22 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockComposter; +import cn.nukkit.block.BlockRailActivator; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityContainer; +import cn.nukkit.blockentity.BlockEntityFurnace; import cn.nukkit.entity.Entity; import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.inventory.InventoryMoveItemEvent; +import cn.nukkit.inventory.FurnaceInventory; +import cn.nukkit.inventory.Inventory; import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.inventory.MinecartHopperInventory; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.AxisAlignedBB; +import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; @@ -20,14 +30,12 @@ public class EntityMinecartHopper extends EntityMinecartAbstract implements Inve protected MinecartHopperInventory inventory; + public int transferCooldown; + public EntityMinecartHopper(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); setDisplayBlock(Block.get(Block.HOPPER_BLOCK), false); - } - - @Override - public String getName() { - return getType().getName(); + setName("Minecart with Hopper"); } @Override @@ -36,7 +44,7 @@ public MinecartType getType() { } @Override - public boolean isRideable(){ + public boolean isRideable() { return false; } @@ -47,10 +55,6 @@ public int getNetworkId() { @Override public void dropItem() { - for (Item item : this.inventory.getContents().values()) { - this.level.dropItem(this, item); - } - if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) this.lastDamageCause).getDamager(); if (damager instanceof Player && ((Player) damager).isCreative()) { @@ -58,12 +62,13 @@ public void dropItem() { } } this.level.dropItem(this, Item.get(Item.HOPPER_MINECART)); - } - - @Override - public void kill() { - super.kill(); - this.inventory.clearAll(); + if (this.inventory != null) { + this.inventory.getViewers().clear(); + for (Item item : this.inventory.getContents().values()) { + this.level.dropItem(this, item); + } + this.inventory.clearAll(); + } } @Override @@ -73,7 +78,9 @@ public boolean mountEntity(Entity entity, byte mode) { @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - player.addWindow(this.inventory); + if (this.isAlive()) { + player.addWindow(this.inventory); + } return false; // If true, the count of items player has in hand decreases } @@ -116,8 +123,184 @@ public void saveNBT() { } } + @Override + public boolean onUpdate(int currentTick) { + if (super.onUpdate(currentTick) && !this.closed && isAlive()) { + if (this.isOnTransferCooldown()) { + this.transferCooldown--; + return true; + } + + Block rail = level.getBlock(this.chunk, getFloorX(), getFloorY(), getFloorZ(), false); + if (rail instanceof BlockRailActivator && ((BlockRailActivator) rail).isActive()) { + return false; + } + + boolean changed; + BlockEntity blockEntity = this.level.getBlockEntity(this.chunk, this.up()); + Block block = null; + if (blockEntity instanceof BlockEntityContainer || (block = this.level.getBlock(this.chunk, this.getFloorX(), this.getFloorY() + 1, this.getFloorZ(), false)) instanceof BlockComposter) { + changed = pullItems(blockEntity, block); + } else { + changed = pickupItems(new SimpleAxisAlignedBB(this.x, this.y, this.z, this.x + 1, this.y + 2, this.z + 1)); + } + + if (changed) { + this.setTransferCooldown(8); + } + + return true; + } + + return false; + } + @Override public String getInteractButtonText() { return "action.interact.opencontainer"; } + + public boolean isOnTransferCooldown() { + return this.transferCooldown > 0; + } + + public void setTransferCooldown(int transferCooldown) { + this.transferCooldown = transferCooldown; + } + + private boolean pullItems(BlockEntity blockEntity, Block block) { + if (this.inventory.isFull()) { + return false; + } + + if (blockEntity instanceof BlockEntityFurnace) { + FurnaceInventory inv = ((BlockEntityFurnace) blockEntity).getInventory(); + Item item = inv.getResult(); + + if (!item.isNull()) { + Item itemToAdd = item.clone(); + itemToAdd.count = 1; + + if (!this.inventory.canAddItem(itemToAdd)) { + return false; + } + + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(inv, this.inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + return false; + } + + Item[] items = this.inventory.addItem(itemToAdd); + + if (items.length == 0) { + item.count--; + inv.setResult(item); + return true; + } + } + } else if (blockEntity instanceof InventoryHolder) { + Inventory inv = ((InventoryHolder) blockEntity).getInventory(); + + for (int i = 0; i < inv.getSize(); i++) { + Item item = inv.getItem(i); + + if (!item.isNull()) { + Item itemToAdd = item.clone(); + itemToAdd.count = 1; + + if (!this.inventory.canAddItem(itemToAdd)) { + continue; + } + + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(inv, this.inventory, this, itemToAdd, InventoryMoveItemEvent.Action.SLOT_CHANGE); + this.server.getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + continue; + } + + Item[] items = this.inventory.addItem(itemToAdd); + + if (items.length >= 1) { + continue; + } + + item.count--; + + inv.setItem(i, item); + return true; + } + } + } else if (block instanceof BlockComposter) { + BlockComposter composter = (BlockComposter) block; + Item item = composter.empty(); + if (item == null || item.isNull()) { + return false; + } + Item itemToAdd = item.clone(); + itemToAdd.setCount(1); + if (!this.inventory.canAddItem(itemToAdd)) { + return false; + } + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(null, this.inventory, this, item, InventoryMoveItemEvent.Action.PICKUP); + this.server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } + Item[] items = inventory.addItem(itemToAdd); + return items.length == 0; + } + return false; + } + + private boolean pickupItems(AxisAlignedBB pickupArea) { + if (this.inventory.isFull()) { + return false; + } + + boolean pickedUpItem = false; + + for (Entity entity : this.level.getCollidingEntities(pickupArea)) { + if (entity.isClosed() || !(entity instanceof EntityItem)) { + continue; + } + + EntityItem itemEntity = (EntityItem) entity; + Item item = itemEntity.getItem(); + + if (item.isNull()) { + continue; + } + + int originalCount = item.getCount(); + + if (!this.inventory.canAddItem(item)) { + continue; + } + + InventoryMoveItemEvent ev = new InventoryMoveItemEvent(null, this.inventory, this, item, InventoryMoveItemEvent.Action.PICKUP); + this.server.getPluginManager().callEvent(ev); + + if (ev.isCancelled()) { + continue; + } + + Item[] items = this.inventory.addItem(item); + + if (items.length == 0) { + entity.close(); + pickedUpItem = true; + continue; + } + + if (items[0].getCount() != originalCount) { + pickedUpItem = true; + item.setCount(items[0].getCount()); + } + } + + return pickedUpItem; + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityMinecartTNT.java b/src/main/java/cn/nukkit/entity/item/EntityMinecartTNT.java index c5ce3074bed..9aaac688a16 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityMinecartTNT.java +++ b/src/main/java/cn/nukkit/entity/item/EntityMinecartTNT.java @@ -9,7 +9,6 @@ import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.entity.EntityExplosionPrimeEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemMinecartTNT; import cn.nukkit.level.Explosion; import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; @@ -17,23 +16,22 @@ import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.MinecartType; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * Author: Adam Matthew [larryTheCoder] - *

+ * @author Adam Matthew [larryTheCoder] + * * Nukkit Project. */ public class EntityMinecartTNT extends EntityMinecartAbstract implements EntityExplosive { public static final int NETWORK_ID = 97; private int fuse; - private boolean activated = false; public EntityMinecartTNT(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); - super.setDisplayBlock(Block.get(BlockID.TNT), false); + setDisplayBlock(Block.get(BlockID.TNT), false); + setName("Minecart with TNT"); } @Override @@ -41,11 +39,6 @@ public boolean isRideable() { return false; } - @Override - public String getInteractButtonText() { - return ""; - } - @Override public void initEntity() { super.initEntity(); @@ -55,13 +48,11 @@ public void initEntity() { } else { fuse = 80; } - this.setDataFlag(DATA_FLAGS, DATA_FLAG_CHARGED, false); + //this.setDataFlag(DATA_FLAGS, DATA_FLAG_CHARGED, false); } @Override public boolean onUpdate(int currentTick) { - this.timing.startTiming(); - if (fuse < 80) { int tickDiff = currentTick - lastUpdate; @@ -75,15 +66,13 @@ public boolean onUpdate(int currentTick) { if (isAlive() && fuse <= 0) { if (this.level.getGameRules().getBoolean(GameRule.TNT_EXPLODES)) { - this.explode(ThreadLocalRandom.current().nextInt(5)); + this.explode(Utils.random.nextInt(5)); } this.close(); return false; } } - this.timing.stopTiming(); - return super.onUpdate(currentTick); } @@ -105,7 +94,7 @@ public void explode(double square) { root = 5.0D; } - EntityExplosionPrimeEvent event = new EntityExplosionPrimeEvent(this, (4.0D + ThreadLocalRandom.current().nextDouble() * 1.5D * root)); + EntityExplosionPrimeEvent event = new EntityExplosionPrimeEvent(this, (4.0D + Utils.random.nextDouble() * 1.5D * root)); server.getPluginManager().callEvent(event); if (event.isCancelled()) { return; @@ -126,12 +115,7 @@ public void dropItem() { return; } } - level.dropItem(this, new ItemMinecartTNT()); - } - - @Override - public String getName() { - return getType().getName(); + level.dropItem(this, Item.get(Item.MINECART_WITH_TNT)); } @Override @@ -150,17 +134,16 @@ public void saveNBT() { super.namedTag.putInt("TNTFuse", this.fuse); } - + @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { - boolean interact = super.onInteract(player, item, clickedPos); if (item.getId() == Item.FLINT_AND_STEEL || item.getId() == Item.FIRE_CHARGE) { level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_IGNITE); this.fuse = 79; return true; } - return interact; + return super.onInteract(player, item, clickedPos); } @Override diff --git a/src/main/java/cn/nukkit/entity/item/EntityPainting.java b/src/main/java/cn/nukkit/entity/item/EntityPainting.java index f59f2ca860c..fe8fa170048 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityPainting.java +++ b/src/main/java/cn/nukkit/entity/item/EntityPainting.java @@ -1,13 +1,16 @@ package cn.nukkit.entity.item; import cn.nukkit.Player; +import cn.nukkit.block.Block; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityHanging; import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.entity.EntityDamageEvent; -import cn.nukkit.item.ItemPainting; +import cn.nukkit.item.Item; import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.AddPaintingPacket; import cn.nukkit.network.protocol.DataPacket; @@ -16,7 +19,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityPainting extends EntityHanging { @@ -24,7 +27,9 @@ public class EntityPainting extends EntityHanging { public static final int NETWORK_ID = 83; public final static Motive[] motives = Motive.values(); + private Motive motive; + private SimpleAxisAlignedBB cachedBoundingBox; public EntityPainting(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); @@ -63,10 +68,11 @@ public boolean attack(EntityDamageEvent source) { if (super.attack(source)) { if (source instanceof EntityDamageByEntityEvent) { Entity damager = ((EntityDamageByEntityEvent) source).getDamager(); - if (damager instanceof Player && (((Player) damager).isAdventure() || ((Player) damager).isSurvival()) && this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { - this.level.dropItem(this, new ItemPainting()); + if (damager instanceof Player && ((Player) damager).isSurvival()) { + this.dropItem(); } } + this.level.addParticle(new DestroyBlockParticle(this, Block.get(Block.WOODEN_PLANKS))); this.close(); return true; } else { @@ -134,4 +140,19 @@ public enum Motive { this.height = height; } } + + @Override + protected boolean isSurfaceValid() { + if (this.cachedBoundingBox == null) { + this.cachedBoundingBox = new SimpleAxisAlignedBB(this.x - 0.1, this.y, this.z - 0.1, this.x + 0.1, this.y + 0.1, this.z + 0.1); + } + return this.level.hasCollisionBlocks(this, this.cachedBoundingBox); + } + + @Override + protected void dropItem() { + if (this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { + this.level.dropItem(this, Item.get(Item.PAINTING)); + } + } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityPotion.java b/src/main/java/cn/nukkit/entity/item/EntityPotion.java index 54062ee422a..852654158c0 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityPotion.java +++ b/src/main/java/cn/nukkit/entity/item/EntityPotion.java @@ -1,6 +1,8 @@ package cn.nukkit.entity.item; +import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.data.IntEntityData; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.event.potion.PotionCollideEvent; import cn.nukkit.level.format.FullChunk; @@ -18,12 +20,10 @@ public class EntityPotion extends EntityProjectile { public static final int NETWORK_ID = 86; - public static final int DATA_POTION_ID = 37; - public int potionId; public EntityPotion(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); + this(chunk, nbt, null); } public EntityPotion(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { @@ -38,9 +38,9 @@ protected void initEntity() { this.dataProperties.putShort(DATA_POTION_AUX_VALUE, this.potionId); - /*Effect effect = Potion.getEffect(potionId, true); TODO: potion color + Effect effect = Potion.getEffect(potionId, true); - if(effect != null) { + if (effect != null) { int count = 0; int[] c = effect.getColor(); count += effect.getAmplifier() + 1; @@ -49,8 +49,8 @@ protected void initEntity() { int g = ((c[1] * (effect.getAmplifier() + 1)) / count) & 0xff; int b = ((c[2] * (effect.getAmplifier() + 1)) / count) & 0xff; - this.setDataProperty(new IntEntityData(Entity.DATA_UNKNOWN, (r << 16) + (g << 8) + b)); - }*/ + this.setDataProperty(new IntEntityData(Entity.DATA_POTION_COLOR, (r << 16) + (g << 8) + b)); + } } @Override @@ -83,12 +83,7 @@ protected float getDrag() { return 0.01f; } - @Override - public void onCollideWithEntity(Entity entity) { - this.splash(entity); - } - - private void splash(Entity collidedWith) { + protected void splash(Entity collidedWith) { Potion potion = Potion.getPotion(this.potionId); PotionCollideEvent event = new PotionCollideEvent(potion, this); this.server.getPluginManager().callEvent(event); @@ -129,8 +124,11 @@ private void splash(Entity collidedWith) { this.getLevel().addParticle(particle); this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_GLASS); - Entity[] entities = this.getLevel().getNearbyEntities(this.getBoundingBox().grow(4.125, 2.125, 4.125)); + Entity[] entities = this.getLevel().getNearbyEntities(this.getBoundingBox().grow(4.125, 2.125, 4.125), this); for (Entity anEntity : entities) { + if (anEntity == null || anEntity.closed || !anEntity.isAlive() || anEntity instanceof Player && ((Player) anEntity).isSpectator()) { + continue; + } double distance = anEntity.distanceSquared(this); if (distance < 16) { double d = anEntity.equals(collidedWith) ? 1 : 1 - Math.sqrt(distance) / 4; @@ -139,25 +137,28 @@ private void splash(Entity collidedWith) { } } + @Override + public void onCollideWithEntity(Entity entity) { + this.splash(entity); + this.close(); + } + @Override public boolean onUpdate(int currentTick) { + boolean update = super.onUpdate(currentTick); + if (this.closed) { return false; } - this.timing.startTiming(); - - boolean hasUpdate = super.onUpdate(currentTick); - - if (this.age > 1200) { - this.kill(); - hasUpdate = true; - } else if (this.isCollided) { + if (this.isCollided) { this.splash(null); - hasUpdate = true; } - this.timing.stopTiming(); - return hasUpdate; + if (this.age > 1200 || this.isCollided) { + this.close(); + return false; + } + return update; } } diff --git a/src/main/java/cn/nukkit/entity/item/EntityPotionLingering.java b/src/main/java/cn/nukkit/entity/item/EntityPotionLingering.java new file mode 100644 index 00000000000..5b8cc638c53 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/item/EntityPotionLingering.java @@ -0,0 +1,56 @@ +package cn.nukkit.entity.item; + +import cn.nukkit.entity.Entity; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.potion.Effect; +import cn.nukkit.potion.Potion; + +public class EntityPotionLingering extends EntityPotion { + + public static final int NETWORK_ID = 101; + + public EntityPotionLingering(FullChunk chunk, CompoundTag nbt) { + this(chunk, nbt, null); + } + + public EntityPotionLingering(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { + super(chunk, nbt, shootingEntity); + } + + @Override + protected void initEntity() { + super.initEntity(); + setDataFlag(DATA_FLAGS, DATA_FLAG_LINGER, true); + } + + @Override + protected void splash(Entity collidedWith) { + super.splash(collidedWith); + saveNBT(); + ListTag pos = (ListTag) namedTag.getList("Pos", CompoundTag.class).copy(); + EntityAreaEffectCloud entity = (EntityAreaEffectCloud) Entity.createEntity("AreaEffectCloud", getChunk(), + new CompoundTag().putList(pos) + .putList(new ListTag<>("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0)) + ) + .putList(new ListTag<>("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + ) + .putShort("PotionId", potionId) + ); + + Effect effect = Potion.getEffect(potionId, true); + + if (effect != null && entity != null) { + entity.cloudEffects.add(effect.setDuration(1).setVisible(false).setAmbient(false)); + entity.spawnToAll(); + } + } +} diff --git a/src/main/java/cn/nukkit/entity/item/EntityPrimedTNT.java b/src/main/java/cn/nukkit/entity/item/EntityPrimedTNT.java index 01fa145d0c4..600b608e9c1 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityPrimedTNT.java +++ b/src/main/java/cn/nukkit/entity/item/EntityPrimedTNT.java @@ -1,5 +1,6 @@ package cn.nukkit.entity.item; +import cn.nukkit.block.BlockSlab; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityExplosive; import cn.nukkit.entity.data.IntEntityData; @@ -10,7 +11,7 @@ import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.network.protocol.LevelEventPacket; /** * @author MagicDroidX @@ -49,11 +50,6 @@ protected float getBaseOffset() { return 0.49f; } - @Override - public boolean canCollide() { - return false; - } - protected int fuse; protected Entity source; @@ -77,6 +73,7 @@ public boolean attack(EntityDamageEvent source) { return source.getCause() == DamageCause.VOID && super.attack(source); } + @Override protected void initEntity() { super.initEntity(); @@ -89,27 +86,27 @@ protected void initEntity() { this.setDataFlag(DATA_FLAGS, DATA_FLAG_IGNITED, true); this.setDataProperty(new IntEntityData(DATA_FUSE_LENGTH, fuse)); - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_FIZZ); + this.getLevel().addLevelEvent(this, LevelEventPacket.EVENT_SOUND_TNT); } - + @Override public boolean canCollideWith(Entity entity) { return false; } + @Override public void saveNBT() { super.saveNBT(); namedTag.putByte("Fuse", fuse); } + @Override public boolean onUpdate(int currentTick) { if (closed) { return false; } - this.timing.startTiming(); - int tickDiff = currentTick - lastUpdate; if (tickDiff <= 0 && !justCreated) { @@ -126,7 +123,9 @@ public boolean onUpdate(int currentTick) { if (isAlive()) { - motionY -= getGravity(); + if (!isOnGround()) { + motionY -= getGravity(); + } move(motionX, motionY, motionZ); @@ -147,15 +146,14 @@ public boolean onUpdate(int currentTick) { fuse -= tickDiff; if (fuse <= 0) { - if (this.level.getGameRules().getBoolean(GameRule.TNT_EXPLODES)) - explode(); - kill(); + if (this.level.getGameRules().getBoolean(GameRule.TNT_EXPLODES)) { + this.explode(); + } + this.close(); + return false; } - } - this.timing.stopTiming(); - return hasUpdate || fuse >= 0 || Math.abs(motionX) > 0.00001 || Math.abs(motionY) > 0.00001 || Math.abs(motionZ) > 0.00001; } @@ -165,7 +163,7 @@ public void explode() { if (event.isCancelled()) { return; } - Explosion explosion = new Explosion(this, event.getForce(), this); + Explosion explosion = new Explosion(level.getBlock(this) instanceof BlockSlab ? this.add(0, 0.1, 0) : this, event.getForce(), this); if (event.isBlockBreaking()) { explosion.explodeA(); } diff --git a/src/main/java/cn/nukkit/entity/item/EntityVehicle.java b/src/main/java/cn/nukkit/entity/item/EntityVehicle.java index f0a2da30830..40b7542dcfc 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityVehicle.java +++ b/src/main/java/cn/nukkit/entity/item/EntityVehicle.java @@ -13,37 +13,44 @@ import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityVehicle extends Entity implements EntityRideable, EntityInteractable { + private int hurtTime; + private int hurtDirection; + private int damage; + public EntityVehicle(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } public int getRollingAmplitude() { - return this.getDataPropertyInt(DATA_HURT_TIME); + return hurtTime; } public void setRollingAmplitude(int time) { + this.hurtTime = time; this.setDataProperty(new IntEntityData(DATA_HURT_TIME, time)); } public int getRollingDirection() { - return this.getDataPropertyInt(DATA_HURT_DIRECTION); + return hurtDirection; } public void setRollingDirection(int direction) { + this.hurtDirection = direction; this.setDataProperty(new IntEntityData(DATA_HURT_DIRECTION, direction)); } public int getDamage() { - return this.getDataPropertyInt(DATA_HEALTH); // false data name (should be DATA_DAMAGE_TAKEN) + return damage; } public void setDamage(int damage) { - this.setDataProperty(new IntEntityData(DATA_HEALTH, damage)); + this.damage = damage; + this.setDataProperty(new IntEntityData(DATA_HEALTH, damage)); // false data name (should be DATA_DAMAGE_TAKEN) } @Override @@ -58,16 +65,18 @@ public boolean canDoInteraction() { @Override public boolean onUpdate(int currentTick) { - // The rolling amplitude + if (y < (this.getLevel().getMinBlockY() - 16)) { + this.close(); + } + + if (closed) { + return false; + } + if (getRollingAmplitude() > 0) { setRollingAmplitude(getRollingAmplitude() - 1); } - // A killer task - if (y < -16) { - kill(); - } - // Movement code updateMovement(); return true; } @@ -111,4 +120,4 @@ public boolean attack(EntityDamageEvent source) { return super.attack(source); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/entity/item/EntityXPOrb.java b/src/main/java/cn/nukkit/entity/item/EntityXPOrb.java index 93fb79a3cd9..b714d9471b7 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityXPOrb.java +++ b/src/main/java/cn/nukkit/entity/item/EntityXPOrb.java @@ -1,11 +1,14 @@ package cn.nukkit.entity.item; import cn.nukkit.Player; +import cn.nukkit.block.Block; import cn.nukkit.entity.Entity; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.NukkitMath; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.potion.Effect; import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.List; @@ -19,9 +22,45 @@ public class EntityXPOrb extends Entity { public static final int NETWORK_ID = 69; /** - * Split sizes used for dropping experience orbs. + * Split sizes used for dropping experience orbs */ - public static final int[] ORB_SPLIT_SIZES = {2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1}; //This is indexed biggest to smallest so that we can return as soon as we found the biggest value. + public static final int[] ORB_SPLIT_SIZES = {2477, 1237, 617, 307, 149, 73, 37, 17, 7, 3, 1}; // This is indexed biggest to smallest so that we can return as soon as we found the biggest value + public Player closestPlayer = null; + private int pickupDelay; + private int exp; + + public EntityXPOrb(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + /** + * Returns the largest size of normal XP orb that will be spawned for the specified amount of XP. Used to split XP + * up into multiple orbs when an amount of XP is dropped. + */ + public static int getMaxOrbSize(int amount) { + for (int split : ORB_SPLIT_SIZES) { + if (amount >= split) { + return split; + } + } + + return 1; + } + + /** + * Splits the specified amount of XP into an array of acceptable XP orb sizes. + */ + public static List splitIntoOrbSizes(int amount) { + List result = new IntArrayList(); + + while (amount > 0) { + int size = getMaxOrbSize(amount); + result.add(size); + amount -= size; + } + + return result; + } @Override public int getNetworkId() { @@ -30,17 +69,17 @@ public int getNetworkId() { @Override public float getWidth() { - return 0.25f; + return 0.1f; } @Override public float getLength() { - return 0.25f; + return 0.1f; } @Override public float getHeight() { - return 0.25f; + return 0.1f; } @Override @@ -58,32 +97,25 @@ public boolean canCollide() { return false; } - public EntityXPOrb(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - - private int age; - private int pickupDelay; - private int exp; - - public Player closestPlayer = null; - @Override protected void initEntity() { + this.setMaxHealth(5); super.initEntity(); - setMaxHealth(5); - setHealth(5); - if (namedTag.contains("Health")) { this.setHealth(namedTag.getShort("Health")); + } else { + this.setHealth(5); } + if (namedTag.contains("Age")) { this.age = namedTag.getShort("Age"); } + if (namedTag.contains("PickupDelay")) { this.pickupDelay = namedTag.getShort("PickupDelay"); } + if (namedTag.contains("Value")) { this.exp = namedTag.getShort("Value"); } @@ -93,17 +125,15 @@ protected void initEntity() { } this.dataProperties.putInt(DATA_EXPERIENCE_VALUE, this.exp); - - //call event item spawn event } @Override public boolean attack(EntityDamageEvent source) { return (source.getCause() == DamageCause.VOID || source.getCause() == DamageCause.FIRE_TICK || - (source.getCause() == DamageCause.ENTITY_EXPLOSION || + source.getCause() == DamageCause.ENTITY_EXPLOSION || source.getCause() == DamageCause.BLOCK_EXPLOSION) && - !this.isInsideOfWater()) && super.attack(source); + super.attack(source); } @Override @@ -116,43 +146,60 @@ public boolean onUpdate(int currentTick) { if (tickDiff <= 0 && !this.justCreated) { return true; } - this.lastUpdate = currentTick; - boolean hasUpdate = entityBaseTick(tickDiff); - if (this.isAlive()) { + this.minimalEntityTick(currentTick, tickDiff); + + if (this.age > 6000) { + this.close(); + return false; + } - if (this.pickupDelay > 0 && this.pickupDelay < 32767) { //Infinite delay + if (this.isAlive()) { + if (this.pickupDelay > 0) { this.pickupDelay -= tickDiff; if (this.pickupDelay < 0) { this.pickupDelay = 0; } - }/* else { // Done in Player#checkNearEntities - for (Entity entity : this.level.getCollidingEntities(this.boundingBox, this)) { - if (entity instanceof Player) { - if (((Player) entity).pickupEntity(this, false)) { - return true; - } - } - } - }*/ + } - this.motionY -= this.getGravity(); + if (this.isOnGround()) { + this.motionY = 0; + } else if (Block.hasWater(level.getBlockIdAt(this.chunk, this.getFloorX(), NukkitMath.floorDouble(this.y + 0.53), this.getFloorZ()))) { + this.motionY = this.getGravity() / 2; + } else { + this.motionY -= this.getGravity(); + } + + this.checkBlockCollision(); - if (this.checkObstruction(this.x, this.y, this.z)) { + /*if (this.checkObstruction(this.x, this.y, this.z)) { hasUpdate = true; + }*/ + + // Force occasional check for nearby blocks changed even if the item doesn't move + if (this.motionY == 0 && this.age % 20 == 0) { + this.motionY = 0.00001; } - if (this.closestPlayer == null || this.closestPlayer.distanceSquared(this) > 64.0D) { - for (Player p : this.getViewers().values()) { - if (!p.isSpectator() && p.distance(this) <= 8) { - this.closestPlayer = p; - break; - } + if (this.age % 2 == 0) { + if (this.closestPlayer != null && + (this.closestPlayer.level != this.level || + this.closestPlayer.closed || + !this.closestPlayer.isAlive() || + this.closestPlayer.isSpectator() || + !this.closestPlayer.canPickupXP() || + this.closestPlayer.distanceSquared(this) > 64.0D)) { + this.closestPlayer = null; } - } - if (this.closestPlayer != null && this.closestPlayer.isSpectator()) { - this.closestPlayer = null; + if (this.closestPlayer == null) { + for (Player p : this.getViewers().values()) { + if (!p.isSpectator() && p.canPickupXP() && p.distanceSquared(this) <= 64.0D) { + this.closestPlayer = p; + break; + } + } + } } if (this.closestPlayer != null) { @@ -172,10 +219,33 @@ public boolean onUpdate(int currentTick) { this.move(this.motionX, this.motionY, this.motionZ); + if (this.y < (this.getLevel().getMinBlockY() - 16)) { + this.attack(new EntityDamageEvent(this, DamageCause.VOID, 10)); + } + + if (this.fireTicks > 0) { + if (this.fireProof) { + this.fireTicks -= tickDiff << 2; + if (this.fireTicks < 0) { + this.fireTicks = 0; + } + } else { + if (((this.fireTicks % 20) == 0 || tickDiff > 20) && !this.hasEffect(Effect.FIRE_RESISTANCE)) { + this.attack(new EntityDamageEvent(this, DamageCause.FIRE_TICK, 1)); + } + this.fireTicks -= tickDiff; + } + if (this.fireTicks <= 0) { + this.extinguish(); + } else if (!this.fireProof) { + this.setDataFlag(DATA_FLAGS, DATA_FLAG_ONFIRE, true); + } + } + double friction = 1d - this.getDrag(); if (this.onGround && (Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionZ) > 0.00001)) { - friction = this.getLevel().getBlock(this.temporalVector.setComponents((int) Math.floor(this.x), (int) Math.floor(this.y - 1), (int) Math.floor(this.z) - 1)).getFrictionFactor() * friction; + friction = this.getLevel().getBlock(this.chunk, getFloorX(), getFloorY() - 1, getFloorZ(), false).getFrictionFactor() * friction; } this.motionX *= friction; @@ -183,19 +253,16 @@ public boolean onUpdate(int currentTick) { this.motionZ *= friction; if (this.onGround) { - this.motionY *= -0.5; + this.motionY = 0; } this.updateMovement(); - - if (this.age > 6000) { - this.kill(); - hasUpdate = true; - } - + } else { + this.close(); + return false; } - return hasUpdate || !this.onGround || Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionY) > 0.00001 || Math.abs(this.motionZ) > 0.00001; + return !(this.motionX == 0 && this.motionY == 0 && this.motionZ == 0); } @Override @@ -230,33 +297,4 @@ public int getPickupDelay() { public void setPickupDelay(int pickupDelay) { this.pickupDelay = pickupDelay; } - - /** - * Returns the largest size of normal XP orb that will be spawned for the specified amount of XP. Used to split XP - * up into multiple orbs when an amount of XP is dropped. - */ - public static int getMaxOrbSize(int amount) { - for (int split : ORB_SPLIT_SIZES){ - if (amount >= split) { - return split; - } - } - - return 1; - } - - /** - * Splits the specified amount of XP into an array of acceptable XP orb sizes. - */ - public static List splitIntoOrbSizes(int amount) { - List result = new IntArrayList(); - - while (amount > 0) { - int size = getMaxOrbSize(amount); - result.add(size); - amount -= size; - } - - return result; - } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityBlaze.java b/src/main/java/cn/nukkit/entity/mob/EntityBlaze.java index 52ffea538c0..cac53cb927f 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityBlaze.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityBlaze.java @@ -1,28 +1,21 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityBlaze extends EntityMob { +public class EntityBlaze extends EntityFlyingMob { public static final int NETWORK_ID = 43; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityBlaze(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,7 +29,19 @@ public float getHeight() { } @Override - public String getName() { - return "Blaze"; + public void initEntity() { + this.setMaxHealth(20); + super.initEntity(); + this.fireProof = true; + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.BLAZE_ROD, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 10; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityCaveSpider.java b/src/main/java/cn/nukkit/entity/mob/EntityCaveSpider.java index f0453c33e57..1cfed437071 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityCaveSpider.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityCaveSpider.java @@ -1,29 +1,25 @@ package cn.nukkit.entity.mob; import cn.nukkit.entity.EntityArthropod; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityCaveSpider extends EntityMob implements EntityArthropod { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 40; +public class EntityCaveSpider extends EntityWalkingMob implements EntityArthropod { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 40; public EntityCaveSpider(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(12); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,8 +32,32 @@ public float getHeight() { return 0.5f; } + @Override + public void initEntity() { + this.setMaxHealth(12); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + drops.add(Item.get(Item.STRING, 0, Utils.rand(0, 2))); + + for (int i = 0; i < (Utils.rand(0, 2) == 0 ? 1 : 0); i++) { + drops.add(Item.get(Item.SPIDER_EYE, 0, 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; + } + @Override public String getName() { - return "CaveSpider"; + return this.hasCustomName() ? this.getNameTag() : "Cave Spider"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityCreeper.java b/src/main/java/cn/nukkit/entity/mob/EntityCreeper.java index 293a73321c3..8dec52ac5c3 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityCreeper.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityCreeper.java @@ -1,27 +1,29 @@ package cn.nukkit.entity.mob; import cn.nukkit.entity.Entity; -import cn.nukkit.entity.data.ByteEntityData; +import cn.nukkit.entity.EntityExplosive; import cn.nukkit.entity.weather.EntityLightningStrike; import cn.nukkit.event.entity.CreeperPowerEvent; import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.event.entity.EntityExplosionPrimeEvent; import cn.nukkit.item.Item; +import cn.nukkit.level.Explosion; +import cn.nukkit.level.GameRule; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -import java.util.concurrent.ThreadLocalRandom; +import java.util.ArrayList; +import java.util.List; -/** - * @author Box. - */ -public class EntityCreeper extends EntityMob { +public class EntityCreeper extends EntityWalkingMob implements EntityExplosive { public static final int NETWORK_ID = 33; - public static final int DATA_SWELL_DIRECTION = 16; - public static final int DATA_SWELL = 17; - public static final int DATA_SWELL_OLD = 18; - public static final int DATA_POWERED = 19; + public EntityCreeper(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } @Override public int getNetworkId() { @@ -38,58 +40,80 @@ public float getHeight() { return 1.7f; } - public EntityCreeper(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } + @Override + public void initEntity() { + this.setMaxHealth(20); + super.initEntity(); - public boolean isPowered() { - return getDataPropertyBoolean(DATA_POWERED); + if (this.namedTag.contains("powered")) { + this.setPowered(this.namedTag.getBoolean("powered")); + } } - public void setPowered(EntityLightningStrike bolt) { - CreeperPowerEvent ev = new CreeperPowerEvent(this, bolt, CreeperPowerEvent.PowerCause.LIGHTNING); - this.getServer().getPluginManager().callEvent(ev); + public void explode() { + if (this.closed) return; + + EntityExplosionPrimeEvent ev = new EntityExplosionPrimeEvent(this, this.isPowered() ? 6 : 3); + this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { - this.setDataProperty(new ByteEntityData(DATA_POWERED, 1)); - this.namedTag.putBoolean("powered", true); + Explosion explosion = new Explosion(this, (float) ev.getForce(), this); + + if (ev.isBlockBreaking() && this.level.getGameRules().getBoolean(GameRule.MOB_GRIEFING)) { + explosion.explodeA(); + } + + explosion.explodeB(); } + + this.close(); } - public void setPowered(boolean powered) { - CreeperPowerEvent ev = new CreeperPowerEvent(this, powered ? CreeperPowerEvent.PowerCause.SET_ON : CreeperPowerEvent.PowerCause.SET_OFF); - this.getServer().getPluginManager().callEvent(ev); + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); - if (!ev.isCancelled()) { - this.setDataProperty(new ByteEntityData(DATA_POWERED, powered ? 1 : 0)); - this.namedTag.putBoolean("powered", powered); + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.GUNPOWDER, 0, 1)); } - } - public void onStruckByLightning(Entity entity) { - this.setPowered(true); + return drops.toArray(new Item[0]); } @Override - protected void initEntity() { - super.initEntity(); + public int getKillExperience() { + return 5; + } - if (this.namedTag.getBoolean("powered") || this.namedTag.getBoolean("IsPowered")) { - this.dataProperties.putBoolean(DATA_POWERED, true); - } - this.setMaxHealth(20); + public boolean isPowered() { + return this.getDataFlag(DATA_FLAGS, DATA_FLAG_POWERED); + } + + public void setPowered(boolean charged) { + this.setDataFlag(DATA_FLAGS, DATA_FLAG_POWERED, charged); } @Override - public String getName() { - return "Creeper"; + public void saveNBT() { + super.saveNBT(); + + this.namedTag.putBoolean("powered", this.isPowered()); } @Override - public Item[] getDrops() { - if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { - return new Item[]{Item.get(Item.GUNPOWDER, ThreadLocalRandom.current().nextInt(2) + 1)}; + public void onStruckByLightning(Entity lightning) { + if (this.attack(new EntityDamageByEntityEvent(lightning, this, EntityDamageEvent.DamageCause.LIGHTNING, 5))) { + if (this.fireTicks < 160) { + this.setOnFire(8); + } + + if (lightning instanceof EntityLightningStrike) { + CreeperPowerEvent event = new CreeperPowerEvent(this, (EntityLightningStrike) lightning, CreeperPowerEvent.PowerCause.LIGHTNING); + server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + this.setPowered(true); + } + } } - return new Item[0]; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityDrowned.java b/src/main/java/cn/nukkit/entity/mob/EntityDrowned.java index 7a105ac5aed..072499ba338 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityDrowned.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityDrowned.java @@ -4,11 +4,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityDrowned extends EntityMob implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntityDrowned extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 110; @@ -21,12 +22,6 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); - } - @Override public float getWidth() { return 0.6f; @@ -38,12 +33,30 @@ public float getHeight() { } @Override - public String getName() { - return "Drowned"; + protected void initEntity() { + this.setMaxHealth(20); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.ROTTEN_FLESH)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.ROTTEN_FLESH, 0, 1)); + } + + if (Utils.rand(1, 100) <= 11) { + drops.add(Item.get(Item.GOLD_INGOT, 0, 1)); + } + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityElderGuardian.java b/src/main/java/cn/nukkit/entity/mob/EntityElderGuardian.java index 93ac8f39bc0..b763f4ec925 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityElderGuardian.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityElderGuardian.java @@ -1,43 +1,71 @@ package cn.nukkit.entity.mob; +import cn.nukkit.Player; +import cn.nukkit.block.BlockSponge; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityElderGuardian extends EntityMob { +import java.util.ArrayList; +import java.util.List; + +public class EntityElderGuardian extends EntitySwimmingMob { public static final int NETWORK_ID = 50; + public EntityElderGuardian(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityElderGuardian(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); + @Override + public float getWidth() { + return 1.9975f; } @Override - protected void initEntity() { - super.initEntity(); + public float getHeight() { + return 1.9975f; + } + + @Override + public void initEntity() { this.setMaxHealth(80); + super.initEntity(); + this.setDataFlag(DATA_FLAGS, DATA_FLAG_ELDER, true); } @Override - public float getWidth() { - return 1.9975f; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.PRISMARINE_SHARD, 0, 1)); + } + + if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { + if (((EntityDamageByEntityEvent) this.lastDamageCause).getDamager() instanceof Player) { + drops.add(Item.get(Item.SPONGE, BlockSponge.WET, 1)); + } + } + + return drops.toArray(new Item[0]); } @Override - public float getHeight() { - return 1.9975f; + public int getKillExperience() { + return 10; } @Override public String getName() { - return "Elder Guardian"; + return this.hasCustomName() ? this.getNameTag() : "Elder Guardian"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java b/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java index 230808a5bae..2dfe0f139ea 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityEnderDragon.java @@ -1,14 +1,13 @@ package cn.nukkit.entity.mob; -import cn.nukkit.Player; -import cn.nukkit.item.Item; +import cn.nukkit.entity.Attribute; +import cn.nukkit.entity.EntityBoss; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.AddEntityPacket; +import cn.nukkit.network.protocol.DataPacket; -/** - * @author PikyCZ - */ -public class EntityEnderDragon extends EntityMob { +public class EntityEnderDragon extends EntityFlyingMob implements EntityBoss { public static final int NETWORK_ID = 53; @@ -23,27 +22,50 @@ public EntityEnderDragon(FullChunk chunk, CompoundTag nbt) { @Override public float getWidth() { - return 13f; + return 16f; } @Override public float getHeight() { - return 4f; + return 8f; } @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(200); + super.initEntity(); + + this.fireProof = true; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); } @Override - protected boolean applyNameTag(Player player, Item item) { - return false; + public int getKillExperience() { + return 0; } @Override public String getName() { - return "EnderDragon"; + return this.hasCustomName() ? this.getNameTag() : "Ender Dragon"; + } + + @Override + protected DataPacket createAddEntityPacket() { + AddEntityPacket addEntity = new AddEntityPacket(); + addEntity.type = NETWORK_ID; + addEntity.entityUniqueId = this.getId(); + addEntity.entityRuntimeId = this.getId(); + addEntity.yaw = (float) this.yaw; + addEntity.headYaw = (float) this.yaw; + addEntity.pitch = (float) this.pitch; + addEntity.x = (float) this.x; + addEntity.y = (float) this.y; + addEntity.z = (float) this.z; + addEntity.speedX = (float) this.motionX; + addEntity.speedY = (float) this.motionY; + addEntity.speedZ = (float) this.motionZ; + addEntity.metadata = this.dataProperties.clone(); + addEntity.attributes = new Attribute[]{Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(200).setValue(200)}; + return addEntity; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityEnderman.java b/src/main/java/cn/nukkit/entity/mob/EntityEnderman.java index d3f3cbd6784..860cd99f2d8 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityEnderman.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityEnderman.java @@ -1,28 +1,21 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityEnderman extends EntityMob { +public class EntityEnderman extends EntityWalkingMob { public static final int NETWORK_ID = 38; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityEnderman(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(40); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,7 +29,18 @@ public float getHeight() { } @Override - public String getName() { - return "Enderman"; + protected void initEntity() { + this.setMaxHealth(40); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.ENDER_PEARL, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityEndermite.java b/src/main/java/cn/nukkit/entity/mob/EntityEndermite.java index 4a7d8f21ed3..2dbc3bb5b06 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityEndermite.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityEndermite.java @@ -4,26 +4,17 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author Box. - */ -public class EntityEndermite extends EntityMob implements EntityArthropod { +public class EntityEndermite extends EntityWalkingMob implements EntityArthropod { public static final int NETWORK_ID = 55; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityEndermite(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(8); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -37,7 +28,13 @@ public float getHeight() { } @Override - public String getName() { - return "Endermite"; + public void initEntity() { + this.setMaxHealth(8); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return 3; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityEvoker.java b/src/main/java/cn/nukkit/entity/mob/EntityEvoker.java index 6796e1c4b1b..81241717421 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityEvoker.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityEvoker.java @@ -1,12 +1,11 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityEvoker extends EntityMob { +public class EntityEvoker extends EntityWalkingMob { public static final int NETWORK_ID = 104; @@ -19,12 +18,6 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(24); - } - @Override public float getWidth() { return 0.6f; @@ -36,7 +29,18 @@ public float getHeight() { } @Override - public String getName() { - return "Evoker"; + protected void initEntity() { + this.setMaxHealth(24); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.EMERALD, 0, Utils.rand(0, 1)), Item.get(Item.TOTEM, 0, 1)}; + } + + @Override + public int getKillExperience() { + return 10; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityFlyingMob.java b/src/main/java/cn/nukkit/entity/mob/EntityFlyingMob.java new file mode 100644 index 00000000000..645f2f18a72 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/mob/EntityFlyingMob.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.mob; + +import cn.nukkit.entity.EntityFlying; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityFlyingMob extends EntityFlying implements EntityMob { + + public EntityFlyingMob(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/mob/EntityGhast.java b/src/main/java/cn/nukkit/entity/mob/EntityGhast.java index e2c159c8f2c..917d423b1ee 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityGhast.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityGhast.java @@ -1,28 +1,24 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityGhast extends EntityMob { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 41; +public class EntityGhast extends EntityFlyingMob { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 41; public EntityGhast(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(10); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,7 +32,29 @@ public float getHeight() { } @Override - public String getName() { - return "Ghast"; + public void initEntity() { + this.setMaxHealth(10); + super.initEntity(); + + this.fireProof = true; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_FIRE_IMMUNE, true); + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.GUNPOWDER, 0, 1)); + } + + drops.add(Item.get(Item.GHAST_TEAR, 0, Utils.rand(0, 1))); + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityGuardian.java b/src/main/java/cn/nukkit/entity/mob/EntityGuardian.java index d36e4754f42..d03344904c4 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityGuardian.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityGuardian.java @@ -1,42 +1,46 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityGuardian extends EntityMob { +public class EntityGuardian extends EntitySwimmingMob { public static final int NETWORK_ID = 49; + public EntityGuardian(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityGuardian(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); + @Override + public float getWidth() { + return 0.85f; } @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(30); + public float getHeight() { + return 0.85f; } @Override - public String getName() { - return "Guardian"; + public void initEntity() { + this.setMaxHealth(30); + super.initEntity(); } @Override - public float getWidth() { - return 0.85f; + public Item[] getDrops() { + return new Item[]{Item.get(Item.PRISMARINE_SHARD, 0, Utils.rand(0, 2))}; } @Override - public float getHeight() { - return 0.85f; + public int getKillExperience() { + return 10; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityHoglin.java b/src/main/java/cn/nukkit/entity/mob/EntityHoglin.java index f5f3f8bc136..c7038e1cd71 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityHoglin.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityHoglin.java @@ -1,12 +1,17 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; + +import java.util.ArrayList; +import java.util.List; /** * @author Erik Miller | EinBexiii */ -public class EntityHoglin extends EntityMob { +public class EntityHoglin extends EntityWalkingMob { public final static int NETWORK_ID = 124; @@ -19,10 +24,15 @@ public EntityHoglin(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(40); + super.initEntity(); } @Override @@ -36,7 +46,19 @@ public float getHeight() { } @Override - public String getName() { - return "Hoglin"; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(2, 4); i++) { + drops.add(Item.get(this.isOnFire() ? Item.COOKED_PORKCHOP : Item.RAW_PORKCHOP, 0, 1)); + } + + if (Utils.rand()) { + drops.add(Item.get(Item.LEATHER)); + } + } + + return drops.toArray(new Item[0]); } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityHusk.java b/src/main/java/cn/nukkit/entity/mob/EntityHusk.java index 14d51dcd41b..82f25fea6b2 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityHusk.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityHusk.java @@ -1,29 +1,22 @@ package cn.nukkit.entity.mob; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityHusk extends EntityMob implements EntitySmite { +public class EntityHusk extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 47; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityHusk(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -37,7 +30,18 @@ public float getHeight() { } @Override - public String getName() { - return "Husk"; + protected void initEntity() { + this.setMaxHealth(20); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return this.isBaby() ? new Item[0] : new Item[]{Item.get(Item.ROTTEN_FLESH, 0, Utils.rand(0, 2))}; + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityJumpingMob.java b/src/main/java/cn/nukkit/entity/mob/EntityJumpingMob.java new file mode 100644 index 00000000000..7dea9748f66 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/mob/EntityJumpingMob.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.mob; + +import cn.nukkit.entity.EntityJumping; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityJumpingMob extends EntityJumping implements EntityMob { + + public EntityJumpingMob(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/mob/EntityMagmaCube.java b/src/main/java/cn/nukkit/entity/mob/EntityMagmaCube.java index 14e85f22c72..4bcaa1db4dd 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityMagmaCube.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityMagmaCube.java @@ -1,42 +1,59 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityMagmaCube extends EntityMob { +public class EntityMagmaCube extends EntityJumpingMob { public static final int NETWORK_ID = 42; + public EntityMagmaCube(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityMagmaCube(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); + @Override + public float getWidth() { + return 1f; + } + + @Override + public float getHeight() { + return 1f; + } + + @Override + public float getLength() { + return 1f; } @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(16); + super.initEntity(); + + this.fireProof = true; + this.noFallDamage = true; } @Override - public float getWidth() { - return 2.04f; + public Item[] getDrops() { + return new Item[]{Item.get(Item.MAGMA_CREAM, 0, Utils.rand(0, 1))}; } @Override - public float getHeight() { - return 2.04f; + public int getKillExperience() { + return 4; } @Override public String getName() { - return "Magma Cube"; + return this.hasCustomName() ? this.getNameTag() : "Magma Cube"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityMob.java b/src/main/java/cn/nukkit/entity/mob/EntityMob.java index f80fb439067..32203c66df1 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityMob.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityMob.java @@ -1,17 +1,4 @@ package cn.nukkit.entity.mob; -import cn.nukkit.entity.EntityCreature; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.nbt.tag.CompoundTag; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public abstract class EntityMob extends EntityCreature { - - public EntityMob(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - +public interface EntityMob { } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityPhantom.java b/src/main/java/cn/nukkit/entity/mob/EntityPhantom.java index 0ca90c05810..15abd62e6af 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityPhantom.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityPhantom.java @@ -4,11 +4,9 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityPhantom extends EntityMob implements EntitySmite { +public class EntityPhantom extends EntityFlyingMob implements EntitySmite { public static final int NETWORK_ID = 58; @@ -21,12 +19,6 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); - } - @Override public float getWidth() { return 0.9f; @@ -38,12 +30,23 @@ public float getHeight() { } @Override - public String getName() { - return "Phantom"; + public void initEntity() { + this.setMaxHealth(20); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(470)}; + return new Item[]{Item.get(Item.PHANTOM_MEMBRANE, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 5; + } + + @Override + public boolean dropsOnNaturalDeath() { + return false; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityPiglin.java b/src/main/java/cn/nukkit/entity/mob/EntityPiglin.java index 2501de7ad8f..e25ff1483f3 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityPiglin.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityPiglin.java @@ -6,7 +6,7 @@ /** * @author Erik Miller | EinBexiii */ -public class EntityPiglin extends EntityMob { +public class EntityPiglin extends EntityWalkingMob { public final static int NETWORK_ID = 123; @@ -19,10 +19,15 @@ public EntityPiglin(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return 5; + } + @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(16); + super.initEntity(); } @Override @@ -34,9 +39,4 @@ public float getWidth() { public float getHeight() { return 1.95f; } - - @Override - public String getName() { - return "Piglin"; - } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityPiglinBrute.java b/src/main/java/cn/nukkit/entity/mob/EntityPiglinBrute.java index 06dc5f991d7..cf650ac8296 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityPiglinBrute.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityPiglinBrute.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityPiglinBrute extends EntityMob { +public class EntityPiglinBrute extends EntityWalkingMob { public static final int NETWORK_ID = 127; @@ -12,28 +12,33 @@ public EntityPiglinBrute(FullChunk chunk, CompoundTag nbt) { } @Override - public int getNetworkId() { - return NETWORK_ID; + public void initEntity() { + this.setMaxHealth(50); + super.initEntity(); + } + + @Override + public float getWidth() { + return 0.6f; } @Override public float getHeight() { - return 1.9f; + return 1.95f; } @Override - public float getWidth() { - return 0.6f; + public int getKillExperience() { + return 10; } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(50); + public int getNetworkId() { + return NETWORK_ID; } @Override public String getName() { - return "PiglinBrute"; + return this.hasCustomName() ? this.getNameTag() : "Piglin Brute"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityPillager.java b/src/main/java/cn/nukkit/entity/mob/EntityPillager.java index ff73d77f03a..06d62459ecf 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityPillager.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityPillager.java @@ -1,12 +1,18 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -public class EntityPillager extends EntityMob { +import java.util.ArrayList; +import java.util.List; + +public class EntityPillager extends EntityWalkingMob { public static final int NETWORK_ID = 114; + public EntityPillager(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -16,12 +22,6 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(24); - } - @Override public float getWidth() { return 0.6f; @@ -33,7 +33,28 @@ public float getHeight() { } @Override - public String getName() { - return "Pillager"; + public void initEntity() { + this.setMaxHealth(24); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.ARROW, 0, 1)); + } + + if (Utils.rand(1, 12) == 1) { + drops.add(Item.get(Item.CROSSBOW, Utils.rand(300, 380), Utils.rand(0, 1))); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityRavager.java b/src/main/java/cn/nukkit/entity/mob/EntityRavager.java index d082e3e7ccf..52e6c1ea166 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityRavager.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityRavager.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityRavager extends EntityMob { +public class EntityRavager extends EntityWalkingMob { public static final int NETWORK_ID = 59; @@ -18,22 +18,22 @@ public int getNetworkId() { @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(100); + super.initEntity(); } @Override public float getHeight() { - return 1.9f; + return 2.2f; } @Override public float getWidth() { - return 1.2f; + return 1.95f; } @Override - public String getName() { - return "Ravager"; + public int getKillExperience() { + return 0; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/entity/mob/EntityShulker.java b/src/main/java/cn/nukkit/entity/mob/EntityShulker.java index 02e176c1d38..f3376bcfb0e 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityShulker.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityShulker.java @@ -1,28 +1,21 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityShulker extends EntityMob { +public class EntityShulker extends EntityWalkingMob { public static final int NETWORK_ID = 54; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityShulker(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(30); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,7 +29,20 @@ public float getHeight() { } @Override - public String getName() { - return "Shulker"; + protected void initEntity() { + this.setMaxHealth(15); + super.initEntity(); + this.fireProof = true; + this.noFallDamage = true; + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.SHULKER_SHELL, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySilverfish.java b/src/main/java/cn/nukkit/entity/mob/EntitySilverfish.java index b864ba047d4..8a1346beb48 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntitySilverfish.java +++ b/src/main/java/cn/nukkit/entity/mob/EntitySilverfish.java @@ -4,25 +4,17 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author PikyCZ - */ -public class EntitySilverfish extends EntityMob implements EntityArthropod { +public class EntitySilverfish extends EntityWalkingMob implements EntityArthropod { public static final int NETWORK_ID = 39; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntitySilverfish(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - public String getName() { - return "Silverfish"; + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -37,7 +29,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(8); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySkeleton.java b/src/main/java/cn/nukkit/entity/mob/EntitySkeleton.java index 79253f66dca..7517d5eeb50 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntitySkeleton.java +++ b/src/main/java/cn/nukkit/entity/mob/EntitySkeleton.java @@ -1,30 +1,37 @@ package cn.nukkit.entity.mob; +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.event.entity.EntityDamageByChildEntityEvent; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector2; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntitySkeleton extends EntityMob implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntitySkeleton extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 34; - @Override - public int getNetworkId() { - return NETWORK_ID; - } public EntitySkeleton(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(20); + super.initEntity(); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -38,12 +45,40 @@ public float getHeight() { } @Override - public String getName() { - return "Skeleton"; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.BONE, 0, 1)); + } + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.ARROW, 0, 1)); + } + + return drops.toArray(new Item[0]); } @Override - public Item[] getDrops() { - return new Item[]{Item.get(Item.BONE, Item.ARROW)}; + public int getKillExperience() { + return 5; + } + + @Override + public void kill() { + if (!this.isAlive()) { + return; + } + + super.kill(); + + if (this.lastDamageCause instanceof EntityDamageByChildEntityEvent) { + Entity damager; + if (((EntityDamageByChildEntityEvent) this.lastDamageCause).getChild() instanceof EntityArrow && (damager = ((EntityDamageByChildEntityEvent) this.lastDamageCause).getDamager()) instanceof Player) { + if (new Vector2(this.x, this.z).distance(new Vector2(damager.x, damager.z)) >= 50) { + ((Player) damager).awardAchievement("snipeSkeleton"); + } + } + } } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySlime.java b/src/main/java/cn/nukkit/entity/mob/EntitySlime.java index 34ba44764b2..a9a60296716 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntitySlime.java +++ b/src/main/java/cn/nukkit/entity/mob/EntitySlime.java @@ -3,46 +3,58 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntitySlime extends EntityMob { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 37; +public class EntitySlime extends EntityJumpingMob { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 37; public EntitySlime(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(16); + public int getNetworkId() { + return NETWORK_ID; } @Override public float getWidth() { - return 2.04f; + return 1f; } @Override public float getHeight() { - return 2.04f; + return 1f; } @Override - public String getName() { - return "Slime"; + public float getLength() { + return 1f; + } + + @Override + protected void initEntity() { + this.setMaxHealth(16); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.SLIMEBALL)}; + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.SLIMEBALL, 0, 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 4; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySnowGolem.java b/src/main/java/cn/nukkit/entity/mob/EntitySnowGolem.java index 7b3967c7f00..f6b4c070e93 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntitySnowGolem.java +++ b/src/main/java/cn/nukkit/entity/mob/EntitySnowGolem.java @@ -1,25 +1,23 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; + +public class EntitySnowGolem extends EntityWalkingMob { + + public static final int NETWORK_ID = 21; -public class EntitySnowGolem extends EntityMob { public EntitySnowGolem(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - - public static final int NETWORK_ID = 21; @Override public int getNetworkId() { return NETWORK_ID; } - @Override - public String getName() { - return "Snow Golem"; - } - @Override public float getWidth() { return 0.7f; @@ -31,8 +29,24 @@ public float getHeight() { } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(4); + super.initEntity(); + this.noFallDamage = true; + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.SNOWBALL, 0, Utils.rand(0, 15))}; + } + + @Override + public int getKillExperience() { + return 0; + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Snow Golem"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySpider.java b/src/main/java/cn/nukkit/entity/mob/EntitySpider.java index efc0c021115..7183edcb238 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntitySpider.java +++ b/src/main/java/cn/nukkit/entity/mob/EntitySpider.java @@ -4,27 +4,22 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntitySpider extends EntityMob implements EntityArthropod { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 35; +public class EntitySpider extends EntityWalkingMob implements EntityArthropod { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 35; public EntitySpider(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(16); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -38,12 +33,28 @@ public float getHeight() { } @Override - public String getName() { - return "Spider"; + public void initEntity() { + this.setMaxHealth(16); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.STRING, Item.SPIDER_EYE)}; + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.STRING, 0, 1)); + } + + for (int i = 0; i < (Utils.rand(0, 2) == 0 ? 1 : 0); i++) { + drops.add(Item.get(Item.SPIDER_EYE, 0, 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityStray.java b/src/main/java/cn/nukkit/entity/mob/EntityStray.java index 868f12614fd..d5622e318c4 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityStray.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityStray.java @@ -1,30 +1,36 @@ package cn.nukkit.entity.mob; +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.event.entity.EntityDamageByChildEntityEvent; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector2; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityStray extends EntityMob implements EntitySmite { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 46; +public class EntityStray extends EntityWalkingMob implements EntitySmite { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 46; public EntityStray(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(20); + super.initEntity(); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -38,12 +44,44 @@ public float getHeight() { } @Override - public String getName() { - return "Stray"; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.BONE, 0, 1)); + } + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.ARROW, 0, 1)); + } + + if (Utils.rand()) { + drops.add(Item.get(Item.ARROW, 18, 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; } @Override - public Item[] getDrops() { - return new Item[]{Item.get(Item.BONE, Item.ARROW)}; + public void kill() { + if (!this.isAlive()) { + return; + } + + super.kill(); + + if (this.lastDamageCause instanceof EntityDamageByChildEntityEvent) { + Entity damager; + if (((EntityDamageByChildEntityEvent) this.lastDamageCause).getChild() instanceof EntityArrow && (damager = ((EntityDamageByChildEntityEvent) this.lastDamageCause).getDamager()) instanceof Player) { + if (new Vector2(this.x, this.z).distance(new Vector2(damager.x, damager.z)) >= 50) { + ((Player) damager).awardAchievement("snipeSkeleton"); + } + } + } } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntitySwimmingMob.java b/src/main/java/cn/nukkit/entity/mob/EntitySwimmingMob.java new file mode 100644 index 00000000000..a21fbe6c550 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/mob/EntitySwimmingMob.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.mob; + +import cn.nukkit.entity.EntitySwimming; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntitySwimmingMob extends EntitySwimming implements EntityMob { + + public EntitySwimmingMob(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/mob/EntityTameableMob.java b/src/main/java/cn/nukkit/entity/mob/EntityTameableMob.java new file mode 100644 index 00000000000..f6ebb06279e --- /dev/null +++ b/src/main/java/cn/nukkit/entity/mob/EntityTameableMob.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity.mob; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityTameableMob extends EntityWalkingMob /*implements EntityTameable*/ { + + public EntityTameableMob(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/mob/EntityVex.java b/src/main/java/cn/nukkit/entity/mob/EntityVex.java index ee869bbde49..ebb50685a15 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityVex.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityVex.java @@ -3,10 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author PikyCZ - */ -public class EntityVex extends EntityMob { +public class EntityVex extends EntityFlyingMob { public static final int NETWORK_ID = 105; @@ -20,23 +17,23 @@ public int getNetworkId() { } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(14); + public float getWidth() { + return 0.8f; } @Override - public float getWidth() { + public float getHeight() { return 0.4f; } @Override - public float getHeight() { - return 0.8f; + public void initEntity() { + this.setMaxHealth(14); + super.initEntity(); } @Override - public String getName() { - return "Vex"; + public int getKillExperience() { + return 3; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityVindicator.java b/src/main/java/cn/nukkit/entity/mob/EntityVindicator.java index 6dc8d09d690..c5450f3d08c 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityVindicator.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityVindicator.java @@ -3,11 +3,9 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityVindicator extends EntityMob { +public class EntityVindicator extends EntityWalkingMob { public static final int NETWORK_ID = 57; @@ -20,12 +18,6 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(24); - } - @Override public float getWidth() { return 0.6f; @@ -37,12 +29,18 @@ public float getHeight() { } @Override - public String getName() { - return "Vindicator"; + protected void initEntity() { + this.setMaxHealth(24); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.IRON_AXE)}; + return new Item[]{Item.get(Item.EMERALD, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityWalkingMob.java b/src/main/java/cn/nukkit/entity/mob/EntityWalkingMob.java new file mode 100644 index 00000000000..ddddfc13fb7 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/mob/EntityWalkingMob.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.mob; + +import cn.nukkit.entity.EntityWalking; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityWalkingMob extends EntityWalking implements EntityMob { + + public EntityWalkingMob(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/mob/EntityWarden.java b/src/main/java/cn/nukkit/entity/mob/EntityWarden.java index 2fa585880c9..2259184cd30 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityWarden.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityWarden.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityWarden extends EntityMob { +public class EntityWarden extends EntityWalkingMob { public static final int NETWORK_ID = 131; @@ -11,6 +11,11 @@ public EntityWarden(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return 5; + } + @Override public int getNetworkId() { return NETWORK_ID; @@ -28,12 +33,7 @@ public float getWidth() { @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(500); - } - - @Override - public String getName() { - return "Warden"; + super.initEntity(); } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityWitch.java b/src/main/java/cn/nukkit/entity/mob/EntityWitch.java index 57fbe490993..82bc34d8b72 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityWitch.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityWitch.java @@ -1,28 +1,24 @@ package cn.nukkit.entity.mob; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityWitch extends EntityMob { +import java.util.ArrayList; +import java.util.List; - public static final int NETWORK_ID = 45; +public class EntityWitch extends EntityWalkingMob { - @Override - public int getNetworkId() { - return NETWORK_ID; - } + public static final int NETWORK_ID = 45; public EntityWitch(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(26); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -36,7 +32,47 @@ public float getHeight() { } @Override - public String getName() { - return "Witch"; + protected void initEntity() { + this.setMaxHealth(26); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (Utils.rand(1, 4) == 1) { + drops.add(Item.get(Item.STICK, 0, Utils.rand(0, 2))); + } + + if (Utils.rand(1, 3) == 1) { + switch (Utils.rand(1, 6)) { + case 1: + drops.add(Item.get(Item.BOTTLE, 0, Utils.rand(0, 2))); + break; + case 2: + drops.add(Item.get(Item.GLOWSTONE_DUST, 0, Utils.rand(0, 2))); + break; + case 3: + drops.add(Item.get(Item.GUNPOWDER, 0, Utils.rand(0, 2))); + break; + case 4: + drops.add(Item.get(Item.REDSTONE, 0, Utils.rand(0, 2))); + break; + case 5: + drops.add(Item.get(Item.SPIDER_EYE, 0, Utils.rand(0, 2))); + break; + case 6: + drops.add(Item.get(Item.SUGAR, 0, Utils.rand(0, 2))); + break; + } + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityWither.java b/src/main/java/cn/nukkit/entity/mob/EntityWither.java index d90cf2929bb..8eed3f7165e 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityWither.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityWither.java @@ -1,25 +1,26 @@ package cn.nukkit.entity.mob; -import cn.nukkit.entity.EntitySmite; +import cn.nukkit.block.Block; +import cn.nukkit.entity.*; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.AddEntityPacket; +import cn.nukkit.network.protocol.DataPacket; -/** - * @author PikyCZ - */ -public class EntityWither extends EntityMob implements EntitySmite { +public class EntityWither extends EntityFlyingMob implements EntityBoss, EntitySmite { public static final int NETWORK_ID = 52; + public EntityWither(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityWither(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - @Override public float getWidth() { return 0.9f; @@ -31,13 +32,51 @@ public float getHeight() { } @Override - protected void initEntity() { + public void initEntity() { + this.setMaxHealth(witherMaxHealth()); super.initEntity(); - this.setMaxHealth(300); + + this.fireProof = true; } @Override - public String getName() { - return "Wither"; + public int getKillExperience() { + return 50; + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.NETHER_STAR, 0, 1), Block.get(Item.WITHER_ROSE, 0).toItem()}; + } + + @Override + protected DataPacket createAddEntityPacket() { + AddEntityPacket addEntity = new AddEntityPacket(); + addEntity.type = NETWORK_ID; + addEntity.entityUniqueId = this.getId(); + addEntity.entityRuntimeId = this.getId(); + addEntity.yaw = (float) this.yaw; + addEntity.headYaw = (float) this.yaw; + addEntity.pitch = (float) this.pitch; + addEntity.x = (float) this.x; + addEntity.y = (float) this.y; + addEntity.z = (float) this.z; + addEntity.speedX = (float) this.motionX; + addEntity.speedY = (float) this.motionY; + addEntity.speedZ = (float) this.motionZ; + addEntity.metadata = this.dataProperties.clone(); + addEntity.attributes = new Attribute[]{Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(witherMaxHealth()).setValue(witherMaxHealth())}; + return addEntity; + } + + private int witherMaxHealth() { + switch (this.getServer().getDifficulty()) { + case 2: + return 450; + case 3: + return 600; + default: + return 300; + } } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityWitherSkeleton.java b/src/main/java/cn/nukkit/entity/mob/EntityWitherSkeleton.java index 4f7f9411ad0..414fa3297a3 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityWitherSkeleton.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityWitherSkeleton.java @@ -1,28 +1,38 @@ package cn.nukkit.entity.mob; +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.event.entity.EntityDamageByChildEntityEvent; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector2; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityWitherSkeleton extends EntityMob implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntityWitherSkeleton extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 48; + public EntityWitherSkeleton(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityWitherSkeleton(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - @Override protected void initEntity() { + this.setMaxHealth(20); super.initEntity(); + + this.fireProof = true; } @Override @@ -35,8 +45,54 @@ public float getHeight() { return 2.4f; } + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.BONE, 0, 1)); + } + + if (Utils.rand(1, 3) == 1) { + drops.add(Item.get(Item.COAL, 0, 1)); + } + + if (Utils.rand(1, 40) == 1) { + drops.add(Item.get(Item.SKULL, 1, 1)); + } + + if (Utils.rand(1, 200) <= 17) { + drops.add(Item.get(Item.STONE_SWORD, Utils.rand(0, 131), 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 5; + } + @Override public String getName() { - return "WitherSkeleton"; + return this.hasCustomName() ? this.getNameTag() : "Wither Skeleton"; + } + + @Override + public void kill() { + if (!this.isAlive()) { + return; + } + + super.kill(); + + if (this.lastDamageCause instanceof EntityDamageByChildEntityEvent) { + Entity damager; + if (((EntityDamageByChildEntityEvent) this.lastDamageCause).getChild() instanceof EntityArrow && (damager = ((EntityDamageByChildEntityEvent) this.lastDamageCause).getDamager()) instanceof Player) { + if (new Vector2(this.x, this.z).distance(new Vector2(damager.x, damager.z)) >= 50) { + ((Player) damager).awardAchievement("snipeSkeleton"); + } + } + } } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityZoglin.java b/src/main/java/cn/nukkit/entity/mob/EntityZoglin.java index 1be82ee3714..9626d452760 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityZoglin.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityZoglin.java @@ -1,12 +1,14 @@ package cn.nukkit.entity.mob; +import cn.nukkit.entity.EntitySmite; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; /** * @author Erik Miller | EinBexiii */ -public class EntityZoglin extends EntityMob { +public class EntityZoglin extends EntityWalkingMob implements EntitySmite { public final static int NETWORK_ID = 126; @@ -19,10 +21,15 @@ public EntityZoglin(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return this.isBaby() ? 1 : Utils.rand(1, 3); + } + @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(40); + super.initEntity(); } @Override @@ -34,9 +41,4 @@ public float getWidth() { public float getHeight() { return 0.9f; } - - @Override - public String getName() { - return "Zoglin"; - } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityZombie.java b/src/main/java/cn/nukkit/entity/mob/EntityZombie.java index c47e2ffaa3d..1f931b738ac 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityZombie.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityZombie.java @@ -4,26 +4,17 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * Created by Dr. Nick Doran on 4/23/2017. - */ -public class EntityZombie extends EntityMob implements EntitySmite { +public class EntityZombie extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 32; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityZombie(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -37,7 +28,13 @@ public float getHeight() { } @Override - public String getName() { - return "Zombie"; + protected void initEntity() { + this.setMaxHealth(20); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 12 : 5; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityZombiePigman.java b/src/main/java/cn/nukkit/entity/mob/EntityZombiePigman.java index 15b5280145f..98d127a9b8d 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityZombiePigman.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityZombiePigman.java @@ -1,43 +1,68 @@ package cn.nukkit.entity.mob; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityZombiePigman extends EntityMob implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntityZombiePigman extends EntityWalkingMob implements EntitySmite { public static final int NETWORK_ID = 36; + public EntityZombiePigman(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + @Override public int getNetworkId() { return NETWORK_ID; } - public EntityZombiePigman(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); + @Override + public float getWidth() { + return 0.6f; + } + + @Override + public float getHeight() { + return 1.95f; } @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(20); + super.initEntity(); + + this.fireProof = true; } @Override - public float getWidth() { - return 0.6f; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + drops.add(Item.get(Item.ROTTEN_FLESH, 0, Utils.rand(0, 1))); + drops.add(Item.get(Item.GOLD_NUGGET, 0, Utils.rand(0, 1))); + + for (int i = 0; i < (Utils.rand(0, 101) <= 9 ? 1 : 0); i++) { + drops.add(Item.get(Item.GOLD_SWORD, Utils.rand(20, 30), 1)); + } + } + + return drops.toArray(new Item[0]); } @Override - public float getHeight() { - return 1.95f; + public int getKillExperience() { + return this.isBaby() ? 0 : 5; } @Override public String getName() { - return "ZombiePigman"; + return this.hasCustomName() ? this.getNameTag() : "Zombified Piglin"; } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityZombieVillager.java b/src/main/java/cn/nukkit/entity/mob/EntityZombieVillager.java index 25f84616d30..08e3cd9ad14 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityZombieVillager.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityZombieVillager.java @@ -4,7 +4,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityZombieVillager extends EntityMob implements EntitySmite { +public class EntityZombieVillager extends EntityZombieVillagerV1 implements EntitySmite { public static final int NETWORK_ID = 116; @@ -16,25 +16,4 @@ public EntityZombieVillager(FullChunk chunk, CompoundTag nbt) { public int getNetworkId() { return NETWORK_ID; } - - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); - } - - @Override - public float getWidth() { - return 0.6f; - } - - @Override - public float getHeight() { - return 1.95f; - } - - @Override - public String getName() { - return "Zombie Villager"; - } } diff --git a/src/main/java/cn/nukkit/entity/mob/EntityZombieVillagerV1.java b/src/main/java/cn/nukkit/entity/mob/EntityZombieVillagerV1.java index f6d63d33271..13e60b026ab 100644 --- a/src/main/java/cn/nukkit/entity/mob/EntityZombieVillagerV1.java +++ b/src/main/java/cn/nukkit/entity/mob/EntityZombieVillagerV1.java @@ -1,43 +1,23 @@ package cn.nukkit.entity.mob; -import cn.nukkit.entity.EntitySmite; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author PikyCZ - */ -public class EntityZombieVillagerV1 extends EntityMob implements EntitySmite { +public class EntityZombieVillagerV1 extends EntityZombie { public static final int NETWORK_ID = 44; - @Override - public int getNetworkId() { - return NETWORK_ID; - } - public EntityZombieVillagerV1(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(20); - } - - @Override - public float getWidth() { - return 0.6f; - } - - @Override - public float getHeight() { - return 1.95f; + public int getNetworkId() { + return NETWORK_ID; } @Override public String getName() { - return "Zombie Villager"; + return this.hasCustomName() ? this.getNameTag() : "Zombie Villager"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityAllay.java b/src/main/java/cn/nukkit/entity/passive/EntityAllay.java index 21733cbf4a9..7fbae4ceac8 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityAllay.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityAllay.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityAllay extends EntityAnimal { +public class EntityAllay extends EntityFlyingAnimal { public static final int NETWORK_ID = 134; @@ -17,23 +17,23 @@ public int getNetworkId() { } @Override - public float getHeight() { + public float getWidth() { return 0.6f; } @Override - public float getWidth() { + public float getHeight() { return 0.6f; } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(20); + super.initEntity(); } @Override - public String getName() { - return "Allay"; + public int getKillExperience() { + return 0; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java index a8943d2b50c..1be98d23874 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityAnimal.java @@ -1,28 +1,4 @@ package cn.nukkit.entity.passive; -import cn.nukkit.entity.Entity; -import cn.nukkit.entity.EntityAgeable; -import cn.nukkit.entity.EntityCreature; -import cn.nukkit.item.Item; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.nbt.tag.CompoundTag; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public abstract class EntityAnimal extends EntityCreature implements EntityAgeable { - public EntityAnimal(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - - @Override - public boolean isBaby() { - return this.getDataFlag(DATA_FLAGS, Entity.DATA_FLAG_BABY); - } - - public boolean isBreedingItem(Item item) { - return item.getId() == Item.WHEAT; //default - } - +public interface EntityAnimal { } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityAxolotl.java b/src/main/java/cn/nukkit/entity/passive/EntityAxolotl.java index 428bb0014a6..05290633d52 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityAxolotl.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityAxolotl.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityAxolotl extends EntityAnimal { +public class EntityAxolotl extends EntityFish { public static final int NETWORK_ID = 130; @@ -12,13 +12,14 @@ public EntityAxolotl(FullChunk chunk, CompoundTag nbt) { } @Override - public int getNetworkId() { - return NETWORK_ID; + public void initEntity() { + this.setMaxHealth(14); + super.initEntity(); } @Override - public float getHeight() { - return 0.42f; + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -27,13 +28,12 @@ public float getWidth() { } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(14); + public float getHeight() { + return 0.42f; } @Override - public String getName() { - return "Axolotl"; + int getBucketMeta() { + return 12; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityBat.java b/src/main/java/cn/nukkit/entity/passive/EntityBat.java index de801a1efe8..b755369ac26 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityBat.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityBat.java @@ -3,10 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author PikyCZ - */ -public class EntityBat extends EntityAnimal { +public class EntityBat extends EntityFlyingAnimal { public static final int NETWORK_ID = 19; @@ -31,7 +28,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(6); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return 0; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityBee.java b/src/main/java/cn/nukkit/entity/passive/EntityBee.java index 8bd10b0db07..14b9cacbcd5 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityBee.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityBee.java @@ -1,13 +1,11 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.mob.EntityFlyingMob; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author Kaooot - * @version 1.0 - */ -public class EntityBee extends EntityAnimal { +public class EntityBee extends EntityFlyingMob { public static final int NETWORK_ID = 122; @@ -15,6 +13,11 @@ public EntityBee(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + @Override public int getNetworkId() { return NETWORK_ID; @@ -23,22 +26,22 @@ public int getNetworkId() { @Override public float getWidth() { if (this.isBaby()) { - return 0.35f; + return 0.275f; } - return 0.7f; + return 0.55f; } @Override public float getHeight() { if (this.isBaby()) { - return 0.30f; + return 0.25f; } - return 0.6f; + return 0.5f; } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(10); + super.initEntity(); } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityCamel.java b/src/main/java/cn/nukkit/entity/passive/EntityCamel.java new file mode 100644 index 00000000000..ae194c133f2 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityCamel.java @@ -0,0 +1,46 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; + +public class EntityCamel extends EntityWalkingAnimal { + + public static final int NETWORK_ID = 138; + + public EntityCamel(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public void initEntity() { + this.setMaxHealth(32); + super.initEntity(); + } + + @Override + public float getWidth() { + if (this.isBaby()) { + return 0.85f; + } + return 1.77f; + } + + @Override + public float getHeight() { + if (this.isBaby()) { + return 1.1875f; + } + return 2.375f; + } + + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityCat.java b/src/main/java/cn/nukkit/entity/passive/EntityCat.java index 6381ea60561..4f5e183cae8 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityCat.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityCat.java @@ -1,9 +1,14 @@ package cn.nukkit.entity.passive; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -public class EntityCat extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityCat extends EntityWalkingAnimal { public static final int NETWORK_ID = 75; @@ -34,7 +39,26 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(10); + super.initEntity(); + this.noFallDamage = true; + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.STRING, 0, 1)); + } + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityChicken.java b/src/main/java/cn/nukkit/entity/passive/EntityChicken.java index 844461254f8..d9bbf09a666 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityChicken.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityChicken.java @@ -1,13 +1,15 @@ package cn.nukkit.entity.passive; +import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityChicken extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityChicken extends EntityWalkingAnimal { public static final int NETWORK_ID = 10; @@ -15,6 +17,11 @@ public EntityChicken(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -32,30 +39,48 @@ public float getHeight() { } @Override - public String getName() { - return "Chicken"; + public float getDrag() { + return 0.2f; } @Override - public Item[] getDrops() { - return new Item[]{Item.get(((this.isOnFire()) ? Item.COOKED_CHICKEN : Item.RAW_CHICKEN)), Item.get(Item.FEATHER)}; + public float getGravity() { + return 0.08f; //Should be lower but that breaks jumping } @Override - public int getNetworkId() { - return NETWORK_ID; + public void initEntity() { + this.setMaxHealth(4); + super.initEntity(); + this.noFallDamage = true; } @Override - protected void initEntity() { - super.initEntity(); - setMaxHealth(4); + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.FEATHER, 0, 1)); + } + + drops.add(Item.get(this.isOnFire() ? Item.COOKED_CHICKEN : Item.RAW_CHICKEN, 0, 1)); + } + + return drops.toArray(new Item[0]); } @Override - public boolean isBreedingItem(Item item) { - int id = item.getId(); + public boolean attack(EntityDamageEvent ev) { + if (ev.getCause() != EntityDamageEvent.DamageCause.FALL) { + return super.attack(ev); + } - return id == Item.WHEAT_SEEDS || id == Item.MELON_SEEDS || id == Item.PUMPKIN_SEEDS || id == Item.BEETROOT_SEEDS; + return false; + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityCod.java b/src/main/java/cn/nukkit/entity/passive/EntityCod.java index 795174f7e3c..b637454fbad 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityCod.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityCod.java @@ -1,12 +1,11 @@ package cn.nukkit.entity.passive; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityCod extends EntityAnimal { +public class EntityCod extends EntityFish { public static final int NETWORK_ID = 112; @@ -15,12 +14,13 @@ public EntityCod(FullChunk chunk, CompoundTag nbt) { } @Override - public int getNetworkId() { - return NETWORK_ID; + int getBucketMeta() { + return 2; } - public String getName() { - return "Cod"; + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -35,7 +35,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(3); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.RAW_FISH, 0, 1), Item.get(Item.BONE, 0, Utils.rand(0, 2))}; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityCow.java b/src/main/java/cn/nukkit/entity/passive/EntityCow.java index 17f13ac2640..71fcd7bbc27 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityCow.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityCow.java @@ -3,11 +3,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityCow extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityCow extends EntityWalkingAnimal { public static final int NETWORK_ID = 11; @@ -15,6 +16,11 @@ public EntityCow(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -32,23 +38,30 @@ public float getHeight() { } @Override - public String getName() { - return "Cow"; + public void initEntity() { + this.setMaxHealth(10); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.LEATHER), Item.get(((this.isOnFire()) ? Item.COOKED_BEEF : Item.RAW_BEEF))}; - } + List drops = new ArrayList<>(); - @Override - public int getNetworkId() { - return NETWORK_ID; + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + + for (int i = 0; i < Utils.rand(1, 3); i++) { + drops.add(Item.get(this.isOnFire() ? Item.STEAK : Item.RAW_BEEF, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(10); + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityDolphin.java b/src/main/java/cn/nukkit/entity/passive/EntityDolphin.java index 27585ac8727..db197330417 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityDolphin.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityDolphin.java @@ -3,11 +3,9 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityDolphin extends EntityAnimal { +public class EntityDolphin extends EntityWaterAnimal { public static final int NETWORK_ID = 31; @@ -20,10 +18,6 @@ public int getNetworkId() { return NETWORK_ID; } - public String getName() { - return "Dolphin"; - } - @Override public float getWidth() { return 0.9f; @@ -36,12 +30,17 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(10); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.RAW_FISH)}; + return new Item[]{Item.get(Item.RAW_FISH, 0, Utils.rand(0, 1))}; + } + + @Override + public int getKillExperience() { + return 0; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityDonkey.java b/src/main/java/cn/nukkit/entity/passive/EntityDonkey.java index 143bbaa6f58..d23a29c22e0 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityDonkey.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityDonkey.java @@ -3,11 +3,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityDonkey extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityDonkey extends EntityHorseBase { public static final int NETWORK_ID = 24; @@ -38,12 +39,20 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(15); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.LEATHER)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityFish.java b/src/main/java/cn/nukkit/entity/passive/EntityFish.java new file mode 100644 index 00000000000..34cc5122446 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityFish.java @@ -0,0 +1,40 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; + +public abstract class EntityFish extends EntityWaterAnimal { + + public EntityFish(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + + @Override + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { + if (item.getId() == Item.BUCKET && (item.getDamage() == 0 || item.getDamage() == 8) && this.isInsideOfWater()) { + this.close(); + if (item.getCount() <= 1) { + player.getInventory().setItemInHand(Item.get(Item.BUCKET, this.getBucketMeta(), 1)); + return false; + } else { + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + } + player.getInventory().addItem(Item.get(Item.BUCKET, this.getBucketMeta(), 1)); + return true; + } + } + return super.onInteract(player, item, clickedPos); + } + + abstract int getBucketMeta(); +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityFlyingAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityFlyingAnimal.java new file mode 100644 index 00000000000..b3a57050512 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityFlyingAnimal.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.entity.EntityFlying; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityFlyingAnimal extends EntityFlying implements EntityAnimal { + + public EntityFlyingAnimal(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityFox.java b/src/main/java/cn/nukkit/entity/passive/EntityFox.java index 825c17108da..5bc676a787a 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityFox.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityFox.java @@ -2,12 +2,9 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author Kaooot - * @version 1.0 - */ -public class EntityFox extends EntityAnimal { +public class EntityFox extends EntityWalkingAnimal { public static final int NETWORK_ID = 121; @@ -31,13 +28,13 @@ public float getHeight() { } @Override - protected void initEntity() { - super.initEntity(); + public void initEntity() { this.setMaxHealth(20); + super.initEntity(); } @Override - public String getName() { - return "Fox"; + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 2); } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityFrog.java b/src/main/java/cn/nukkit/entity/passive/EntityFrog.java index a1c7492d6ee..828c2947d61 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityFrog.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityFrog.java @@ -2,8 +2,9 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -public class EntityFrog extends EntityAnimal { +public class EntityFrog extends EntityJumpingAnimal { public static final int NETWORK_ID = 132; @@ -11,6 +12,11 @@ public EntityFrog(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + @Override public int getNetworkId() { return NETWORK_ID; @@ -28,12 +34,7 @@ public float getWidth() { @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(10); - } - - @Override - public String getName() { - return "Frog"; + super.initEntity(); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityGlowSquid.java b/src/main/java/cn/nukkit/entity/passive/EntityGlowSquid.java index c408c718b96..ed7e6621e24 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityGlowSquid.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityGlowSquid.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityGlowSquid extends EntityWaterAnimal { +public class EntityGlowSquid extends EntitySquid { public static final int NETWORK_ID = 129; @@ -15,25 +15,4 @@ public EntityGlowSquid(FullChunk chunk, CompoundTag nbt) { public int getNetworkId() { return NETWORK_ID; } - - @Override - public float getHeight() { - return 0.95f; - } - - @Override - public float getWidth() { - return 0.475f; - } - - @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(10); - } - - @Override - public String getName() { - return "GlowSquid"; - } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityGoat.java b/src/main/java/cn/nukkit/entity/passive/EntityGoat.java index e337b8ce825..8232c9fbd37 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityGoat.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityGoat.java @@ -2,8 +2,9 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -public class EntityGoat extends EntityAnimal { +public class EntityGoat extends EntityWalkingAnimal { public static final int NETWORK_ID = 128; @@ -12,28 +13,34 @@ public EntityGoat(FullChunk chunk, CompoundTag nbt) { } @Override - public int getNetworkId() { - return NETWORK_ID; + public void initEntity() { + this.setMaxHealth(10); + super.initEntity(); } @Override - public float getHeight() { - return this.isBaby() ? 0.65f : 1.3f; + public float getWidth() { + if (this.isBaby()) { + return 0.65f; + } + return 1.3f; } @Override - public float getWidth() { - return this.isBaby() ? 0.45f : 0.9f; + public float getHeight() { + if (this.isBaby()) { + return 0.45f; + } + return 0.9f; } @Override - protected void initEntity() { - super.initEntity(); - this.setMaxHealth(10); + public int getKillExperience() { + return Utils.rand(1, 3); } @Override - public String getName() { - return "Goat"; + public int getNetworkId() { + return NETWORK_ID; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityHorse.java b/src/main/java/cn/nukkit/entity/passive/EntityHorse.java index a0c84895329..20ede148e77 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityHorse.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityHorse.java @@ -1,16 +1,23 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.data.IntEntityData; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityHorse extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityHorse extends EntityHorseBase { public static final int NETWORK_ID = 23; + public int variant; + + private static final int[] VARIANTS = {0, 1, 2, 3, 4, 5, 6, 256, 257, 258, 259, 260, 261, 262, 512, 513, 514, 515, 516, 517, 518, + 768, 769, 770, 771, 772, 773, 774, 1024, 1025, 1026, 1027, 1028, 1029, 1030}; + public EntityHorse(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -38,12 +45,39 @@ public float getHeight() { @Override public void initEntity() { + this.setMaxHealth(Utils.rand(15, 30)); //TODO: save this super.initEntity(); - this.setMaxHealth(15); + + if (this.namedTag.contains("Variant")) { + this.variant = this.namedTag.getInt("Variant"); + } else { + this.variant = getRandomVariant(); + } + + this.setDataProperty(new IntEntityData(DATA_VARIANT, this.variant)); + } + + @Override + public void saveNBT() { + super.saveNBT(); + + this.namedTag.putInt("Variant", this.variant); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.LEATHER)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + } + + return drops.toArray(new Item[0]); + } + + private static int getRandomVariant() { + return VARIANTS[Utils.rand(0, VARIANTS.length - 1)]; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityHorseBase.java b/src/main/java/cn/nukkit/entity/passive/EntityHorseBase.java new file mode 100644 index 00000000000..5f33a61e462 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityHorseBase.java @@ -0,0 +1,16 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityHorseBase extends EntityWalkingAnimal /*implements EntityRideable, EntityControllable*/ { + + public EntityHorseBase(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public int getKillExperience() { + return 0; + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityIronGolem.java b/src/main/java/cn/nukkit/entity/passive/EntityIronGolem.java new file mode 100644 index 00000000000..746b1ec2aa5 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityIronGolem.java @@ -0,0 +1,94 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.Attribute; +import cn.nukkit.entity.mob.EntityWalkingMob; +import cn.nukkit.item.Item; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.UpdateAttributesPacket; +import cn.nukkit.utils.Utils; + +import java.util.ArrayList; +import java.util.List; + +public class EntityIronGolem extends EntityWalkingMob { + + public static final int NETWORK_ID = 20; + + public EntityIronGolem(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } + + @Override + public float getWidth() { + return 1.4f; + } + + @Override + public float getHeight() { + return 2.9f; + } + + @Override + public void initEntity() { + this.setMaxHealth(100); + super.initEntity(); + this.noFallDamage = true; + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + for (int i = 0; i < Utils.rand(3, 5); i++) { + drops.add(Item.get(Item.IRON_INGOT, 0, 1)); + } + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.POPPY, 0, 1)); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return 0; + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Iron Golem"; + } + + @Override + public void spawnTo(Player player) { + super.spawnTo(player); + + this.sendHealth(); + } + + @Override + public void setHealth(float health) { + super.setHealth(health); + + this.sendHealth(); + } + + private void sendHealth() { + if (this.isAlive()) { + UpdateAttributesPacket pk = new UpdateAttributesPacket(); + int max = this.getMaxHealth(); + pk.entries = new Attribute[]{Attribute.getAttribute(Attribute.MAX_HEALTH).setMaxValue(max).setValue(this.health < max ? this.health : max)}; + pk.entityId = this.id; + Server.broadcastPacket(this.getViewers().values(), pk); + } + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityJumpingAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityJumpingAnimal.java new file mode 100644 index 00000000000..e81cc88c585 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityJumpingAnimal.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.entity.EntityJumping; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityJumpingAnimal extends EntityJumping implements EntityAnimal { + + public EntityJumpingAnimal(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityLlama.java b/src/main/java/cn/nukkit/entity/passive/EntityLlama.java index 6536a79ee0c..62ecda1b7fa 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityLlama.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityLlama.java @@ -1,15 +1,19 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.data.IntEntityData; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityLlama extends EntityAnimal { +public class EntityLlama extends EntityHorseBase { public static final int NETWORK_ID = 29; + private int variant; + + private static final int[] VARIANTS = {0, 1, 2, 3}; + public EntityLlama(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -35,17 +39,33 @@ public float getHeight() { return 1.87f; } + @Override - public float getEyeHeight() { - if (this.isBaby()) { - return 0.65f; + public void initEntity() { + this.setMaxHealth(15); + super.initEntity(); + + if (this.namedTag.contains("Variant")) { + this.variant = this.namedTag.getInt("Variant"); + } else { + this.variant = getRandomVariant(); } - return 1.2f; + + this.setDataProperty(new IntEntityData(DATA_VARIANT, this.variant)); } @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(15); + public void saveNBT() { + super.saveNBT(); + this.namedTag.putInt("Variant", this.variant); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.LEATHER, 0, Utils.rand(0, 2))}; + } + + private static int getRandomVariant() { + return VARIANTS[Utils.rand(0, VARIANTS.length - 1)]; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityMooshroom.java b/src/main/java/cn/nukkit/entity/passive/EntityMooshroom.java index 9d2e2cd76e6..015f1cb39cd 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityMooshroom.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityMooshroom.java @@ -1,13 +1,16 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.data.IntEntityData; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityMooshroom extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityMooshroom extends EntityWalkingAnimal { public static final int NETWORK_ID = 16; @@ -15,9 +18,14 @@ public EntityMooshroom(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { - if (isBaby()) { + if (this.isBaby()) { return 0.45f; } return 0.9f; @@ -25,30 +33,61 @@ public float getWidth() { @Override public float getHeight() { - if (isBaby()) { + if (this.isBaby()) { return 0.7f; } return 1.4f; } @Override - public String getName() { - return "Mooshroom"; + public void initEntity() { + this.setMaxHealth(10); + super.initEntity(); + + if (this.namedTag.contains("Variant")) { + this.setBrown(this.namedTag.getInt("Variant") == 1); + } } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.LEATHER), Item.get(Item.RAW_BEEF)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + + for (int i = 0; i < Utils.rand(1, 3); i++) { + drops.add(Item.get(this.isOnFire() ? Item.STEAK : Item.RAW_BEEF, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } @Override - public int getNetworkId() { - return NETWORK_ID; + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } @Override - protected void initEntity() { - super.initEntity(); - setMaxHealth(10); + public void saveNBT() { + super.saveNBT(); + this.namedTag.putInt("Variant", this.isBrown() ? 1 : 0); + } + + @Override + public void onStruckByLightning(Entity lightning) { + this.setBrown(!this.isBrown()); + super.onStruckByLightning(lightning); + } + + public boolean isBrown() { + return this.getDataPropertyInt(DATA_VARIANT) == 1; + } + + public void setBrown(boolean brown) { + this.setDataProperty(new IntEntityData(DATA_VARIANT, brown ? 1 : 0)); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityMule.java b/src/main/java/cn/nukkit/entity/passive/EntityMule.java index 29303769205..eaa3afce09d 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityMule.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityMule.java @@ -1,13 +1,14 @@ package cn.nukkit.entity.passive; +import cn.nukkit.utils.Utils; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * @author PikyCZ - */ -public class EntityMule extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityMule extends EntityHorseBase { public static final int NETWORK_ID = 25; @@ -15,11 +16,6 @@ public EntityMule(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - @Override - public Item[] getDrops() { - return new Item[]{Item.get(Item.LEATHER)}; - } - @Override public int getNetworkId() { return NETWORK_ID; @@ -43,7 +39,20 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(15); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityNPC.java b/src/main/java/cn/nukkit/entity/passive/EntityNPC.java index f84595c72f1..93c6033d73d 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityNPC.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityNPC.java @@ -4,5 +4,4 @@ * Created by Pub4Game on 21.06.2016. */ public interface EntityNPC { - } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityOcelot.java b/src/main/java/cn/nukkit/entity/passive/EntityOcelot.java index 552e5fcc227..47d4a7bfad4 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityOcelot.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityOcelot.java @@ -1,13 +1,10 @@ package cn.nukkit.entity.passive; -import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityOcelot extends EntityAnimal { +public class EntityOcelot extends EntityTameableAnimal { public static final int NETWORK_ID = 22; @@ -15,6 +12,11 @@ public EntityOcelot(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -32,23 +34,14 @@ public float getHeight() { } @Override - public String getName() { - return "Ocelot"; - } - - @Override - public int getNetworkId() { - return NETWORK_ID; - } - - @Override - public void initEntity() { + protected void initEntity() { + this.setMaxHealth(10); super.initEntity(); - setMaxHealth(10); + this.noFallDamage = true; } @Override - public boolean isBreedingItem(Item item) { - return item.getId() == Item.RAW_FISH; + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityPanda.java b/src/main/java/cn/nukkit/entity/passive/EntityPanda.java index c9520c750cc..f40b46f87b1 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityPanda.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityPanda.java @@ -1,9 +1,10 @@ package cn.nukkit.entity.passive; +import cn.nukkit.utils.Utils; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityPanda extends EntityAnimal { +public class EntityPanda extends EntityWalkingAnimal { public static final int NETWORK_ID = 113; @@ -33,7 +34,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(20); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityParrot.java b/src/main/java/cn/nukkit/entity/passive/EntityParrot.java index 8195000fb54..1809f5f1d76 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityParrot.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityParrot.java @@ -1,16 +1,19 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.data.IntEntityData; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityParrot extends EntityAnimal { +public class EntityParrot extends EntityFlyingAnimal { public static final int NETWORK_ID = 30; + private int variant; + + private static final int[] VARIANTS = {0, 1, 2, 3, 4}; + public EntityParrot(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @@ -20,10 +23,6 @@ public int getNetworkId() { return NETWORK_ID; } - public String getName() { - return "Parrot"; - } - @Override public float getWidth() { return 0.5f; @@ -36,12 +35,35 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(6); + super.initEntity(); + + if (this.namedTag.contains("Variant")) { + this.variant = this.namedTag.getInt("Variant"); + } else { + this.variant = getRandomVariant(); + } + + this.setDataProperty(new IntEntityData(DATA_VARIANT, this.variant)); + } + + @Override + public void saveNBT() { + super.saveNBT(); + this.namedTag.putInt("Variant", this.variant); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.FEATHER)}; + return new Item[]{Item.get(Item.FEATHER, 0, Utils.rand(1, 2))}; + } + + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + + private static int getRandomVariant() { + return VARIANTS[Utils.rand(0, VARIANTS.length - 1)]; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityPig.java b/src/main/java/cn/nukkit/entity/passive/EntityPig.java index 4b5ef278fff..d14938ec796 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityPig.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityPig.java @@ -1,13 +1,17 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.*; +import cn.nukkit.entity.mob.EntityZombiePigman; +import cn.nukkit.event.entity.CreatureSpawnEvent; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityPig extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityPig extends EntityWalkingAnimal /*implements EntityRideable, EntityControllable*/ { public static final int NETWORK_ID = 12; @@ -15,6 +19,11 @@ public EntityPig(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -33,29 +42,71 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(10); + super.initEntity(); } @Override - public String getName() { - return "Pig"; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(1, 3); i++) { + drops.add(Item.get(this.isOnFire() ? Item.COOKED_PORKCHOP : Item.RAW_PORKCHOP, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } - @Override - public Item[] getDrops() { - return new Item[]{Item.get(((this.isOnFire()) ? Item.COOKED_PORKCHOP : Item.RAW_PORKCHOP))}; + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } @Override - public int getNetworkId() { - return NETWORK_ID; + public void onStruckByLightning(Entity lightning) { + Entity ent = Entity.createEntity("ZombiePigman", this); + if (ent != null) { + CreatureSpawnEvent cse = new CreatureSpawnEvent(EntityZombiePigman.NETWORK_ID, this, ent.namedTag, CreatureSpawnEvent.SpawnReason.LIGHTNING); + this.getServer().getPluginManager().callEvent(cse); + + if (cse.isCancelled()) { + ent.close(); + return; + } + + ent.yaw = this.yaw; + ent.pitch = this.pitch; + ent.setImmobile(this.isImmobile()); + if (this.hasCustomName()) { + ent.setNameTag(this.getNameTag()); + ent.setNameTagVisible(this.isNameTagVisible()); + ent.setNameTagAlwaysVisible(this.isNameTagAlwaysVisible()); + } + /*if (this.isBaby()) { + ent.setBaby(); // TODO + }*/ + + this.close(); + ent.spawnToAll(); + } else { + super.onStruckByLightning(lightning); + } } @Override - public boolean isBreedingItem(Item item) { - int id = item.getId(); + public void updatePassengers() { + if (this.passengers.isEmpty()) { + return; + } - return id == Item.CARROT || id == Item.POTATO || id == Item.BEETROOT; + for (Entity passenger : new ArrayList<>(this.passengers)) { + if (!passenger.isAlive() || this.isInsideOfWater()) { + dismountEntity(passenger); + continue; + } + + updatePassengerPosition(passenger); + } } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityPolarBear.java b/src/main/java/cn/nukkit/entity/passive/EntityPolarBear.java index 21f29c0458d..6ae5e4b0ae4 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityPolarBear.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityPolarBear.java @@ -1,13 +1,15 @@ package cn.nukkit.entity.passive; +import cn.nukkit.entity.mob.EntityWalkingMob; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityPolarBear extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityPolarBear extends EntityWalkingMob { public static final int NETWORK_ID = 28; @@ -38,12 +40,29 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(30); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.RAW_FISH), Item.get(Item.RAW_SALMON)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + drops.add(Item.get(Item.RAW_FISH, 0, Utils.rand(0, 2))); + drops.add(Item.get(Item.RAW_SALMON, 0, Utils.rand(0, 2))); + } + + return drops.toArray(new Item[0]); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Polar Bear"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityPufferfish.java b/src/main/java/cn/nukkit/entity/passive/EntityPufferfish.java index 727a67fc51b..1bb098f5ff1 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityPufferfish.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityPufferfish.java @@ -1,26 +1,34 @@ package cn.nukkit.entity.passive; +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.data.ByteEntityData; +import cn.nukkit.event.entity.EntityDamageByEntityEvent; +import cn.nukkit.event.entity.EntityDamageEvent; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.potion.Effect; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityPufferfish extends EntityAnimal { +public class EntityPufferfish extends EntityFish { public static final int NETWORK_ID = 108; + private int puffed = 0; + public EntityPufferfish(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - public int getNetworkId() { - return NETWORK_ID; + int getBucketMeta() { + return 5; } - public String getName() { - return "Pufferfish"; + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -35,7 +43,50 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(3); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.PUFFERFISH, 0, 1), Item.get(Item.BONE, 0, Utils.rand(0, 2))}; + } + + @Override + public boolean attack(EntityDamageEvent ev) { + super.attack(ev); + + if (ev instanceof EntityDamageByEntityEvent && ev.getCause() == EntityDamageEvent.DamageCause.ENTITY_ATTACK) { + Entity damager = ((EntityDamageByEntityEvent) ev).getDamager(); + if (damager instanceof Player) { + if (this.isPuffed()) return true; + this.puffed = 200; + damager.addEffect(Effect.getEffect(Effect.POISON).setDuration(200)); + this.setDataProperty(new ByteEntityData(DATA_PUFFERFISH_SIZE, 2)); + } + } + + return true; + } + + @Override + public boolean entityBaseTick(int tickDiff) { + if (this.puffed > 0) { + this.puffed--; + + if (this.puffed <= 0) { + this.setDataProperty(new ByteEntityData(DATA_PUFFERFISH_SIZE, 0)); + } + } + + return super.entityBaseTick(tickDiff); + } + + /** + * Is puffed + * @return whether the pufferfish is puffed + */ + public boolean isPuffed() { + return this.puffed > 0; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityRabbit.java b/src/main/java/cn/nukkit/entity/passive/EntityRabbit.java index 8917295a505..b7bf7283a3b 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityRabbit.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityRabbit.java @@ -1,13 +1,14 @@ package cn.nukkit.entity.passive; +import cn.nukkit.utils.Utils; import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityRabbit extends EntityAnimal { +import java.util.ArrayList; +import java.util.List; + +public class EntityRabbit extends EntityJumpingAnimal { public static final int NETWORK_ID = 18; @@ -15,6 +16,11 @@ public EntityRabbit(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -32,23 +38,29 @@ public float getHeight() { } @Override - public String getName() { - return "Rabbit"; + public void initEntity() { + this.setMaxHealth(3); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(((this.isOnFire()) ? Item.COOKED_RABBIT : Item.RAW_RABBIT)), Item.get(Item.RABBIT_HIDE), Item.get(Item.RABBIT_FOOT)}; - } + List drops = new ArrayList<>(); - @Override - public int getNetworkId() { - return NETWORK_ID; + if (!this.isBaby()) { + drops.add(Item.get(Item.RABBIT_HIDE, 0, Utils.rand(0, 1))); + drops.add(Item.get(this.isOnFire() ? Item.COOKED_RABBIT : Item.RAW_RABBIT, 0, Utils.rand(0, 1))); + + for (int i = 0; i < (Utils.rand(0, 101) <= 9 ? 1 : 0); i++) { + drops.add(Item.get(Item.RABBIT_FOOT, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } @Override - protected void initEntity() { - super.initEntity(); - setMaxHealth(10); + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntitySalmon.java b/src/main/java/cn/nukkit/entity/passive/EntitySalmon.java index 30709191046..be299167080 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntitySalmon.java +++ b/src/main/java/cn/nukkit/entity/passive/EntitySalmon.java @@ -1,12 +1,11 @@ package cn.nukkit.entity.passive; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntitySalmon extends EntityAnimal { +public class EntitySalmon extends EntityFish { public static final int NETWORK_ID = 109; @@ -15,12 +14,13 @@ public EntitySalmon(FullChunk chunk, CompoundTag nbt) { } @Override - public int getNetworkId() { - return NETWORK_ID; + int getBucketMeta() { + return 3; } - public String getName() { - return "Salmon"; + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -35,7 +35,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(3); + super.initEntity(); + } + + @Override + public Item[] getDrops() { + return new Item[]{Item.get(Item.RAW_SALMON, 0, 1), Item.get(Item.BONE, 0, Utils.rand(0, 2))}; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntitySheep.java b/src/main/java/cn/nukkit/entity/passive/EntitySheep.java index 8a50ee51881..3d2c6653937 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntitySheep.java +++ b/src/main/java/cn/nukkit/entity/passive/EntitySheep.java @@ -2,30 +2,33 @@ import cn.nukkit.Player; import cn.nukkit.entity.data.ByteEntityData; -import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.item.Item; import cn.nukkit.item.ItemDye; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.utils.DyeColor; +import cn.nukkit.utils.Utils; -import java.util.concurrent.ThreadLocalRandom; +import java.util.ArrayList; +import java.util.List; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntitySheep extends EntityAnimal { +public class EntitySheep extends EntityWalkingAnimal { public static final int NETWORK_ID = 13; - public boolean sheared = false; - public int color = 0; + private boolean sheared = false; + private int color; public EntitySheep(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -42,19 +45,10 @@ public float getHeight() { return 1.3f; } - @Override - public String getName() { - return "Sheep"; - } - - @Override - public int getNetworkId() { - return NETWORK_ID; - } - @Override public void initEntity() { this.setMaxHealth(8); + super.initEntity(); if (!this.namedTag.contains("Color")) { this.setColor(randomColor()); @@ -63,74 +57,98 @@ public void initEntity() { } if (!this.namedTag.contains("Sheared")) { - this.namedTag.putByte("Sheared", 0); - } else { - this.sheared = this.namedTag.getBoolean("Sheared"); + this.namedTag.putBoolean("Sheared", false); } - - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHEARED, this.sheared); } - @Override public void saveNBT() { super.saveNBT(); this.namedTag.putByte("Color", this.color); - this.namedTag.putBoolean("Sheared", this.sheared); + this.namedTag.putBoolean("Sheared", this.isSheared()); } @Override public boolean onInteract(Player player, Item item, Vector3 clickedPos) { if (item.getId() == Item.DYE) { - this.setColor(((ItemDye) item).getDyeColor().getWoolData()); + switch (item.getDamage()) { + case ItemDye.BONE_MEAL: + case ItemDye.LAPIS_LAZULI: + return false; + case ItemDye.WHITE_NEW: + this.setColor(DyeColor.WHITE.getWoolData()); + break; + case ItemDye.BLUE_NEW: + this.setColor(DyeColor.BLUE.getWoolData()); + break; + case ItemDye.BROWN_NEW: + this.setColor(DyeColor.BROWN.getWoolData()); + break; + case ItemDye.BLACK_NEW: + this.setColor(DyeColor.BLACK.getWoolData()); + break; + default: + this.setColor(((ItemDye) item).getDyeColor().getWoolData()); + } return true; } - - return item.getId() == Item.SHEARS && shear(); + return super.onInteract(player, item, clickedPos); } - public boolean shear() { - if (sheared) { - return false; + public void shear(boolean shear) { + this.sheared = shear; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHEARED, shear); + if (shear) { + this.level.dropItem(this, Item.get(Item.WOOL, getColor(), Utils.rand(1, 3))); } - - this.sheared = true; - this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHEARED, true); - - this.level.dropItem(this, Item.get(Item.WOOL, getColor(), ThreadLocalRandom.current().nextInt(2) + 1)); - return true; } @Override public Item[] getDrops() { - if (this.lastDamageCause instanceof EntityDamageByEntityEvent) { - return new Item[]{Item.get(((this.isOnFire()) ? Item.COOKED_MUTTON : Item.RAW_MUTTON)), Item.get(Item.WOOL, getColor(), 1)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + if (!this.sheared) drops.add(Item.get(Item.WOOL, this.getColor(), 1)); + + for (int i = 0; i < Utils.rand(1, 2); i++) { + drops.add(Item.get(this.isOnFire() ? Item.COOKED_MUTTON : Item.RAW_MUTTON, 0, 1)); + } } - return new Item[0]; + + return drops.toArray(new Item[0]); } - public void setColor(int color) { - this.color = color; - this.setDataProperty(new ByteEntityData(DATA_COLOUR, color)); - this.namedTag.putByte("Color", this.color); + + public void setColor(int woolColor) { + this.color = woolColor; + this.namedTag.putByte("Color", woolColor); + this.setDataProperty(new ByteEntityData(DATA_COLOR, woolColor)); } public int getColor() { - return namedTag.getByte("Color"); + return this.color; } private int randomColor() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - double rand = random.nextDouble(1, 100); - - if (rand <= 0.164) { - return DyeColor.PINK.getWoolData(); - } + int rand = Utils.rand(1, 200); + + if (rand == 1) return DyeColor.PINK.getWoolData(); + else if (rand < 8) return DyeColor.BROWN.getWoolData(); + else if (rand < 18) return DyeColor.GRAY.getWoolData(); + else if (rand < 28) return DyeColor.LIGHT_GRAY.getWoolData(); + else if (rand < 38) return DyeColor.BLACK.getWoolData(); + else return DyeColor.WHITE.getWoolData(); + } - if (rand <= 15) { - return random.nextBoolean() ? DyeColor.BLACK.getWoolData() : random.nextBoolean() ? DyeColor.GRAY.getWoolData() : DyeColor.LIGHT_GRAY.getWoolData(); - } + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); + } - return DyeColor.WHITE.getWoolData(); + /** + * @return whether the sheep is sheared + */ + public boolean isSheared() { + return this.sheared; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntitySkeletonHorse.java b/src/main/java/cn/nukkit/entity/passive/EntitySkeletonHorse.java index 95a3956ab0c..7588b863192 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntitySkeletonHorse.java +++ b/src/main/java/cn/nukkit/entity/passive/EntitySkeletonHorse.java @@ -4,11 +4,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntitySkeletonHorse extends EntityAnimal implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntitySkeletonHorse extends EntityHorseBase implements EntitySmite { public static final int NETWORK_ID = 26; @@ -23,22 +24,43 @@ public int getNetworkId() { @Override public float getWidth() { - return 1.4f; + if (this.isBaby()) { + return 0.6982f; + } + return 1.3965f; } @Override public float getHeight() { + if (this.isBaby()) { + return 0.8f; + } return 1.6f; } @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(15); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.BONE)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + + drops.add(Item.get(Item.BONE, 0, Utils.rand(0, 1))); + } + + return drops.toArray(new Item[0]); + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Skeleton Horse"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntitySquid.java b/src/main/java/cn/nukkit/entity/passive/EntitySquid.java index 12430abea18..1c84007f01d 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntitySquid.java +++ b/src/main/java/cn/nukkit/entity/passive/EntitySquid.java @@ -1,14 +1,13 @@ package cn.nukkit.entity.passive; +import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemDye; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.network.protocol.EntityEventPacket; import cn.nukkit.utils.DyeColor; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ public class EntitySquid extends EntityWaterAnimal { public static final int NETWORK_ID = 17; @@ -24,22 +23,41 @@ public int getNetworkId() { @Override public float getWidth() { - return 0.8f; + return 0.95f; } @Override public float getHeight() { - return 0.8f; + return 0.95f; } @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(10); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{new ItemDye(DyeColor.BLACK.getDyeData())}; + return new Item[]{Item.get(Item.DYE, DyeColor.BLACK.getDyeData(), Utils.rand(1, 3))}; + } + + @Override + public int getKillExperience() { + return Utils.rand(1, 3); + } + + @Override + public boolean attack(EntityDamageEvent source) { + boolean att = super.attack(source); + if (source.isCancelled()) { + return att; + } + + EntityEventPacket pk = new EntityEventPacket(); + pk.eid = this.getId(); + pk.event = EntityEventPacket.SQUID_INK_CLOUD; + this.level.addChunkPacket(this.getChunkX(), this.getChunkZ(), pk); + return att; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityStrider.java b/src/main/java/cn/nukkit/entity/passive/EntityStrider.java index 64558d8bb30..51fda8fb71c 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityStrider.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityStrider.java @@ -1,15 +1,21 @@ package cn.nukkit.entity.passive; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; + +import java.util.ArrayList; +import java.util.List; /** * @author Erik Miller | EinBexiii */ -public class EntityStrider extends EntityAnimal { +public class EntityStrider extends EntityWalkingAnimal /*implements EntityRideable, EntityControllable*/ { public final static int NETWORK_ID = 125; + @Override public int getNetworkId() { return NETWORK_ID; @@ -19,10 +25,16 @@ public EntityStrider(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); + } + @Override protected void initEntity() { + this.setMaxHealth(20); super.initEntity(); - this.setMaxHealth(15); + this.fireProof = true; } @Override @@ -36,7 +48,15 @@ public float getHeight() { } @Override - public String getName() { - return "Strider"; + public Item[] getDrops() { + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(2, 5); i++) { + drops.add(Item.get(Item.STRING, 0, 1)); + } + } + + return drops.toArray(new Item[0]); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityTadpole.java b/src/main/java/cn/nukkit/entity/passive/EntityTadpole.java index 0ee26ec23e6..09a045cd14c 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityTadpole.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityTadpole.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityTadpole extends EntityAnimal { +public class EntityTadpole extends EntityFish { public static final int NETWORK_ID = 133; @@ -11,6 +11,16 @@ public EntityTadpole(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getKillExperience() { + return 0; + } + + @Override + int getBucketMeta() { + return 13; + } + @Override public int getNetworkId() { return NETWORK_ID; @@ -28,12 +38,7 @@ public float getWidth() { @Override protected void initEntity() { - super.initEntity(); this.setMaxHealth(6); - } - - @Override - public String getName() { - return "Tadpole"; + super.initEntity(); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityTameable.java b/src/main/java/cn/nukkit/entity/passive/EntityTameable.java deleted file mode 100644 index 57d6ae0bb3d..00000000000 --- a/src/main/java/cn/nukkit/entity/passive/EntityTameable.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.nukkit.entity.passive; - -import cn.nukkit.Player; -import cn.nukkit.entity.EntityOwnable; -import cn.nukkit.entity.data.ByteEntityData; -import cn.nukkit.entity.data.StringEntityData; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.nbt.tag.CompoundTag; - -/** - * Author: BeYkeRYkt - * Nukkit Project - */ -public abstract class EntityTameable extends EntityAnimal implements EntityOwnable { - - public static final int DATA_TAMED_FLAG = 16; - public static final int DATA_OWNER_NAME = 17; - - public EntityTameable(FullChunk chunk, CompoundTag nbt) { - super(chunk, nbt); - } - - @Override - protected void initEntity() { - super.initEntity(); - if (getDataProperty(DATA_TAMED_FLAG) == null) { - setDataProperty(new ByteEntityData(DATA_TAMED_FLAG, (byte) 0)); - } - - if (getDataProperty(DATA_OWNER_NAME) == null) { - setDataProperty(new StringEntityData(DATA_OWNER_NAME, "")); - } - - String ownerName = ""; - - if (namedTag != null) { - if (namedTag.contains("Owner")) { - ownerName = namedTag.getString("Owner"); - } - - if (ownerName.length() > 0) { - this.setOwnerName(ownerName); - this.setTamed(true); - } - - this.setSitting(namedTag.getBoolean("Sitting")); - } - } - - @Override - public void saveNBT() { - super.saveNBT(); - - if (this.getOwnerName() == null) { - namedTag.putString("Owner", ""); - } else { - namedTag.putString("Owner", getOwnerName()); - } - - namedTag.putBoolean("Sitting", isSitting()); - } - - @Override - public String getOwnerName() { - return getDataPropertyString(DATA_OWNER_NAME); - } - - @Override - public void setOwnerName(String playerName) { - setDataProperty(new StringEntityData(DATA_OWNER_NAME, playerName)); - } - - @Override - public Player getOwner() { - return getServer().getPlayer(getOwnerName()); - } - - @Override - public String getName() { - return getNameTag(); - } - - public boolean isTamed() { - return (getDataPropertyByte(DATA_TAMED_FLAG) & 4) != 0; - } - - public void setTamed(boolean flag) { - int var = getDataPropertyByte(DATA_TAMED_FLAG); // ? - - if (flag) { - setDataProperty(new ByteEntityData(DATA_TAMED_FLAG, (byte) (var | 4))); - } else { - setDataProperty(new ByteEntityData(DATA_TAMED_FLAG, (byte) (var & -5))); - } - } - - public boolean isSitting() { - return (getDataPropertyByte(DATA_TAMED_FLAG) & 1) != 0; - } - - public void setSitting(boolean flag) { - int var = getDataPropertyByte(DATA_TAMED_FLAG); // ? - - if (flag) { - setDataProperty(new ByteEntityData(DATA_TAMED_FLAG, (byte) (var | 1))); - } else { - setDataProperty(new ByteEntityData(DATA_TAMED_FLAG, (byte) (var & -2))); - } - } -} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityTameableAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityTameableAnimal.java new file mode 100644 index 00000000000..d5bdcc01851 --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityTameableAnimal.java @@ -0,0 +1,11 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityTameableAnimal extends EntityWalkingAnimal /*implements EntityTameable*/ { + + public EntityTameableAnimal(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityTropicalFish.java b/src/main/java/cn/nukkit/entity/passive/EntityTropicalFish.java index 60db3244a62..46ca238fc6e 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityTropicalFish.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityTropicalFish.java @@ -1,26 +1,68 @@ package cn.nukkit.entity.passive; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ -public class EntityTropicalFish extends EntityAnimal { +public class EntityTropicalFish extends EntityFish { public static final int NETWORK_ID = 111; + private int variantA; + private int variantB; + private int color; + public EntityTropicalFish(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } @Override - public int getNetworkId() { - return NETWORK_ID; + protected void initEntity() { + this.setMaxHealth(3); + super.initEntity(); + + if (this.namedTag.contains("VariantA")) { + this.variantA = this.namedTag.getInt("VariantA"); + } else { + this.variantA = Utils.rand(0, 5); + + } + this.dataProperties.putInt(DATA_VARIANT, this.variantA); + + if (this.namedTag.contains("VariantB")) { + this.variantB = this.namedTag.getInt("VariantB"); + } else { + this.variantB = Utils.rand(0, 5); + + } + this.dataProperties.putInt(DATA_MARK_VARIANT, this.variantB); + + if (this.namedTag.contains("Color")) { + this.color = this.namedTag.getInt("Color"); + } else { + this.color = Utils.rand(0, 15); + + } + this.dataProperties.putByte(DATA_COLOR, this.color); } - public String getName() { - return "Tropical Fish"; + @Override + public void saveNBT() { + super.saveNBT(); + this.namedTag.putInt("VariantA", this.variantA); + this.namedTag.putInt("VariantB", this.variantB); + this.namedTag.putInt("Color", this.color); + } + + @Override + int getBucketMeta() { + return 4; + } + + @Override + public int getNetworkId() { + return NETWORK_ID; } @Override @@ -34,8 +76,12 @@ public float getHeight() { } @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(3); + public Item[] getDrops() { + return new Item[]{Item.get(Item.CLOWNFISH, 0, 1), Item.get(Item.BONE, 0, Utils.rand(0, 2))}; + } + + @Override + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Tropical Fish"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityTurtle.java b/src/main/java/cn/nukkit/entity/passive/EntityTurtle.java index aa9a25c79ac..1ca30347854 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityTurtle.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityTurtle.java @@ -1,12 +1,10 @@ package cn.nukkit.entity.passive; +import cn.nukkit.utils.Utils; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * Created by PetteriM1 - */ -public class EntityTurtle extends EntityAnimal { +public class EntityTurtle extends EntityWaterAnimal { public static final int NETWORK_ID = 74; @@ -19,10 +17,6 @@ public int getNetworkId() { return NETWORK_ID; } - public String getName() { - return "Turtle"; - } - @Override public float getWidth() { return 1.2f; @@ -35,7 +29,12 @@ public float getHeight() { @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(30); + super.initEntity(); + } + + @Override + public int getKillExperience() { + return this.isBaby() ? 0 : Utils.rand(1, 3); } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java index fca7bc48fe8..2148b4ef873 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillager.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillager.java @@ -1,11 +1,9 @@ package cn.nukkit.entity.passive; -import cn.nukkit.entity.EntityAgeable; -import cn.nukkit.entity.EntityCreature; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityVillager extends EntityCreature implements EntityNPC, EntityAgeable { +public class EntityVillager extends EntityVillagerV1 { public static final int NETWORK_ID = 115; @@ -18,39 +16,8 @@ public int getNetworkId() { return NETWORK_ID; } - @Override - public float getWidth() { - if (this.isBaby()) { - return 0.3f; - } - return 0.6f; - } - - @Override - public float getHeight() { - if (this.isBaby()) { - return 0.9f; - } - return 1.8f; - } - @Override public String getName() { - return "Villager"; - } - - @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(20); - } - - public boolean isBaby() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_BABY); - } - - public void setBaby(boolean baby) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_BABY, baby); - this.setScale(baby ? 0.5f : 1); + return this.hasCustomName() ? this.getNameTag() : "Villager"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityVillagerV1.java b/src/main/java/cn/nukkit/entity/passive/EntityVillagerV1.java index 0349f46ac5c..51f06261619 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityVillagerV1.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityVillagerV1.java @@ -1,15 +1,22 @@ package cn.nukkit.entity.passive; +import cn.nukkit.Player; import cn.nukkit.entity.Entity; -import cn.nukkit.entity.EntityAgeable; -import cn.nukkit.entity.EntityCreature; +import cn.nukkit.entity.mob.EntityWitch; +import cn.nukkit.event.entity.CreatureSpawnEvent; +import cn.nukkit.inventory.InventoryHolder; +import cn.nukkit.inventory.TradeInventory; +import cn.nukkit.inventory.TradeInventoryRecipe; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; -/** - * Created by Pub4Game on 21.06.2016. - */ -public class EntityVillagerV1 extends EntityCreature implements EntityNPC, EntityAgeable { +import java.util.ArrayList; +import java.util.List; + +public class EntityVillagerV1 extends EntityWalkingAnimal implements InventoryHolder { public static final int PROFESSION_FARMER = 0; public static final int PROFESSION_LIBRARIAN = 1; @@ -17,12 +24,23 @@ public class EntityVillagerV1 extends EntityCreature implements EntityNPC, Entit public static final int PROFESSION_BLACKSMITH = 3; public static final int PROFESSION_BUTCHER = 4; public static final int PROFESSION_GENERIC = 5; + public static final int NETWORK_ID = 15; + private TradeInventory inventory; + private final List recipes = new ArrayList<>(); + private int tradeTier = 0; + private boolean willing = true; + public EntityVillagerV1(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { if (this.isBaby()) { @@ -34,25 +52,29 @@ public float getWidth() { @Override public float getHeight() { if (this.isBaby()) { - return 0.9f; + return 0.975f; } - return 1.8f; - } - - @Override - public String getName() { - return "Villager"; - } - - @Override - public int getNetworkId() { - return NETWORK_ID; + return 1.95f; } @Override public void initEntity() { + this.setMaxHealth(10); super.initEntity(); - this.setMaxHealth(20); + + this.inventory = new TradeInventory(this); + + try { + CompoundTag offers = this.namedTag.getCompound("Offers"); + if (offers != null) { + ListTag nbtRecipes = offers.getList("Recipes", CompoundTag.class); + for (CompoundTag nbt : nbtRecipes.getAll()) { + recipes.add(TradeInventoryRecipe.toNBT(nbt)); + } + } + } catch (Exception ex) { + server.getLogger().error("Failed to load trade recipes for a villager with entity id " + this.id, ex); + } if (!this.namedTag.contains("Profession")) { this.setProfession(PROFESSION_GENERIC); @@ -68,7 +90,101 @@ public void setProfession(int profession) { } @Override - public boolean isBaby() { - return this.getDataFlag(DATA_FLAGS, Entity.DATA_FLAG_BABY); + public int getKillExperience() { + return 0; + } + + @Override + public void onStruckByLightning(Entity lightning) { + Entity ent = Entity.createEntity("Witch", this); + if (ent != null) { + CreatureSpawnEvent cse = new CreatureSpawnEvent(EntityWitch.NETWORK_ID, this, ent.namedTag, CreatureSpawnEvent.SpawnReason.LIGHTNING); + this.getServer().getPluginManager().callEvent(cse); + + if (cse.isCancelled()) { + ent.close(); + return; + } + + ent.yaw = this.yaw; + ent.pitch = this.pitch; + ent.setImmobile(this.isImmobile()); + if (this.hasCustomName()) { + ent.setNameTag(this.getNameTag()); + ent.setNameTagVisible(this.isNameTagVisible()); + ent.setNameTagAlwaysVisible(this.isNameTagAlwaysVisible()); + } + /*if (this.isBaby()) { + ent.setBaby(); // TODO + }*/ + + this.close(); + ent.spawnToAll(); + } else { + super.onStruckByLightning(lightning); + } + } + + public void setTradeTier(int tier) { + this.tradeTier = tier; + } + + public int getTradeTier() { + return this.tradeTier; + } + + public void setWilling(boolean value) { + this.willing = value; + } + + public boolean isWilling() { + return this.willing; + } + + @Override + public boolean onInteract(Player player, Item item, Vector3 clickedPos) { + if (recipes.size() > 0) { + player.addWindow(this.getInventory()); + return true; + } + return false; + } + + public void addTradeRecipe(TradeInventoryRecipe recipe) { + this.recipes.add(recipe); + } + + public List getRecipes() { + return this.recipes; + } + + public CompoundTag getOffers() { + CompoundTag nbt = new CompoundTag(); + nbt.putList(recipesToNbt()); + nbt.putList(getDefaultTierExpRequirements()); + return nbt; + } + + private ListTag recipesToNbt() { + ListTag tag = new ListTag<>("Recipes"); + for (TradeInventoryRecipe recipe : this.recipes) { + tag.add(recipe.toNBT()); + } + return tag; + } + + private ListTag getDefaultTierExpRequirements() { + ListTag tag = new ListTag<>("TierExpRequirements"); + tag.add(new CompoundTag().putInt("0", 0)); + tag.add(new CompoundTag().putInt("1", 10)); + tag.add(new CompoundTag().putInt("2", 70)); + tag.add(new CompoundTag().putInt("3", 150)); + tag.add(new CompoundTag().putInt("4", 250)); + return tag; + } + + @Override + public TradeInventory getInventory() { + return this.inventory; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityWalkingAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityWalkingAnimal.java new file mode 100644 index 00000000000..35859b85cce --- /dev/null +++ b/src/main/java/cn/nukkit/entity/passive/EntityWalkingAnimal.java @@ -0,0 +1,12 @@ +package cn.nukkit.entity.passive; + +import cn.nukkit.entity.EntityWalking; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class EntityWalkingAnimal extends EntityWalking implements EntityAnimal { + + public EntityWalkingAnimal(FullChunk chunk, CompoundTag nbt) { + super(chunk, nbt); + } +} diff --git a/src/main/java/cn/nukkit/entity/passive/EntityWanderingTrader.java b/src/main/java/cn/nukkit/entity/passive/EntityWanderingTrader.java index 911cbf30383..500c7922073 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityWanderingTrader.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityWanderingTrader.java @@ -1,10 +1,9 @@ package cn.nukkit.entity.passive; -import cn.nukkit.entity.EntityCreature; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -public class EntityWanderingTrader extends EntityCreature implements EntityNPC { +public class EntityWanderingTrader extends EntityWalkingAnimal { public static final int NETWORK_ID = 118; @@ -12,6 +11,11 @@ public EntityWanderingTrader(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } + @Override + public int getNetworkId() { + return NETWORK_ID; + } + @Override public float getWidth() { return 0.6f; @@ -19,22 +23,22 @@ public float getWidth() { @Override public float getHeight() { - return 1.8f; + return 1.95f; } @Override - public String getName() { - return "Wandering Trader"; + public void initEntity() { + this.setMaxHealth(20); + super.initEntity(); } @Override - public int getNetworkId() { - return NETWORK_ID; + public int getKillExperience() { + return 0; } @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(20); + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Wandering Trader"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityWaterAnimal.java b/src/main/java/cn/nukkit/entity/passive/EntityWaterAnimal.java index 617b426204f..3258634f47f 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityWaterAnimal.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityWaterAnimal.java @@ -1,22 +1,12 @@ package cn.nukkit.entity.passive; -import cn.nukkit.entity.Entity; -import cn.nukkit.entity.EntityAgeable; -import cn.nukkit.entity.EntityCreature; +import cn.nukkit.entity.EntitySwimming; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * author: MagicDroidX - * Nukkit Project - */ -public abstract class EntityWaterAnimal extends EntityCreature implements EntityAgeable { +public abstract class EntityWaterAnimal extends EntitySwimming implements EntityAnimal { + public EntityWaterAnimal(FullChunk chunk, CompoundTag nbt) { super(chunk, nbt); } - - @Override - public boolean isBaby() { - return this.getDataFlag(DATA_FLAGS, Entity.DATA_FLAG_BABY); - } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityWolf.java b/src/main/java/cn/nukkit/entity/passive/EntityWolf.java index 656442b6793..eaff8465940 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityWolf.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityWolf.java @@ -1,13 +1,10 @@ package cn.nukkit.entity.passive; -import cn.nukkit.item.Item; +import cn.nukkit.entity.mob.EntityTameableMob; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; -/** - * Author: BeYkeRYkt Nukkit Project - */ -public class EntityWolf extends EntityAnimal { +public class EntityWolf extends EntityTameableMob { public static final int NETWORK_ID = 14; @@ -16,33 +13,33 @@ public EntityWolf(FullChunk chunk, CompoundTag nbt) { } @Override - public float getWidth() { - return 0.6f; + public int getNetworkId() { + return NETWORK_ID; } @Override - public float getHeight() { - return 0.85f; + public float getWidth() { + return 0.6f; } @Override - public String getName() { - return "Wolf"; + public float getHeight() { + return 0.8f; } @Override - public int getNetworkId() { - return NETWORK_ID; + protected void initEntity() { + this.setMaxHealth(8); + super.initEntity(); } @Override - public void initEntity() { - super.initEntity(); - this.setMaxHealth(8); + public int getKillExperience() { + return this.isBaby() ? 0 : 3; } @Override - public boolean isBreedingItem(Item item) { - return false; //only certain food + public String getName() { + return this.hasCustomName() ? this.getNameTag() : "Wolf"; } } diff --git a/src/main/java/cn/nukkit/entity/passive/EntityZombieHorse.java b/src/main/java/cn/nukkit/entity/passive/EntityZombieHorse.java index e6ec30f18f0..6db158c51e6 100644 --- a/src/main/java/cn/nukkit/entity/passive/EntityZombieHorse.java +++ b/src/main/java/cn/nukkit/entity/passive/EntityZombieHorse.java @@ -4,11 +4,12 @@ import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.utils.Utils; -/** - * @author PikyCZ - */ -public class EntityZombieHorse extends EntityAnimal implements EntitySmite { +import java.util.ArrayList; +import java.util.List; + +public class EntityZombieHorse extends EntityHorseBase implements EntitySmite { public static final int NETWORK_ID = 27; @@ -23,22 +24,45 @@ public int getNetworkId() { @Override public float getWidth() { - return 1.4f; + if (this.isBaby()) { + return 0.6982f; + } + return 1.3965f; } @Override public float getHeight() { + if (this.isBaby()) { + return 0.8f; + } return 1.6f; } @Override public void initEntity() { - super.initEntity(); this.setMaxHealth(15); + super.initEntity(); } @Override public Item[] getDrops() { - return new Item[]{Item.get(Item.ROTTEN_FLESH, 1, 1)}; + List drops = new ArrayList<>(); + + if (!this.isBaby()) { + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.LEATHER, 0, 1)); + } + + for (int i = 0; i < Utils.rand(0, 2); i++) { + drops.add(Item.get(Item.ROTTEN_FLESH, 0, 1)); + } + } + + return drops.toArray(new Item[0]); + } + + @Override + public String getName() { + return "Zombie Horse"; } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java b/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java index 74a4a266b45..eb48e455a7e 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityArrow.java @@ -1,13 +1,16 @@ package cn.nukkit.entity.projectile; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.data.ByteEntityData; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityArrow extends EntityProjectile { @@ -15,6 +18,9 @@ public class EntityArrow extends EntityProjectile { public static final int NETWORK_ID = 80; protected int pickupMode; + protected boolean critical; + private int arrowData; + private boolean isFromCrossbow; @Override public int getNetworkId() { @@ -23,7 +29,7 @@ public int getNetworkId() { @Override public float getWidth() { - return 0.5f; + return 0.05f; } @Override @@ -33,7 +39,7 @@ public float getLength() { @Override public float getHeight() { - return 0.5f; + return 0.05f; } @Override @@ -46,9 +52,6 @@ public float getDrag() { return 0.01f; } - protected float gravity = 0.05f; - protected float drag = 0.01f; - public EntityArrow(FullChunk chunk, CompoundTag nbt) { this(chunk, nbt, null); } @@ -58,16 +61,58 @@ public EntityArrow(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { } public EntityArrow(FullChunk chunk, CompoundTag nbt, Entity shootingEntity, boolean critical) { + this(chunk, nbt, shootingEntity, critical, false); + } + + public EntityArrow(FullChunk chunk, CompoundTag nbt, Entity shootingEntity, boolean critical, boolean isFromCrossbow) { super(chunk, nbt, shootingEntity); this.setCritical(critical); + this.isFromCrossbow = isFromCrossbow; + } + + /** + * Set arrow data. + * Used internally for tipped arrows. + * Notice: The data is not updated to players unless you call sendData(). + * + * @param data arrow data + */ + public void setData(int data) { + if (data < 0) throw new IllegalArgumentException("data < 0"); + this.arrowData = data; + this.setDataProperty(new ByteEntityData(DATA_HAS_DISPLAY, this.arrowData), false); + } + + /** + * Get arrow data. + * + * @return arrow data + */ + public int getData() { + return this.arrowData; + } + + /** + * Get whether the arrow was shot from a crossbow. + * + * @return arrow is from crossbow + */ + public boolean isFromCrossbow() { + return this.isFromCrossbow; } @Override protected void initEntity() { super.initEntity(); - this.damage = namedTag.contains("damage") ? namedTag.getDouble("damage") : 2; this.pickupMode = namedTag.contains("pickup") ? namedTag.getByte("pickup") : PICKUP_ANY; + + int data = namedTag.getByte("arrowData"); + if (data != 0) { + this.setData(data); + } + + this.isFromCrossbow = namedTag.getBoolean("isFromCrossbow"); } public void setCritical() { @@ -75,19 +120,26 @@ public void setCritical() { } public void setCritical(boolean value) { - this.setDataFlag(DATA_FLAGS, DATA_FLAG_CRITICAL, value); + if (this.critical != value) { + this.critical = value; + this.setDataFlag(DATA_FLAGS, DATA_FLAG_CRITICAL, value); + } } public boolean isCritical() { - return this.getDataFlag(DATA_FLAGS, DATA_FLAG_CRITICAL); + return this.critical; } @Override public int getResultDamage() { - int base = super.getResultDamage(); + int base = NukkitMath.ceilDouble(Math.sqrt(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ) * getDamage()); if (this.isCritical()) { - base += ThreadLocalRandom.current().nextInt(base / 2 + 2); + base += Utils.random.nextInt((base >> 1) + 2); + } + + if (this.isFromCrossbow) { + base += 2; // magic value } return base; @@ -104,22 +156,16 @@ public boolean onUpdate(int currentTick) { return false; } - this.timing.startTiming(); - - boolean hasUpdate = super.onUpdate(currentTick); - - if (this.onGround || this.hadCollision) { - this.setCritical(false); - } - if (this.age > 1200) { this.close(); - hasUpdate = true; + return false; } - this.timing.stopTiming(); + if (this.fireTicks > 0 && this.level.isRaining() && this.canSeeSky()) { + this.extinguish(); + } - return hasUpdate; + return super.onUpdate(currentTick); } @Override @@ -127,6 +173,8 @@ public void saveNBT() { super.saveNBT(); this.namedTag.putByte("pickup", this.pickupMode); + this.namedTag.putByte("arrowData", this.arrowData); + this.namedTag.putBoolean("isFromCrossbow", this.isFromCrossbow); } public int getPickupMode() { @@ -136,4 +184,16 @@ public int getPickupMode() { public void setPickupMode(int pickupMode) { this.pickupMode = pickupMode; } + + @Override + public void onHit() { + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BOW_HIT); + } + + @Override + public void onHitGround(Vector3 moveVector) { + super.onHitGround(moveVector); + + this.setCritical(false); + } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityEgg.java b/src/main/java/cn/nukkit/entity/projectile/EntityEgg.java index 3b45d59e7e1..389cf2c1ce5 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityEgg.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityEgg.java @@ -1,14 +1,17 @@ package cn.nukkit.entity.projectile; import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.particle.ItemBreakParticle; import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityEgg extends EntityProjectile { + public static final int NETWORK_ID = 82; @Override @@ -33,7 +36,7 @@ public float getHeight() { @Override protected float getGravity() { - return 0.03f; + return 0.04f; } @Override @@ -55,13 +58,18 @@ public boolean onUpdate(int currentTick) { return false; } - boolean hasUpdate = super.onUpdate(currentTick); - - if (this.age > 1200 || this.isCollided) { - this.kill(); - hasUpdate = true; + if (this.age > 1200) { + this.close(); + } else if (this.isCollided) { + this.close(); } - return hasUpdate; + super.onUpdate(currentTick); + return !this.closed; + } + + @Override + public void onHit() { + level.addParticle(new ItemBreakParticle(this, Item.get(Item.EGG)), null, 5); } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityEnderEye.java b/src/main/java/cn/nukkit/entity/projectile/EntityEnderEye.java new file mode 100644 index 00000000000..812aa0de06a --- /dev/null +++ b/src/main/java/cn/nukkit/entity/projectile/EntityEnderEye.java @@ -0,0 +1,50 @@ +package cn.nukkit.entity.projectile; + +import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.CompoundTag; + +import java.util.concurrent.ThreadLocalRandom; + +public class EntityEnderEye extends EntityProjectile { + + public static final int NETWORK_ID = 70; + + public EntityEnderEye(FullChunk chunk, CompoundTag nbt) { + this(chunk, nbt, null); + } + + public EntityEnderEye(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { + super(chunk, nbt, shootingEntity); + } + + @Override + public int getNetworkId() { + return NETWORK_ID; + } + + @Override + public boolean onUpdate(int currentTick) { + boolean hasUpdate = super.onUpdate(currentTick); + + if (!this.isAlive()) return hasUpdate; + + if (this.age >= 60) { + if (ThreadLocalRandom.current().nextFloat() > 0.2) { + this.level.dropItem(this, Item.get(Item.ENDER_EYE)); + } + + this.close(); + + hasUpdate = true; + } else if (this.age == 40) { + this.setMotion(new Vector3(0, 0.2, 0)); + + hasUpdate = true; + } + + return hasUpdate; + } +} diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityEnderPearl.java b/src/main/java/cn/nukkit/entity/projectile/EntityEnderPearl.java index 8d59b5c78f5..d917f2730e5 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityEnderPearl.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityEnderPearl.java @@ -10,11 +10,16 @@ import cn.nukkit.math.NukkitMath; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.network.protocol.LevelSoundEventPacket; + +import java.util.List; public class EntityEnderPearl extends EntityProjectile { + public static final int NETWORK_ID = 87; + private int currentLevel; + @Override public int getNetworkId() { return NETWORK_ID; @@ -51,59 +56,67 @@ public EntityEnderPearl(FullChunk chunk, CompoundTag nbt) { public EntityEnderPearl(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { super(chunk, nbt, shootingEntity); + + if (this.shootingEntity != null) { + this.currentLevel = this.shootingEntity.getLevel().getId(); + } } @Override public boolean onUpdate(int currentTick) { + boolean hasUpdate = super.onUpdate(currentTick); + if (this.closed) { return false; } - this.timing.startTiming(); - - boolean hasUpdate = super.onUpdate(currentTick); - if (this.isCollided && this.shootingEntity instanceof Player) { + List b = this.getCollisionBlocks(); + boolean portal = false; - for (Block collided : this.getCollisionBlocks()) { + for (Block collided : b) { if (collided.getId() == Block.NETHER_PORTAL) { portal = true; + break; } } + + this.close(); + if (!portal) { teleport(); } - } - if (this.age > 1200 || this.isCollided) { - this.kill(); - hasUpdate = true; + return false; } - this.timing.stopTiming(); + if (this.isCollided || this.hadCollision || (this.shootingEntity instanceof Player && !((Player) this.shootingEntity).isOnline())) { + this.close(); + return false; + } return hasUpdate; } - + @Override public void onCollideWithEntity(Entity entity) { - if (this.shootingEntity instanceof Player) { - teleport(); - } + teleport(); + super.onCollideWithEntity(entity); } private void teleport() { - if (!this.level.equals(this.shootingEntity.getLevel())) { + if (!(this.shootingEntity instanceof Player) || this.shootingEntity.getLevel().getId() != this.currentLevel || !((Player) this.shootingEntity).isOnline()) { return; } - this.level.addLevelEvent(this.shootingEntity.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_PORTAL); this.shootingEntity.teleport(new Vector3(NukkitMath.floorDouble(this.x) + 0.5, this.y, NukkitMath.floorDouble(this.z) + 0.5), TeleportCause.ENDER_PEARL); - if ((((Player) this.shootingEntity).getGamemode() & 0x01) == 0) { - this.shootingEntity.attack(new EntityDamageByEntityEvent(this, shootingEntity, EntityDamageEvent.DamageCause.PROJECTILE, 5f, 0f)); + + int gamemode = ((Player) this.shootingEntity).getGamemode(); + if (gamemode == 0 || gamemode == 2) { + this.shootingEntity.attack(new EntityDamageByEntityEvent(this, shootingEntity, EntityDamageEvent.DamageCause.FALL, 5f, 0f)); } - this.level.addLevelEvent(this, LevelEventPacket.EVENT_PARTICLE_ENDERMAN_TELEPORT); - this.level.addLevelEvent(this.shootingEntity.add(0.5, 0.5, 0.5), LevelEventPacket.EVENT_SOUND_PORTAL); + + this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_TELEPORT); } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java b/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java index 7efb621f93b..884e2ea71cb 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityProjectile.java @@ -1,30 +1,58 @@ package cn.nukkit.entity.projectile; +import cn.nukkit.Player; +import cn.nukkit.block.Block; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityLiving; -import cn.nukkit.entity.data.LongEntityData; -import cn.nukkit.entity.item.EntityBoat; -import cn.nukkit.entity.item.EntityEndCrystal; -import cn.nukkit.entity.item.EntityMinecartAbstract; +import cn.nukkit.entity.item.*; import cn.nukkit.entity.mob.EntityBlaze; import cn.nukkit.event.entity.*; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; +import cn.nukkit.item.ItemArrow; import cn.nukkit.level.MovingObjectPosition; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.math.NukkitMath; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.potion.Effect; + +import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityProjectile extends Entity { public static final int DATA_SHOOTER_ID = 17; - public Entity shootingEntity = null; + /** + * Arrow will be removed on pickup. + */ + public static final int PICKUP_NONE_REMOVE = -1; + /** + * No player can pick up the arrow. + */ + public static final int PICKUP_NONE = 0; + /** + * All players can pick up the arrow. + */ + public static final int PICKUP_ANY = 1; + /** + * All players on creative mode can pick up the arrow. + */ + public static final int PICKUP_CREATIVE = 2; + + /** + * The entity who shot this arrow. May be null. + */ + public Entity shootingEntity; + + /** + * The knockback this arrow causes on hit. + */ + public float knockBack = 0.3f; protected double getDamage() { return namedTag.contains("damage") ? namedTag.getDouble("damage") : getBaseDamage(); @@ -36,13 +64,7 @@ protected double getBaseDamage() { public boolean hadCollision = false; - public boolean closeOnCollide = true; - - protected double damage = 0; - - public static final int PICKUP_NONE = 0; - public static final int PICKUP_ANY = 1; - public static final int PICKUP_CREATIVE = 2; + public int piercing; public EntityProjectile(FullChunk chunk, CompoundTag nbt) { this(chunk, nbt, null); @@ -51,13 +73,17 @@ public EntityProjectile(FullChunk chunk, CompoundTag nbt) { public EntityProjectile(FullChunk chunk, CompoundTag nbt, Entity shootingEntity) { super(chunk, nbt); this.shootingEntity = shootingEntity; - if (shootingEntity != null) { + /*if (shootingEntity != null) { this.setDataProperty(new LongEntityData(DATA_SHOOTER_ID, shootingEntity.getId())); - } + }*/ } + /** + * Get the amount of damage this projectile will deal to the entity it hits. + * @return damage + */ public int getResultDamage() { - return NukkitMath.ceilDouble(Math.sqrt(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ) * getDamage()); + return NukkitMath.ceilDouble(this.getDamage()); } public boolean attack(EntityDamageEvent source) { @@ -66,44 +92,59 @@ public boolean attack(EntityDamageEvent source) { public void onCollideWithEntity(Entity entity) { this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromEntity(entity))); - float damage = this instanceof EntitySnowball && entity.getNetworkId() == EntityBlaze.NETWORK_ID ? 3f : this.getResultDamage(); + float damage = this instanceof EntitySnowball && entity instanceof EntityBlaze ? 3 : this.getResultDamage(); EntityDamageEvent ev; if (this.shootingEntity == null) { - ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage); + ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage, knockBack); } else { - ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage); + ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage, knockBack); } + if (entity.attack(ev)) { this.hadCollision = true; - if (this.fireTicks > 0) { + this.onHit(); + + if (this.fireTicks > 0 && entity.isAlive()) { EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this, entity, 5); this.server.getPluginManager().callEvent(event); if (!event.isCancelled()) { entity.setOnFire(event.getDuration()); } } + + if (this instanceof EntityArrow && entity instanceof EntityLiving) { + Effect e = ItemArrow.getEffect(((EntityArrow) this).getData()); + if (e != null) { + addEffectFromTippedArrow(entity, e, ev.getFinalDamage()); + } + } } - if (closeOnCollide) { - this.close(); - } + + this.close(); } @Override protected void initEntity() { - super.initEntity(); - this.setMaxHealth(1); + super.initEntity(); this.setHealth(1); + if (this.namedTag.contains("Age")) { this.age = this.namedTag.getShort("Age"); } + + if (this.namedTag.contains("knockback")) { + this.knockBack = this.namedTag.getFloat("knockback"); + } + + this.updateRotation(); } @Override public boolean canCollideWith(Entity entity) { - return (entity instanceof EntityLiving || entity instanceof EntityEndCrystal || entity instanceof EntityMinecartAbstract || entity instanceof EntityBoat) && !this.onGround; + return (entity instanceof EntityLiving || entity instanceof EntityEndCrystal || entity instanceof EntityMinecartAbstract || entity instanceof EntityBoat || entity instanceof EntityPainting) && !this.onGround && !entity.noClip && !this.noClip; } @Override @@ -127,24 +168,36 @@ public boolean onUpdate(int currentTick) { boolean hasUpdate = this.entityBaseTick(tickDiff); if (this.isAlive()) { - MovingObjectPosition movingObjectPosition = null; if (!this.isCollided) { - this.motionY -= this.getGravity(); + if (this.isInsideOfWater()) { + this.motionY -= this.getGravity() - (this.getGravity() / 2); + } else { + this.motionY -= this.getGravity(); + } + this.motionX *= 1 - this.getDrag(); + this.motionZ *= 1 - this.getDrag(); + } else if (this.updateMode % 3 == 1) { + this.updateMode = 5; + + // Force check for nearby blocks changed + this.blocksAround = null; + this.collisionBlocks = null; + if (!this.level.hasCollisionBlocks(this, this.boundingBox.grow(0.01, 0.01, 0.01))) { + this.motionY = -0.00001; + } } Vector3 moveVector = new Vector3(this.x + this.motionX, this.y + this.motionY, this.z + this.motionZ); - Entity[] list = this.getLevel().getCollidingEntities(this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(1, 1, 1), this); + Entity[] list = this.noClip ? new Entity[0] : this.getLevel().getCollidingEntities(this.boundingBox.addCoord(this.motionX, this.motionY, this.motionZ).expand(1, 1, 1), this); double nearDistance = Integer.MAX_VALUE; Entity nearEntity = null; for (Entity entity : list) { - if (/*!entity.canCollideWith(this) or */ - (entity == this.shootingEntity && this.ticksLived < 5) - ) { + if (/*!entity.canCollideWith(this) || */(entity == this.shootingEntity && this.age < 5) || (entity instanceof Player && ((Player) entity).getGamemode() == Player.SPECTATOR)) { continue; } @@ -176,7 +229,11 @@ public boolean onUpdate(int currentTick) { this.move(this.motionX, this.motionY, this.motionZ); - if (this.isCollided && !this.hadCollision) { //collide with block + if (this.isCollided && !this.hadCollision) { // Collide with block + // Make sure last move tick is broadcast + // However previous yaw & pitch are actually the correct ones + if (!(this instanceof EntityFishingHook)) this.addMovement(this.x, this.y, this.z, this.lastYaw, this.lastPitch, this.lastYaw); + this.hadCollision = true; this.motionX = 0; @@ -184,22 +241,56 @@ public boolean onUpdate(int currentTick) { this.motionZ = 0; this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromBlock(this.getFloorX(), this.getFloorY(), this.getFloorZ(), -1, this))); + this.onHit(); + this.onHitGround(moveVector); + + if (this instanceof EntityArrow || this instanceof EntityThrownTrident) { + this.updateMode = 5; // The projectile is collided with a block so only update it if the block changes or to check age for despawn + } return false; } else if (!this.isCollided && this.hadCollision) { this.hadCollision = false; } if (!this.hadCollision || Math.abs(this.motionX) > 0.00001 || Math.abs(this.motionY) > 0.00001 || Math.abs(this.motionZ) > 0.00001) { - double f = Math.sqrt((this.motionX * this.motionX) + (this.motionZ * this.motionZ)); - this.yaw = Math.atan2(this.motionX, this.motionZ) * 180 / Math.PI; - this.pitch = Math.atan2(this.motionY, f) * 180 / Math.PI; + updateRotation(); hasUpdate = true; } this.updateMovement(); - } return hasUpdate; } + + /** + * Update projectile rotation to match its motion. + */ + public void updateRotation() { + double f = Math.sqrt((this.motionX * this.motionX) + (this.motionZ * this.motionZ)); + this.yaw = Math.atan2(this.motionX, this.motionZ) * 180 / Math.PI; + this.pitch = Math.atan2(this.motionY, f) * 180 / Math.PI; + } + + /** + * Add inaccuracy to projectile movement. Used internally with dispensers. + * + * @param modifier multiplier + */ + public void inaccurate(float modifier) { + ThreadLocalRandom rand = ThreadLocalRandom.current(); + + this.motionX += rand.nextGaussian() * 0.007499999832361937 * modifier; + this.motionY += rand.nextGaussian() * 0.007499999832361937 * modifier; + this.motionZ += rand.nextGaussian() * 0.007499999832361937 * modifier; + } + + protected void onHit() { + + } + + protected void onHitGround(Vector3 moveVector) { + Block block = level.getBlock(this.chunk, moveVector.getFloorX(), moveVector.getFloorY(), moveVector.getFloorZ(), false); + block.onEntityCollide(this); + } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntitySnowball.java b/src/main/java/cn/nukkit/entity/projectile/EntitySnowball.java index ebad4aa4e94..36a32ec5cb4 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntitySnowball.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntitySnowball.java @@ -1,14 +1,17 @@ package cn.nukkit.entity.projectile; import cn.nukkit.entity.Entity; +import cn.nukkit.item.Item; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.particle.ItemBreakParticle; import cn.nukkit.nbt.tag.CompoundTag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntitySnowball extends EntityProjectile { + public static final int NETWORK_ID = 81; @Override @@ -55,17 +58,17 @@ public boolean onUpdate(int currentTick) { return false; } - this.timing.startTiming(); - - boolean hasUpdate = super.onUpdate(currentTick); - - if (this.age > 1200 || this.isCollided) { - this.kill(); - hasUpdate = true; + if (this.age > 1200 || this.isCollided || this.hadCollision) { + this.close(); + return false; } - this.timing.stopTiming(); + super.onUpdate(currentTick); + return !this.closed; + } - return hasUpdate; + @Override + public void onHit() { + level.addParticle(new ItemBreakParticle(this, Item.get(Item.SNOWBALL)), null, 5); } } diff --git a/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java b/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java index 35602725646..921107a49ac 100644 --- a/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java +++ b/src/main/java/cn/nukkit/entity/projectile/EntityThrownTrident.java @@ -1,6 +1,8 @@ package cn.nukkit.entity.projectile; +import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.data.LongEntityData; import cn.nukkit.entity.weather.EntityLightning; import cn.nukkit.event.entity.EntityDamageByChildEntityEvent; import cn.nukkit.event.entity.EntityDamageByEntityEvent; @@ -10,15 +12,18 @@ import cn.nukkit.event.weather.LightningStrikeEvent; import cn.nukkit.item.Item; import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.level.GameRule; import cn.nukkit.level.MovingObjectPosition; import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.BlockVector3; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.IntTag; +import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; -/** - * Created by PetteriM1 - */ public class EntityThrownTrident extends EntityProjectile { public static final int NETWORK_ID = 73; @@ -26,7 +31,25 @@ public class EntityThrownTrident extends EntityProjectile { protected Item trident; protected int pickupMode; - public boolean alreadyCollided; + + private Vector3 collisionPos; + + private BlockVector3 stuckToBlockPos; + + private int favoredSlot; + + private boolean player; + + private int loyaltyLevel; + + private boolean hasChanneling; + private boolean didHit; + + private int impalingLevel; + + private static final Vector3 defaultCollisionPos = new Vector3(0, 0, 0); + + private static final BlockVector3 defaultStuckToBlockPos = new BlockVector3(0, 0, 0); @Override public int getNetworkId() { @@ -35,22 +58,22 @@ public int getNetworkId() { @Override public float getWidth() { - return 0.05f; + return 0.25f; } @Override public float getLength() { - return 0.5f; + return 0.25f; } @Override public float getHeight() { - return 0.05f; + return 0.35f; } @Override public float getGravity() { - return 0.05f; + return 0.04f; } @Override @@ -66,17 +89,49 @@ public EntityThrownTrident(FullChunk chunk, CompoundTag nbt, Entity shootingEnti super(chunk, nbt, shootingEntity); } - @Deprecated - public EntityThrownTrident(FullChunk chunk, CompoundTag nbt, Entity shootingEntity, boolean critical) { - this(chunk, nbt, shootingEntity); - } - @Override protected void initEntity() { super.initEntity(); - this.trident = namedTag.contains("Trident") ? NBTIO.getItemHelper(namedTag.getCompound("Trident")) : Item.get(0); this.pickupMode = namedTag.contains("pickup") ? namedTag.getByte("pickup") : PICKUP_ANY; + + if (namedTag.contains("Trident")) { + this.trident = NBTIO.getItemHelper(namedTag.getCompound("Trident")); + this.loyaltyLevel = this.trident.getEnchantmentLevel(Enchantment.ID_TRIDENT_LOYALTY); + this.hasChanneling = this.trident.hasEnchantment(Enchantment.ID_TRIDENT_CHANNELING); + this.impalingLevel = this.trident.getEnchantmentLevel(Enchantment.ID_TRIDENT_IMPALING); + } else { + this.trident = Item.get(0); + this.loyaltyLevel = 0; + this.hasChanneling = false; + this.impalingLevel = 0; + } + + if (namedTag.contains("CollisionPos")) { + ListTag collisionPosList = this.namedTag.getList("CollisionPos", DoubleTag.class); + collisionPos = new Vector3(collisionPosList.get(0).data, collisionPosList.get(1).data, collisionPosList.get(2).data); + } else { + collisionPos = defaultCollisionPos.clone(); + } + + if (namedTag.contains("StuckToBlockPos")) { + ListTag stuckToBlockPosList = this.namedTag.getList("StuckToBlockPos", IntTag.class); + stuckToBlockPos = new BlockVector3(stuckToBlockPosList.get(0).data, stuckToBlockPosList.get(1).data, stuckToBlockPosList.get(2).data); + } else { + stuckToBlockPos = defaultStuckToBlockPos.clone(); + } + + if (namedTag.contains("favoredSlot")) { + this.favoredSlot = namedTag.getInt("favoredSlot"); + } else { + this.favoredSlot = -1; + } + + if (namedTag.contains("player")) { + this.player = namedTag.getBoolean("player"); + } else { + this.player = true; + } } @Override @@ -85,6 +140,18 @@ public void saveNBT() { this.namedTag.put("Trident", NBTIO.putItemHelper(this.trident)); this.namedTag.putByte("pickup", this.pickupMode); + this.namedTag.putList(new ListTag("CollisionPos") + .add(new DoubleTag("0", this.collisionPos.x)) + .add(new DoubleTag("1", this.collisionPos.y)) + .add(new DoubleTag("2", this.collisionPos.z)) + ); + this.namedTag.putList(new ListTag("StuckToBlockPos") + .add(new IntTag("0", this.stuckToBlockPos.x)) + .add(new IntTag("1", this.stuckToBlockPos.y)) + .add(new IntTag("2", this.stuckToBlockPos.z)) + ); + this.namedTag.putInt("favoredSlot", this.favoredSlot); + this.namedTag.putBoolean("player", this.player); } public Item getItem() { @@ -93,6 +160,9 @@ public Item getItem() { public void setItem(Item item) { this.trident = item.clone(); + this.loyaltyLevel = this.trident.getEnchantmentLevel(Enchantment.ID_TRIDENT_LOYALTY); + this.hasChanneling = this.trident.hasEnchantment(Enchantment.ID_TRIDENT_CHANNELING); + this.impalingLevel = this.trident.getEnchantmentLevel(Enchantment.ID_TRIDENT_IMPALING); } @Override @@ -100,43 +170,160 @@ protected double getBaseDamage() { return 8; } + @Override + public boolean onUpdate(int currentTick) { + if (this.closed) { + return false; + } + + if (this.age > 1200 && this.pickupMode < 1) { // On Bedrock tridents shouldn't despawn + this.close(); + return false; + } + + boolean hasUpdate = super.onUpdate(currentTick); + + if (this.noClip) { + if (this.canReturnToShooter()) { + Entity shooter = this.shootingEntity; + Vector3 vector3 = new Vector3(shooter.x - this.x, shooter.y + shooter.getEyeHeight() - this.y, shooter.z - this.z); + this.setPosition(new Vector3(this.x, this.y + vector3.y * 0.015 * ((double) loyaltyLevel), this.z)); + this.setMotion(this.getMotion().multiply(0.95).add(vector3.multiply(loyaltyLevel * 0.05))); + hasUpdate = true; + } else { + if (!this.closed && level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS)) { + this.level.dropItem(this, this.trident); + } + this.close(); + } + } + + return hasUpdate; + } + @Override public void onCollideWithEntity(Entity entity) { - if (this.alreadyCollided) { - this.move(this.motionX, this.motionY, this.motionZ); + if (this.noClip) { return; } this.server.getPluginManager().callEvent(new ProjectileHitEvent(this, MovingObjectPosition.fromEntity(entity))); float damage = this.getResultDamage(); + if (this.impalingLevel > 0 && (entity.isInsideOfWater() || (entity.getLevel().isRaining() && entity.canSeeSky()))) { + damage = damage + (2.5f * (float) this.impalingLevel); + } + EntityDamageEvent ev; if (this.shootingEntity == null) { - ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage); + ev = new EntityDamageByEntityEvent(this, entity, DamageCause.PROJECTILE, damage, knockBack); } else { - ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage); + ev = new EntityDamageByChildEntityEvent(this.shootingEntity, this, entity, DamageCause.PROJECTILE, damage, knockBack); } entity.attack(ev); this.hadCollision = true; - this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_HIT); - this.close(); - if (trident != null && level.isThundering() && trident.hasEnchantment(Enchantment.ID_TRIDENT_CHANNELING) && level.canBlockSeeSky(this)) { - EntityLightning bolt = new EntityLightning(this.getChunk(), getDefaultNBT(this)); - LightningStrikeEvent strikeEvent = new LightningStrikeEvent(level, bolt); - server.getPluginManager().callEvent(strikeEvent); - if (!strikeEvent.isCancelled()) { - bolt.spawnToAll(); - level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_THUNDER); - } else { - bolt.setEffect(false); + this.onHit(); + this.setCollisionPos(this); + this.setMotion(new Vector3(this.getMotion().getX() * -0.01, this.getMotion().getY() * -0.1, this.getMotion().getZ() * -0.01)); + + if (this.hasChanneling && !this.didHit) { + this.didHit = true; + if (level.isThundering() && this.canSeeSky()) { + EntityLightning bolt = new EntityLightning(this.getChunk(), getDefaultNBT(this)); + LightningStrikeEvent strikeEvent = new LightningStrikeEvent(level, bolt); + server.getPluginManager().callEvent(strikeEvent); + if (!strikeEvent.isCancelled()) { + bolt.spawnToAll(); + level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_THUNDER); + } else { + bolt.setEffect(false); + } } } - EntityThrownTrident newTrident = (EntityThrownTrident) Entity.createEntity("ThrownTrident", this); - newTrident.alreadyCollided = true; - newTrident.pickupMode = this.pickupMode; - newTrident.shootingEntity = this.shootingEntity; - newTrident.setItem(this.trident); - newTrident.spawnToAll(); + + if (this.canReturnToShooter()) { + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RETURN); + this.noClip = true; + this.hadCollision = false; + this.setRope(true); + } + } + + @Override + public void onHit() { + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_HIT); + } + + @Override + public void onHitGround(Vector3 moveVector) { + if (this.noClip) { + return; + } + super.onHitGround(moveVector); + + this.setStuckToBlockPos(new BlockVector3(moveVector.getFloorX(), moveVector.getFloorY(), moveVector.getFloorZ())); + if (this.canReturnToShooter()) { + this.getLevel().addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RETURN); + this.noClip = true; + this.setRope(true); + } + } + + public Vector3 getCollisionPos() { + return collisionPos; + } + + public void setCollisionPos(Vector3 collisionPos) { + this.collisionPos = collisionPos; + } + + public BlockVector3 getStuckToBlockPos() { + return stuckToBlockPos; + } + + public void setStuckToBlockPos(BlockVector3 stuckToBlockPos) { + this.stuckToBlockPos = stuckToBlockPos; + } + + public int getFavoredSlot() { + return favoredSlot; + } + + public void setFavoredSlot(int favoredSlot) { + this.favoredSlot = favoredSlot; + } + + public boolean shotByPlayer() { + return player; + } + + public void setShotByPlayer(boolean player) { + this.player = player; + } + + public void setRope(boolean tridentRope) { + if (tridentRope) { + this.setDataProperty(new LongEntityData(DATA_OWNER_EID, this.shootingEntity.getId())); + } else { + this.setDataProperty(new LongEntityData(DATA_OWNER_EID, -1)); + } + this.setDataFlag(DATA_FLAGS, DATA_FLAG_SHOW_TRIDENT_ROPE, tridentRope); + } + + private boolean canReturnToShooter() { + if (this.loyaltyLevel <= 0) { + return false; + } + + if (this.getCollisionPos().equals(defaultCollisionPos) && this.getStuckToBlockPos().equals(defaultStuckToBlockPos)) { + return false; + } + + Entity shooter = this.shootingEntity; + if (shooter != null) { + return shooter instanceof Player && shooter.isAlive() && !shooter.isClosed() && shooter.getLevel().getId() == this.getLevel().getId() && !(((Player) shooter).isSpectator()); + } + return false; } public int getPickupMode() { diff --git a/src/main/java/cn/nukkit/entity/weather/EntityLightning.java b/src/main/java/cn/nukkit/entity/weather/EntityLightning.java index f624fd37a2b..05f0737a7ab 100644 --- a/src/main/java/cn/nukkit/entity/weather/EntityLightning.java +++ b/src/main/java/cn/nukkit/entity/weather/EntityLightning.java @@ -11,8 +11,7 @@ import cn.nukkit.math.AxisAlignedBB; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** * Created by boybook on 2016/2/27. @@ -21,12 +20,11 @@ public class EntityLightning extends Entity implements EntityLightningStrike { public static final int NETWORK_ID = 93; - protected boolean isEffect = true; + private boolean isEffect = true; public int state; public int liveTime; - @Override public int getNetworkId() { return NETWORK_ID; @@ -40,11 +38,8 @@ public EntityLightning(FullChunk chunk, CompoundTag nbt) { protected void initEntity() { super.initEntity(); - this.setHealth(4); - this.setMaxHealth(4); - this.state = 2; - this.liveTime = ThreadLocalRandom.current().nextInt(3) + 1; + this.liveTime = Utils.random.nextInt(3) + 1; if (isEffect && this.level.gameRules.getBoolean(GameRule.DO_FIRE_TICK) && (this.server.getDifficulty() >= 2)) { Block block = this.getLevelBlock(); @@ -62,7 +57,7 @@ protected void initEntity() { if (!e.isCancelled()) { level.setBlock(fire, fire, true); - level.scheduleUpdate(fire, fire.tickRate() + ThreadLocalRandom.current().nextInt(10)); + level.scheduleUpdate(fire, fire.tickRate() + Utils.random.nextInt(10)); } } } @@ -79,7 +74,6 @@ public void setEffect(boolean e) { @Override public boolean attack(EntityDamageEvent source) { - //false? source.setDamage(0); return super.attack(source); } @@ -111,7 +105,7 @@ public boolean onUpdate(int currentTick) { if (this.liveTime == 0) { this.close(); return false; - } else if (this.state < -ThreadLocalRandom.current().nextInt(10)) { + } else if (this.state < -Utils.random.nextInt(10)) { this.liveTime--; this.state = 1; @@ -145,6 +139,4 @@ public boolean onUpdate(int currentTick) { return true; } - - } diff --git a/src/main/java/cn/nukkit/entity/weather/EntityLightningStrike.java b/src/main/java/cn/nukkit/entity/weather/EntityLightningStrike.java index a82f8c28972..21fd57fc39d 100644 --- a/src/main/java/cn/nukkit/entity/weather/EntityLightningStrike.java +++ b/src/main/java/cn/nukkit/entity/weather/EntityLightningStrike.java @@ -8,5 +8,4 @@ public interface EntityLightningStrike extends EntityWeather { boolean isEffect(); void setEffect(boolean e); - } diff --git a/src/main/java/cn/nukkit/event/Event.java b/src/main/java/cn/nukkit/event/Event.java index 1c320636e62..0f7a313112c 100644 --- a/src/main/java/cn/nukkit/event/Event.java +++ b/src/main/java/cn/nukkit/event/Event.java @@ -5,21 +5,20 @@ /** * 描述服务器中可能发生的事情的类。
* Describes things that happens in the server. - * - *

服务器中可能发生的事情称作事件。定义一个需要它在一个事件发生时被运行的过程,这个过程称作监听器
+ * + * 服务器中可能发生的事情称作事件。定义一个需要它在一个事件发生时被运行的过程,这个过程称作监听器
* Things that happens in the server is called a event. Define a procedure that should be executed - * when a event happens, this procedure is called a listener.

- * - *

Nukkit调用事件的处理器时,会通过参数的类型判断需要被监听的事件。
- * When Nukkit is calling a handler, the event needed to listen is judged by the type of the parameter.

- * - *

关于监听器的实现,参阅:{@link Listener}
- * For the way to implement a listener, see: {@link cn.nukkit.event.Listener}

+ * when a event happens, this procedure is called a listener. + * + * Nukkit调用事件的处理器时,会通过参数的类型判断需要被监听的事件。
+ * When Nukkit is calling a handler, the event needed to listen is judged by the type of the parameter. + * + * 关于监听器的实现,参阅:{@link Listener}
+ * For the way to implement a listener, see: {@link cn.nukkit.event.Listener} * * @author Unknown(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.event.EventHandler - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public abstract class Event { diff --git a/src/main/java/cn/nukkit/event/EventHandler.java b/src/main/java/cn/nukkit/event/EventHandler.java index 156f9ed807b..9c223a42fff 100644 --- a/src/main/java/cn/nukkit/event/EventHandler.java +++ b/src/main/java/cn/nukkit/event/EventHandler.java @@ -8,19 +8,18 @@ /** * 定义一个事件的处理器的注解。
* Annotation that defines a handler. - * - *

一个处理器的重要程度被称作处理器的优先级,优先级高的处理器有更多的决定权。参见:{@link #priority()}
+ * + * 一个处理器的重要程度被称作处理器的优先级,优先级高的处理器有更多的决定权。参见:{@link #priority()}
* The importance of a handler is called its priority, handlers with higher priority speaks louder then - * lower ones. See: {@link #priority()}

- * - *

处理器可以选择忽略或不忽略被取消的事件,这种特性可以在{@link #ignoreCancelled()}中定义。
- * A handler can choose to ignore a cancelled event or not, that can be defined in {@link #ignoreCancelled()}.

+ * lower ones. See: {@link #priority()} + * + * 处理器可以选择忽略或不忽略被取消的事件,这种特性可以在{@link #ignoreCancelled()}中定义。
+ * A handler can choose to ignore a cancelled event or not, that can be defined in {@link #ignoreCancelled()}. * * @author MagicDroidX(code) @ Nukkit Project * @author null(javadoc) @ Nukkit Project * @see cn.nukkit.event.Listener * @see cn.nukkit.event.Event - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ @Target(ElementType.METHOD) @@ -29,9 +28,9 @@ /** * 定义这个处理器的优先级。
* Define the priority of the handler. - * - *

Nukkit调用处理器时会按照优先级从低到高的顺序调用,这样保证了高优先级的监听器能覆盖低优先级监听器做出的处理。 - * 调用的先后顺序如下:

+ * + * Nukkit调用处理器时会按照优先级从低到高的顺序调用,这样保证了高优先级的监听器能覆盖低优先级监听器做出的处理。 + * 调用的先后顺序如下:
* When Nukkit calls all handlers, ones with lower priority is called earlier, * that make handlers with higher priority can replace the decisions made by lower ones. * The order that Nukkit call handlers is from the first to the last as: @@ -45,20 +44,18 @@ * * * @return 这个处理器的优先级。
The priority of this handler. - * @see cn.nukkit.event.EventHandler */ EventPriority priority() default EventPriority.NORMAL; /** * 定义这个处理器是否忽略被取消的事件。
* Define if the handler ignores a cancelled event. - * - *

如果为{@code true}而且事件发生,这个处理器不会被调用,反之相反。
+ * + * 如果为{@code true}而且事件发生,这个处理器不会被调用,反之相反。
* If ignoreCancelled is {@code true} and the event is cancelled, the method is - * not called. Otherwise, the method is always called.

+ * not called. Otherwise, the method is always called. * * @return 这个处理器是否忽略被取消的事件。
Whether cancelled events should be ignored. - * @see cn.nukkit.event.EventHandler */ boolean ignoreCancelled() default false; } diff --git a/src/main/java/cn/nukkit/event/EventPriority.java b/src/main/java/cn/nukkit/event/EventPriority.java index f0e6304f20e..232c5e8d30d 100644 --- a/src/main/java/cn/nukkit/event/EventPriority.java +++ b/src/main/java/cn/nukkit/event/EventPriority.java @@ -1,7 +1,7 @@ package cn.nukkit.event; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public enum EventPriority { @@ -31,7 +31,7 @@ public enum EventPriority { HIGHEST(4), /** * Event is listened to purely for monitoring the outcome of an event. - *

+ * * No modifications to the event should be made under this priority */ MONITOR(5); diff --git a/src/main/java/cn/nukkit/event/HandlerList.java b/src/main/java/cn/nukkit/event/HandlerList.java index c63b9fe0efc..0423967d126 100644 --- a/src/main/java/cn/nukkit/event/HandlerList.java +++ b/src/main/java/cn/nukkit/event/HandlerList.java @@ -2,6 +2,8 @@ import cn.nukkit.plugin.Plugin; import cn.nukkit.plugin.RegisteredListener; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.*; @@ -12,9 +14,9 @@ public class HandlerList { private volatile RegisteredListener[] handlers = null; - private final EnumMap> handlerslots; - - private static final ArrayList allLists = new ArrayList<>(); + private final EnumMap> handlerslots; + private static final List allLists = new ObjectArrayList<>(); + private static final Map, HandlerList> eventHandlerLists = new Object2ObjectOpenHashMap<>(); public static void bakeAll() { synchronized (allLists) { @@ -56,7 +58,7 @@ public static void unregisterAll(Listener listener) { public HandlerList() { handlerslots = new EnumMap<>(EventPriority.class); for (EventPriority o : EventPriority.values()) { - handlerslots.put(o, new ArrayList<>()); + handlerslots.put(o, new ObjectArrayList<>()); } synchronized (allLists) { allLists.add(this); @@ -110,8 +112,8 @@ public synchronized void unregister(Listener listener) { public synchronized void bake() { if (handlers != null) return; // don't re-bake when still valid - List entries = new ArrayList<>(); - for (Map.Entry> entry : handlerslots.entrySet()) { + List entries = new ObjectArrayList<>(); + for (Map.Entry> entry : handlerslots.entrySet()) { entries.addAll(entry.getValue()); } handlers = entries.toArray(new RegisteredListener[0]); @@ -150,4 +152,11 @@ public static ArrayList getHandlerLists() { } } + public static HandlerList getCachedHandlerList(Class clazz) { + return eventHandlerLists.get(clazz); + } + + public static void putCachedHandlerList(Class clazz, HandlerList handlerList) { + eventHandlerLists.put(clazz, handlerList); + } } diff --git a/src/main/java/cn/nukkit/event/Listener.java b/src/main/java/cn/nukkit/event/Listener.java index 5bd029f168f..411483e148a 100644 --- a/src/main/java/cn/nukkit/event/Listener.java +++ b/src/main/java/cn/nukkit/event/Listener.java @@ -3,26 +3,26 @@ /** * 所有的监听事件的类必须实现的接口。
* An interface implemented by all classes that handles events. - * - *

插件要监听事件,需要一个类实现这个接口,在这个类里编写方法来监听。这个类称作监听类。 + * + * 插件要监听事件,需要一个类实现这个接口,在这个类里编写方法来监听。这个类称作监听类。 * 监听类中监听事件的方法称作事件的处理器。一个监听类可以包含多个不同的事件处理器。 * 实现监听类后,插件需要在插件管理器中注册这个监听类。
* If a plugin need to listen events, there must be a class implement this interface. This class is called a listener class. * Methods with specified parameters should be written in order to listen events. This method is called a handler. * One listener class could contain many different handlers. - * After implemented the listener class, plugin should register it in plugin manager.

- * - *

事件监听器被注册后,Nukkit会在需要监听的事件发生时,使用反射来调用监听类中对应的处理器。
- * After registered, Nukkit will call the handler in the listener classes by reflection when a event happens.

- * - *

这是一个编写监听类和处理器的例子。注意的是,标签{@code @EventHandler}和参数的类型是必需的:
+ * After implemented the listener class, plugin should register it in plugin manager. + * + * 事件监听器被注册后,Nukkit会在需要监听的事件发生时,使用反射来调用监听类中对应的处理器。
+ * After registered, Nukkit will call the handler in the listener classes by reflection when a event happens. + * + * 这是一个编写监听类和处理器的例子。注意的是,标签{@code @EventHandler}和参数的类型是必需的:
* Here is an example for writing a listener class and a handler method. - * Note that for the handler, tag {@code @EventHandler} and the parameter is required:

+ * Note that for the handler, tag {@code @EventHandler} and the parameter is required: *
  * public class ExampleListener implements Listener {
  *    {@code @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = false)}
  *     public void onBlockBreak(BlockBreakEvent event) {
- *          int blockID = event.getBlock().getPackId();
+ *          int blockID = event.getBlock().getId();
  *          if (blockID == Block.STONE) {
  *              event.getPlayer().sendMessage("Oops, my ExampleListener won't let you break a stone!")
  *              event.setCancelled(true);
@@ -31,16 +31,15 @@
  * }
  * 
* - *

关于注册监听类,请看:{@link cn.nukkit.plugin.PluginManager#registerEvents}.
- * For registering listener class, See: {@link cn.nukkit.plugin.PluginManager#registerEvents}.

+ * 关于注册监听类,请看:{@link cn.nukkit.plugin.PluginManager#registerEvents}.
+ * For registering listener class, See: {@link cn.nukkit.plugin.PluginManager#registerEvents}. * - *

关于处理器的优先级和处理器是否忽略被取消的事件,请看:{@link EventHandler}.
- * For the priority of handler and whether the handler ignore cancelled events or not, See: {@link EventHandler}.

+ * 关于处理器的优先级和处理器是否忽略被取消的事件,请看:{@link EventHandler}.
+ * For the priority of handler and whether the handler ignore cancelled events or not, See: {@link EventHandler}. * * @author Unknown(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.event.Event - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface Listener { } diff --git a/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java b/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java index aee8e00eefe..043f2844bf2 100644 --- a/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java +++ b/src/main/java/cn/nukkit/event/block/AnvilDamageEvent.java @@ -15,9 +15,17 @@ public static HandlerList getHandlers() { private final int oldDamage; private int newDamage; - private DamageCause cause; + private final DamageCause cause; private final Player player; + /** + * This event is called when an anvil is damaged. + * @param block The block (anvil) that has been damaged. + * @param oldDamage Old damage value. + * @param newDamage New damage value. + * @param cause Cause of the anvil being damaged. + * @param player The player who used the anvil. + */ public AnvilDamageEvent(Block block, int oldDamage, int newDamage, DamageCause cause, Player player) { super(block); this.oldDamage = oldDamage; diff --git a/src/main/java/cn/nukkit/event/block/BellRingEvent.java b/src/main/java/cn/nukkit/event/block/BellRingEvent.java new file mode 100644 index 00000000000..1129b76f08c --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/BellRingEvent.java @@ -0,0 +1,46 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.BlockBell; +import cn.nukkit.entity.Entity; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +public class BellRingEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final RingCause cause; + private final Entity entity; + + public BellRingEvent(BlockBell bell, RingCause cause, Entity entity) { + super(bell); + this.cause = cause; + this.entity = entity; + } + + @Override + public BlockBell getBlock() { + return (BlockBell) super.getBlock(); + } + + public Entity getEntity() { + return entity; + } + + public RingCause getCause() { + return cause; + } + + public enum RingCause { + HUMAN_INTERACTION, + REDSTONE, + PROJECTILE, + DROPPED_ITEM, + UNKNOWN + } + +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java b/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java index c2b7995cae9..7b6d683468b 100644 --- a/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockBreakEvent.java @@ -6,10 +6,11 @@ import cn.nukkit.event.HandlerList; import cn.nukkit.item.Item; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; /** - * author: MagicDroidX - * Nukkit Project + * Event for Block being broken. + * @author MagicDroidX */ public class BlockBreakEvent extends BlockEvent implements Cancellable { @@ -24,12 +25,21 @@ public static HandlerList getHandlers() { protected final Item item; protected final BlockFace face; - protected boolean instaBreak = false; - protected Item[] blockDrops = new Item[0]; - protected int blockXP = 0; + protected boolean instaBreak; + protected Item[] blockDrops; + protected int blockXP; - protected boolean fastBreak = false; + protected boolean fastBreak; + protected Vector3 dropPosition; + + /** + * This event is called when a block is broken. + * @param player Player who broke the block. + * @param block Block that was broken. + * @param item Item used to break the block. + * @param drops Items dropped by the block. + */ public BlockBreakEvent(Player player, Block block, Item item, Item[] drops) { this(player, block, item, drops, false, false); } @@ -39,10 +49,10 @@ public BlockBreakEvent(Player player, Block block, Item item, Item[] drops, bool } public BlockBreakEvent(Player player, Block block, Item item, Item[] drops, boolean instaBreak, boolean fastBreak) { - this(player, block, null, item, drops, instaBreak, fastBreak); + this(player, block, null, item, drops, instaBreak, fastBreak, null); } - public BlockBreakEvent(Player player, Block block, BlockFace face, Item item, Item[] drops, boolean instaBreak, boolean fastBreak) { + public BlockBreakEvent(Player player, Block block, BlockFace face, Item item, Item[] drops, boolean instaBreak, boolean fastBreak, Vector3 dropPosition) { super(block); this.face = face; this.item = item; @@ -74,12 +84,20 @@ public Item[] getDrops() { } public void setDrops(Item[] drops) { + if (drops == null) { + drops = new Item[0]; + } + this.blockDrops = drops; } - public int getDropExp() { return this.blockXP; } + public int getDropExp() { + return this.blockXP; + } - public void setDropExp(int xp) { this.blockXP = xp; } + public void setDropExp(int xp) { + this.blockXP = xp; + } public void setInstaBreak(boolean instaBreak) { this.instaBreak = instaBreak; @@ -88,4 +106,12 @@ public void setInstaBreak(boolean instaBreak) { public boolean isFastBreak() { return this.fastBreak; } -} + + public void setDropPosition(Vector3 dropPosition) { + this.dropPosition = dropPosition; + } + + public Vector3 getDropPosition() { + return this.dropPosition; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/BlockBurnEvent.java b/src/main/java/cn/nukkit/event/block/BlockBurnEvent.java index f9f88f39125..5710e7181db 100644 --- a/src/main/java/cn/nukkit/event/block/BlockBurnEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockBurnEvent.java @@ -5,13 +5,17 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for Block being burned. + * @author MagicDroidX */ public class BlockBurnEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); + /** + * This event is called when a block is burned. + * @param block Block that is burned. + */ public BlockBurnEvent(Block block) { super(block); } @@ -19,5 +23,4 @@ public BlockBurnEvent(Block block) { public static HandlerList getHandlers() { return handlers; } - } diff --git a/src/main/java/cn/nukkit/event/block/BlockEvent.java b/src/main/java/cn/nukkit/event/block/BlockEvent.java index 438ddfa56f3..bb1e39abece 100644 --- a/src/main/java/cn/nukkit/event/block/BlockEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockEvent.java @@ -4,13 +4,18 @@ import cn.nukkit.event.Event; /** - * author: MagicDroidX - * Nukkit Project + * Generic block event. + * @author MagicDroidX */ public abstract class BlockEvent extends Event { protected final Block block; + /** + * Generic block event. + * NOTICE: This event isn't meant to be called. + * @param block Block. + */ public BlockEvent(Block block) { this.block = block; } diff --git a/src/main/java/cn/nukkit/event/block/BlockExplodeEvent.java b/src/main/java/cn/nukkit/event/block/BlockExplodeEvent.java new file mode 100644 index 00000000000..5fbd3490c1d --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/BlockExplodeEvent.java @@ -0,0 +1,55 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.level.Position; + +import java.util.List; + +public class BlockExplodeEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + protected final Position position; + protected List blocks; + protected double yield; + + /** + * Block explode event is called when a block explodes (For example a bed in nether) + * @param block Block that exploded + * @param position Position + * @param blocks Blocks affected by the explosion + * @param yield Explosion yield + */ + public BlockExplodeEvent(Block block, Position position, List blocks, double yield) { + super(block); + this.position = position; + this.blocks = blocks; + this.yield = yield; + } + + public Position getPosition() { + return this.position; + } + + public List getBlockList() { + return this.blocks; + } + + public void setBlockList(List blocks) { + this.blocks = blocks; + } + + public double getYield() { + return this.yield; + } + + public void setYield(double yield) { + this.yield = yield; + } +} diff --git a/src/main/java/cn/nukkit/event/block/BlockFadeEvent.java b/src/main/java/cn/nukkit/event/block/BlockFadeEvent.java index 950247145ba..24865a187fc 100644 --- a/src/main/java/cn/nukkit/event/block/BlockFadeEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockFadeEvent.java @@ -4,6 +4,9 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +/** + * Event for Block fading. + */ public class BlockFadeEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -14,6 +17,14 @@ public static HandlerList getHandlers() { private final Block newState; + /** + * Currently used when: + * - Snow melts because of block light. + * - Ice melts because of block light. + * - Glowing redstone ore turns to normal redstone ore. + * @param block Block that has faded/melted. + * @param newState New state of the block. + */ public BlockFadeEvent(Block block, Block newState) { super(block); this.newState = newState; diff --git a/src/main/java/cn/nukkit/event/block/BlockFallEvent.java b/src/main/java/cn/nukkit/event/block/BlockFallEvent.java index 5f3c2001787..d4b5f59d7bd 100644 --- a/src/main/java/cn/nukkit/event/block/BlockFallEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockFallEvent.java @@ -4,6 +4,9 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +/** + * Event for Block falling + */ public class BlockFallEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -12,6 +15,10 @@ public static HandlerList getHandlers() { return handlers; } + /** + * This event is called when a block is falling. + * @param block Block that has fallen. + */ public BlockFallEvent(Block block) { super(block); } diff --git a/src/main/java/cn/nukkit/event/block/BlockFormEvent.java b/src/main/java/cn/nukkit/event/block/BlockFormEvent.java index 5b2b025fc04..1f631c67fca 100644 --- a/src/main/java/cn/nukkit/event/block/BlockFormEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockFormEvent.java @@ -5,8 +5,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for forming blocks. + * @author MagicDroidX */ public class BlockFormEvent extends BlockGrowEvent implements Cancellable { @@ -15,9 +15,13 @@ public class BlockFormEvent extends BlockGrowEvent implements Cancellable { public static HandlerList getHandlers() { return handlers; } - + /** + * Event for forming blocks. + * NOTICE: This event isn't meant to be called. + * @param block Block affected by the event. + * @param newState New state of the block. + */ public BlockFormEvent(Block block, Block newState) { super(block, newState); } - } diff --git a/src/main/java/cn/nukkit/event/block/BlockFromToEvent.java b/src/main/java/cn/nukkit/event/block/BlockFromToEvent.java index 106e7d5dbdd..bbb9c9f94a9 100644 --- a/src/main/java/cn/nukkit/event/block/BlockFromToEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockFromToEvent.java @@ -4,6 +4,9 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +/** + * Event for Block change "From To". + */ public class BlockFromToEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -14,6 +17,11 @@ public static HandlerList getHandlers() { private Block to; + /** + * Event called on block changed from one type to another. E.g Redstone. + * @param block Block that is being replaced. + * @param to The replacement Block + */ public BlockFromToEvent(Block block, Block to) { super(block); this.to = to; diff --git a/src/main/java/cn/nukkit/event/block/BlockGrowEvent.java b/src/main/java/cn/nukkit/event/block/BlockGrowEvent.java index 9dba0550982..03a18ba3085 100644 --- a/src/main/java/cn/nukkit/event/block/BlockGrowEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockGrowEvent.java @@ -5,8 +5,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for Block growth. + * @author MagicDroidX */ public class BlockGrowEvent extends BlockEvent implements Cancellable { @@ -18,6 +18,11 @@ public static HandlerList getHandlers() { private final Block newState; + /** + * Called on block grow. + * @param block Block affected by event E.g Vine. + * @param newState New state of the affected block. + */ public BlockGrowEvent(Block block, Block newState) { super(block); this.newState = newState; @@ -26,5 +31,4 @@ public BlockGrowEvent(Block block, Block newState) { public Block getNewState() { return newState; } - } diff --git a/src/main/java/cn/nukkit/event/block/BlockHarvestEvent.java b/src/main/java/cn/nukkit/event/block/BlockHarvestEvent.java new file mode 100644 index 00000000000..937bec30be8 --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/BlockHarvestEvent.java @@ -0,0 +1,41 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.item.Item; + +public class BlockHarvestEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private Block newState; + private Item[] drops; + + public BlockHarvestEvent(Block block, Block newState, Item[] drops) { + super(block); + this.newState = newState; + this.drops = drops; + } + + public Block getNewState() { + return newState; + } + + public void setNewState(Block newState) { + this.newState = newState; + } + + public Item[] getDrops() { + return drops; + } + + public void setDrops(Item[] drops) { + this.drops = drops; + } + + public static HandlerList getHandlers() { + return handlers; + } + +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/BlockIgniteEvent.java b/src/main/java/cn/nukkit/event/block/BlockIgniteEvent.java index 7bad13ea485..de85bb22b04 100644 --- a/src/main/java/cn/nukkit/event/block/BlockIgniteEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockIgniteEvent.java @@ -5,6 +5,9 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +/** + * Event for Block fire (ignite). + */ public class BlockIgniteEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -17,6 +20,13 @@ public static HandlerList getHandlers() { private final Entity entity; private final BlockIgniteCause cause; + /** + * Block ignite event is called when a block is ignited (lit on fire). + * @param block Block that has been ignited. + * @param source Block source of ignition. + * @param entity Entity source of ignition. + * @param cause Cause of the ignition. + */ public BlockIgniteEvent(Block block, Block source, Entity entity, BlockIgniteCause cause) { super(block); this.source = source; diff --git a/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java b/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java index ab5d0184f07..f81178af9cb 100644 --- a/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockPistonChangeEvent.java @@ -4,7 +4,8 @@ import cn.nukkit.event.HandlerList; /** - * Created by CreeperFace on 2.8.2017. + * Event for Block piston change. + * @author CreeperFace on 2.8.2017. */ public class BlockPistonChangeEvent extends BlockEvent { @@ -14,9 +15,15 @@ public static HandlerList getHandlers() { return handlers; } - private int oldPower; - private int newPower; + private final int oldPower; + private final int newPower; + /** + * This event is called on piston activation/deactivation/change. + * @param block Block (Piston) that is affected. + * @param oldPower Old power (charge) of piston. + * @param newPower New charge (updated) of piston. + */ public BlockPistonChangeEvent(Block block, int oldPower, int newPower) { super(block); this.oldPower = oldPower; diff --git a/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java b/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java new file mode 100644 index 00000000000..2d1cc3debd7 --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/BlockPistonEvent.java @@ -0,0 +1,53 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockPistonBase; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.math.BlockFace; + +import java.util.ArrayList; +import java.util.List; + +public class BlockPistonEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final BlockFace direction; + private final List blocks; + private final List destroyedBlocks; + private final boolean extending; + + public BlockPistonEvent(BlockPistonBase piston, BlockFace direction, List blocks, List destroyedBlocks, boolean extending) { + super(piston); + this.direction = direction; + this.blocks = blocks; + this.destroyedBlocks = destroyedBlocks; + this.extending = extending; + } + + public BlockFace getDirection() { + return direction; + } + + public List getBlocks() { + return new ArrayList<>(blocks); + } + + public List getDestroyedBlocks() { + return destroyedBlocks; + } + + public boolean isExtending() { + return extending; + } + + @Override + public BlockPistonBase getBlock() { + return (BlockPistonBase) super.getBlock(); + } +} diff --git a/src/main/java/cn/nukkit/event/block/BlockPlaceEvent.java b/src/main/java/cn/nukkit/event/block/BlockPlaceEvent.java index 7ba1c6ceefe..3ada1db8105 100644 --- a/src/main/java/cn/nukkit/event/block/BlockPlaceEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockPlaceEvent.java @@ -7,8 +7,8 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX - * Nukkit Project + * Event for a block being placed. + * @author MagicDroidX */ public class BlockPlaceEvent extends BlockEvent implements Cancellable { @@ -25,6 +25,14 @@ public static HandlerList getHandlers() { protected final Block blockReplace; protected final Block blockAgainst; + /** + * This event is called when a block is placed. + * @param player Player who placed block. + * @param blockPlace Placed Block. + * @param blockReplace Block replace. + * @param blockAgainst Block against. + * @param item Item that was placed. + */ public BlockPlaceEvent(Player player, Block blockPlace, Block blockReplace, Block blockAgainst, Item item) { super(blockPlace); this.blockReplace = blockReplace; diff --git a/src/main/java/cn/nukkit/event/block/BlockRedstoneEvent.java b/src/main/java/cn/nukkit/event/block/BlockRedstoneEvent.java index 38f1d13e7ac..49fdb932249 100644 --- a/src/main/java/cn/nukkit/event/block/BlockRedstoneEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockRedstoneEvent.java @@ -4,7 +4,8 @@ import cn.nukkit.event.HandlerList; /** - * Created by CreeperFace on 12.5.2017. + * Event for Redstone Block. + * @author CreeperFace on 12.5.2017. */ public class BlockRedstoneEvent extends BlockEvent { @@ -14,9 +15,15 @@ public static HandlerList getHandlers() { return handlers; } - private int oldPower; - private int newPower; + private final int oldPower; + private final int newPower; + /** + * Event called on redstone change. E.g Redstone power. + * @param block Block that is affected. + * @param oldPower Old power of the block. + * @param newPower New power of the block. + */ public BlockRedstoneEvent(Block block, int oldPower, int newPower) { super(block); this.oldPower = oldPower; diff --git a/src/main/java/cn/nukkit/event/block/BlockSpreadEvent.java b/src/main/java/cn/nukkit/event/block/BlockSpreadEvent.java index b687d92e14b..baad5406cf8 100644 --- a/src/main/java/cn/nukkit/event/block/BlockSpreadEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockSpreadEvent.java @@ -5,8 +5,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for Block spread. + * @author MagicDroidX */ public class BlockSpreadEvent extends BlockFormEvent implements Cancellable { @@ -18,6 +18,12 @@ public static HandlerList getHandlers() { private final Block source; + /** + * Event for block spread, such as grass or mycelium. + * @param block Block that is being spread. + * @param source The source block. + * @param newState New state of spread block. + */ public BlockSpreadEvent(Block block, Block source, Block newState) { super(block, newState); this.source = source; diff --git a/src/main/java/cn/nukkit/event/block/BlockUpdateEvent.java b/src/main/java/cn/nukkit/event/block/BlockUpdateEvent.java index 9f2c1066bc2..35127f03d54 100644 --- a/src/main/java/cn/nukkit/event/block/BlockUpdateEvent.java +++ b/src/main/java/cn/nukkit/event/block/BlockUpdateEvent.java @@ -5,8 +5,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for Block Update + * @author MagicDroidX */ public class BlockUpdateEvent extends BlockEvent implements Cancellable { @@ -16,8 +16,11 @@ public static HandlerList getHandlers() { return handlers; } + /** + * Event called on a block being updated. + * @param block Block updated. + */ public BlockUpdateEvent(Block block) { super(block); } - } diff --git a/src/main/java/cn/nukkit/event/block/CampfireSmeltEvent.java b/src/main/java/cn/nukkit/event/block/CampfireSmeltEvent.java new file mode 100644 index 00000000000..860a5145358 --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/CampfireSmeltEvent.java @@ -0,0 +1,52 @@ +package cn.nukkit.event.block; + +import cn.nukkit.blockentity.BlockEntityCampfire; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.item.Item; + +public class CampfireSmeltEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final BlockEntityCampfire campfire; + private final Item source; + private Item result; + private boolean keepItem; + + public CampfireSmeltEvent(BlockEntityCampfire campfire, Item source, Item result) { + super(campfire.getBlock()); + this.source = source.clone(); + this.source.setCount(1); + this.result = result; + this.campfire = campfire; + } + + public BlockEntityCampfire getCampfire() { + return campfire; + } + + public Item getSource() { + return source; + } + + public Item getResult() { + return result; + } + + public void setResult(Item result) { + this.result = result; + } + + public boolean getKeepItem() { + return keepItem; + } + + public void setKeepItem(boolean keepItem) { + this.keepItem = keepItem; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/ComposterEmptyEvent.java b/src/main/java/cn/nukkit/event/block/ComposterEmptyEvent.java new file mode 100644 index 00000000000..7d070aa6b37 --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/ComposterEmptyEvent.java @@ -0,0 +1,73 @@ +package cn.nukkit.event.block; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.item.Item; +import cn.nukkit.math.Vector3; + +public class ComposterEmptyEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private Item drop; + private Item itemUsed; + private int newLevel; + private Vector3 motion; + + public ComposterEmptyEvent(Block block, Player player, Item itemUsed, Item drop, int newLevel) { + super(block); + this.player = player; + this.drop = drop; + this.itemUsed = itemUsed; + this.newLevel = Math.max(0, Math.min(newLevel, 8)); + } + + public Player getPlayer() { + return player; + } + + public Item getDrop() { + return drop.clone(); + } + + public void setDrop(Item drop) { + if (drop == null) { + drop = Item.get(Item.AIR); + } else { + drop = drop.clone(); + } + this.drop = drop; + } + + public Item getItemUsed() { + return itemUsed; + } + + public void setItemUsed(Item itemUsed) { + this.itemUsed = itemUsed; + } + + public int getNewLevel() { + return newLevel; + } + + public void setNewLevel(int newLevel) { + this.newLevel = Math.max(0, Math.min(newLevel, 8)); + } + + public Vector3 getMotion() { + return motion; + } + + public void setMotion(Vector3 motion) { + this.motion = motion; + } + + public static HandlerList getHandlers() { + return handlers; + } + +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/ComposterFillEvent.java b/src/main/java/cn/nukkit/event/block/ComposterFillEvent.java new file mode 100644 index 00000000000..84e2a13cfee --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/ComposterFillEvent.java @@ -0,0 +1,49 @@ +package cn.nukkit.event.block; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.item.Item; + +public class ComposterFillEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + private final Player player; + private final Item item; + private final int chance; + private boolean success; + + public ComposterFillEvent(Block block, Player player, Item item, int chance, boolean success) { + super(block); + this.player = player; + this.item = item; + this.chance = chance; + this.success = success; + } + + public Player getPlayer() { + return player; + } + + public Item getItem() { + return item; + } + + public int getChance() { + return chance; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static HandlerList getHandlers() { + return handlers; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/block/DoorToggleEvent.java b/src/main/java/cn/nukkit/event/block/DoorToggleEvent.java index 5f60578b3b7..40ec3019f96 100644 --- a/src/main/java/cn/nukkit/event/block/DoorToggleEvent.java +++ b/src/main/java/cn/nukkit/event/block/DoorToggleEvent.java @@ -6,8 +6,8 @@ import cn.nukkit.event.HandlerList; /** - * Created by Snake1999 on 2016/1/22. - * Package cn.nukkit.event.block in project nukkit. + * Event for door Interactions. + * @author Snake1999 on 2016/1/22. */ public class DoorToggleEvent extends BlockUpdateEvent implements Cancellable { @@ -19,6 +19,11 @@ public static HandlerList getHandlers() { private Player player; + /** + * Event for player door interactions. + * @param block Door block that has been affected by the player. + * @param player Player that is interacting with the door. + */ public DoorToggleEvent(Block block, Player player) { super(block); this.player = player; diff --git a/src/main/java/cn/nukkit/event/block/ItemFrameDropItemEvent.java b/src/main/java/cn/nukkit/event/block/ItemFrameDropItemEvent.java index 4753c478bb7..ca9fb143e0e 100644 --- a/src/main/java/cn/nukkit/event/block/ItemFrameDropItemEvent.java +++ b/src/main/java/cn/nukkit/event/block/ItemFrameDropItemEvent.java @@ -8,7 +8,8 @@ import cn.nukkit.item.Item; /** - * Created by Pub4Game on 03.07.2016. + * Event for Item Frame drops. + * @author Pub4Game on 03.07.2016. */ public class ItemFrameDropItemEvent extends BlockEvent implements Cancellable { @@ -17,6 +18,13 @@ public class ItemFrameDropItemEvent extends BlockEvent implements Cancellable { private final Item item; private final BlockEntityItemFrame itemFrame; + /** + * Event for item being dropped from an item frame + * @param player Player related to the event. + * @param block Block (item frame) affected by change. + * @param itemFrame Item frame block entity. + * @param item Item that is dropped/contained in the item frame. + */ public ItemFrameDropItemEvent(Player player, Block block, BlockEntityItemFrame itemFrame, Item item) { super(block); this.player = player; diff --git a/src/main/java/cn/nukkit/event/block/LeavesDecayEvent.java b/src/main/java/cn/nukkit/event/block/LeavesDecayEvent.java index 19eca2ef8e7..98050b20364 100644 --- a/src/main/java/cn/nukkit/event/block/LeavesDecayEvent.java +++ b/src/main/java/cn/nukkit/event/block/LeavesDecayEvent.java @@ -5,8 +5,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for Leaves decay. + * @author MagicDroidX */ public class LeavesDecayEvent extends BlockEvent implements Cancellable { @@ -16,8 +16,11 @@ public static HandlerList getHandlers() { return handlers; } + /** + * Event for leaves decaying / disappearing. + * @param block Leaves block. + */ public LeavesDecayEvent(Block block) { super(block); } - } diff --git a/src/main/java/cn/nukkit/event/block/LiquidFlowEvent.java b/src/main/java/cn/nukkit/event/block/LiquidFlowEvent.java index 16a30c4f52b..d2badb633c3 100644 --- a/src/main/java/cn/nukkit/event/block/LiquidFlowEvent.java +++ b/src/main/java/cn/nukkit/event/block/LiquidFlowEvent.java @@ -5,6 +5,9 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +/** + * Event for liquid flow. + */ public class LiquidFlowEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -16,7 +19,12 @@ public static HandlerList getHandlers() { private final Block to; private final BlockLiquid source; private final int newFlowDecay; - + /** + * Event for liquid flowing (water/lava). + * @param to Flowing from one place to another. + * @param source Source of liquid flow. + * @param newFlowDecay Number for when water stops flowing. + */ public LiquidFlowEvent(Block to, BlockLiquid source, int newFlowDecay) { super(to); this.to = to; diff --git a/src/main/java/cn/nukkit/event/block/SignChangeEvent.java b/src/main/java/cn/nukkit/event/block/SignChangeEvent.java index 62727e9eef6..6cf41029006 100644 --- a/src/main/java/cn/nukkit/event/block/SignChangeEvent.java +++ b/src/main/java/cn/nukkit/event/block/SignChangeEvent.java @@ -6,8 +6,8 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX - * Nukkit Project + * Event for sign text change. + * @author MagicDroidX */ public class SignChangeEvent extends BlockEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -18,8 +18,14 @@ public static HandlerList getHandlers() { private final Player player; - private String[] lines = new String[4]; + private final String[] lines; + /** + * This event is called when a sign is being edited. + * @param block Sign block. + * @param player Player that edited the sign. + * @param lines Sign text (String[4]) after the edit. + */ public SignChangeEvent(Block block, Player player, String[] lines) { super(block); this.player = player; diff --git a/src/main/java/cn/nukkit/event/block/WaterFrostEvent.java b/src/main/java/cn/nukkit/event/block/WaterFrostEvent.java new file mode 100644 index 00000000000..8e752842e4d --- /dev/null +++ b/src/main/java/cn/nukkit/event/block/WaterFrostEvent.java @@ -0,0 +1,25 @@ +package cn.nukkit.event.block; + +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +/** + * Event for water freezing. + */ +public class WaterFrostEvent extends BlockEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + /** + * Event called on water freezing. + * @param block Block frozen. + */ + public WaterFrostEvent(Block block) { + super(block); + } +} diff --git a/src/main/java/cn/nukkit/event/entity/CreatureSpawnEvent.java b/src/main/java/cn/nukkit/event/entity/CreatureSpawnEvent.java index bcd86b314b5..91625611572 100644 --- a/src/main/java/cn/nukkit/event/entity/CreatureSpawnEvent.java +++ b/src/main/java/cn/nukkit/event/entity/CreatureSpawnEvent.java @@ -19,6 +19,14 @@ public static HandlerList getHandlers() { private final Position position; private final CompoundTag compoundTag; + public CreatureSpawnEvent(int networkId, SpawnReason reason) { + this(networkId, new Position(), new CompoundTag(), reason); + } + + public CreatureSpawnEvent(int networkId, Position position, SpawnReason reason) { + this(networkId, position, new CompoundTag(), reason); + } + public CreatureSpawnEvent(int networkId, Position position, CompoundTag nbt, SpawnReason reason) { this.reason = reason; this.entityNetworkId = networkId; @@ -65,7 +73,7 @@ public enum SpawnReason { */ EGG, /** - * When a creature spawns from a Spawner Egg + * When a creature spawns from a spawn egg */ SPAWN_EGG, /** @@ -159,7 +167,7 @@ public enum SpawnReason { */ CUSTOM, /** - * When an entity is missing a SpawnReason + * When SpawnReason is missing */ DEFAULT } diff --git a/src/main/java/cn/nukkit/event/entity/CreeperPowerEvent.java b/src/main/java/cn/nukkit/event/entity/CreeperPowerEvent.java index 3c02167c363..0be43d60e6a 100644 --- a/src/main/java/cn/nukkit/event/entity/CreeperPowerEvent.java +++ b/src/main/java/cn/nukkit/event/entity/CreeperPowerEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class CreeperPowerEvent extends EntityEvent implements Cancellable { @@ -59,19 +59,19 @@ public enum PowerCause { /** * Power change caused by a lightning bolt - *

+ * * Powered state: true */ LIGHTNING, /** * Power change caused by something else (probably a plugin) - *

+ * * Powered state: true */ SET_ON, /** * Power change caused by something else (probably a plugin) - *

+ * * Powered state: false */ SET_OFF diff --git a/src/main/java/cn/nukkit/event/entity/EndermanBlockPickUpEvent.java b/src/main/java/cn/nukkit/event/entity/EndermanBlockPickUpEvent.java new file mode 100644 index 00000000000..37a3ee46cc5 --- /dev/null +++ b/src/main/java/cn/nukkit/event/entity/EndermanBlockPickUpEvent.java @@ -0,0 +1,31 @@ +package cn.nukkit.event.entity; + +import cn.nukkit.block.Block; +import cn.nukkit.entity.mob.EntityEnderman; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +public class EndermanBlockPickUpEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Block block; + + public EndermanBlockPickUpEvent(EntityEnderman entity, Block block) { + this.entity = entity; + this.block = block; + } + + public Block getBlock() { + return this.block; + } + + @Override + public EntityEnderman getEntity() { + return (EntityEnderman) super.getEntity(); + } +} diff --git a/src/main/java/cn/nukkit/event/entity/EntityArmorChangeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityArmorChangeEvent.java index 0983830f022..d2131139c1d 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityArmorChangeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityArmorChangeEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityArmorChangeEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityBlockChangeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityBlockChangeEvent.java index 9e72203944d..507dfcd8693 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityBlockChangeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityBlockChangeEvent.java @@ -5,9 +5,6 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; -/** - * Created on 15-10-26. - */ public class EntityBlockChangeEvent extends EntityEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -31,5 +28,4 @@ public Block getFrom() { public Block getTo() { return to; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityCombustByBlockEvent.java b/src/main/java/cn/nukkit/event/entity/EntityCombustByBlockEvent.java index 0e8d24061d6..903430ff1ab 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityCombustByBlockEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityCombustByBlockEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.entity.Entity; /** - * author: Box + * @author Box * Nukkit Project */ public class EntityCombustByBlockEvent extends EntityCombustEvent { diff --git a/src/main/java/cn/nukkit/event/entity/EntityCombustByEntityEvent.java b/src/main/java/cn/nukkit/event/entity/EntityCombustByEntityEvent.java index 6b81b12eceb..6c4477b5bc2 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityCombustByEntityEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityCombustByEntityEvent.java @@ -3,7 +3,7 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityCombustByEntityEvent extends EntityCombustEvent { diff --git a/src/main/java/cn/nukkit/event/entity/EntityCombustEvent.java b/src/main/java/cn/nukkit/event/entity/EntityCombustEvent.java index 129962a61ce..c125568ddc5 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityCombustEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityCombustEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityCombustEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityDamageBlockedEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDamageBlockedEvent.java new file mode 100644 index 00000000000..a40c91cf65e --- /dev/null +++ b/src/main/java/cn/nukkit/event/entity/EntityDamageBlockedEvent.java @@ -0,0 +1,56 @@ +package cn.nukkit.event.entity; + +import cn.nukkit.entity.Entity; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +public class EntityDamageBlockedEvent extends EntityEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final EntityDamageEvent damage; + private boolean knockBackAttacker; + private boolean animation; + + public EntityDamageBlockedEvent(Entity entity, EntityDamageEvent damage, boolean knockBack, boolean animation) { + this.entity = entity; + this.damage = damage; + this.knockBackAttacker = knockBack; + this.animation = animation; + } + + public EntityDamageEvent.DamageCause getCause() { + return damage.getCause(); + } + + public Entity getAttacker() { + if (damage instanceof EntityDamageByEntityEvent) { + return ((EntityDamageByEntityEvent) damage).getDamager(); + } + return damage.getEntity(); + } + + public EntityDamageEvent getDamage() { + return damage; + } + + public boolean getKnockBackAttacker() { + return knockBackAttacker; + } + + public boolean getAnimation() { + return animation; + } + + public void setKnockBackAttacker(boolean val) { + knockBackAttacker = val; + } + + public void setAnimation(boolean val) { + animation = val; + } +} diff --git a/src/main/java/cn/nukkit/event/entity/EntityDamageByBlockEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDamageByBlockEvent.java index 740869dd951..a193f71d87e 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDamageByBlockEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDamageByBlockEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDamageByBlockEvent extends EntityDamageEvent { @@ -19,5 +19,4 @@ public EntityDamageByBlockEvent(Block damager, Entity entity, DamageCause cause, public Block getDamager() { return damager; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityDamageByChildEntityEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDamageByChildEntityEvent.java index b4bff9357d8..d3c471a6f6c 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDamageByChildEntityEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDamageByChildEntityEvent.java @@ -3,7 +3,7 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent { @@ -11,7 +11,11 @@ public class EntityDamageByChildEntityEvent extends EntityDamageByEntityEvent { private final Entity childEntity; public EntityDamageByChildEntityEvent(Entity damager, Entity childEntity, Entity entity, DamageCause cause, float damage) { - super(damager, entity, cause, damage); + this(damager, childEntity, entity, cause, damage, 0.3f); + } + + public EntityDamageByChildEntityEvent(Entity damager, Entity childEntity, Entity entity, DamageCause cause, float damage, float knockBack) { + super(damager, entity, cause, damage, knockBack); this.childEntity = childEntity; } diff --git a/src/main/java/cn/nukkit/event/entity/EntityDamageByEntityEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDamageByEntityEvent.java index 402ec40f1e9..2f09d063a03 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDamageByEntityEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDamageByEntityEvent.java @@ -7,7 +7,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDamageByEntityEvent extends EntityDamageEvent { diff --git a/src/main/java/cn/nukkit/event/entity/EntityDamageEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDamageEvent.java index 5f640894c85..473da0bff79 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDamageEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDamageEvent.java @@ -3,18 +3,22 @@ import cn.nukkit.entity.Entity; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; +import cn.nukkit.item.Item; import cn.nukkit.potion.Effect; import cn.nukkit.utils.EventException; import com.google.common.collect.ImmutableMap; +import lombok.Getter; +import lombok.Setter; import java.util.EnumMap; import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDamageEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { @@ -27,19 +31,18 @@ public static HandlerList getHandlers() { private final Map modifiers; private final Map originals; + @Getter + @Setter + private Item weapon; + public EntityDamageEvent(Entity entity, DamageCause cause, float damage) { - this(entity, cause, new EnumMap(DamageModifier.class) { - { - put(DamageModifier.BASE, damage); - } - }); + this(entity, cause, new DamageModifierFloatEnumMap(damage)); } public EntityDamageEvent(Entity entity, DamageCause cause, Map modifiers) { this.entity = entity; this.cause = cause; this.modifiers = new EnumMap<>(modifiers); - this.originals = ImmutableMap.copyOf(this.modifiers); if (!this.modifiers.containsKey(DamageModifier.BASE)) { @@ -99,13 +102,13 @@ public float getFinalDamage() { } } - return damage; + return Math.max(damage, 0); } - + public int getAttackCooldown() { return this.attackCooldown; } - + public void setAttackCooldown(int attackCooldown) { this.attackCooldown = attackCooldown; } @@ -151,9 +154,13 @@ public enum DamageModifier { */ ABSORPTION, /** - * Damage reduction caused by the armor enchantments worn. + * Damage reduction caused by armor's enchantments + */ + ARMOR_ENCHANTMENTS, + /** + * Additional damage caused by critical hit */ - ARMOR_ENCHANTMENTS + CRITICAL } public enum DamageCause { @@ -189,6 +196,10 @@ public enum DamageCause { * Damage caused by standing in lava */ LAVA, + /** + * Damage caused by standing on magma block + */ + MAGMA, /** * Damage caused by running out of air underwater */ @@ -224,6 +235,18 @@ public enum DamageCause { /** * Damage caused by hunger */ - HUNGER + HUNGER, + /** + * Damage caused by thorns enchantment + */ + THORNS + } + + private static class DamageModifierFloatEnumMap extends EnumMap { + + public DamageModifierFloatEnumMap(float damage) { + super(DamageModifier.class); + put(DamageModifier.BASE, damage); + } } } diff --git a/src/main/java/cn/nukkit/event/entity/EntityDeathEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDeathEvent.java index 80a1a0e5c18..6085eac7d4f 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDeathEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDeathEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDeathEvent extends EntityEvent { diff --git a/src/main/java/cn/nukkit/event/entity/EntityDespawnEvent.java b/src/main/java/cn/nukkit/event/entity/EntityDespawnEvent.java index 71193b61e7c..3f1e49c9502 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityDespawnEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityDespawnEvent.java @@ -4,12 +4,13 @@ import cn.nukkit.entity.EntityCreature; import cn.nukkit.entity.EntityHuman; import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.event.HandlerList; import cn.nukkit.level.Position; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityDespawnEvent extends EntityEvent { @@ -21,7 +22,7 @@ public static HandlerList getHandlers() { private final int entityType; - public EntityDespawnEvent(cn.nukkit.entity.Entity entity) { + public EntityDespawnEvent(Entity entity) { this.entity = entity; this.entityType = entity.getNetworkId(); } @@ -47,11 +48,10 @@ public boolean isProjectile() { } public boolean isVehicle() { - return this.entity instanceof Entity; + return this.entity instanceof EntityVehicle; } public boolean isItem() { return this.entity instanceof EntityItem; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityEvent.java b/src/main/java/cn/nukkit/event/entity/EntityEvent.java index da93a9a87e2..c96a8c15bd9 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.event.Event; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EntityEvent extends Event { diff --git a/src/main/java/cn/nukkit/event/entity/EntityExplodeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityExplodeEvent.java index cbc96f9b3a9..cc91fae0ddc 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityExplodeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityExplodeEvent.java @@ -9,7 +9,7 @@ import java.util.List; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class EntityExplodeEvent extends EntityEvent implements Cancellable { @@ -50,5 +50,4 @@ public double getYield() { public void setYield(double yield) { this.yield = yield; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityExplosionPrimeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityExplosionPrimeEvent.java index 97d6c901298..80aea0948ff 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityExplosionPrimeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityExplosionPrimeEvent.java @@ -4,9 +4,6 @@ import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; -/** - * Created on 15-10-27. - */ public class EntityExplosionPrimeEvent extends EntityEvent implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -39,5 +36,4 @@ public boolean isBlockBreaking() { public void setBlockBreaking(boolean blockBreaking) { this.blockBreaking = blockBreaking; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityInteractEvent.java b/src/main/java/cn/nukkit/event/entity/EntityInteractEvent.java index 5d198f2e9c2..de9c82ec9d6 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityInteractEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityInteractEvent.java @@ -16,7 +16,7 @@ public static HandlerList getHandlers() { return handlers; } - private Block block; + private final Block block; public EntityInteractEvent(Entity entity, Block block) { this.entity = entity; diff --git a/src/main/java/cn/nukkit/event/entity/EntityInventoryChangeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityInventoryChangeEvent.java index aca7a6f4ad0..a90a6ed848c 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityInventoryChangeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityInventoryChangeEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityInventoryChangeEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityLevelChangeEvent.java b/src/main/java/cn/nukkit/event/entity/EntityLevelChangeEvent.java index 9f40a740b45..fbbfb929048 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityLevelChangeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityLevelChangeEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityLevelChangeEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityMotionEvent.java b/src/main/java/cn/nukkit/event/entity/EntityMotionEvent.java index 82a7cd9fbc3..857da24840b 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityMotionEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityMotionEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityMotionEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityRegainHealthEvent.java b/src/main/java/cn/nukkit/event/entity/EntityRegainHealthEvent.java index 265ec089713..bd7c52e71eb 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityRegainHealthEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityRegainHealthEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityRegainHealthEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/EntityShootBowEvent.java b/src/main/java/cn/nukkit/event/entity/EntityShootBowEvent.java index 8992f9f26a7..1d6a9a82514 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityShootBowEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityShootBowEvent.java @@ -8,10 +8,11 @@ import cn.nukkit.item.Item; /** - * author: Box + * @author Box * Nukkit Project */ public class EntityShootBowEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { @@ -48,8 +49,7 @@ public EntityProjectile getProjectile() { public void setProjectile(Entity projectile) { if (projectile != this.projectile) { - if (this.projectile.getViewers().size() == 0) { - this.projectile.kill(); + if (this.projectile.getViewers().isEmpty()) { this.projectile.close(); } this.projectile = (EntityProjectile) projectile; diff --git a/src/main/java/cn/nukkit/event/entity/EntitySpawnEvent.java b/src/main/java/cn/nukkit/event/entity/EntitySpawnEvent.java index c922109dc44..123be58a667 100644 --- a/src/main/java/cn/nukkit/event/entity/EntitySpawnEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntitySpawnEvent.java @@ -4,12 +4,13 @@ import cn.nukkit.entity.EntityCreature; import cn.nukkit.entity.EntityHuman; import cn.nukkit.entity.item.EntityItem; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.event.HandlerList; import cn.nukkit.level.Position; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntitySpawnEvent extends EntityEvent { @@ -21,7 +22,7 @@ public static HandlerList getHandlers() { private final int entityType; - public EntitySpawnEvent(cn.nukkit.entity.Entity entity) { + public EntitySpawnEvent(Entity entity) { this.entity = entity; this.entityType = entity.getNetworkId(); } @@ -47,11 +48,10 @@ public boolean isProjectile() { } public boolean isVehicle() { - return this.entity instanceof Entity; + return this.entity instanceof EntityVehicle; } public boolean isItem() { return this.entity instanceof EntityItem; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityTeleportEvent.java b/src/main/java/cn/nukkit/event/entity/EntityTeleportEvent.java index 60c203628e8..3fae1c86d8b 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityTeleportEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityTeleportEvent.java @@ -7,10 +7,11 @@ import cn.nukkit.level.Location; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityTeleportEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { diff --git a/src/main/java/cn/nukkit/event/entity/EntityVehicleEnterEvent.java b/src/main/java/cn/nukkit/event/entity/EntityVehicleEnterEvent.java index c1edf7072b5..247289b068c 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityVehicleEnterEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityVehicleEnterEvent.java @@ -1,6 +1,7 @@ package cn.nukkit.event.entity; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -12,15 +13,14 @@ public static HandlerList getHandlers() { return handlers; } - private final Entity vehicle; + private final EntityVehicle vehicle; - public EntityVehicleEnterEvent(Entity entity, Entity vehicle) { + public EntityVehicleEnterEvent(Entity entity, EntityVehicle vehicle) { this.entity = entity; this.vehicle = vehicle; } - public Entity getVehicle() { + public EntityVehicle getVehicle() { return vehicle; } - } diff --git a/src/main/java/cn/nukkit/event/entity/EntityVehicleExitEvent.java b/src/main/java/cn/nukkit/event/entity/EntityVehicleExitEvent.java index cbee54dfa52..68cf50ac917 100644 --- a/src/main/java/cn/nukkit/event/entity/EntityVehicleExitEvent.java +++ b/src/main/java/cn/nukkit/event/entity/EntityVehicleExitEvent.java @@ -1,6 +1,7 @@ package cn.nukkit.event.entity; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -12,15 +13,14 @@ public static HandlerList getHandlers() { return handlers; } - private final Entity vehicle; + private final EntityVehicle vehicle; - public EntityVehicleExitEvent(Entity entity, Entity vehicle) { + public EntityVehicleExitEvent(Entity entity, EntityVehicle vehicle) { this.entity = entity; this.vehicle = vehicle; } - public Entity getVehicle() { + public EntityVehicle getVehicle() { return vehicle; } - } diff --git a/src/main/java/cn/nukkit/event/entity/ExplosionPrimeEvent.java b/src/main/java/cn/nukkit/event/entity/ExplosionPrimeEvent.java index cd8d79690da..6fb1e90d8f0 100644 --- a/src/main/java/cn/nukkit/event/entity/ExplosionPrimeEvent.java +++ b/src/main/java/cn/nukkit/event/entity/ExplosionPrimeEvent.java @@ -5,12 +5,13 @@ import cn.nukkit.event.HandlerList; /** - * author: Box + * @author Box * Nukkit Project - *

+ * * Called when a entity decides to explode */ public class ExplosionPrimeEvent extends EntityEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { diff --git a/src/main/java/cn/nukkit/event/entity/ItemDespawnEvent.java b/src/main/java/cn/nukkit/event/entity/ItemDespawnEvent.java index d449c3c5fd5..b871ff990e0 100644 --- a/src/main/java/cn/nukkit/event/entity/ItemDespawnEvent.java +++ b/src/main/java/cn/nukkit/event/entity/ItemDespawnEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemDespawnEvent extends EntityEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/entity/ItemSpawnEvent.java b/src/main/java/cn/nukkit/event/entity/ItemSpawnEvent.java index 9816d555836..3d454745818 100644 --- a/src/main/java/cn/nukkit/event/entity/ItemSpawnEvent.java +++ b/src/main/java/cn/nukkit/event/entity/ItemSpawnEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSpawnEvent extends EntityEvent { diff --git a/src/main/java/cn/nukkit/event/entity/ProjectileHitEvent.java b/src/main/java/cn/nukkit/event/entity/ProjectileHitEvent.java index c751fa8c6e8..9e4dbdb621e 100644 --- a/src/main/java/cn/nukkit/event/entity/ProjectileHitEvent.java +++ b/src/main/java/cn/nukkit/event/entity/ProjectileHitEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.level.MovingObjectPosition; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ProjectileHitEvent extends EntityEvent implements Cancellable { @@ -34,5 +34,4 @@ public MovingObjectPosition getMovingObjectPosition() { public void setMovingObjectPosition(MovingObjectPosition movingObjectPosition) { this.movingObjectPosition = movingObjectPosition; } - } diff --git a/src/main/java/cn/nukkit/event/inventory/CraftItemEvent.java b/src/main/java/cn/nukkit/event/inventory/CraftItemEvent.java index 855b83c4a01..f63a82beff8 100644 --- a/src/main/java/cn/nukkit/event/inventory/CraftItemEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/CraftItemEvent.java @@ -9,7 +9,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class CraftItemEvent extends Event implements Cancellable { @@ -20,7 +20,7 @@ public static HandlerList getHandlers() { return handlers; } - private Item[] input = new Item[0]; + private final Item[] input; private final Recipe recipe; @@ -30,7 +30,6 @@ public static HandlerList getHandlers() { public CraftItemEvent(CraftingTransaction transaction) { this.transaction = transaction; - this.player = transaction.getSource(); this.input = transaction.getInputList().toArray(new Item[0]); this.recipe = transaction.getRecipe(); @@ -57,4 +56,4 @@ public Recipe getRecipe() { public Player getPlayer() { return this.player; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/inventory/EnchantItemEvent.java b/src/main/java/cn/nukkit/event/inventory/EnchantItemEvent.java index f67d3abbcc2..004e20ae8ac 100644 --- a/src/main/java/cn/nukkit/event/inventory/EnchantItemEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/EnchantItemEvent.java @@ -11,6 +11,7 @@ @Getter @Setter public class EnchantItemEvent extends InventoryEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { diff --git a/src/main/java/cn/nukkit/event/inventory/FurnaceBurnEvent.java b/src/main/java/cn/nukkit/event/inventory/FurnaceBurnEvent.java index 001455b54e7..cae4831281c 100644 --- a/src/main/java/cn/nukkit/event/inventory/FurnaceBurnEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/FurnaceBurnEvent.java @@ -7,7 +7,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FurnaceBurnEvent extends BlockEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/FurnaceSmeltEvent.java b/src/main/java/cn/nukkit/event/inventory/FurnaceSmeltEvent.java index a612d93a3ce..19623f44ee2 100644 --- a/src/main/java/cn/nukkit/event/inventory/FurnaceSmeltEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/FurnaceSmeltEvent.java @@ -7,7 +7,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FurnaceSmeltEvent extends BlockEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryClickEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryClickEvent.java index 91eb7e8270a..91f97312a81 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryClickEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryClickEvent.java @@ -7,7 +7,7 @@ import cn.nukkit.item.Item; /** - * author: boybook + * @author boybook * Nukkit Project */ public class InventoryClickEvent extends InventoryEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryCloseEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryCloseEvent.java index a9202cfde0f..9b8de2211f1 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryCloseEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryCloseEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.inventory.Inventory; /** - * author: Box + * @author Box * Nukkit Project */ public class InventoryCloseEvent extends InventoryEvent { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryEvent.java index b01c333f94d..d11adb185a6 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.inventory.Inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class InventoryEvent extends Event { @@ -23,5 +23,4 @@ public Inventory getInventory() { public Player[] getViewers() { return this.inventory.getViewers().toArray(new Player[0]); } - } diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryOpenEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryOpenEvent.java index 2838d6844d3..ce78783f626 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryOpenEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryOpenEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.inventory.Inventory; /** - * author: Box + * @author Box * Nukkit Project */ public class InventoryOpenEvent extends InventoryEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryPickupArrowEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryPickupArrowEvent.java index 4c8604e8b91..585c48d363b 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryPickupArrowEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryPickupArrowEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.inventory.Inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class InventoryPickupArrowEvent extends InventoryEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryPickupItemEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryPickupItemEvent.java index 3200d6efa4b..b064b09d75e 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryPickupItemEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryPickupItemEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.inventory.Inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class InventoryPickupItemEvent extends InventoryEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/InventoryTransactionEvent.java b/src/main/java/cn/nukkit/event/inventory/InventoryTransactionEvent.java index c416b726e63..c0e6ce46dd0 100644 --- a/src/main/java/cn/nukkit/event/inventory/InventoryTransactionEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/InventoryTransactionEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.inventory.transaction.InventoryTransaction; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class InventoryTransactionEvent extends Event implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/inventory/LoomItemEvent.java b/src/main/java/cn/nukkit/event/inventory/LoomItemEvent.java new file mode 100644 index 00000000000..fd91c7607a7 --- /dev/null +++ b/src/main/java/cn/nukkit/event/inventory/LoomItemEvent.java @@ -0,0 +1,33 @@ +package cn.nukkit.event.inventory; + +import cn.nukkit.Player; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.inventory.LoomInventory; +import cn.nukkit.item.Item; + +public class LoomItemEvent extends InventoryEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Item newItem; + private final Player player; + + public LoomItemEvent(LoomInventory inventory, Item newItem, Player player) { + super(inventory); + this.newItem = newItem; + this.player = player; + } + + public Item getNewItem() { + return this.newItem; + } + + public Player getPlayer() { + return this.player; + } +} diff --git a/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java b/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java index dd7acdc3df3..e2917bf2ff3 100644 --- a/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java +++ b/src/main/java/cn/nukkit/event/inventory/RepairItemEvent.java @@ -14,11 +14,11 @@ public static HandlerList getHandlers() { return handlers; } - private Item oldItem; - private Item newItem; - private Item materialItem; + private final Item oldItem; + private final Item newItem; + private final Item materialItem; private int cost; - private Player player; + private final Player player; public RepairItemEvent(AnvilInventory inventory, Item oldItem, Item newItem, Item materialItem, int cost, Player player) { super(inventory); diff --git a/src/main/java/cn/nukkit/event/inventory/SmithItemEvent.java b/src/main/java/cn/nukkit/event/inventory/SmithItemEvent.java new file mode 100644 index 00000000000..40fcda71576 --- /dev/null +++ b/src/main/java/cn/nukkit/event/inventory/SmithItemEvent.java @@ -0,0 +1,45 @@ +package cn.nukkit.event.inventory; + +import cn.nukkit.Player; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.inventory.SmithingInventory; +import cn.nukkit.item.Item; + +public class SmithItemEvent extends InventoryEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Item oldItem; + private final Item newItem; + private final Item materialItem; + private final Player player; + + public SmithItemEvent(SmithingInventory inventory, Item oldItem, Item newItem, Item materialItem, Player player) { + super(inventory); + this.oldItem = oldItem; + this.newItem = newItem; + this.materialItem = materialItem; + this.player = player; + } + + public Item getOldItem() { + return this.oldItem; + } + + public Item getNewItem() { + return this.newItem; + } + + public Item getMaterialItem() { + return this.materialItem; + } + + public Player getPlayer() { + return this.player; + } +} diff --git a/src/main/java/cn/nukkit/event/level/ChunkEvent.java b/src/main/java/cn/nukkit/event/level/ChunkEvent.java index 12ee9a1c758..7b932a35d72 100644 --- a/src/main/java/cn/nukkit/event/level/ChunkEvent.java +++ b/src/main/java/cn/nukkit/event/level/ChunkEvent.java @@ -3,7 +3,7 @@ import cn.nukkit.level.format.FullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class ChunkEvent extends LevelEvent { diff --git a/src/main/java/cn/nukkit/event/level/ChunkLoadEvent.java b/src/main/java/cn/nukkit/event/level/ChunkLoadEvent.java index f922e0b7427..23520f4ed19 100644 --- a/src/main/java/cn/nukkit/event/level/ChunkLoadEvent.java +++ b/src/main/java/cn/nukkit/event/level/ChunkLoadEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.format.FullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ChunkLoadEvent extends ChunkEvent { diff --git a/src/main/java/cn/nukkit/event/level/ChunkPopulateEvent.java b/src/main/java/cn/nukkit/event/level/ChunkPopulateEvent.java index b25b145c366..4694bca35a1 100644 --- a/src/main/java/cn/nukkit/event/level/ChunkPopulateEvent.java +++ b/src/main/java/cn/nukkit/event/level/ChunkPopulateEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.format.FullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ChunkPopulateEvent extends ChunkEvent { @@ -18,5 +18,4 @@ public static HandlerList getHandlers() { public ChunkPopulateEvent(FullChunk chunk) { super(chunk); } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/level/ChunkUnloadEvent.java b/src/main/java/cn/nukkit/event/level/ChunkUnloadEvent.java index 3621f519bf2..b557169cef2 100644 --- a/src/main/java/cn/nukkit/event/level/ChunkUnloadEvent.java +++ b/src/main/java/cn/nukkit/event/level/ChunkUnloadEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.level.format.FullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ChunkUnloadEvent extends ChunkEvent implements Cancellable { @@ -19,5 +19,4 @@ public static HandlerList getHandlers() { public ChunkUnloadEvent(FullChunk chunk) { super(chunk); } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/level/LevelEvent.java b/src/main/java/cn/nukkit/event/level/LevelEvent.java index 766e01e9731..9fcc470765e 100644 --- a/src/main/java/cn/nukkit/event/level/LevelEvent.java +++ b/src/main/java/cn/nukkit/event/level/LevelEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class LevelEvent extends Event { diff --git a/src/main/java/cn/nukkit/event/level/LevelInitEvent.java b/src/main/java/cn/nukkit/event/level/LevelInitEvent.java index 5bfdbb7ec8b..03d76e5d796 100644 --- a/src/main/java/cn/nukkit/event/level/LevelInitEvent.java +++ b/src/main/java/cn/nukkit/event/level/LevelInitEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LevelInitEvent extends LevelEvent { @@ -18,5 +18,4 @@ public static HandlerList getHandlers() { public LevelInitEvent(Level level) { super(level); } - } diff --git a/src/main/java/cn/nukkit/event/level/LevelLoadEvent.java b/src/main/java/cn/nukkit/event/level/LevelLoadEvent.java index e78f025ae0f..3a54164f08d 100644 --- a/src/main/java/cn/nukkit/event/level/LevelLoadEvent.java +++ b/src/main/java/cn/nukkit/event/level/LevelLoadEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LevelLoadEvent extends LevelEvent { @@ -18,5 +18,4 @@ public static HandlerList getHandlers() { public LevelLoadEvent(Level level) { super(level); } - } diff --git a/src/main/java/cn/nukkit/event/level/LevelSaveEvent.java b/src/main/java/cn/nukkit/event/level/LevelSaveEvent.java index 6b6e1d6a53d..4e18629b2c7 100644 --- a/src/main/java/cn/nukkit/event/level/LevelSaveEvent.java +++ b/src/main/java/cn/nukkit/event/level/LevelSaveEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LevelSaveEvent extends LevelEvent { @@ -18,5 +18,4 @@ public static HandlerList getHandlers() { public LevelSaveEvent(Level level) { super(level); } - } diff --git a/src/main/java/cn/nukkit/event/level/LevelUnloadEvent.java b/src/main/java/cn/nukkit/event/level/LevelUnloadEvent.java index ed3852470ce..8dd81f9c31d 100644 --- a/src/main/java/cn/nukkit/event/level/LevelUnloadEvent.java +++ b/src/main/java/cn/nukkit/event/level/LevelUnloadEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LevelUnloadEvent extends LevelEvent implements Cancellable { @@ -19,5 +19,4 @@ public static HandlerList getHandlers() { public LevelUnloadEvent(Level level) { super(level); } - } diff --git a/src/main/java/cn/nukkit/event/level/NetherPortalSpawnEvent.java b/src/main/java/cn/nukkit/event/level/NetherPortalSpawnEvent.java new file mode 100644 index 00000000000..d3326332a91 --- /dev/null +++ b/src/main/java/cn/nukkit/event/level/NetherPortalSpawnEvent.java @@ -0,0 +1,26 @@ +package cn.nukkit.event.level; + +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; +import cn.nukkit.level.Position; + +public class NetherPortalSpawnEvent extends LevelEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Position portalPosition; + + public NetherPortalSpawnEvent(Position portalPosition) { + super(portalPosition.getLevel()); + + this.portalPosition = portalPosition.clone(); + } + + public Position getPortalPosition() { + return this.portalPosition; + } +} diff --git a/src/main/java/cn/nukkit/event/level/SpawnChangeEvent.java b/src/main/java/cn/nukkit/event/level/SpawnChangeEvent.java index c5c4303a253..78781f75ee3 100644 --- a/src/main/java/cn/nukkit/event/level/SpawnChangeEvent.java +++ b/src/main/java/cn/nukkit/event/level/SpawnChangeEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.level.Position; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class SpawnChangeEvent extends LevelEvent { diff --git a/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java b/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java index 1bea3eeb218..ed7d87fde28 100644 --- a/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java +++ b/src/main/java/cn/nukkit/event/level/StructureGrowEvent.java @@ -37,8 +37,8 @@ public List getBlockList() { public void setBlockList(List blocks) { this.blocks.clear(); - if(blocks != null) + if (blocks != null) { this.blocks.addAll(blocks); + } } - } diff --git a/src/main/java/cn/nukkit/event/level/ThunderChangeEvent.java b/src/main/java/cn/nukkit/event/level/ThunderChangeEvent.java index 93e4d0a86ef..36497d120ae 100644 --- a/src/main/java/cn/nukkit/event/level/ThunderChangeEvent.java +++ b/src/main/java/cn/nukkit/event/level/ThunderChangeEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.level.Level; /** - * author: funcraft + * @author funcraft * Nukkit Project */ public class ThunderChangeEvent extends WeatherEvent implements Cancellable { @@ -31,5 +31,4 @@ public ThunderChangeEvent(Level level, boolean to) { public boolean toThunderState() { return to; } - } diff --git a/src/main/java/cn/nukkit/event/level/WeatherChangeEvent.java b/src/main/java/cn/nukkit/event/level/WeatherChangeEvent.java index d0e1c2157bb..7d9e80a7bb8 100644 --- a/src/main/java/cn/nukkit/event/level/WeatherChangeEvent.java +++ b/src/main/java/cn/nukkit/event/level/WeatherChangeEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.level.Level; /** - * author: funcraft + * @author funcraft * Nukkit Project */ public class WeatherChangeEvent extends WeatherEvent implements Cancellable { @@ -31,5 +31,4 @@ public WeatherChangeEvent(Level level, boolean to) { public boolean toWeatherState() { return to; } - } diff --git a/src/main/java/cn/nukkit/event/level/WeatherEvent.java b/src/main/java/cn/nukkit/event/level/WeatherEvent.java index 0537a10838d..634fa79506f 100644 --- a/src/main/java/cn/nukkit/event/level/WeatherEvent.java +++ b/src/main/java/cn/nukkit/event/level/WeatherEvent.java @@ -4,7 +4,7 @@ import cn.nukkit.level.Level; /** - * author: funcraft + * @author funcraft * Nukkit Project */ public abstract class WeatherEvent extends Event { diff --git a/src/main/java/cn/nukkit/event/player/CraftingTableOpenEvent.java b/src/main/java/cn/nukkit/event/player/CraftingTableOpenEvent.java new file mode 100644 index 00000000000..43fc42e4386 --- /dev/null +++ b/src/main/java/cn/nukkit/event/player/CraftingTableOpenEvent.java @@ -0,0 +1,26 @@ +package cn.nukkit.event.player; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.event.Cancellable; +import cn.nukkit.event.HandlerList; + +public class CraftingTableOpenEvent extends PlayerEvent implements Cancellable { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlers() { + return handlers; + } + + private final Block craftingTable; + + public CraftingTableOpenEvent(Player player, Block craftingTable) { + this.player = player; + this.craftingTable = craftingTable; + } + + public Block getCraftingTable() { + return this.craftingTable; + } +} diff --git a/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java b/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java index 58bebb1dad7..2c3c95724bd 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerAsyncPreLoginEvent.java @@ -77,7 +77,7 @@ public int getPort() { } public LoginResult getLoginResult() { - return this.loginResult; + return loginResult; } public void setLoginResult(LoginResult loginResult) { @@ -113,4 +113,4 @@ public enum LoginResult { SUCCESS, KICK } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/event/player/PlayerBedEnterEvent.java b/src/main/java/cn/nukkit/event/player/PlayerBedEnterEvent.java index 8c1eaf14930..d71b3ae5733 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerBedEnterEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerBedEnterEvent.java @@ -6,6 +6,7 @@ import cn.nukkit.event.HandlerList; public class PlayerBedEnterEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { @@ -13,13 +14,27 @@ public static HandlerList getHandlers() { } private final Block bed; + private final boolean onlySetSpawn; public PlayerBedEnterEvent(Player player, Block bed) { + this(player, bed, false); + } + + public PlayerBedEnterEvent(Player player, Block bed, boolean onlySetSpawn) { this.player = player; this.bed = bed; + this.onlySetSpawn = onlySetSpawn; } public Block getBed() { return bed; } + + /** + * Whether the event is called when a player is trying to only set spawn point and not actually sleep on the bed (on day). + * @return is only setting spawn point + */ + public boolean isOnlySetSpawn() { + return onlySetSpawn; + } } diff --git a/src/main/java/cn/nukkit/event/player/PlayerBucketEmptyEvent.java b/src/main/java/cn/nukkit/event/player/PlayerBucketEmptyEvent.java index fb1a02c4fbe..142402d82de 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerBucketEmptyEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerBucketEmptyEvent.java @@ -26,7 +26,8 @@ public PlayerBucketEmptyEvent(Player who, Block blockClicked, BlockFace blockFac } /** - * Whether a fish can be spawned when a fish bucket is emptied. + * Whether a fish can be spawned if a fish bucket is emptied. + * Notice: Disabling 'block-listener' in server.properties disables fish spawning from buckets. * * @return can spawn a fish */ @@ -35,7 +36,8 @@ public boolean isMobSpawningAllowed() { } /** - * Set whether a fish can be spawned when a fish bucket is emptied. + * Whether a fish can be spawned if a fish bucket is emptied. + * Notice: Disabling 'block-listener' in server.properties disables fish spawning from buckets. * * @param mobSpawningAllowed can spawn a fish */ diff --git a/src/main/java/cn/nukkit/event/player/PlayerBucketFillEvent.java b/src/main/java/cn/nukkit/event/player/PlayerBucketFillEvent.java index e3771d32fa0..9f10e52ac70 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerBucketFillEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerBucketFillEvent.java @@ -16,5 +16,4 @@ public static HandlerList getHandlers() { public PlayerBucketFillEvent(Player who, Block blockClicked, BlockFace blockFace, Item bucket, Item itemInHand) { super(who, blockClicked, blockFace, bucket, itemInHand); } - } diff --git a/src/main/java/cn/nukkit/event/player/PlayerChangeSkinEvent.java b/src/main/java/cn/nukkit/event/player/PlayerChangeSkinEvent.java index 637b1ac9543..4a32d4ea845 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerChangeSkinEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerChangeSkinEvent.java @@ -6,7 +6,7 @@ import cn.nukkit.event.HandlerList; /** - * author: KCodeYT + * @author KCodeYT * Nukkit Project */ public class PlayerChangeSkinEvent extends PlayerEvent implements Cancellable { @@ -27,5 +27,4 @@ public PlayerChangeSkinEvent(Player player, Skin skin) { public Skin getSkin() { return this.skin; } - } diff --git a/src/main/java/cn/nukkit/event/player/PlayerChatEvent.java b/src/main/java/cn/nukkit/event/player/PlayerChatEvent.java index 7cbd5841737..69565d79904 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerChatEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerChatEvent.java @@ -37,7 +37,6 @@ public PlayerChatEvent(Player player, String message, String format, Set name; private final Optional uuid; @@ -21,9 +22,7 @@ public PlayerDataSerializeEvent(String name, PlayerDataSerializer serializer) { UUID uuid = null; try { uuid = UUID.fromString(name); - } catch (Exception e) { - // ignore - } + } catch (Exception ignored) {} this.uuid = Optional.ofNullable(uuid); this.name = this.uuid.isPresent() ? Optional.empty() : Optional.of(name); } diff --git a/src/main/java/cn/nukkit/event/server/QueryRegenerateEvent.java b/src/main/java/cn/nukkit/event/server/QueryRegenerateEvent.java index 0383b7dd896..d75938d8a30 100644 --- a/src/main/java/cn/nukkit/event/server/QueryRegenerateEvent.java +++ b/src/main/java/cn/nukkit/event/server/QueryRegenerateEvent.java @@ -3,21 +3,19 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.event.HandlerList; -import cn.nukkit.nbt.stream.FastByteArrayOutputStream; import cn.nukkit.plugin.Plugin; import cn.nukkit.plugin.PluginDescription; import cn.nukkit.utils.Binary; -import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class QueryRegenerateEvent extends ServerEvent { - //alot todo private static final HandlerList handlers = new HandlerList(); @@ -25,17 +23,13 @@ public static HandlerList getHandlers() { return handlers; } - private static final String GAME_ID = "MINECRAFTPE"; - private int timeout; private String serverName; private boolean listPlugins; private Plugin[] plugins; private Player[] players; - private final String gameType; private final String version; - private final String server_engine; private String map; private int numPlayers; private int maxPlayers; @@ -43,8 +37,6 @@ public static HandlerList getHandlers() { private final int port; private final String ip; - private Map extraData = new HashMap<>(); - public QueryRegenerateEvent(Server server) { this(server, 5); } @@ -52,12 +44,11 @@ public QueryRegenerateEvent(Server server) { public QueryRegenerateEvent(Server server, int timeout) { this.timeout = timeout; this.serverName = server.getMotd(); - this.listPlugins = server.getConfig("settings.query-plugins", true); + this.listPlugins = server.queryPlugins; this.plugins = server.getPluginManager().getPlugins().values().toArray(new Plugin[0]); this.players = server.getOnlinePlayers().values().toArray(new Player[0]); - this.gameType = (server.getGamemode() & 0x01) == 0 ? "SMP" : "CMP"; + this.gameType = server.getGamemode() == 1 ? "CMP" : "SMP"; this.version = server.getVersion(); - this.server_engine = server.getName() + " " + server.getNukkitVersion(); this.map = server.getDefaultLevel() == null ? "unknown" : server.getDefaultLevel().getName(); this.numPlayers = this.players.length; this.maxPlayers = server.getMaxPlayers(); @@ -130,91 +121,70 @@ public void setWorld(String world) { this.map = world; } - public Map getExtraData() { - return extraData; - } - - public void setExtraData(Map extraData) { - this.extraData = extraData; - } - - public byte[] getLongQuery(byte[] buffer) { - if (buffer == null) buffer = new byte[Character.MAX_VALUE]; - FastByteArrayOutputStream query = new FastByteArrayOutputStream(buffer); - try { - StringBuilder plist = new StringBuilder(this.server_engine); - if (this.plugins.length > 0 && this.listPlugins) { - plist.append(":"); - for (Plugin p : this.plugins) { - PluginDescription d = p.getDescription(); - plist.append(" ").append(d.getName().replace(";", "").replace(":", "").replace(" ", "_")).append(" ").append(d.getVersion().replace(";", "").replace(":", "").replace(" ", "_")).append(";"); - } - plist = new StringBuilder(plist.substring(0, plist.length() - 2)); - } - - query.write("splitnum".getBytes()); - query.write((byte) 0x00); - query.write((byte) 128); - query.write((byte) 0x00); - - LinkedHashMap KVdata = new LinkedHashMap<>(); - KVdata.put("hostname", this.serverName); - KVdata.put("gametype", this.gameType); - KVdata.put("game_id", GAME_ID); - KVdata.put("version", this.version); - KVdata.put("server_engine", this.server_engine); - KVdata.put("plugins", plist.toString()); - KVdata.put("map", this.map); - KVdata.put("numplayers", String.valueOf(this.numPlayers)); - KVdata.put("maxplayers", String.valueOf(this.maxPlayers)); - KVdata.put("whitelist", this.whitelist); - KVdata.put("hostip", this.ip); - KVdata.put("hostport", String.valueOf(this.port)); - - for (Map.Entry entry : KVdata.entrySet()) { - query.write(entry.getKey().getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(entry.getValue().getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); + public byte[] getLongQuery() { + ByteBuffer query = ByteBuffer.allocate(65536); + StringBuilder plist = new StringBuilder("Nukkit"); + if (this.listPlugins && this.plugins.length > 0) { + plist.append(':'); + for (Plugin p : this.plugins) { + PluginDescription d = p.getDescription(); + plist.append(' ').append(d.getName().replace(";", "").replace(":", "").replace(" ", "_")).append(' ').append(d.getVersion().replace(";", "").replace(":", "").replace(" ", "_")).append(';'); } + plist = new StringBuilder(plist.substring(0, plist.length() - 2)); + } - query.write(new byte[]{0x00, 0x01}); - query.write("player_".getBytes()); - query.write(new byte[]{0x00, 0x00}); + query.put("splitnum".getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put((byte) 128); + query.put((byte) 0x00); + + Map KVdata = new LinkedHashMap<>(); + KVdata.put("hostname", this.serverName); + KVdata.put("gametype", this.gameType); + KVdata.put("game_id", "MINECRAFTPE"); + KVdata.put("version", this.version); + KVdata.put("server_engine", "Nukkit"); + KVdata.put("plugins", plist.toString()); + KVdata.put("map", this.map); + KVdata.put("numplayers", String.valueOf(this.numPlayers)); + KVdata.put("maxplayers", String.valueOf(this.maxPlayers)); + KVdata.put("whitelist", this.whitelist); + KVdata.put("hostip", this.ip); + KVdata.put("hostport", String.valueOf(this.port)); + + for (Map.Entry entry : KVdata.entrySet()) { + query.put(entry.getKey().getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(entry.getValue().getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + } - for (Player player : this.players) { - query.write(player.getName().getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - } + query.put(new byte[]{0x00, 0x01}).put("player_".getBytes(StandardCharsets.UTF_8)).put(new byte[]{0x00, 0x00}); - query.write((byte) 0x00); - } catch (IOException e) { - e.printStackTrace(); + for (Player player : this.players) { + query.put(player.getName().getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); } - return query.toByteArray(); - } - - public byte[] getShortQuery(byte[] buffer) { - if (buffer == null) buffer = new byte[Character.MAX_VALUE]; - FastByteArrayOutputStream query = new FastByteArrayOutputStream(buffer); - try { - query.write(this.serverName.getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(this.gameType.getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(this.map.getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(String.valueOf(this.numPlayers).getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(String.valueOf(this.maxPlayers).getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - query.write(Binary.writeLShort(this.port)); - query.write(this.ip.getBytes(StandardCharsets.UTF_8)); - query.write((byte) 0x00); - } catch (IOException e) { - e.printStackTrace(); - } - return query.toByteArray(); - } + query.put((byte) 0x00); + return Arrays.copyOf(query.array(), query.position()); + } + + public byte[] getShortQuery() { + ByteBuffer query = ByteBuffer.allocate(65536); + query.put(this.serverName.getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(this.gameType.getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(this.map.getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(String.valueOf(this.numPlayers).getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(String.valueOf(this.maxPlayers).getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + query.put(Binary.writeLShort(this.port)); + query.put(this.ip.getBytes(StandardCharsets.UTF_8)); + query.put((byte) 0x00); + return Arrays.copyOf(query.array(), query.position()); + } } diff --git a/src/main/java/cn/nukkit/event/server/RemoteServerCommandEvent.java b/src/main/java/cn/nukkit/event/server/RemoteServerCommandEvent.java index 47ae4afd8a2..58be9956cd6 100644 --- a/src/main/java/cn/nukkit/event/server/RemoteServerCommandEvent.java +++ b/src/main/java/cn/nukkit/event/server/RemoteServerCommandEvent.java @@ -9,6 +9,7 @@ * @author Tee7even */ public class RemoteServerCommandEvent extends ServerCommandEvent { + private static final HandlerList handlers = new HandlerList(); public static HandlerList getHandlers() { diff --git a/src/main/java/cn/nukkit/event/server/ServerCommandEvent.java b/src/main/java/cn/nukkit/event/server/ServerCommandEvent.java index c3da65869c0..4d4ab50d066 100644 --- a/src/main/java/cn/nukkit/event/server/ServerCommandEvent.java +++ b/src/main/java/cn/nukkit/event/server/ServerCommandEvent.java @@ -5,7 +5,7 @@ import cn.nukkit.event.HandlerList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ServerCommandEvent extends ServerEvent implements Cancellable { diff --git a/src/main/java/cn/nukkit/event/server/ServerEvent.java b/src/main/java/cn/nukkit/event/server/ServerEvent.java index ad7032615a4..a90a329cd6d 100644 --- a/src/main/java/cn/nukkit/event/server/ServerEvent.java +++ b/src/main/java/cn/nukkit/event/server/ServerEvent.java @@ -3,7 +3,7 @@ import cn.nukkit.event.Event; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ abstract public class ServerEvent extends Event { diff --git a/src/main/java/cn/nukkit/event/server/ServerStopEvent.java b/src/main/java/cn/nukkit/event/server/ServerStopEvent.java index 65b46996c57..8b80ff24449 100644 --- a/src/main/java/cn/nukkit/event/server/ServerStopEvent.java +++ b/src/main/java/cn/nukkit/event/server/ServerStopEvent.java @@ -3,7 +3,7 @@ import cn.nukkit.event.HandlerList; /** - * author: NycuRO + * @author NycuRO * NukkitX Project */ public class ServerStopEvent extends ServerEvent { diff --git a/src/main/java/cn/nukkit/event/vehicle/EntityEnterVehicleEvent.java b/src/main/java/cn/nukkit/event/vehicle/EntityEnterVehicleEvent.java index f7fcd4b1c56..370f4b94c7b 100644 --- a/src/main/java/cn/nukkit/event/vehicle/EntityEnterVehicleEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/EntityEnterVehicleEvent.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -13,19 +14,18 @@ public static HandlerList getHandlers() { return handlers; } - private final cn.nukkit.entity.Entity riding; + private final Entity riding; - public EntityEnterVehicleEvent(cn.nukkit.entity.Entity riding, Entity vehicle) { + public EntityEnterVehicleEvent(Entity riding, EntityVehicle vehicle) { super(vehicle); this.riding = riding; } - public cn.nukkit.entity.Entity getEntity() { + public Entity getEntity() { return riding; } public boolean isPlayer() { return riding instanceof Player; } - } diff --git a/src/main/java/cn/nukkit/event/vehicle/EntityExitVehicleEvent.java b/src/main/java/cn/nukkit/event/vehicle/EntityExitVehicleEvent.java index 818988c2b40..b120373dbb1 100644 --- a/src/main/java/cn/nukkit/event/vehicle/EntityExitVehicleEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/EntityExitVehicleEvent.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -13,19 +14,18 @@ public static HandlerList getHandlers() { return handlers; } - private final cn.nukkit.entity.Entity riding; + private final Entity riding; - public EntityExitVehicleEvent(cn.nukkit.entity.Entity riding, Entity vehicle) { + public EntityExitVehicleEvent(Entity riding, EntityVehicle vehicle) { super(vehicle); this.riding = riding; } - public cn.nukkit.entity.Entity getEntity() { + public Entity getEntity() { return riding; } public boolean isPlayer() { return riding instanceof Player; } - } diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleCreateEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleCreateEvent.java index f81f7997b9a..15d4cf94a6e 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleCreateEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleCreateEvent.java @@ -1,7 +1,6 @@ package cn.nukkit.event.vehicle; - -import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -13,8 +12,7 @@ public static HandlerList getHandlers() { return handlers; } - public VehicleCreateEvent(Entity vehicle) { + public VehicleCreateEvent(EntityVehicle vehicle) { super(vehicle); } - } diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleDamageEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleDamageEvent.java index 36de23c4295..f9c40d8903e 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleDamageEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleDamageEvent.java @@ -1,6 +1,7 @@ package cn.nukkit.event.vehicle; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -12,16 +13,16 @@ public static HandlerList getHandlers() { return handlers; } - private final cn.nukkit.entity.Entity attacker; + private final Entity attacker; private double damage; - public VehicleDamageEvent(Entity vehicle, Entity attacker, double damage) { + public VehicleDamageEvent(EntityVehicle vehicle, Entity attacker, double damage) { super(vehicle); this.attacker = attacker; this.damage = damage; } - public cn.nukkit.entity.Entity getAttacker() { + public Entity getAttacker() { return attacker; } @@ -32,5 +33,4 @@ public double getDamage() { public void setDamage(double damage) { this.damage = damage; } - } diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleDestroyEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleDestroyEvent.java index 0cfaca9c3c9..ebb2fc1c309 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleDestroyEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleDestroyEvent.java @@ -1,6 +1,7 @@ package cn.nukkit.event.vehicle; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Cancellable; import cn.nukkit.event.HandlerList; @@ -12,15 +13,14 @@ public static HandlerList getHandlers() { return handlers; } - private final cn.nukkit.entity.Entity attacker; + private final Entity attacker; - public VehicleDestroyEvent(Entity vehicle, cn.nukkit.entity.Entity attacker) { + public VehicleDestroyEvent(EntityVehicle vehicle, Entity attacker) { super(vehicle); this.attacker = attacker; } - public cn.nukkit.entity.Entity getAttacker() { + public Entity getAttacker() { return attacker; } - } diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleEvent.java index dab135f059d..a04071b80f9 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleEvent.java @@ -1,22 +1,22 @@ package cn.nukkit.event.vehicle; -import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.Event; /** * Created by larryTheCoder at 7/5/2017. - *

+ * * Nukkit Project */ public abstract class VehicleEvent extends Event { - private final Entity vehicle; + private final EntityVehicle vehicle; - public VehicleEvent(Entity vehicle) { + public VehicleEvent(EntityVehicle vehicle) { this.vehicle = vehicle; } - public Entity getVehicle() { + public EntityVehicle getVehicle() { return vehicle; } } diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleMoveEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleMoveEvent.java index 814590c451d..0cf898755e8 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleMoveEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleMoveEvent.java @@ -1,6 +1,6 @@ package cn.nukkit.event.vehicle; -import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.HandlerList; import cn.nukkit.level.Location; @@ -15,7 +15,7 @@ public static HandlerList getHandlers() { private final Location from; private final Location to; - public VehicleMoveEvent(Entity vehicle, Location from, Location to) { + public VehicleMoveEvent(EntityVehicle vehicle, Location from, Location to) { super(vehicle); this.from = from; this.to = to; diff --git a/src/main/java/cn/nukkit/event/vehicle/VehicleUpdateEvent.java b/src/main/java/cn/nukkit/event/vehicle/VehicleUpdateEvent.java index 2078c54909a..4f56750f411 100644 --- a/src/main/java/cn/nukkit/event/vehicle/VehicleUpdateEvent.java +++ b/src/main/java/cn/nukkit/event/vehicle/VehicleUpdateEvent.java @@ -1,6 +1,6 @@ package cn.nukkit.event.vehicle; -import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityVehicle; import cn.nukkit.event.HandlerList; public class VehicleUpdateEvent extends VehicleEvent { @@ -11,8 +11,7 @@ public static HandlerList getHandlers() { return handlers; } - public VehicleUpdateEvent(Entity vehicle) { + public VehicleUpdateEvent(EntityVehicle vehicle) { super(vehicle); } - } diff --git a/src/main/java/cn/nukkit/event/weather/LightningStrikeEvent.java b/src/main/java/cn/nukkit/event/weather/LightningStrikeEvent.java index 3a4e2d40f85..d667d5608a9 100644 --- a/src/main/java/cn/nukkit/event/weather/LightningStrikeEvent.java +++ b/src/main/java/cn/nukkit/event/weather/LightningStrikeEvent.java @@ -7,7 +7,7 @@ import cn.nukkit.level.Level; /** - * author: funcraft + * @author funcraft * Nukkit Project */ public class LightningStrikeEvent extends WeatherEvent implements Cancellable { @@ -31,5 +31,4 @@ public LightningStrikeEvent(Level level, final EntityLightningStrike bolt) { public EntityLightningStrike getLightning() { return bolt; } - } diff --git a/src/main/java/cn/nukkit/form/element/Element.java b/src/main/java/cn/nukkit/form/element/Element.java index fc80c0b7db7..0bb96a433fe 100644 --- a/src/main/java/cn/nukkit/form/element/Element.java +++ b/src/main/java/cn/nukkit/form/element/Element.java @@ -1,5 +1,4 @@ package cn.nukkit.form.element; public abstract class Element { - } diff --git a/src/main/java/cn/nukkit/form/element/ElementButton.java b/src/main/java/cn/nukkit/form/element/ElementButton.java index a95846582ff..9126cc1ec5d 100644 --- a/src/main/java/cn/nukkit/form/element/ElementButton.java +++ b/src/main/java/cn/nukkit/form/element/ElementButton.java @@ -29,5 +29,4 @@ public ElementButtonImageData getImage() { public void addImage(ElementButtonImageData image) { if (!image.getData().isEmpty() && !image.getType().isEmpty()) this.image = image; } - } diff --git a/src/main/java/cn/nukkit/form/element/ElementDropdown.java b/src/main/java/cn/nukkit/form/element/ElementDropdown.java index ec204f00467..9b3a5fb5add 100644 --- a/src/main/java/cn/nukkit/form/element/ElementDropdown.java +++ b/src/main/java/cn/nukkit/form/element/ElementDropdown.java @@ -7,8 +7,10 @@ public class ElementDropdown extends Element { - private final String type = "dropdown"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "dropdown"; private String text = ""; + @SuppressWarnings("FieldMayBeFinal") private List options; @SerializedName("default") private int defaultOptionIndex = 0; @@ -56,5 +58,4 @@ public void addOption(String option, boolean isDefault) { options.add(option); if (isDefault) this.defaultOptionIndex = options.size() - 1; } - } diff --git a/src/main/java/cn/nukkit/form/element/ElementInput.java b/src/main/java/cn/nukkit/form/element/ElementInput.java index 33638ae01bb..c5a33043661 100644 --- a/src/main/java/cn/nukkit/form/element/ElementInput.java +++ b/src/main/java/cn/nukkit/form/element/ElementInput.java @@ -4,7 +4,8 @@ public class ElementInput extends Element { - private final String type = "input"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "input"; private String text = ""; private String placeholder = ""; @SerializedName("default") diff --git a/src/main/java/cn/nukkit/form/element/ElementLabel.java b/src/main/java/cn/nukkit/form/element/ElementLabel.java index 449eae05613..2f3a165fe82 100644 --- a/src/main/java/cn/nukkit/form/element/ElementLabel.java +++ b/src/main/java/cn/nukkit/form/element/ElementLabel.java @@ -2,7 +2,8 @@ public class ElementLabel extends Element { - private final String type = "label"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "label"; private String text = ""; public ElementLabel(String text) { diff --git a/src/main/java/cn/nukkit/form/element/ElementSlider.java b/src/main/java/cn/nukkit/form/element/ElementSlider.java index 16a8380e3dd..3b11f376c7d 100644 --- a/src/main/java/cn/nukkit/form/element/ElementSlider.java +++ b/src/main/java/cn/nukkit/form/element/ElementSlider.java @@ -4,7 +4,8 @@ public class ElementSlider extends Element { - private final String type = "slider"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "slider"; private String text = ""; private float min = 0f; private float max = 100f; @@ -24,8 +25,8 @@ public ElementSlider(String text, float min, float max, int step, float defaultV this.text = text; this.min = Math.max(min, 0f); this.max = Math.max(max, this.min); - if (step != -1f && step > 0) this.step = step; - if (defaultValue != -1f) this.defaultValue = defaultValue; + if (step != -1.0f && step > 0) this.step = step; + if (defaultValue != -1.0f) this.defaultValue = defaultValue; } public String getText() { diff --git a/src/main/java/cn/nukkit/form/element/ElementStepSlider.java b/src/main/java/cn/nukkit/form/element/ElementStepSlider.java index 0c93e0a7568..7f510f3360f 100644 --- a/src/main/java/cn/nukkit/form/element/ElementStepSlider.java +++ b/src/main/java/cn/nukkit/form/element/ElementStepSlider.java @@ -7,8 +7,10 @@ public class ElementStepSlider extends Element { - private final String type = "step_slider"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "step_slider"; private String text = ""; + @SuppressWarnings("FieldMayBeFinal") private List steps; @SerializedName("default") private int defaultStepIndex = 0; @@ -56,5 +58,4 @@ public void addStep(String step, boolean isDefault) { steps.add(step); if (isDefault) this.defaultStepIndex = steps.size() - 1; } - } diff --git a/src/main/java/cn/nukkit/form/element/ElementToggle.java b/src/main/java/cn/nukkit/form/element/ElementToggle.java index bd65ced16f5..c2fc4f55754 100644 --- a/src/main/java/cn/nukkit/form/element/ElementToggle.java +++ b/src/main/java/cn/nukkit/form/element/ElementToggle.java @@ -4,7 +4,8 @@ public class ElementToggle extends Element { - private final String type = "toggle"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "toggle"; private String text; @SerializedName("default") private boolean defaultValue; diff --git a/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java b/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java index d3192e63f31..effc95ca7d7 100644 --- a/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java +++ b/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java @@ -6,10 +6,11 @@ public interface FormResponseHandler { + static FormResponseHandler withoutPlayer(IntConsumer formIDConsumer) { return (player, formID) -> formIDConsumer.accept(formID); } + void handle(Player player, int formID); - } diff --git a/src/main/java/cn/nukkit/form/response/FormResponse.java b/src/main/java/cn/nukkit/form/response/FormResponse.java index 1d128d60f45..8f83c5892c6 100644 --- a/src/main/java/cn/nukkit/form/response/FormResponse.java +++ b/src/main/java/cn/nukkit/form/response/FormResponse.java @@ -1,5 +1,4 @@ package cn.nukkit.form.response; public abstract class FormResponse { - } diff --git a/src/main/java/cn/nukkit/form/response/FormResponseData.java b/src/main/java/cn/nukkit/form/response/FormResponseData.java index fffb87dad28..6a96cda0440 100644 --- a/src/main/java/cn/nukkit/form/response/FormResponseData.java +++ b/src/main/java/cn/nukkit/form/response/FormResponseData.java @@ -17,5 +17,4 @@ public int getElementID() { public String getElementContent() { return elementContent; } - } diff --git a/src/main/java/cn/nukkit/form/response/FormResponseModal.java b/src/main/java/cn/nukkit/form/response/FormResponseModal.java index e09a6173593..6b610096d70 100644 --- a/src/main/java/cn/nukkit/form/response/FormResponseModal.java +++ b/src/main/java/cn/nukkit/form/response/FormResponseModal.java @@ -17,5 +17,4 @@ public int getClickedButtonId() { public String getClickedButtonText() { return clickedButtonText; } - } diff --git a/src/main/java/cn/nukkit/form/response/FormResponseSimple.java b/src/main/java/cn/nukkit/form/response/FormResponseSimple.java index 6fa48b38fa0..1e308415916 100644 --- a/src/main/java/cn/nukkit/form/response/FormResponseSimple.java +++ b/src/main/java/cn/nukkit/form/response/FormResponseSimple.java @@ -19,5 +19,4 @@ public int getClickedButtonId() { public ElementButton getClickedButton() { return clickedButton; } - } diff --git a/src/main/java/cn/nukkit/form/window/FormWindow.java b/src/main/java/cn/nukkit/form/window/FormWindow.java index 1d48a5d35c7..b934973f5ce 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindow.java +++ b/src/main/java/cn/nukkit/form/window/FormWindow.java @@ -9,7 +9,7 @@ public abstract class FormWindow { - protected static final Gson GSON = new Gson(); + static final Gson GSON = new Gson(); protected transient boolean closed = false; protected final transient List handlers = new ObjectArrayList<>(); @@ -33,5 +33,4 @@ public void addHandler(FormResponseHandler handler) { public List getHandlers() { return handlers; } - } diff --git a/src/main/java/cn/nukkit/form/window/FormWindowCustom.java b/src/main/java/cn/nukkit/form/window/FormWindowCustom.java index 1919a1370ef..f7682ee7ba5 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowCustom.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowCustom.java @@ -11,9 +11,11 @@ public class FormWindowCustom extends FormWindow { - private final String type = "custom_form"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "custom_form"; private String title = ""; private ElementButtonImageData icon; + @SuppressWarnings("FieldMayBeFinal") private List content; private FormResponseCustom response; @@ -74,9 +76,7 @@ public void setResponse(String data) { return; } - List elementResponses = GSON.fromJson(data, new TypeToken>() { - }.getType()); - //elementResponses.remove(elementResponses.size() - 1); //submit button //maybe mojang removed that? + List elementResponses = GSON.fromJson(data, new ListTypeToken().getType()); int i = 0; @@ -150,4 +150,6 @@ public void setElementsFromResponse() { } } + private static class ListTypeToken extends TypeToken> { + } } diff --git a/src/main/java/cn/nukkit/form/window/FormWindowModal.java b/src/main/java/cn/nukkit/form/window/FormWindowModal.java index 9eb7e95c2ce..cb58679c9d9 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowModal.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowModal.java @@ -4,7 +4,8 @@ public class FormWindowModal extends FormWindow { - private final String type = "modal"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "modal"; private String title = ""; private String content = ""; private String button1 = ""; @@ -63,5 +64,4 @@ public void setResponse(String data) { if (data.equals("true")) response = new FormResponseModal(0, button1); else response = new FormResponseModal(1, button2); } - } diff --git a/src/main/java/cn/nukkit/form/window/FormWindowSimple.java b/src/main/java/cn/nukkit/form/window/FormWindowSimple.java index 0b7ff67ef2e..5731a7745ae 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowSimple.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowSimple.java @@ -8,9 +8,11 @@ public class FormWindowSimple extends FormWindow { - private final String type = "form"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 + @SuppressWarnings("unused") + private final String type = "form"; private String title = ""; private String content = ""; + @SuppressWarnings("FieldMayBeFinal") private List buttons; private FormResponseSimple response = null; @@ -70,5 +72,4 @@ public void setResponse(String data) { } this.response = new FormResponseSimple(buttonID, buttons.get(buttonID)); } - } diff --git a/src/main/java/cn/nukkit/inventory/AnvilInventory.java b/src/main/java/cn/nukkit/inventory/AnvilInventory.java index b359a10ab47..c91e7cd464d 100644 --- a/src/main/java/cn/nukkit/inventory/AnvilInventory.java +++ b/src/main/java/cn/nukkit/inventory/AnvilInventory.java @@ -5,7 +5,7 @@ import cn.nukkit.level.Position; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class AnvilInventory extends FakeBlockUIComponent { @@ -24,18 +24,6 @@ public AnvilInventory(PlayerUIInventory playerUI, Position position) { super(playerUI, InventoryType.ANVIL, 1, position); } - @Override - public void onClose(Player who) { - super.onClose(who); - who.craftingType = Player.CRAFTING_SMALL; - who.resetCraftingGridType(); - - for (int i = 0; i < 2; ++i) { - this.getHolder().getLevel().dropItem(this.getHolder().add(0.5, 0.5, 0.5), this.getItem(i)); - this.clear(i); - } - } - @Override public void onOpen(Player who) { super.onOpen(who); diff --git a/src/main/java/cn/nukkit/inventory/BarrelInventory.java b/src/main/java/cn/nukkit/inventory/BarrelInventory.java new file mode 100644 index 00000000000..c28de6a32c6 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/BarrelInventory.java @@ -0,0 +1,68 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockBarrel; +import cn.nukkit.blockentity.BlockEntityBarrel; +import cn.nukkit.level.Level; +import cn.nukkit.network.protocol.LevelSoundEventPacket; + +public class BarrelInventory extends ContainerInventory { + + public BarrelInventory(BlockEntityBarrel barrel) { + super(barrel, InventoryType.BARREL); + } + + @Override + public BlockEntityBarrel getHolder() { + return (BlockEntityBarrel) this.holder; + } + + @Override + public void onOpen(Player who) { + super.onOpen(who); + if (this.getViewers().size() != 1) { + return; + } + + BlockEntityBarrel barrel = this.getHolder(); + Level level = barrel.getLevel(); + if (level == null) { + return; + } + + Block block = barrel.getBlock(); + if (block instanceof BlockBarrel) { + BlockBarrel blockBarrel = (BlockBarrel) block; + if (!blockBarrel.isOpen()) { + blockBarrel.setOpen(true); + level.setBlock(blockBarrel, blockBarrel, true, true); + level.addLevelSoundEvent(blockBarrel, LevelSoundEventPacket.SOUND_BLOCK_BARREL_OPEN); + } + } + } + + @Override + public void onClose(Player who) { + super.onClose(who); + if (!this.getViewers().isEmpty()) { + return; + } + + BlockEntityBarrel barrel = this.getHolder(); + Level level = barrel.getLevel(); + if (level == null) { + return; + } + + Block block = barrel.getBlock(); + if (block instanceof BlockBarrel) { + BlockBarrel blockBarrel = (BlockBarrel) block; + if (blockBarrel.isOpen()) { + blockBarrel.setOpen(false); + level.setBlock(blockBarrel, blockBarrel, true, true); + level.addLevelSoundEvent(blockBarrel, LevelSoundEventPacket.SOUND_BLOCK_BARREL_CLOSE); + } + } + } +} diff --git a/src/main/java/cn/nukkit/inventory/BaseInventory.java b/src/main/java/cn/nukkit/inventory/BaseInventory.java index c6b110ec34d..cb8d4867079 100644 --- a/src/main/java/cn/nukkit/inventory/BaseInventory.java +++ b/src/main/java/cn/nukkit/inventory/BaseInventory.java @@ -12,11 +12,12 @@ import cn.nukkit.item.ItemBlock; import cn.nukkit.network.protocol.InventoryContentPacket; import cn.nukkit.network.protocol.InventorySlotPacket; +import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BaseInventory implements Inventory { @@ -37,6 +38,8 @@ public abstract class BaseInventory implements Inventory { protected InventoryHolder holder; + final Item air; + public BaseInventory(InventoryHolder holder, InventoryType type) { this(holder, type, new HashMap<>()); } @@ -50,6 +53,8 @@ public BaseInventory(InventoryHolder holder, InventoryType type, Map items, Integer overrideSize, String overrideTitle) { + air = new ItemBlock(Block.get(BlockID.AIR, null), 0, 0); + this.holder = holder; this.type = type; @@ -99,7 +104,14 @@ public String getTitle() { @Override public Item getItem(int index) { - return this.slots.containsKey(index) ? this.slots.get(index).clone() : new ItemBlock(Block.get(BlockID.AIR), null, 0); + Item get = this.slots.get(index); + return get == null ? new ItemBlock(Block.get(BlockID.AIR), null, 0) : get.clone(); + } + + @Override + public Item getItemFast(int index) { + Item get = this.slots.get(index); + return get == null ? air : get; } @Override @@ -110,12 +122,8 @@ public Map getContents() { @Override public void setContents(Map items) { if (items.size() > this.size) { + items = new TreeMap<>(items); TreeMap newItems = new TreeMap<>(); - for (Map.Entry entry : items.entrySet()) { - newItems.put(entry.getKey(), entry.getValue()); - } - items = newItems; - newItems = new TreeMap<>(); int i = 0; for (Map.Entry entry : items.entrySet()) { newItems.put(entry.getKey(), entry.getValue()); @@ -142,7 +150,7 @@ public void setContents(Map items) { @Override public boolean setItem(int index, Item item, boolean send) { - item = item.clone(); + //item = item.clone(); if (index < 0 || index >= this.size) { return false; } else if (item.getId() == 0 || item.getCount() <= 0) { @@ -168,7 +176,6 @@ public boolean setItem(int index, Item item, boolean send) { Item old = this.getItem(index); this.slots.put(index, item.clone()); this.onSlotChange(index, old, send); - return true; } @@ -231,7 +238,7 @@ public int first(Item item, boolean exact) { @Override public int firstEmpty(Item item) { for (int i = 0; i < this.size; ++i) { - if (this.getItem(i).getId() == Item.AIR) { + if (this.getItemFast(i).getId() == Item.AIR) { return i; } } @@ -248,24 +255,25 @@ public void decreaseCount(int slot) { this.setItem(slot, item); } } - + @Override public boolean canAddItem(Item item) { - item = item.clone(); + int count = item.getCount(); boolean checkDamage = item.hasMeta(); boolean checkTag = item.getCompoundTag() != null; - for (int i = 0; i < this.getSize(); ++i) { - Item slot = this.getItem(i); + int i1 = this.getSize(); + for (int i = 0; i < i1; ++i) { + Item slot = this.getItemFast(i); if (item.equals(slot, checkDamage, checkTag)) { int diff; if ((diff = slot.getMaxStackSize() - slot.getCount()) > 0) { - item.setCount(item.getCount() - diff); + count -= diff; } } else if (slot.getId() == Item.AIR) { - item.setCount(item.getCount() - this.getMaxStackSize()); + count -= this.getMaxStackSize(); } - if (item.getCount() <= 0) { + if (count <= 0) { return true; } } @@ -282,7 +290,7 @@ public Item[] addItem(Item... slots) { } } - List emptySlots = new ArrayList<>(); + IntArrayList emptySlots = new IntArrayList(); for (int i = 0; i < this.getSize(); ++i) { Item item = this.getItem(i); @@ -293,7 +301,7 @@ public Item[] addItem(Item... slots) { for (Item slot : new ArrayList<>(itemSlots)) { if (slot.equals(item) && item.getCount() < item.getMaxStackSize()) { int amount = Math.min(item.getMaxStackSize() - item.getCount(), slot.getCount()); - amount = Math.min(amount, this.getMaxStackSize()); + amount = Math.min(amount, this.maxStackSize); if (amount > 0) { slot.setCount(slot.getCount() - amount); item.setCount(item.getCount() + amount); @@ -314,7 +322,7 @@ public Item[] addItem(Item... slots) { if (!itemSlots.isEmpty()) { Item slot = itemSlots.get(0); int amount = Math.min(slot.getMaxStackSize(), slot.getCount()); - amount = Math.min(amount, this.getMaxStackSize()); + amount = Math.min(amount, this.maxStackSize); slot.setCount(slot.getCount() - amount); Item item = slot.clone(); item.setCount(amount); @@ -353,11 +361,10 @@ public Item[] removeItem(Item... slots) { if (slot.getCount() <= 0) { itemSlots.remove(slot); } - } } - if (itemSlots.size() == 0) { + if (itemSlots.isEmpty()) { break; } } @@ -380,6 +387,7 @@ public boolean clear(int index, boolean send) { } item = ev.getNewItem(); } + if (holder instanceof BlockEntity) { ((BlockEntity) holder).setDirty(); } @@ -420,7 +428,6 @@ public void setMaxStackSize(int maxStackSize) { @Override public boolean open(Player who) { - //if (this.viewers.contains(who)) return false; InventoryOpenEvent ev = new InventoryOpenEvent(this, who); who.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -468,7 +475,7 @@ public void sendContents(Player... players) { for (Player player : players) { int id = player.getWindowId(this); - if (id == -1 || !player.spawned) { + if (id == -1 /*|| !player.spawned*/) { this.close(player); continue; } @@ -484,7 +491,7 @@ public boolean isFull() { } for (Item item : this.slots.values()) { - if (item == null || item.getId() == 0 || item.getCount() < item.getMaxStackSize() || item.getCount() < this.getMaxStackSize()) { + if (item == null || item.getId() == 0 || item.getCount() < item.getMaxStackSize() || item.getCount() < this.maxStackSize) { return false; } } @@ -494,7 +501,7 @@ public boolean isFull() { @Override public boolean isEmpty() { - if (this.getMaxStackSize() <= 0) { + if (this.maxStackSize <= 0) { return false; } @@ -508,7 +515,7 @@ public boolean isEmpty() { } public int getFreeSpace(Item item) { - int maxStackSize = Math.min(item.getMaxStackSize(), this.getMaxStackSize()); + int maxStackSize = Math.min(item.getMaxStackSize(), this.maxStackSize); int space = (this.getSize() - this.slots.size()) * maxStackSize; for (Item slot : this.getContents().values()) { @@ -532,14 +539,23 @@ public void sendContents(Collection players) { @Override public void sendSlot(int index, Player player) { - this.sendSlot(index, new Player[]{player}); + InventorySlotPacket pk = new InventorySlotPacket(); + pk.slot = index; + pk.item = this.getItem(index); + int id = player.getWindowId(this); + if (id == -1) { + this.close(player); + return; + } + pk.inventoryId = id; + player.dataPacket(pk); } @Override public void sendSlot(int index, Player... players) { InventorySlotPacket pk = new InventorySlotPacket(); pk.slot = index; - pk.item = this.getItem(index).clone(); + pk.item = this.getItem(index); for (Player player : players) { int id = player.getWindowId(this); diff --git a/src/main/java/cn/nukkit/inventory/BeaconInventory.java b/src/main/java/cn/nukkit/inventory/BeaconInventory.java index 51af284d77d..9bcb7bc656e 100644 --- a/src/main/java/cn/nukkit/inventory/BeaconInventory.java +++ b/src/main/java/cn/nukkit/inventory/BeaconInventory.java @@ -1,25 +1,35 @@ package cn.nukkit.inventory; import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.level.Position; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; /** - * author: Rover656 + * @author Rover656 */ public class BeaconInventory extends FakeBlockUIComponent { + + /** + * Items that can be put into beacon inventory + */ + private static final IntSet ITEMS = new IntOpenHashSet(new int[]{Item.AIR, ItemID.NETHERITE_INGOT, ItemID.EMERALD, ItemID.DIAMOND, ItemID.GOLD_INGOT, ItemID. IRON_INGOT}); + public BeaconInventory(PlayerUIInventory playerUI, Position position) { super(playerUI, InventoryType.BEACON, 27, position); } @Override - public void onClose(Player who) { - super.onClose(who); + public void onOpen(Player who) { + super.onOpen(who); + who.craftingType = Player.CRAFTING_BEACON; + } - // Drop item in slot if client doesn't automatically move it to player's inventory - if (!who.isConnected()) { - this.getHolder().getLevel().dropItem(this.getHolder().add(0.5, 0.5, 0.5), this.getItem(0)); - } - this.clear(0); + @Override + public boolean allowedToAdd(int itemId) { + return ITEMS.contains(itemId); } } diff --git a/src/main/java/cn/nukkit/inventory/BigCraftingGrid.java b/src/main/java/cn/nukkit/inventory/BigCraftingGrid.java index bcf0bdf0ca6..9cc4fed1a74 100644 --- a/src/main/java/cn/nukkit/inventory/BigCraftingGrid.java +++ b/src/main/java/cn/nukkit/inventory/BigCraftingGrid.java @@ -4,6 +4,7 @@ * @author CreeperFace */ public class BigCraftingGrid extends CraftingGrid { + BigCraftingGrid(PlayerUIInventory playerUI) { super(playerUI, 32, 9); } diff --git a/src/main/java/cn/nukkit/inventory/BlastFurnaceInventory.java b/src/main/java/cn/nukkit/inventory/BlastFurnaceInventory.java new file mode 100644 index 00000000000..bbe84def40e --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/BlastFurnaceInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityBlastFurnace; + +public class BlastFurnaceInventory extends FurnaceInventory { + + public BlastFurnaceInventory(BlockEntityBlastFurnace furnace) { + super(furnace, InventoryType.BLAST_FURNACE); + } + + @Override + public BlockEntityBlastFurnace getHolder() { + return (BlockEntityBlastFurnace) this.holder; + } +} diff --git a/src/main/java/cn/nukkit/inventory/BrewingInventory.java b/src/main/java/cn/nukkit/inventory/BrewingInventory.java index 8d7454be766..0b66fdf7fb5 100644 --- a/src/main/java/cn/nukkit/inventory/BrewingInventory.java +++ b/src/main/java/cn/nukkit/inventory/BrewingInventory.java @@ -3,8 +3,14 @@ import cn.nukkit.blockentity.BlockEntityBrewingStand; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +import java.util.ArrayList; +import java.util.List; public class BrewingInventory extends ContainerInventory { + public BrewingInventory(BlockEntityBrewingStand brewingStand) { super(brewingStand, InventoryType.BREWING_STAND); } @@ -40,4 +46,64 @@ public void onSlotChange(int index, Item before, boolean send) { this.getHolder().scheduleUpdate(); } -} \ No newline at end of file + + @Override + public Item[] addItem(Item... slots) { + List itemSlots = new ArrayList<>(); + for (Item slot : slots) { + if (slot.getId() != 0 && slot.getCount() > 0) { + itemSlots.add(slot.clone()); + } + } + + IntArrayList emptySlots = new IntArrayList(); + + boolean isFuel = slots.length > 0 && slots[0].getId() == ItemID.BLAZE_POWDER; + for (int i = (isFuel ? 4 : 1); + i < (isFuel ? 5 : this.getSize() - 1); + ++i) { // Brewing: Separate fuel & bottles + ignore ingredient + + Item item = this.getItem(i); + if (item.getId() == Item.AIR || item.getCount() <= 0) { + emptySlots.add(i); + } + + for (Item slot : new ArrayList<>(itemSlots)) { + if (slot.equals(item) && item.getCount() < item.getMaxStackSize()) { + int amount = Math.min(item.getMaxStackSize() - item.getCount(), slot.getCount()); + amount = Math.min(amount, this.maxStackSize); + if (amount > 0) { + slot.setCount(slot.getCount() - amount); + item.setCount(item.getCount() + amount); + this.setItem(i, item); + if (slot.getCount() <= 0) { + itemSlots.remove(slot); + } + } + } + } + if (itemSlots.isEmpty()) { + break; + } + } + + if (!itemSlots.isEmpty() && !emptySlots.isEmpty()) { + for (int slotIndex : emptySlots) { + if (!itemSlots.isEmpty()) { + Item slot = itemSlots.get(0); + int amount = Math.min(slot.getMaxStackSize(), slot.getCount()); + amount = Math.min(amount, this.maxStackSize); + slot.setCount(slot.getCount() - amount); + Item item = slot.clone(); + item.setCount(amount); + this.setItem(slotIndex, item); + if (slot.getCount() <= 0) { + itemSlots.remove(slot); + } + } + } + } + + return itemSlots.toArray(new Item[0]); + } +} diff --git a/src/main/java/cn/nukkit/inventory/BrewingRecipe.java b/src/main/java/cn/nukkit/inventory/BrewingRecipe.java index 51017b4661c..6b14f765504 100644 --- a/src/main/java/cn/nukkit/inventory/BrewingRecipe.java +++ b/src/main/java/cn/nukkit/inventory/BrewingRecipe.java @@ -3,7 +3,6 @@ import cn.nukkit.item.Item; - public class BrewingRecipe extends MixRecipe { public BrewingRecipe(Item input, Item ingredient, Item output) { diff --git a/src/main/java/cn/nukkit/inventory/CampfireInventory.java b/src/main/java/cn/nukkit/inventory/CampfireInventory.java new file mode 100644 index 00000000000..ad573d9649f --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/CampfireInventory.java @@ -0,0 +1,113 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityCampfire; +import cn.nukkit.item.Item; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +import java.util.ArrayList; +import java.util.List; + +public class CampfireInventory extends ContainerInventory { + + public CampfireInventory(BlockEntityCampfire campfire) { + super(campfire, InventoryType.CAMPFIRE); + } + + @Override + public BlockEntityCampfire getHolder() { + return (BlockEntityCampfire) this.holder; + } + + @Override + public void onSlotChange(int index, Item before, boolean send) { + super.onSlotChange(index, before, send); + + this.getHolder().scheduleUpdate(); + this.getHolder().spawnToAll(); + } + + @Override + public int getMaxStackSize() { + return 1; + } + + @Override // Max stack size 1 + public boolean canAddItem(Item item) { + int count = item.getCount(); + boolean checkDamage = item.hasMeta(); + boolean checkTag = item.getCompoundTag() != null; + int i1 = this.getSize(); + for (int i = 0; i < i1; ++i) { + Item slot = this.getItemFast(i); + if (item.equals(slot, checkDamage, checkTag)) { + int diff; + if ((diff = 1 - slot.getCount()) > 0) { + count -= diff; + } + } else if (slot.getId() == Item.AIR) { + count -= 1; + } + + if (count <= 0) { + return true; + } + } + + return false; + } + + @Override // Max stack size 1 + public Item[] addItem(Item... slots) { + List itemSlots = new ArrayList<>(); + for (Item slot : slots) { + if (slot.getId() != 0 && slot.getCount() > 0) { + itemSlots.add(slot.clone()); + } + } + + IntArrayList emptySlots = new IntArrayList(); + + for (int i = 0; i < this.getSize(); ++i) { + Item item = this.getItem(i); + if (item.getId() == Item.AIR || item.getCount() <= 0) { + emptySlots.add(i); + } + + for (Item slot : new ArrayList<>(itemSlots)) { + if (slot.equals(item) && item.getCount() < 1) { + int amount = Math.min(1 - item.getCount(), slot.getCount()); + amount = Math.min(amount, 1); + if (amount > 0) { + slot.setCount(slot.getCount() - amount); + item.setCount(item.getCount() + amount); + this.setItem(i, item); + if (slot.getCount() <= 0) { + itemSlots.remove(slot); + } + } + } + } + if (itemSlots.isEmpty()) { + break; + } + } + + if (!itemSlots.isEmpty() && !emptySlots.isEmpty()) { + for (int slotIndex : emptySlots) { + if (!itemSlots.isEmpty()) { + Item slot = itemSlots.get(0); + int amount = Math.min(1, slot.getCount()); + slot.setCount(slot.getCount() - amount); + Item item = slot.clone(); + item.setCount(amount); + this.setItem(slotIndex, item); + if (slot.getCount() <= 0) { + itemSlots.remove(slot); + } + } + } + } + + return itemSlots.toArray(new Item[0]); + } +} diff --git a/src/main/java/cn/nukkit/inventory/CampfireRecipe.java b/src/main/java/cn/nukkit/inventory/CampfireRecipe.java new file mode 100644 index 00000000000..f82b4562dc0 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/CampfireRecipe.java @@ -0,0 +1,38 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; + +public class CampfireRecipe implements Recipe { + + private final Item output; + + private Item ingredient; + + public CampfireRecipe(Item result, Item ingredient) { + this.output = result.clone(); + this.ingredient = ingredient.clone(); + } + + public void setInput(Item item) { + this.ingredient = item.clone(); + } + + public Item getInput() { + return this.ingredient.clone(); + } + + @Override + public Item getResult() { + return this.output.clone(); + } + + @Override + public void registerToCraftingManager(CraftingManager manager) { + manager.registerCampfireRecipe(this); + } + + @Override + public RecipeType getType() { + return this.ingredient.hasMeta() ? RecipeType.CAMPFIRE_DATA : RecipeType.CAMPFIRE; + } +} diff --git a/src/main/java/cn/nukkit/inventory/ChestBoatInventory.java b/src/main/java/cn/nukkit/inventory/ChestBoatInventory.java new file mode 100644 index 00000000000..1d11c609a70 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/ChestBoatInventory.java @@ -0,0 +1,44 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.ContainerOpenPacket; + +public class ChestBoatInventory extends ContainerInventory { + + public ChestBoatInventory(EntityChestBoat holder) { + super(holder, InventoryType.CHEST_BOAT); + } + + @Override + public EntityChestBoat getHolder() { + return (EntityChestBoat) super.getHolder(); + } + + @Override + public void onOpen(Player who) { + this.viewers.add(who); + + ContainerOpenPacket pk = new ContainerOpenPacket(); + pk.windowId = who.getWindowId(this); + pk.type = InventoryType.CHEST_BOAT.getNetworkType(); + InventoryHolder holder = this.getHolder(); + if (holder != null) { + pk.x = (int) ((Vector3) holder).getX(); + pk.y = (int) ((Vector3) holder).getY(); + pk.z = (int) ((Vector3) holder).getZ(); + } else { + pk.x = pk.y = pk.z = 0; + } + + if (holder != null) { + pk.entityId = ((Entity) holder).getId(); + } + + who.dataPacket(pk); + + this.sendContents(who); + } +} diff --git a/src/main/java/cn/nukkit/inventory/ChestInventory.java b/src/main/java/cn/nukkit/inventory/ChestInventory.java index 33cb80ea98e..d5ea3678d81 100644 --- a/src/main/java/cn/nukkit/inventory/ChestInventory.java +++ b/src/main/java/cn/nukkit/inventory/ChestInventory.java @@ -1,13 +1,14 @@ package cn.nukkit.inventory; import cn.nukkit.Player; +import cn.nukkit.block.BlockTrappedChest; import cn.nukkit.blockentity.BlockEntityChest; import cn.nukkit.level.Level; import cn.nukkit.network.protocol.BlockEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ChestInventory extends ContainerInventory { @@ -39,6 +40,10 @@ public void onOpen(Player who) { if (level != null) { level.addLevelSoundEvent(this.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_CHEST_OPEN); level.addChunkPacket((int) this.getHolder().getX() >> 4, (int) this.getHolder().getZ() >> 4, pk); + + if (this.getHolder().getBlock() instanceof BlockTrappedChest) { + level.updateAroundRedstone(this.getHolder(), null); + } } } } @@ -61,6 +66,15 @@ public void onClose(Player who) { } super.onClose(who); + + if (this.getViewers().isEmpty()) { + Level level = this.getHolder().getLevel(); + if (level != null) { + if (this.getHolder().getBlock() instanceof BlockTrappedChest) { + level.updateAroundRedstone(this.getHolder(), null); + } + } + } } public void setDoubleInventory(DoubleChestInventory doubleInventory) { diff --git a/src/main/java/cn/nukkit/inventory/ContainerInventory.java b/src/main/java/cn/nukkit/inventory/ContainerInventory.java index 0b8af477475..99a4787f9fe 100644 --- a/src/main/java/cn/nukkit/inventory/ContainerInventory.java +++ b/src/main/java/cn/nukkit/inventory/ContainerInventory.java @@ -11,7 +11,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class ContainerInventory extends BaseInventory { @@ -45,6 +45,7 @@ public void onOpen(Player who) { } else { pk.x = pk.y = pk.z = 0; } + if (holder instanceof Entity) { pk.entityId = ((Entity) holder).getId(); } diff --git a/src/main/java/cn/nukkit/inventory/ContainerRecipe.java b/src/main/java/cn/nukkit/inventory/ContainerRecipe.java index 01d46624454..72226dfb721 100644 --- a/src/main/java/cn/nukkit/inventory/ContainerRecipe.java +++ b/src/main/java/cn/nukkit/inventory/ContainerRecipe.java @@ -3,6 +3,7 @@ import cn.nukkit.item.Item; public class ContainerRecipe extends MixRecipe { + public ContainerRecipe(Item input, Item ingredient, Item output) { super(input, ingredient, output); } diff --git a/src/main/java/cn/nukkit/inventory/CraftingGrid.java b/src/main/java/cn/nukkit/inventory/CraftingGrid.java index 9cd6eab3522..125176071df 100644 --- a/src/main/java/cn/nukkit/inventory/CraftingGrid.java +++ b/src/main/java/cn/nukkit/inventory/CraftingGrid.java @@ -1,7 +1,7 @@ package cn.nukkit.inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class CraftingGrid extends PlayerUIComponent { diff --git a/src/main/java/cn/nukkit/inventory/CraftingManager.java b/src/main/java/cn/nukkit/inventory/CraftingManager.java index d1b5601761d..dab85d69827 100644 --- a/src/main/java/cn/nukkit/inventory/CraftingManager.java +++ b/src/main/java/cn/nukkit/inventory/CraftingManager.java @@ -1,13 +1,12 @@ package cn.nukkit.inventory; import cn.nukkit.Server; -import cn.nukkit.item.Item; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.*; import cn.nukkit.network.protocol.CraftingDataPacket; import cn.nukkit.network.protocol.DataPacket; -import cn.nukkit.utils.BinaryStream; -import cn.nukkit.utils.Config; -import cn.nukkit.utils.MainLogger; -import cn.nukkit.utils.Utils; +import cn.nukkit.utils.*; import io.netty.util.collection.CharObjectHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.extern.log4j.Log4j2; @@ -15,9 +14,10 @@ import java.io.File; import java.io.InputStream; import java.util.*; +import java.util.zip.Deflater; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @Log4j2 @@ -25,18 +25,19 @@ public class CraftingManager { public final Collection recipes = new ArrayDeque<>(); - public static DataPacket packet = null; + public static DataPacket packet; protected final Map> shapedRecipes = new Int2ObjectOpenHashMap<>(); - - public final Map furnaceRecipes = new Int2ObjectOpenHashMap<>(); + protected final Map> shapelessRecipes = new Int2ObjectOpenHashMap<>(); public final Map multiRecipes = new HashMap<>(); - + public final Map furnaceRecipes = new Int2ObjectOpenHashMap<>(); public final Map brewingRecipes = new Int2ObjectOpenHashMap<>(); public final Map containerRecipes = new Int2ObjectOpenHashMap<>(); + public final Map campfireRecipes = new Int2ObjectOpenHashMap<>(); // Server only + public final Map smithingRecipes = new Int2ObjectOpenHashMap<>(); private static int RECIPE_COUNT = 0; - protected final Map> shapelessRecipes = new Int2ObjectOpenHashMap<>(); + static int NEXT_NETWORK_ID; public static final Comparator recipeComparator = (i1, i2) -> { if (i1.getId() > i2.getId()) { @@ -67,9 +68,195 @@ public CraftingManager() { Config customRecipes = new Config(filePath, Config.JSON); this.loadRecipes(customRecipes); } + + this.buildMissingRecipes(); + this.rebuildPacket(); - MainLogger.getLogger().info("Loaded " + this.recipes.size() + " recipes"); + MainLogger.getLogger().info("Successfully loaded " + this.recipes.size() + " recipes"); + } + + private void buildMissingRecipes() { + List oakChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 0, 1)); + oakChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_oak_chest_boat", 0, Item.get(Item.OAK_CHEST_BOAT, 0, 1), oakChestBoat)); + List birchChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 2, 1)); + birchChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_birch_chest_boat", 0, Item.get(Item.BIRCH_CHEST_BOAT, 0, 1), birchChestBoat)); + List jungleChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 3, 1)); + jungleChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_jungle_chest_boat", 0, Item.get(Item.JUNGLE_CHEST_BOAT, 0, 1), jungleChestBoat)); + List spruceChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 1, 1)); + spruceChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_spruce_chest_boat", 0, Item.get(Item.SPRUCE_CHEST_BOAT, 0, 1), spruceChestBoat)); + List acaciaChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 4, 1)); + acaciaChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_acacia_chest_boat", 0, Item.get(Item.ACACIA_CHEST_BOAT, 0, 1), acaciaChestBoat)); + List darkOakChestBoat = Arrays.asList(Item.get(Item.CHEST, 0, 1), Item.get(Item.BOAT, 5, 1)); + darkOakChestBoat.sort(recipeComparator); + this.registerRecipe(new ShapelessRecipe("craft_dark_oak_chest_boat", 0, Item.get(Item.DARK_OAK_CHEST_BOAT, 0, 1), darkOakChestBoat)); + Map record5 = new CharObjectHashMap<>(); + record5.put('A', Item.get(Item.DISC_FRAGMENT_5, 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_record_5", 0, Item.get(Item.RECORD_5), new String[]{"AAA", "AAA", "AAA"}, record5, new ArrayList<>())); + + Map recoveryCompass = new CharObjectHashMap<>(); + recoveryCompass.put('A', Item.get(Item.ECHO_SHARD, 0, 1)); + recoveryCompass.put('B', Item.get(Item.COMPASS, 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_recovery_compass", 0, Item.get(Item.RECOVERY_COMPASS), new String[]{"AAA", "ABA", "AAA"}, recoveryCompass, new ArrayList<>())); + + Map copperBlock = new CharObjectHashMap<>(); + copperBlock.put('A', Item.get(Item.COPPER_INGOT, 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_copper_block", 0, new ItemBlock(Block.get(BlockID.COPPER_BLOCK)), new String[]{"AAA", "AAA", "AAA"}, copperBlock, new ArrayList<>())); + + Map copperSlab1 = new CharObjectHashMap<>(); + copperSlab1.put('A', new ItemBlock(Block.get(BlockID.CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab1", 0, new ItemBlock(Block.get(BlockID.CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab1, new ArrayList<>())); + + Map copperSlab2 = new CharObjectHashMap<>(); + copperSlab2.put('A', new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab2", 0, new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab2, new ArrayList<>())); + + Map copperSlab3 = new CharObjectHashMap<>(); + copperSlab3.put('A', new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab3", 0, new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab3, new ArrayList<>())); + + Map copperSlab4 = new CharObjectHashMap<>(); + copperSlab4.put('A', new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab4", 0, new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab4, new ArrayList<>())); + + Map copperSlab1w = new CharObjectHashMap<>(); + copperSlab1w.put('A', new ItemBlock(Block.get(BlockID.WAXED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab1w", 0, new ItemBlock(Block.get(BlockID.WAXED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab1w, new ArrayList<>())); + + Map copperSlab2w = new CharObjectHashMap<>(); + copperSlab2w.put('A', new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab2w", 0, new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab2w, new ArrayList<>())); + + Map copperSlab3w = new CharObjectHashMap<>(); + copperSlab3w.put('A', new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab3w", 0, new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab3w, new ArrayList<>())); + + Map copperSlab4w = new CharObjectHashMap<>(); + copperSlab4w.put('A', new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_slab4w", 0, new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_CUT_COPPER_SLAB), 0, 6), new String[]{" ", " ", "AAA"}, copperSlab4w, new ArrayList<>())); + + Map copperStairs1 = new CharObjectHashMap<>(); + copperStairs1.put('A', new ItemBlock(Block.get(BlockID.CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs1", 0, new ItemBlock(Block.get(BlockID.CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs1, new ArrayList<>())); + + Map copperStairs2 = new CharObjectHashMap<>(); + copperStairs2.put('A', new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs2", 0, new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs2, new ArrayList<>())); + + Map copperStairs3 = new CharObjectHashMap<>(); + copperStairs3.put('A', new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs3", 0, new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs3, new ArrayList<>())); + + Map copperStairs4 = new CharObjectHashMap<>(); + copperStairs4.put('A', new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs4", 0, new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs4, new ArrayList<>())); + + Map copperStairs1w = new CharObjectHashMap<>(); + copperStairs1w.put('A', new ItemBlock(Block.get(BlockID.WAXED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs1w", 0, new ItemBlock(Block.get(BlockID.WAXED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs1w, new ArrayList<>())); + + Map copperStairs2w = new CharObjectHashMap<>(); + copperStairs2w.put('A', new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs2w", 0, new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs2w, new ArrayList<>())); + + Map copperStairs3w = new CharObjectHashMap<>(); + copperStairs3w.put('A', new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs3w", 0, new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs3w, new ArrayList<>())); + + Map copperStairs4w = new CharObjectHashMap<>(); + copperStairs4w.put('A', new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_CUT_COPPER))); + this.registerRecipe(new ShapedRecipe("craft_copper_stairs4w", 0, new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_CUT_COPPER_STAIRS), 0, 4), new String[]{"A ", "AA ", "AAA"}, copperStairs4w, new ArrayList<>())); + + Map lightningRod = new CharObjectHashMap<>(); + lightningRod.put('A', Item.get(Item.COPPER_INGOT, 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_lightning_rod", 0, new ItemBlock(Block.get(BlockID.LIGHTNING_ROD)), new String[]{" A ", " A ", " A "}, lightningRod, new ArrayList<>())); + + Map spyglass = new CharObjectHashMap<>(); + spyglass.put('A', Item.get(Item.COPPER_INGOT, 0, 1)); + spyglass.put('B', Item.get(Item.AMETHYST_SHARD, 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_spyglass", 0, Item.get(Item.SPYGLASS), new String[]{" B ", " A ", " A "}, spyglass, new ArrayList<>())); + + Map tintedGlass = new CharObjectHashMap<>(); + tintedGlass.put('A', Item.get(Item.AMETHYST_SHARD, 0, 1)); + tintedGlass.put('B', new ItemBlock(Block.get(BlockID.GLASS), 0, 1)); + this.registerRecipe(new ShapedRecipe("craft_tinted_glass", 0, new ItemBlock(Block.get(BlockID.TINTED_GLASS), 0, 2), new String[]{" A ", "ABA", " A "}, tintedGlass, new ArrayList<>())); + + this.registerRecipe(new ShapelessRecipe("craft_copper_ingot", 0, Item.get(ItemID.COPPER_INGOT, 0, 9), Collections.singleton(new ItemBlock(Block.get(BlockID.COPPER_BLOCK))))); + this.registerRecipe(new ShapelessRecipe("craft_amethyst_block", 0, Item.get(ItemID.GLOW_ITEM_FRAME), Arrays.asList(Item.get(ItemID.ITEM_FRAME), Item.get(ItemID.DYE, ItemDye.GLOW_INK_SAC)))); + this.registerRecipe(new ShapelessRecipe("craft_glow_frame", 0, new ItemBlock(Block.get(BlockID.AMETHYST_BLOCK)), Arrays.asList(Item.get(ItemID.AMETHYST_SHARD), Item.get(ItemID.AMETHYST_SHARD), Item.get(ItemID.AMETHYST_SHARD), Item.get(ItemID.AMETHYST_SHARD)))); + + this.registerRecipe(new ShapelessRecipe("craft_moss_stone", 0, new ItemBlock(Block.get(BlockID.MOSS_STONE)), Arrays.asList(new ItemBlock(Block.get(BlockID.MOSS_BLOCK)), new ItemBlock(Block.get(BlockID.COBBLESTONE))))); + this.registerRecipe(new ShapelessRecipe("craft_moss_stone_brick", 0, new ItemBlock(Block.get(BlockID.STONE_BRICK, 1)), Arrays.asList(new ItemBlock(Block.get(BlockID.MOSS_BLOCK)), new ItemBlock(Block.get(BlockID.STONE_BRICK))))); + this.registerRecipe(new ShapelessRecipe("craft_moss_carpet", 0, new ItemBlock(Block.get(BlockID.MOSS_CARPET), 0, 3), Arrays.asList(new ItemBlock(Block.get(BlockID.MOSS_BLOCK)), new ItemBlock(Block.get(BlockID.MOSS_BLOCK))))); + + this.registerRecipe(new ShapelessRecipe("craft_wax_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.COPPER_BLOCK))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_cut_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.CUT_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_o_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.OXIDIZED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_oc_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_OXIDIZED_CUT_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_w_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.WEATHERED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_wc_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_WEATHERED_CUT_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_e_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.EXPOSED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_wax_ec_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_EXPOSED_CUT_COPPER)), Arrays.asList(Item.get(ItemID.HONEYCOMB), new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER))))); + + this.registerRecipe(new ShapelessRecipe("craft_cut_copper", 0, new ItemBlock(Block.get(BlockID.CUT_COPPER), 0, 4), + Arrays.asList(new ItemBlock(Block.get(BlockID.COPPER_BLOCK)), new ItemBlock(Block.get(BlockID.COPPER_BLOCK)), new ItemBlock(Block.get(BlockID.COPPER_BLOCK)), new ItemBlock(Block.get(BlockID.COPPER_BLOCK))))); + this.registerRecipe(new ShapelessRecipe("craft_waxed_cut_copper", 0, new ItemBlock(Block.get(BlockID.WAXED_CUT_COPPER), 0, 4), + Arrays.asList(new ItemBlock(Block.get(BlockID.WAXED_COPPER)), new ItemBlock(Block.get(BlockID.WAXED_COPPER)), new ItemBlock(Block.get(BlockID.WAXED_COPPER)), new ItemBlock(Block.get(BlockID.WAXED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_oxidized_cut_copper", 0, new ItemBlock(Block.get(BlockID.OXIDIZED_CUT_COPPER), 0, 4), + Arrays.asList(new ItemBlock(Block.get(BlockID.OXIDIZED_COPPER)), new ItemBlock(Block.get(BlockID.OXIDIZED_COPPER)), new ItemBlock(Block.get(BlockID.OXIDIZED_COPPER)), new ItemBlock(Block.get(BlockID.OXIDIZED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_weathered_cut_copper", 0, new ItemBlock(Block.get(BlockID.WEATHERED_CUT_COPPER), 0, 4), + Arrays.asList(new ItemBlock(Block.get(BlockID.WEATHERED_COPPER)), new ItemBlock(Block.get(BlockID.WEATHERED_COPPER)), new ItemBlock(Block.get(BlockID.WEATHERED_COPPER)), new ItemBlock(Block.get(BlockID.WEATHERED_COPPER))))); + this.registerRecipe(new ShapelessRecipe("craft_exposed_cut_copper", 0, new ItemBlock(Block.get(BlockID.EXPOSED_CUT_COPPER), 0, 4), + Arrays.asList(new ItemBlock(Block.get(BlockID.EXPOSED_COPPER)), new ItemBlock(Block.get(BlockID.EXPOSED_COPPER)), new ItemBlock(Block.get(BlockID.EXPOSED_COPPER)), new ItemBlock(Block.get(BlockID.EXPOSED_COPPER))))); + + + this.registerRecipe(new FurnaceRecipe(new ItemBlock(Block.get(BlockID.SMOOTH_BASALT)), new ItemBlock(Block.get(BlockID.BASALT)))); + this.registerRecipe(new FurnaceRecipe(new ItemBlock(Block.get(BlockID.DEEPSLATE)), new ItemBlock(Block.get(BlockID.COBBLED_DEEPSLATE)))); + this.registerRecipe(new FurnaceRecipe(new ItemBlock(Block.get(BlockID.CRACKED_DEEPSLATE_BRICKS)), new ItemBlock(Block.get(BlockID.DEEPSLATE_BRICKS)))); + this.registerRecipe(new FurnaceRecipe(new ItemBlock(Block.get(BlockID.CRACKED_DEEPSLATE_TILES)), new ItemBlock(Block.get(BlockID.DEEPSLATE_TILES)))); + this.registerRecipe(new FurnaceRecipe(new ItemBlock(Block.get(BlockID.CRACKED_POLISHED_BLACKSTONE_BRICKS)), new ItemBlock(Block.get(BlockID.POLISHED_BLACKSTONE_BRICKS)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.IRON_INGOT), Item.get(ItemID.RAW_IRON))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.GOLD_INGOT), Item.get(ItemID.RAW_GOLD))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.COPPER_INGOT), Item.get(ItemID.RAW_COPPER))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.IRON_INGOT), new ItemBlock(Block.get(BlockID.DEEPSLATE_IRON_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.GOLD_INGOT), new ItemBlock(Block.get(BlockID.DEEPSLATE_GOLD_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.COPPER_INGOT), new ItemBlock(Block.get(BlockID.DEEPSLATE_COPPER_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.COAL), new ItemBlock(Block.get(BlockID.DEEPSLATE_COAL_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.EMERALD), new ItemBlock(Block.get(BlockID.DEEPSLATE_EMERALD_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.DIAMOND), new ItemBlock(Block.get(BlockID.DEEPSLATE_DIAMOND_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.REDSTONE), new ItemBlock(Block.get(BlockID.DEEPSLATE_REDSTONE_ORE)))); + this.registerRecipe(new FurnaceRecipe(Item.get(ItemID.DYE, ItemDye.LAPIS_LAZULI), new ItemBlock(Block.get(BlockID.DEEPSLATE_LAPIS_ORE)))); + + this.registerSmithingRecipes(); + } + + private void registerSmithingRecipes() { + ConfigSection smithing = new Config(Config.YAML).loadFromStream(Server.class.getClassLoader().getResourceAsStream("smithing.json")).getRootSection(); + for (Map recipe : (List>) smithing.get((Object) "smithing")) { + List outputs = ((List) recipe.get("output")); + if (outputs.size() > 1) { + continue; + } + + String recipeId = (String) recipe.get("id"); + + Map first = outputs.get(0); + Item item = Item.get(RuntimeItems.getMapping().fromIdentifier((String) first.get("id")).getLegacyId(), 0, 1); + + List ingredients = new ArrayList<>(); + for (Map ingredient : ((List) recipe.get("input"))) { + Item ing = Item.get(RuntimeItems.getMapping().fromIdentifier((String) ingredient.get("id")).getLegacyId(), 0, 1); + ingredients.add(ing); + } + + this.registerRecipe(new SmithingRecipe(recipeId, 0, ingredients, item)); + this.registerRecipe(new SmithingRecipe(recipeId, 0, ingredients, item)); + } } @SuppressWarnings("unchecked") @@ -99,8 +286,7 @@ private void loadRecipes(Config config) { // Bake sorted list sorted.sort(recipeComparator); - Item resultItem = Item.fromJson(first, true); - if (resultItem == null) continue; // TODO: remove when new blocks are supported + Item resultItem = Item.fromJson(first); this.registerRecipe(new ShapelessRecipe(null, Utils.toInt(recipe.get("priority")), resultItem, sorted)); // null recipeId will be replaced with recipe uuid break; case 1: @@ -134,8 +320,7 @@ private void loadRecipes(Config config) { extraResults.add(Item.fromJson(data)); } - resultItem = Item.fromJson(first, true); - if (resultItem == null) continue; // TODO: remove when new blocks are supported + resultItem = Item.fromJson(first); this.registerRecipe(new ShapedRecipe(null, Utils.toInt(recipe.get("priority")), resultItem, shape, ingredients, extraResults)); break; case 2: @@ -146,8 +331,7 @@ private void loadRecipes(Config config) { continue; } Map resultMap = (Map) recipe.get("output"); - resultItem = Item.fromJson(resultMap, true); - if (resultItem == null) continue; // TODO: remove when new blocks are supported + resultItem = Item.fromJson(resultMap); Item inputItem; try { Map inputMap = (Map) recipe.get("input"); @@ -245,9 +429,7 @@ public void rebuildPacket() { } pk.tryEncode(); - // TODO: find out whats wrong with compression - // packet = pk.compress(Deflater.BEST_COMPRESSION); - packet = pk; + packet = pk.compress(Deflater.BEST_COMPRESSION); } public Collection getRecipes() { @@ -265,7 +447,7 @@ public FurnaceRecipe matchFurnaceRecipe(Item input) { } private static UUID getMultiItemHash(Collection items) { - BinaryStream stream = new BinaryStream(); + BinaryStream stream = new BinaryStream(new byte[5 * items.size()]).reset(); for (Item item : items) { stream.putVarInt(getFullItemHash(item)); } @@ -407,6 +589,12 @@ public CraftingRecipe matchRecipe(List inputList, Item primaryOutput, List return null; } + public CampfireRecipe matchCampfireRecipe(Item input) { + CampfireRecipe recipe = this.campfireRecipes.get(getItemHash(input)); + if (recipe == null) recipe = this.campfireRecipes.get(getItemHash(input.getId(), 0)); + return recipe; + } + private boolean matchItemsAccumulation(CraftingRecipe recipe, List inputList, Item primaryOutput, List extraOutputList) { Item recipeResult = recipe.getResult(); if (primaryOutput.equals(recipeResult, recipeResult.hasMeta(), recipeResult.hasCompoundTag()) && primaryOutput.getCount() % recipeResult.getCount() == 0) { @@ -416,10 +604,24 @@ private boolean matchItemsAccumulation(CraftingRecipe recipe, List inputLi return false; } + public SmithingRecipe matchSmithingRecipe(Item equipment, Item ingredient) { + return this.smithingRecipes.get(getContainerHash(ingredient.getId(), equipment.getId())); + } + public void registerMultiRecipe(MultiRecipe recipe) { this.multiRecipes.put(recipe.getId(), recipe); } + public void registerCampfireRecipe(CampfireRecipe recipe) { + this.campfireRecipes.put(getItemHash(recipe.getInput()), recipe); + } + + public void registerSmithingRecipe(SmithingRecipe recipe) { + Item input = recipe.getIngredient(); + Item potion = recipe.getEquipment(); + this.smithingRecipes.put(getContainerHash(input.getId(), potion.getId()), recipe); + } + public static class Entry { final int resultItemId; final int resultMeta; diff --git a/src/main/java/cn/nukkit/inventory/CraftingRecipe.java b/src/main/java/cn/nukkit/inventory/CraftingRecipe.java index 2c21e8fbccf..550c1010a9e 100644 --- a/src/main/java/cn/nukkit/inventory/CraftingRecipe.java +++ b/src/main/java/cn/nukkit/inventory/CraftingRecipe.java @@ -28,7 +28,7 @@ public interface CraftingRecipe extends Recipe { * Returns whether the specified list of crafting grid inputs and outputs matches this recipe. Outputs DO NOT * include the primary result item. * - * @param inputList list of items taken from the crafting grid + * @param inputList list of items taken from the crafting grid * @param extraOutputList list of items put back into the crafting grid (secondary results) * @return bool */ diff --git a/src/main/java/cn/nukkit/inventory/CustomInventory.java b/src/main/java/cn/nukkit/inventory/CustomInventory.java index 2d93e24bdc9..0caed17a772 100644 --- a/src/main/java/cn/nukkit/inventory/CustomInventory.java +++ b/src/main/java/cn/nukkit/inventory/CustomInventory.java @@ -5,10 +5,11 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class CustomInventory extends ContainerInventory { + public CustomInventory(InventoryHolder holder, InventoryType type) { super(holder, type); } diff --git a/src/main/java/cn/nukkit/inventory/DispenserInventory.java b/src/main/java/cn/nukkit/inventory/DispenserInventory.java new file mode 100644 index 00000000000..5e427a89ea4 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/DispenserInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityDispenser; + +public class DispenserInventory extends ContainerInventory { + + public DispenserInventory(BlockEntityDispenser dispenser) { + super(dispenser, InventoryType.DISPENSER); + } + + @Override + public BlockEntityDispenser getHolder() { + return (BlockEntityDispenser) super.getHolder(); + } +} diff --git a/src/main/java/cn/nukkit/inventory/DoubleChestInventory.java b/src/main/java/cn/nukkit/inventory/DoubleChestInventory.java index c2f26d356c0..a31d25ca9a7 100644 --- a/src/main/java/cn/nukkit/inventory/DoubleChestInventory.java +++ b/src/main/java/cn/nukkit/inventory/DoubleChestInventory.java @@ -5,14 +5,14 @@ import cn.nukkit.item.Item; import cn.nukkit.level.Level; import cn.nukkit.network.protocol.BlockEventPacket; -import cn.nukkit.network.protocol.InventorySlotPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.network.protocol.InventorySlotPacket; import java.util.HashMap; import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class DoubleChestInventory extends ContainerInventory implements InventoryHolder { @@ -62,6 +62,11 @@ public Item getItem(int index) { return index < this.left.getSize() ? this.left.getItem(index) : this.right.getItem(index - this.right.getSize()); } + @Override + public Item getItemFast(int index) { + return index < this.left.getSize() ? this.left.getItemFast(index) : this.right.getItemFast(index - this.right.getSize()); + } + @Override public boolean setItem(int index, Item item, boolean send) { return index < this.left.getSize() ? this.left.setItem(index, item, send) : this.right.setItem(index - this.right.getSize(), item, send); @@ -115,28 +120,24 @@ public void onOpen(Player who) { this.right.viewers.add(who); if (this.getViewers().size() == 1) { - BlockEventPacket pk1 = new BlockEventPacket(); - pk1.x = (int) this.left.getHolder().getX(); - pk1.y = (int) this.left.getHolder().getY(); - pk1.z = (int) this.left.getHolder().getZ(); - pk1.case1 = 1; - pk1.case2 = 2; Level level = this.left.getHolder().getLevel(); if (level != null) { + BlockEventPacket pk1 = new BlockEventPacket(); + pk1.x = (int) this.left.getHolder().getX(); + pk1.y = (int) this.left.getHolder().getY(); + pk1.z = (int) this.left.getHolder().getZ(); + pk1.case1 = 1; + pk1.case2 = 2; + + BlockEventPacket pk2 = new BlockEventPacket(); + pk2.x = (int) this.right.getHolder().getX(); + pk2.y = (int) this.right.getHolder().getY(); + pk2.z = (int) this.right.getHolder().getZ(); + pk2.case1 = 1; + pk2.case2 = 2; + level.addLevelSoundEvent(this.left.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_CHEST_OPEN); level.addChunkPacket((int) this.left.getHolder().getX() >> 4, (int) this.left.getHolder().getZ() >> 4, pk1); - } - - BlockEventPacket pk2 = new BlockEventPacket(); - pk2.x = (int) this.right.getHolder().getX(); - pk2.y = (int) this.right.getHolder().getY(); - pk2.z = (int) this.right.getHolder().getZ(); - pk2.case1 = 1; - pk2.case2 = 2; - - level = this.right.getHolder().getLevel(); - if (level != null) { - level.addLevelSoundEvent(this.right.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_CHEST_OPEN); level.addChunkPacket((int) this.right.getHolder().getX() >> 4, (int) this.right.getHolder().getZ() >> 4, pk2); } } @@ -145,29 +146,24 @@ public void onOpen(Player who) { @Override public void onClose(Player who) { if (this.getViewers().size() == 1) { - BlockEventPacket pk1 = new BlockEventPacket(); - pk1.x = (int) this.right.getHolder().getX(); - pk1.y = (int) this.right.getHolder().getY(); - pk1.z = (int) this.right.getHolder().getZ(); - pk1.case1 = 1; - pk1.case2 = 0; - Level level = this.right.getHolder().getLevel(); if (level != null) { - level.addLevelSoundEvent(this.right.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_CHEST_CLOSED); - level.addChunkPacket((int) this.right.getHolder().getX() >> 4, (int) this.right.getHolder().getZ() >> 4, pk1); - } - - BlockEventPacket pk2 = new BlockEventPacket(); - pk2.x = (int) this.left.getHolder().getX(); - pk2.y = (int) this.left.getHolder().getY(); - pk2.z = (int) this.left.getHolder().getZ(); - pk2.case1 = 1; - pk2.case2 = 0; + BlockEventPacket pk1 = new BlockEventPacket(); + pk1.x = (int) this.right.getHolder().getX(); + pk1.y = (int) this.right.getHolder().getY(); + pk1.z = (int) this.right.getHolder().getZ(); + pk1.case1 = 1; + pk1.case2 = 0; + + BlockEventPacket pk2 = new BlockEventPacket(); + pk2.x = (int) this.left.getHolder().getX(); + pk2.y = (int) this.left.getHolder().getY(); + pk2.z = (int) this.left.getHolder().getZ(); + pk2.case1 = 1; + pk2.case2 = 0; - level = this.left.getHolder().getLevel(); - if (level != null) { level.addLevelSoundEvent(this.left.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_CHEST_CLOSED); + level.addChunkPacket((int) this.right.getHolder().getX() >> 4, (int) this.right.getHolder().getZ() >> 4, pk1); level.addChunkPacket((int) this.left.getHolder().getX() >> 4, (int) this.left.getHolder().getZ() >> 4, pk2); } } diff --git a/src/main/java/cn/nukkit/inventory/DropperInventory.java b/src/main/java/cn/nukkit/inventory/DropperInventory.java new file mode 100644 index 00000000000..ce7dcb74bc4 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/DropperInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntityDropper; + +public class DropperInventory extends ContainerInventory { + + public DropperInventory(BlockEntityDropper dropper) { + super(dropper, InventoryType.DROPPER); + } + + @Override + public BlockEntityDropper getHolder() { + return (BlockEntityDropper) super.getHolder(); + } +} diff --git a/src/main/java/cn/nukkit/inventory/EnchantInventory.java b/src/main/java/cn/nukkit/inventory/EnchantInventory.java index 16cdf629830..0f08ed569d0 100644 --- a/src/main/java/cn/nukkit/inventory/EnchantInventory.java +++ b/src/main/java/cn/nukkit/inventory/EnchantInventory.java @@ -4,9 +4,8 @@ import cn.nukkit.item.Item; import cn.nukkit.level.Position; - /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantInventory extends FakeBlockUIComponent { @@ -24,19 +23,6 @@ public void onOpen(Player who) { who.craftingType = Player.CRAFTING_ENCHANT; } - @Override - public void onClose(Player who) { - super.onClose(who); - if (this.getViewers().size() == 0) { - for (int i = 0; i < 2; ++i) { - who.getInventory().addItem(this.getItem(i)); - this.clear(i); - } - } - who.craftingType = Player.CRAFTING_SMALL; - who.resetCraftingGridType(); - } - public Item getInputSlot() { return this.getItem(0); } diff --git a/src/main/java/cn/nukkit/inventory/EntityArmorInventory.java b/src/main/java/cn/nukkit/inventory/EntityArmorInventory.java new file mode 100644 index 00000000000..9a856f6907d --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/EntityArmorInventory.java @@ -0,0 +1,137 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.entity.item.EntityArmorStand; +import cn.nukkit.item.Item; +import cn.nukkit.network.protocol.InventoryContentPacket; +import cn.nukkit.network.protocol.InventorySlotPacket; +import cn.nukkit.network.protocol.MobArmorEquipmentPacket; + +import java.util.HashSet; +import java.util.Set; + +public class EntityArmorInventory extends BaseInventory { + + private final EntityArmorStand entityLiving; + + private final Set viewers = new HashSet<>(); + + public static final int SLOT_HEAD = 0; + public static final int SLOT_CHEST = 1; + public static final int SLOT_LEGS = 2; + public static final int SLOT_FEET = 3; + + public EntityArmorInventory(EntityArmorStand entity) { + super(entity, InventoryType.ENTITY_ARMOR); + this.entityLiving = entity; + } + + @Override + public InventoryHolder getHolder() { + return this.holder; + } + + @Override + public String getName() { + return "Entity Armor"; + } + + @Override + public int getSize() { + return 4; + } + + public Item getHelmet() { + return this.getItem(SLOT_HEAD); + } + + public Item getChestplate() { + return this.getItem(SLOT_CHEST); + } + + public Item getLeggings() { + return this.getItem(SLOT_LEGS); + } + + public Item getBoots() { + return this.getItem(SLOT_FEET); + } + + public void setHelmet(Item item) { + this.setItem(SLOT_CHEST, item); + } + + public void setChestplate(Item item) { + this.setItem(SLOT_CHEST, item); + } + + public void setLeggings(Item item) { + this.setItem(SLOT_LEGS, item); + } + + public void setBoots(Item item) { + this.setItem(SLOT_FEET, item); + } + + @Override + public void sendSlot(int index, Player... players) { + for (Player player : players) { + this.sendSlot(index, player); + } + } + + @Override + public void sendSlot(int index, Player player) { + MobArmorEquipmentPacket mobArmorEquipmentPacket = new MobArmorEquipmentPacket(); + mobArmorEquipmentPacket.eid = this.entityLiving.getId(); + mobArmorEquipmentPacket.slots = new Item[]{this.getHelmet(), this.getChestplate(), this.getLeggings(), this.getBoots()}; + + if (player == this.holder) { + InventorySlotPacket inventorySlotPacket = new InventorySlotPacket(); + inventorySlotPacket.inventoryId = player.getWindowId(this); + inventorySlotPacket.slot = index; + inventorySlotPacket.item = this.getItem(index); + player.dataPacket(inventorySlotPacket); + } else { + player.dataPacket(mobArmorEquipmentPacket); + } + } + + @Override + public void sendContents(Player... players) { + for (Player player : players) { + this.sendContents(player); + } + } + + @Override + public void sendContents(Player player) { + MobArmorEquipmentPacket mobArmorEquipmentPacket = new MobArmorEquipmentPacket(); + mobArmorEquipmentPacket.eid = this.entityLiving.getId(); + mobArmorEquipmentPacket.slots = new Item[]{this.getHelmet(), this.getChestplate(), this.getLeggings(), this.getBoots()}; + + if (player == this.holder) { + InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); + inventoryContentPacket.inventoryId = player.getWindowId(this); + inventoryContentPacket.slots = new Item[]{this.getHelmet(), this.getChestplate(), this.getLeggings(), this.getBoots()}; + player.dataPacket(inventoryContentPacket); + } else { + player.dataPacket(mobArmorEquipmentPacket); + } + } + + @Override + public void onOpen(Player who) { + this.viewers.add(who); + } + + @Override + public void onClose(Player who) { + this.viewers.remove(who); + } + + @Override + public Set getViewers() { + return this.viewers; + } +} diff --git a/src/main/java/cn/nukkit/inventory/EntityEquipmentInventory.java b/src/main/java/cn/nukkit/inventory/EntityEquipmentInventory.java new file mode 100644 index 00000000000..3109fe0b1e7 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/EntityEquipmentInventory.java @@ -0,0 +1,80 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.entity.item.EntityArmorStand; +import cn.nukkit.item.Item; +import cn.nukkit.network.protocol.MobEquipmentPacket; + +public class EntityEquipmentInventory extends BaseInventory { + + private final EntityArmorStand entityLiving; + + private static final int MAINHAND = 0; + private static final int OFFHAND = 1; + + public EntityEquipmentInventory(EntityArmorStand entity) { + super(entity, InventoryType.ENTITY_EQUIPMENT); + this.entityLiving = entity; + } + + + @Override + public String getName() { + return "Entity Equipment"; + } + + @Override + public int getSize() { + return 2; + } + + @Override + public InventoryHolder getHolder() { + return this.holder; + } + + @Override + public void sendSlot(int index, Player... players) { + for (Player player : players) { + this.sendSlot(index, player); + } + } + + @Override + public void sendSlot(int index, Player player) { + MobEquipmentPacket mobEquipmentPacket = new MobEquipmentPacket(); + mobEquipmentPacket.eid = this.entityLiving.getId(); + mobEquipmentPacket.inventorySlot = mobEquipmentPacket.hotbarSlot = index; + mobEquipmentPacket.item = this.getItem(index); + player.dataPacket(mobEquipmentPacket); + } + + public Item getItemInHand() { + return this.getItem(MAINHAND); + } + + public Item getOffHandItem() { + return this.getItem(OFFHAND); + } + + public boolean setItemInHand(Item item, boolean send) { + return this.setItem(MAINHAND, item, send); + } + + public boolean setOffhandItem(Item item, boolean send) { + return this.setItem(OFFHAND, item, send); + } + + @Override + public void sendContents(Player target) { + this.sendSlot(MAINHAND, target); + this.sendSlot(OFFHAND, target); + } + + @Override + public void sendContents(Player... target) { + for (Player player : target) { + this.sendContents(player); + } + } +} diff --git a/src/main/java/cn/nukkit/inventory/FakeBlockMenu.java b/src/main/java/cn/nukkit/inventory/FakeBlockMenu.java index fab31b5e7fc..574c75e5f52 100644 --- a/src/main/java/cn/nukkit/inventory/FakeBlockMenu.java +++ b/src/main/java/cn/nukkit/inventory/FakeBlockMenu.java @@ -3,7 +3,7 @@ import cn.nukkit.level.Position; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FakeBlockMenu extends Position implements InventoryHolder { diff --git a/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java b/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java index 0c802b22460..baccee9334c 100644 --- a/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java +++ b/src/main/java/cn/nukkit/inventory/FakeBlockUIComponent.java @@ -9,6 +9,7 @@ import cn.nukkit.network.protocol.ContainerOpenPacket; public class FakeBlockUIComponent extends PlayerUIComponent { + private final InventoryType type; FakeBlockUIComponent(PlayerUIInventory playerUI, InventoryType type, int offset, Position position) { @@ -35,14 +36,6 @@ public boolean open(Player who) { return true; } - @Override - public void close(Player who) { - InventoryCloseEvent ev = new InventoryCloseEvent(this, who); - who.getServer().getPluginManager().callEvent(ev); - - this.onClose(who); - } - @Override public void onOpen(Player who) { super.onOpen(who); @@ -65,10 +58,20 @@ public void onOpen(Player who) { @Override public void onClose(Player who) { + who.resetCraftingGridType(); // Handles moving of UI inventory contents + ContainerClosePacket pk = new ContainerClosePacket(); pk.windowId = who.getWindowId(this); pk.wasServerInitiated = who.getClosingWindowId() != pk.windowId; who.dataPacket(pk); + super.onClose(who); } + + @Override + public void close(Player who) { + InventoryCloseEvent ev = new InventoryCloseEvent(this, who); + who.getServer().getPluginManager().callEvent(ev); + this.onClose(who); + } } diff --git a/src/main/java/cn/nukkit/inventory/Fuel.java b/src/main/java/cn/nukkit/inventory/Fuel.java index 87bffb2c581..e974e033d07 100644 --- a/src/main/java/cn/nukkit/inventory/Fuel.java +++ b/src/main/java/cn/nukkit/inventory/Fuel.java @@ -1,21 +1,23 @@ package cn.nukkit.inventory; +import cn.nukkit.block.BlockID; import cn.nukkit.item.Item; import java.util.Map; import java.util.TreeMap; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Fuel { + public static final Map duration = new TreeMap<>(); static { duration.put(Item.COAL, (short) 1600); duration.put(Item.COAL_BLOCK, (short) 16000); - duration.put(Item.TRUNK, (short) 300); + duration.put(Item.WOOD, (short) 300); duration.put(Item.WOODEN_PLANKS, (short) 300); duration.put(Item.SAPLING, (short) 100); duration.put(Item.WOODEN_AXE, (short) 200); @@ -55,6 +57,13 @@ public abstract class Fuel { duration.put(Item.WOOD_SLAB, (short) 300); duration.put(Item.DOUBLE_WOOD_SLAB, (short) 300); duration.put(Item.BOAT, (short) 1200); + duration.put(Item.ACACIA_CHEST_BOAT, (short) 1200); + duration.put(Item.BIRCH_CHEST_BOAT, (short) 1200); + duration.put(Item.JUNGLE_CHEST_BOAT, (short) 1200); + duration.put(Item.DARK_OAK_CHEST_BOAT, (short) 1200); + duration.put(Item.MANGROVE_CHEST_BOAT, (short) 1200); + duration.put(Item.OAK_CHEST_BOAT, (short) 1200); + duration.put(Item.SPRUCE_CHEST_BOAT, (short) 1200); duration.put(Item.BLAZE_ROD, (short) 2400); duration.put(Item.BROWN_MUSHROOM_BLOCK, (short) 300); duration.put(Item.RED_MUSHROOM_BLOCK, (short) 300); @@ -67,7 +76,51 @@ public abstract class Fuel { duration.put(Item.ACACIA_DOOR, (short) 200); duration.put(Item.DARK_OAK_DOOR, (short) 200); duration.put(Item.BANNER, (short) 300); + duration.put(Item.CROSSBOW, (short) 200); duration.put(Item.DEAD_BUSH, (short) 100); duration.put(Item.SIGN, (short) 200); + duration.put(Item.ACACIA_SIGN, (short) 200); + duration.put(Item.BIRCH_SIGN, (short) 200); + duration.put(Item.SPRUCE_SIGN, (short) 200); + duration.put(Item.DARKOAK_SIGN, (short) 200); + duration.put(Item.JUNGLE_SIGN, (short) 200); + + /* New blocks use negative IDs in their item form */ + duration.put(255 - BlockID.DRIED_KELP_BLOCK, (short) 4000); + duration.put(255 - BlockID.SCAFFOLDING, (short) 50); + duration.put(255 - BlockID.BEE_NEST, (short) 300); + duration.put(255 - BlockID.BEEHIVE, (short) 300); + duration.put(255 - BlockID.BAMBOO, (short) 50); + duration.put(255 - BlockID.BARREL, (short) 300); + duration.put(255 - BlockID.LECTERN, (short) 300); + duration.put(255 - BlockID.CARTOGRAPHY_TABLE, (short) 300); + duration.put(255 - BlockID.FLETCHING_TABLE, (short) 300); + duration.put(255 - BlockID.SMITHING_TABLE, (short) 300); + duration.put(255 - BlockID.COMPOSTER, (short) 300); + duration.put(255 - BlockID.LOOM, (short) 300); + duration.put(255 - BlockID.WOOD_BARK, (short) 300); + duration.put(255 - BlockID.STRIPPED_OAK_LOG, (short) 300); + duration.put(255 - BlockID.STRIPPED_SPRUCE_LOG, (short) 300); + duration.put(255 - BlockID.STRIPPED_BIRCH_LOG, (short) 300); + duration.put(255 - BlockID.STRIPPED_JUNGLE_LOG, (short) 300); + duration.put(255 - BlockID.STRIPPED_ACACIA_LOG, (short) 300); + duration.put(255 - BlockID.STRIPPED_DARK_OAK_LOG, (short) 300); + duration.put(255 - BlockID.SPRUCE_TRAPDOOR, (short) 300); + duration.put(255 - BlockID.BIRCH_TRAPDOOR, (short) 300); + duration.put(255 - BlockID.JUNGLE_TRAPDOOR, (short) 300); + duration.put(255 - BlockID.ACACIA_TRAPDOOR, (short) 300); + duration.put(255 - BlockID.DARK_OAK_TRAPDOOR, (short) 300); + duration.put(255 - BlockID.SPRUCE_BUTTON, (short) 300); + duration.put(255 - BlockID.BIRCH_BUTTON, (short) 300); + duration.put(255 - BlockID.JUNGLE_BUTTON, (short) 300); + duration.put(255 - BlockID.ACACIA_BUTTON, (short) 300); + duration.put(255 - BlockID.DARK_OAK_BUTTON, (short) 300); + duration.put(255 - BlockID.SPRUCE_PRESSURE_PLATE, (short) 300); + duration.put(255 - BlockID.BIRCH_PRESSURE_PLATE, (short) 300); + duration.put(255 - BlockID.JUNGLE_PRESSURE_PLATE, (short) 300); + duration.put(255 - BlockID.ACACIA_PRESSURE_PLATE, (short) 300); + duration.put(255 - BlockID.DARK_OAK_PRESSURE_PLATE, (short) 300); + duration.put(255 - BlockID.AZALEA, (short) 100); + duration.put(255 - BlockID.FLOWERING_AZALEA, (short) 100); } } diff --git a/src/main/java/cn/nukkit/inventory/FurnaceInventory.java b/src/main/java/cn/nukkit/inventory/FurnaceInventory.java index 34cef286466..7aac5a6aa03 100644 --- a/src/main/java/cn/nukkit/inventory/FurnaceInventory.java +++ b/src/main/java/cn/nukkit/inventory/FurnaceInventory.java @@ -4,7 +4,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FurnaceInventory extends ContainerInventory { @@ -13,6 +13,10 @@ public FurnaceInventory(BlockEntityFurnace furnace) { super(furnace, InventoryType.FURNACE); } + public FurnaceInventory(BlockEntityFurnace furnace, InventoryType type) { + super(furnace, type); // For blast furnace + } + @Override public BlockEntityFurnace getHolder() { return (BlockEntityFurnace) this.holder; diff --git a/src/main/java/cn/nukkit/inventory/FurnaceRecipe.java b/src/main/java/cn/nukkit/inventory/FurnaceRecipe.java index bab63a69bbe..a0e1e2e2d16 100644 --- a/src/main/java/cn/nukkit/inventory/FurnaceRecipe.java +++ b/src/main/java/cn/nukkit/inventory/FurnaceRecipe.java @@ -3,7 +3,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FurnaceRecipe implements Recipe { diff --git a/src/main/java/cn/nukkit/inventory/Inventory.java b/src/main/java/cn/nukkit/inventory/Inventory.java index 9b62bfd3bf8..90116705985 100644 --- a/src/main/java/cn/nukkit/inventory/Inventory.java +++ b/src/main/java/cn/nukkit/inventory/Inventory.java @@ -8,7 +8,7 @@ import java.util.Set; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface Inventory { @@ -27,6 +27,10 @@ public interface Inventory { Item getItem(int index); + default Item getItemFast(int index) { + return getItem(index); + } + default boolean setItem(int index, Item item) { return setItem(index, item, true); } @@ -37,6 +41,10 @@ default boolean setItem(int index, Item item) { boolean canAddItem(Item item); + default boolean allowedToAdd(int itemId) { + return true; + } + Item[] removeItem(Item... slots); Map getContents(); @@ -68,7 +76,7 @@ default int first(Item item) { int firstEmpty(Item item); void decreaseCount(int slot); - + void remove(Item item); default boolean clear(int index) { diff --git a/src/main/java/cn/nukkit/inventory/InventoryHolder.java b/src/main/java/cn/nukkit/inventory/InventoryHolder.java index 85897e18a50..a55aa4efb07 100644 --- a/src/main/java/cn/nukkit/inventory/InventoryHolder.java +++ b/src/main/java/cn/nukkit/inventory/InventoryHolder.java @@ -1,7 +1,7 @@ package cn.nukkit.inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface InventoryHolder { diff --git a/src/main/java/cn/nukkit/inventory/InventoryType.java b/src/main/java/cn/nukkit/inventory/InventoryType.java index 1b16ae7b9a6..bdad1d1437d 100644 --- a/src/main/java/cn/nukkit/inventory/InventoryType.java +++ b/src/main/java/cn/nukkit/inventory/InventoryType.java @@ -1,29 +1,42 @@ package cn.nukkit.inventory; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public enum InventoryType { - CHEST(27, "Chest", 0), - ENDER_CHEST(27, "Ender Chest", 0), - DOUBLE_CHEST(27 + 27, "Double Chest", 0), + + CHEST(27, "Chest", 0), //27 CONTAINER + ENDER_CHEST(27, "Ender Chest", 0), //27 CONTAINER + DOUBLE_CHEST(54, "Double Chest", 0), //27 + 27 CONTAINER PLAYER(40, "Player", -1), //36 CONTAINER, 4 ARMOR - FURNACE(3, "Furnace", 2), - CRAFTING(5, "Crafting", 1), //4 CRAFTING slots, 1 RESULT - WORKBENCH(10, "Crafting", 1), //9 CRAFTING slots, 1 RESULT - BREWING_STAND(5, "Brewing", 4), //1 INPUT, 3 POTION, 1 fuel + FURNACE(3, "Furnace", 2), //1 INPUT/OUTPUT, 1 FUEL + BLAST_FURNACE(3, "Blast Furnace", 27), //1 INPUT/OUTPUT, 1 FUEL + SMOKER(3, "Smoker", 28), //1 INPUT/OUTPUT, 1 FUEL + CAMPFIRE(4, "Campfire", -1), //4 CONTAINER + CRAFTING(5, "Crafting", 1), //4 CRAFTING SLOTS, 1 RESULT + WORKBENCH(10, "Crafting", 1), //9 CRAFTING SLOTS, 1 RESULT + BREWING_STAND(5, "Brewing", 4), //1 INPUT, 3 POTION, 1 FUEL ANVIL(3, "Anvil", 5), //2 INPUT, 1 OUTPUT - ENCHANT_TABLE(2, "Enchant", 3), //1 INPUT/OUTPUT, 1 LAPIS - DISPENSER(0, "Dispenser", 6), //9 CONTAINER + ENCHANT_TABLE(2, "Enchantment Table", 3), //1 INPUT/OUTPUT, 1 LAPIS + DISPENSER(9, "Dispenser", 6), //9 CONTAINER DROPPER(9, "Dropper", 7), //9 CONTAINER HOPPER(5, "Hopper", 8), //5 CONTAINER - UI(1, "UI", -1), - SHULKER_BOX(27, "Shulker Box", 0), - BEACON(1, "Beacon", 13), - OFFHAND(1, "Offhand", -1), - MINECART_CHEST(27, "Minecart with Chest", 0), - MINECART_HOPPER(5, "Minecart with Hopper", 8); + UI(1, "UI", -1), //1 CONTAINER + SHULKER_BOX(27, "Shulker Box", 0), //27 CONTAINER + BEACON(1, "Beacon", 13), //1 INPUT + ENTITY_ARMOR(4, "Entity Armor", -1), //4 ARMOR + ENTITY_EQUIPMENT(36, "Entity Equipment", -1), //36 CONTAINER + MINECART_CHEST(27, "Minecart with Chest", 0), //27 CONTAINER + MINECART_HOPPER(5, "Minecart with Hopper", 8), //5 CONTAINER + OFFHAND(1, "Offhand", -1), //1 CONTAINER + TRADING(3, "Villager Trade", 15), //3 CONTAINER + BARREL(27, "Barrel", 0), //27 CONTAINER + LOOM(4, "Loom", 24), //4 CONTAINER + CHEST_BOAT(27, "Boat with Chest", 0), //27 CONTAINER + DONKEY(15, "Donkey", 12), //15 CONTAINER + SMITHING_TABLE(2, "Smithing Table", 33), + LECTERN(0, "Lectern", 25); private final int size; private final String title; diff --git a/src/main/java/cn/nukkit/inventory/LoomInventory.java b/src/main/java/cn/nukkit/inventory/LoomInventory.java new file mode 100644 index 00000000000..e67c5866957 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/LoomInventory.java @@ -0,0 +1,36 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; +import cn.nukkit.level.Position; + +public class LoomInventory extends FakeBlockUIComponent { + + public static final int OFFSET = 9; + + public LoomInventory(PlayerUIInventory playerUI, Position position) { + super(playerUI, InventoryType.LOOM, OFFSET, position); + } + + @Override + public void onOpen(Player who) { + super.onOpen(who); + who.craftingType = Player.CRAFTING_LOOM; + } + + public Item getFirstItem() { + return getItem(0); + } + + public Item getSecondItem() { + return getItem(1); + } + + public void setFirstItem(Item item) { + this.setItem(0, item); + } + + public void setSecondItem(Item item) { + this.setItem(1, item); + } +} diff --git a/src/main/java/cn/nukkit/inventory/MultiRecipe.java b/src/main/java/cn/nukkit/inventory/MultiRecipe.java index adf791436b9..dd9834e05a4 100644 --- a/src/main/java/cn/nukkit/inventory/MultiRecipe.java +++ b/src/main/java/cn/nukkit/inventory/MultiRecipe.java @@ -8,8 +8,11 @@ public class MultiRecipe implements Recipe { private final UUID id; + private final int networkId; + public MultiRecipe(UUID id) { this.id = id; + this.networkId = ++CraftingManager.NEXT_NETWORK_ID; } @Override @@ -30,4 +33,8 @@ public RecipeType getType() { public UUID getId() { return this.id; } + + public int getNetworkId() { + return this.networkId; + } } diff --git a/src/main/java/cn/nukkit/inventory/PlayerCursorInventory.java b/src/main/java/cn/nukkit/inventory/PlayerCursorInventory.java index 97643f3c8c5..38d85d98751 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerCursorInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerCursorInventory.java @@ -6,6 +6,7 @@ * @author CreeperFace */ public class PlayerCursorInventory extends PlayerUIComponent { + private final PlayerUIInventory playerUI; PlayerCursorInventory(PlayerUIInventory playerUI) { diff --git a/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java b/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java index faf894b73f6..e75585e2f66 100755 --- a/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerEnderChestInventory.java @@ -4,7 +4,6 @@ import cn.nukkit.block.BlockEnderChest; import cn.nukkit.entity.EntityHuman; import cn.nukkit.entity.EntityHumanType; -import cn.nukkit.level.Level; import cn.nukkit.network.protocol.BlockEventPacket; import cn.nukkit.network.protocol.ContainerClosePacket; import cn.nukkit.network.protocol.ContainerOpenPacket; @@ -51,10 +50,9 @@ public void onOpen(Player who) { blockEventPacket.case1 = 1; blockEventPacket.case2 = 2; - Level level = this.getHolder().getLevel(); - if (level != null) { - level.addLevelSoundEvent(this.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_ENDERCHEST_OPEN); - level.addChunkPacket((int) this.getHolder().getX() >> 4, (int) this.getHolder().getZ() >> 4, blockEventPacket); + if (chest.level != null && chest.level == who.level) { + chest.level.addLevelSoundEvent(chest.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_ENDERCHEST_OPEN); + chest.level.addChunkPacket((int) chest.getX() >> 4, (int) chest.getZ() >> 4, blockEventPacket); } } } @@ -76,15 +74,14 @@ public void onClose(Player who) { blockEventPacket.case1 = 1; blockEventPacket.case2 = 0; - Level level = this.getHolder().getLevel(); - if (level != null) { - level.addLevelSoundEvent(this.getHolder().add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_ENDERCHEST_CLOSED); - level.addChunkPacket((int) this.getHolder().getX() >> 4, (int) this.getHolder().getZ() >> 4, blockEventPacket); + if (chest.level != null && chest.level == who.level) { + chest.level.addLevelSoundEvent(chest.add(0.5, 0.5, 0.5), LevelSoundEventPacket.SOUND_ENDERCHEST_CLOSED); + chest.level.addChunkPacket((int) chest.getX() >> 4, (int) chest.getZ() >> 4, blockEventPacket); } - - who.setViewingEnderChest(null); } + who.setViewingEnderChest(null); + super.onClose(who); } } diff --git a/src/main/java/cn/nukkit/inventory/PlayerInventory.java b/src/main/java/cn/nukkit/inventory/PlayerInventory.java index 6febe54ea13..2827827bd0a 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerInventory.java @@ -17,22 +17,15 @@ import java.util.Collection; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PlayerInventory extends BaseInventory { protected int itemInHandIndex = 0; - private int[] hotbar; public PlayerInventory(EntityHumanType player) { super(player, InventoryType.PLAYER); - this.hotbar = new int[this.getHotbarSize()]; - - for (int i = 0; i < this.hotbar.length; i++) { - this.hotbar[i] = i; - } - } @Override @@ -55,7 +48,10 @@ public void setSize(int size) { */ public boolean equipItem(int slot) { if (!isHotbarSlot(slot)) { - this.sendContents((Player) this.getHolder()); + Server.getInstance().getLogger().debug(this.getHolder().getName() + ": equipItem: slot is not a hotbar slot: " + slot); + /*if (this.getHolder() instanceof Player) { + this.sendContents((Player) this.getHolder()); + }*/ return false; } @@ -81,17 +77,14 @@ public boolean equipItem(int slot) { } private boolean isHotbarSlot(int slot) { - return slot >= 0 && slot <= this.getHotbarSize(); + return slot >= 0 && slot < this.getHotbarSize(); } - @Deprecated public int getHotbarSlotIndex(int index) { return index; } - @Deprecated public void setHotbarSlotIndex(int index, int slot) { - } public int getHeldItemIndex() { @@ -115,7 +108,7 @@ public void setHeldItemIndex(int index, boolean send) { } public Item getItemInHand() { - Item item = this.getItem(this.getHeldItemIndex()); + Item item = this.getItem(this.itemInHandIndex); if (item != null) { return item; } else { @@ -123,11 +116,19 @@ public Item getItemInHand() { } } + public Item getItemInHandFast() { + Item item = this.getItemFast(this.getHeldItemIndex()); + if (item != null) { + return item; + } else { + return air; + } + } + public boolean setItemInHand(Item item) { - return this.setItem(this.getHeldItemIndex(), item); + return this.setItem(this.itemInHandIndex, item); } - @Deprecated public int getHeldItemSlot() { return this.itemInHandIndex; } @@ -147,20 +148,24 @@ public void setHeldItemSlot(int slot) { } public void sendHeldItem(Player... players) { - Item item = this.getItemInHand(); - - MobEquipmentPacket pk = new MobEquipmentPacket(); - pk.item = item; - pk.inventorySlot = pk.hotbarSlot = this.getHeldItemIndex(); + Item item = this.getItemInHandFast(); for (Player player : players) { - pk.eid = this.getHolder().getId(); if (player.equals(this.getHolder())) { - pk.eid = player.getId(); - this.sendSlot(this.getHeldItemIndex(), player); - } + this.sendSlot(this.itemInHandIndex, player); - player.dataPacket(pk); + MobEquipmentPacket pk = new MobEquipmentPacket(); + pk.item = item; + pk.inventorySlot = pk.hotbarSlot = this.itemInHandIndex; + pk.eid = this.getHolder().getId(); + player.dataPacket(pk); + } else { + MobEquipmentPacket pk = new MobEquipmentPacket(); + pk.item = item; + pk.inventorySlot = pk.hotbarSlot = this.itemInHandIndex; + pk.eid = this.getHolder().getId(); + player.dataPacket(pk); + } } } @@ -203,18 +208,34 @@ public Item getHelmet() { return this.getItem(this.getSize()); } + public Item getHelmetFast() { + return this.getItemFast(36); + } + public Item getChestplate() { return this.getItem(this.getSize() + 1); } + public Item getChestplateFast() { + return this.getItemFast(37); + } + public Item getLeggings() { return this.getItem(this.getSize() + 2); } + public Item getLeggingsFast() { + return this.getItemFast(38); + } + public Item getBoots() { return this.getItem(this.getSize() + 3); } + public Item getBootsFast() { + return this.getItemFast(39); + } + public boolean setHelmet(Item helmet) { return this.setItem(this.getSize(), helmet); } @@ -233,18 +254,18 @@ public boolean setBoots(Item boots) { @Override public boolean setItem(int index, Item item) { - return setItem(index, item, true, false); + return setItem(index, item, true); } - private boolean setItem(int index, Item item, boolean send, boolean ignoreArmorEvents) { + @Override + public boolean setItem(int index, Item item, boolean send) { if (index < 0 || index >= this.size) { return false; } else if (item.getId() == 0 || item.getCount() <= 0) { - return this.clear(index); + return this.clear(index, send); } - //Armor change - if (!ignoreArmorEvents && index >= this.getSize()) { + if (index >= this.getSize()) { // Armor change EntityArmorChangeEvent ev = new EntityArmorChangeEvent(this.getHolder(), this.getItem(index), item, index); Server.getInstance().getPluginManager().callEvent(ev); if (ev.isCancelled() && this.getHolder() != null) { @@ -261,6 +282,7 @@ private boolean setItem(int index, Item item, boolean send, boolean ignoreArmorE } item = ev.getNewItem(); } + Item old = this.getItem(index); this.slots.put(index, item.clone()); this.onSlotChange(index, old, send); @@ -335,18 +357,16 @@ public void sendArmorContents(Player player) { public void sendArmorContents(Player[] players) { Item[] armor = this.getArmorContents(); - MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); - pk.eid = this.getHolder().getId(); - pk.slots = armor; - pk.tryEncode(); - for (Player player : players) { if (player.equals(this.getHolder())) { - InventoryContentPacket pk2 = new InventoryContentPacket(); - pk2.inventoryId = InventoryContentPacket.SPECIAL_ARMOR; - pk2.slots = armor; - player.dataPacket(pk2); + InventoryContentPacket pk = new InventoryContentPacket(); + pk.inventoryId = InventoryContentPacket.SPECIAL_ARMOR; + pk.slots = armor; + player.dataPacket(pk); } else { + MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); + pk.eid = this.getHolder().getId(); + pk.slots = armor; player.dataPacket(pk); } } @@ -383,19 +403,17 @@ public void sendArmorSlot(int index, Player player) { public void sendArmorSlot(int index, Player[] players) { Item[] armor = this.getArmorContents(); - MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); - pk.eid = this.getHolder().getId(); - pk.slots = armor; - pk.tryEncode(); - for (Player player : players) { if (player.equals(this.getHolder())) { - InventorySlotPacket pk2 = new InventorySlotPacket(); - pk2.inventoryId = InventoryContentPacket.SPECIAL_ARMOR; - pk2.slot = index - this.getSize(); - pk2.item = this.getItem(index); - player.dataPacket(pk2); + InventorySlotPacket pk = new InventorySlotPacket(); + pk.inventoryId = InventoryContentPacket.SPECIAL_ARMOR; + pk.slot = index - this.getSize(); + pk.item = this.getItem(index); + player.dataPacket(pk); } else { + MobArmorEquipmentPacket pk = new MobArmorEquipmentPacket(); + pk.eid = this.getHolder().getId(); + pk.slots = armor; player.dataPacket(pk); } } @@ -423,22 +441,14 @@ public void sendContents(Player[] players) { 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(int i = this.getSize(); i < this.getSize() + this.getHotbarSize(); ++i){ - pk.slots[i] = new ItemBlock(Block.get(BlockID.AIR)); - } - pk.slots[i] = new ItemBlock(Block.get(BlockID.AIR)); - }*/ - for (Player player : players) { int id = player.getWindowId(this); - if (id == -1 || !player.spawned) { + if (id == -1 /*|| !player.spawned*/) { if (this.getHolder() != player) this.close(player); continue; } pk.inventoryId = id; - player.dataPacket(pk.clone()); - + player.dataPacket(pk); } } @@ -456,12 +466,11 @@ public void sendSlot(int index, Collection players) { public void sendSlot(int index, Player... players) { InventorySlotPacket pk = new InventorySlotPacket(); pk.slot = index; - pk.item = this.getItem(index).clone(); + pk.item = this.getItem(index); for (Player player : players) { if (player.equals(this.getHolder())) { pk.inventoryId = ContainerIds.INVENTORY; - player.dataPacket(pk); } else { int id = player.getWindowId(this); if (id == -1) { @@ -469,8 +478,8 @@ public void sendSlot(int index, Player... players) { continue; } pk.inventoryId = id; - player.dataPacket(pk.clone()); } + player.dataPacket(pk); } } @@ -481,11 +490,7 @@ public void sendCreativeContents() { Player p = (Player) this.getHolder(); CreativeContentPacket pk = new CreativeContentPacket(); - - if (!p.isSpectator()) { //fill it for all gamemodes except spectator - pk.entries = Item.getCreativeItems().toArray(new Item[0]); - } - + pk.entries = p.isSpectator() ? new Item[0] : Item.getCreativeItems().toArray(new Item[0]); p.dataPacket(pk); } @@ -513,7 +518,7 @@ public void onClose(Player who) { pk.windowId = who.getWindowId(this); pk.wasServerInitiated = who.getClosingWindowId() != pk.windowId; who.dataPacket(pk); - // player can never stop viewing their own inventory + // Player can never stop viewing their own inventory if (who != holder) { super.onClose(who); } diff --git a/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java b/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java index 7e2d3e5ae43..d73b5dde4d8 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerOffhandInventory.java @@ -4,13 +4,21 @@ import cn.nukkit.entity.EntityHuman; import cn.nukkit.entity.EntityHumanType; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.network.protocol.InventoryContentPacket; import cn.nukkit.network.protocol.InventorySlotPacket; import cn.nukkit.network.protocol.MobEquipmentPacket; import cn.nukkit.network.protocol.types.ContainerIds; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; public class PlayerOffhandInventory extends BaseInventory { + /** + * Items that can be put to offhand inventory on Bedrock Edition + */ + private static final IntSet OFFHAND_ITEMS = new IntOpenHashSet(new int[]{Item.AIR, ItemID.SHIELD, ItemID.ARROW, ItemID.TOTEM, ItemID.MAP, ItemID.FIREWORKS, ItemID.NAUTILUS_SHELL, ItemID.SPARKLER}); + public PlayerOffhandInventory(EntityHumanType holder) { super(holder, InventoryType.OFFHAND); } @@ -34,15 +42,15 @@ public void onSlotChange(int index, Item before, boolean send) { @Override public void sendContents(Player... players) { Item item = this.getItem(0); - MobEquipmentPacket pk = this.createMobEquipmentPacket(item); for (Player player : players) { if (player == this.getHolder()) { - InventoryContentPacket pk2 = new InventoryContentPacket(); - pk2.inventoryId = ContainerIds.OFFHAND; - pk2.slots = new Item[]{item}; - player.dataPacket(pk2); + InventoryContentPacket pk = new InventoryContentPacket(); // content vs slot + pk.inventoryId = ContainerIds.OFFHAND; + pk.slots = new Item[]{item}; + player.dataPacket(pk); } else { + MobEquipmentPacket pk = this.createMobEquipmentPacket(item); player.dataPacket(pk); } } @@ -51,15 +59,15 @@ public void sendContents(Player... players) { @Override public void sendSlot(int index, Player... players) { Item item = this.getItem(0); - MobEquipmentPacket pk = this.createMobEquipmentPacket(item); for (Player player : players) { if (player == this.getHolder()) { - InventorySlotPacket pk2 = new InventorySlotPacket(); - pk2.inventoryId = ContainerIds.OFFHAND; - pk2.item = item; - player.dataPacket(pk2); + InventorySlotPacket pk = new InventorySlotPacket(); // slot vs content + pk.inventoryId = ContainerIds.OFFHAND; + pk.item = item; + player.dataPacket(pk); } else { + MobEquipmentPacket pk = this.createMobEquipmentPacket(item); player.dataPacket(pk); } } @@ -71,7 +79,6 @@ private MobEquipmentPacket createMobEquipmentPacket(Item item) { pk.item = item; pk.inventorySlot = 1; pk.windowId = ContainerIds.OFFHAND; - pk.tryEncode(); return pk; } @@ -79,4 +86,9 @@ private MobEquipmentPacket createMobEquipmentPacket(Item item) { public EntityHuman getHolder() { return (EntityHuman) super.getHolder(); } + + @Override + public boolean allowedToAdd(int itemId) { + return OFFHAND_ITEMS.contains(itemId); + } } diff --git a/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java b/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java index 41bc32d7a5f..e3abc2a6673 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java +++ b/src/main/java/cn/nukkit/inventory/PlayerUIComponent.java @@ -11,7 +11,7 @@ public class PlayerUIComponent extends BaseInventory { public static final int CREATED_ITEM_OUTPUT_UI_SLOT = 50; - private final PlayerUIInventory playerUI; + private final PlayerUIInventory playerUI; private final int offset; private final int size; @@ -48,6 +48,11 @@ public Item getItem(int index) { return this.playerUI.getItem(index + this.offset); } + @Override + public Item getItemFast(int index) { + return this.playerUI.getItemFast(index + this.offset); + } + @Override public boolean setItem(int index, Item item, boolean send) { return this.playerUI.setItem(index + this.offset, item, send); diff --git a/src/main/java/cn/nukkit/inventory/PlayerUIInventory.java b/src/main/java/cn/nukkit/inventory/PlayerUIInventory.java index cf5753e35de..0a5899f0cbc 100644 --- a/src/main/java/cn/nukkit/inventory/PlayerUIInventory.java +++ b/src/main/java/cn/nukkit/inventory/PlayerUIInventory.java @@ -1,12 +1,14 @@ package cn.nukkit.inventory; import cn.nukkit.Player; +import cn.nukkit.item.Item; import cn.nukkit.network.protocol.InventorySlotPacket; import cn.nukkit.network.protocol.types.ContainerIds; import java.util.HashMap; public class PlayerUIInventory extends BaseInventory { + private final Player player; private final PlayerCursorInventory cursorInventory; @@ -14,7 +16,7 @@ public class PlayerUIInventory extends BaseInventory { private final BigCraftingGrid bigCraftingGrid; public PlayerUIInventory(Player player) { - super(player, InventoryType.UI, new HashMap<>(), 51); + super(player, InventoryType.UI, new HashMap<>(), 54); this.player = player; this.cursorInventory = new PlayerCursorInventory(this); @@ -41,11 +43,12 @@ public void setSize(int size) { @Override public void sendSlot(int index, Player... target) { - InventorySlotPacket pk = new InventorySlotPacket(); - pk.slot = index; - pk.item = this.getItem(index); + Item item = this.getItem(index); for (Player p : target) { + InventorySlotPacket pk = new InventorySlotPacket(); + pk.slot = index; + pk.item = item; if (p == this.getHolder()) { pk.inventoryId = ContainerIds.UI; } else { @@ -63,7 +66,7 @@ public void sendSlot(int index, Player... target) { @Override public void sendContents(Player... target) { - //doesn't work here + } @Override diff --git a/src/main/java/cn/nukkit/inventory/Recipe.java b/src/main/java/cn/nukkit/inventory/Recipe.java index 53a04e3221b..033c5c59ce3 100644 --- a/src/main/java/cn/nukkit/inventory/Recipe.java +++ b/src/main/java/cn/nukkit/inventory/Recipe.java @@ -3,7 +3,7 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface Recipe { diff --git a/src/main/java/cn/nukkit/inventory/RecipeType.java b/src/main/java/cn/nukkit/inventory/RecipeType.java index ccaf06ddd59..9d22aa9cc6b 100644 --- a/src/main/java/cn/nukkit/inventory/RecipeType.java +++ b/src/main/java/cn/nukkit/inventory/RecipeType.java @@ -1,6 +1,7 @@ package cn.nukkit.inventory; public enum RecipeType { + SHAPELESS, SHAPED, FURNACE, @@ -8,5 +9,9 @@ public enum RecipeType { MULTI, SHULKER_BOX, SHAPELESS_CHEMISTRY, - SHAPED_CHEMISTRY + SHAPED_CHEMISTRY, + REPAIR, + CAMPFIRE, + CAMPFIRE_DATA, + SMITHING_TRANSFORM // not the correct id, map to 8 on 1.19.60+ } diff --git a/src/main/java/cn/nukkit/inventory/ShapedRecipe.java b/src/main/java/cn/nukkit/inventory/ShapedRecipe.java index 1163e53fc99..a37d88be371 100644 --- a/src/main/java/cn/nukkit/inventory/ShapedRecipe.java +++ b/src/main/java/cn/nukkit/inventory/ShapedRecipe.java @@ -6,7 +6,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ShapedRecipe implements CraftingRecipe { @@ -17,18 +17,23 @@ public class ShapedRecipe implements CraftingRecipe { private final List ingredientsAggregate; - private long least,most; + private long least, most; private final String[] shape; private final int priority; private final CharObjectHashMap ingredients = new CharObjectHashMap<>(); + private final int networkId; public ShapedRecipe(Item primaryResult, String[] shape, Map ingredients, List extraResults) { this(null, 1, primaryResult, shape, ingredients, extraResults); } + public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] shape, Map ingredients, List extraResults) { + this(recipeId, priority, primaryResult, shape, ingredients, extraResults, null); + } + /** * Constructs a ShapedRecipe instance. * @@ -41,10 +46,11 @@ public ShapedRecipe(Item primaryResult, String[] shape, Map ing * array MUST have a corresponding item in this list. Space character is automatically treated as air. * @param extraResults
List of additional result items to leave in the crafting grid afterwards. Used for things like cake recipe * empty buckets. - *

+ * @param networkId Unique network id of this recipe. If null, a new networkId will be assigned to this recipe. + * * Note: Recipes **do not** need to be square. Do NOT add padding for empty rows/columns. */ - public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] shape, Map ingredients, List extraResults) { + public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] shape, Map ingredients, List extraResults, Integer networkId) { this.recipeId = recipeId; this.priority = priority; int rowCount = shape.length; @@ -58,17 +64,16 @@ public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] } - //for($shape as $y => $row) { for (String row : shape) { if (row.length() != columnCount) { - throw new RuntimeException("Shaped recipe rows must all have the same length (expected " + columnCount + ", got " + row.length() + ")"); + throw new RuntimeException("Shaped recipe rows must all have the same length (expected " + columnCount + ", got " + row.length() + ')'); } for (int x = 0; x < columnCount; ++x) { char c = row.charAt(x); if (c != ' ' && !ingredients.containsKey(c)) { - throw new RuntimeException("No item specified for symbol '" + c + "'"); + throw new RuntimeException("No item specified for symbol '" + c + '\''); } } } @@ -84,8 +89,9 @@ public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] this.ingredientsAggregate = new ArrayList<>(); for (char c : String.join("", this.shape).toCharArray()) { - if (c == ' ') + if (c == ' ') { continue; + } Item ingredient = this.ingredients.get(c).clone(); for (Item existingIngredient : this.ingredientsAggregate) { if (existingIngredient.equals(ingredient, ingredient.hasMeta(), ingredient.hasCompoundTag())) { @@ -94,10 +100,12 @@ public ShapedRecipe(String recipeId, int priority, Item primaryResult, String[] break; } } - if (ingredient != null) + if (ingredient != null) { this.ingredientsAggregate.add(ingredient); + } } this.ingredientsAggregate.sort(CraftingManager.recipeComparator); + this.networkId = networkId != null ? networkId : ++CraftingManager.NEXT_NETWORK_ID; } public int getWidth() { @@ -127,6 +135,7 @@ public UUID getId() { public void setId(UUID uuid) { this.least = uuid.getLeastSignificantBits(); this.most = uuid.getMostSignificantBits(); + if (this.recipeId == null) { this.recipeId = getId().toString(); } @@ -218,7 +227,7 @@ public boolean matchItems(List inputList, List extraOutputList, int haveInputs.add(item.clone()); } List needInputs = new ArrayList<>(); - if(multiplier != 1){ + if (multiplier != 1) { for (Item item : ingredientsAggregate) { if (item.isNull()) continue; @@ -246,7 +255,7 @@ public boolean matchItems(List inputList, List extraOutputList, int } haveOutputs.sort(CraftingManager.recipeComparator); List needOutputs = new ArrayList<>(); - if(multiplier != 1){ + if (multiplier != 1) { for (Item item : getExtraResults()) { if (item.isNull()) continue; @@ -263,7 +272,7 @@ public boolean matchItems(List inputList, List extraOutputList, int } needOutputs.sort(CraftingManager.recipeComparator); - return this.matchItemList(haveOutputs, needOutputs); + return matchItemList(haveOutputs, needOutputs); } /** @@ -279,7 +288,7 @@ public boolean matchItems(List inputList, List extraOutputList) { return matchItems(inputList, extraOutputList, 1); } - private boolean matchItemList(List haveItems, List needItems) { + private static boolean matchItemList(List haveItems, List needItems) { for (Item needItem : new ArrayList<>(needItems)) { for (Item haveItem : new ArrayList<>(haveItems)) { if (needItem.equals(haveItem, needItem.hasMeta(), needItem.hasCompoundTag())) { @@ -303,7 +312,7 @@ private boolean matchItemList(List haveItems, List needItems) { public String toString() { StringJoiner joiner = new StringJoiner(", "); - ingredients.forEach((character, item) -> joiner.add(item.getName() + ":" + item.getDamage())); + ingredients.forEach((character, item) -> joiner.add(item.getName() + ':' + item.getDamage())); return joiner.toString(); } @@ -317,6 +326,10 @@ public List getIngredientsAggregate() { return ingredientsAggregate; } + public int getNetworkId() { + return this.networkId; + } + public static class Entry { public final int x; public final int y; @@ -326,4 +339,4 @@ public Entry(int x, int y) { this.y = y; } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/inventory/ShapelessRecipe.java b/src/main/java/cn/nukkit/inventory/ShapelessRecipe.java index a3fb10a9191..43607aa75c5 100644 --- a/src/main/java/cn/nukkit/inventory/ShapelessRecipe.java +++ b/src/main/java/cn/nukkit/inventory/ShapelessRecipe.java @@ -5,7 +5,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ShapelessRecipe implements CraftingRecipe { @@ -14,18 +14,24 @@ public class ShapelessRecipe implements CraftingRecipe { private final Item output; - private long least,most; + private long least, most; private final List ingredients; private final List ingredientsAggregate; private final int priority; + private final int networkId; + public ShapelessRecipe(Item result, Collection ingredients) { this(null, 10, result, ingredients); } public ShapelessRecipe(String recipeId, int priority, Item result, Collection ingredients) { + this(recipeId, priority, result, ingredients, null); + } + + public ShapelessRecipe(String recipeId, int priority, Item result, Collection ingredients, Integer networkId) { this.recipeId = recipeId; this.priority = priority; this.output = result.clone(); @@ -38,7 +44,7 @@ public ShapelessRecipe(String recipeId, int priority, Item result, Collection inputList, List extraOutputList, int haveInputs.add(item.clone()); } List needInputs = new ArrayList<>(); - if(multiplier != 1){ + if (multiplier != 1) { for (Item item : ingredientsAggregate) { if (item.isNull()) continue; @@ -160,7 +168,7 @@ public boolean matchItems(List inputList, List extraOutputList, int } haveOutputs.sort(CraftingManager.recipeComparator); List needOutputs = new ArrayList<>(); - if(multiplier != 1){ + if (multiplier != 1) { for (Item item : getExtraResults()) { if (item.isNull()) continue; @@ -177,7 +185,7 @@ public boolean matchItems(List inputList, List extraOutputList, int } needOutputs.sort(CraftingManager.recipeComparator); - return this.matchItemList(haveOutputs, needOutputs); + return matchItemList(haveOutputs, needOutputs); } /** @@ -193,7 +201,7 @@ public boolean matchItems(List inputList, List extraOutputList) { return matchItems(inputList, extraOutputList, 1); } - private boolean matchItemList(List haveItems, List needItems) { + private static boolean matchItemList(List haveItems, List needItems) { for (Item needItem : new ArrayList<>(needItems)) { for (Item haveItem : new ArrayList<>(haveItems)) { if (needItem.equals(haveItem, needItem.hasMeta(), needItem.hasCompoundTag())) { @@ -217,4 +225,8 @@ private boolean matchItemList(List haveItems, List needItems) { public List getIngredientsAggregate() { return ingredientsAggregate; } + + public int getNetworkId() { + return this.networkId; + } } diff --git a/src/main/java/cn/nukkit/inventory/ShulkerBoxInventory.java b/src/main/java/cn/nukkit/inventory/ShulkerBoxInventory.java index ec0600a3712..adafeca6941 100644 --- a/src/main/java/cn/nukkit/inventory/ShulkerBoxInventory.java +++ b/src/main/java/cn/nukkit/inventory/ShulkerBoxInventory.java @@ -8,9 +8,6 @@ import cn.nukkit.network.protocol.BlockEventPacket; import cn.nukkit.network.protocol.LevelSoundEventPacket; -/** - * Created by PetteriM1 - */ public class ShulkerBoxInventory extends ContainerInventory { public ShulkerBoxInventory(BlockEntityShulkerBox box) { @@ -64,15 +61,11 @@ public void onClose(Player who) { @Override public boolean canAddItem(Item item) { - if (item.getId() == BlockID.SHULKER_BOX || item.getId() == BlockID.UNDYED_SHULKER_BOX) { - // Do not allow nested shulker boxes. - return false; - } - return super.canAddItem(item); + return this.allowedToAdd(item.getId()) && super.canAddItem(item); } @Override - public void sendSlot(int index, Player... players) { - super.sendSlot(index, players); + public boolean allowedToAdd(int itemId) { + return itemId != BlockID.SHULKER_BOX && itemId != BlockID.UNDYED_SHULKER_BOX; } } diff --git a/src/main/java/cn/nukkit/inventory/SmithingInventory.java b/src/main/java/cn/nukkit/inventory/SmithingInventory.java new file mode 100644 index 00000000000..5cbbd876a80 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/SmithingInventory.java @@ -0,0 +1,87 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.item.Item; +import cn.nukkit.level.Position; + +/** + * @author joserobjr + */ +public class SmithingInventory extends FakeBlockUIComponent { + + private static final int EQUIPMENT = 0; + private static final int INGREDIENT = 1; + + public static final int SMITHING_EQUIPMENT_UI_SLOT = 51; + + public static final int SMITHING_INGREDIENT_UI_SLOT = 52; + + private Item currentResult = Item.get(0); + + + public SmithingInventory(PlayerUIInventory playerUI, Position position) { + super(playerUI, InventoryType.SMITHING_TABLE, 51, position); + } + + public SmithingRecipe matchRecipe() { + return Server.getInstance().getCraftingManager().matchSmithingRecipe(getEquipment(), getIngredient()); + } + + @Override + public void onSlotChange(int index, Item before, boolean send) { + if (index == EQUIPMENT || index == INGREDIENT) { + updateResult(); + } + super.onSlotChange(index, before, send); + } + + public void updateResult() { + Item result; + SmithingRecipe recipe = matchRecipe(); + if (recipe == null) { + result = Item.get(0); + } else { + result = recipe.getFinalResult(getEquipment()); + } + setResult(result); + } + + private void setResult(Item result) { + this.currentResult = result; + } + + public Item getResult() { + SmithingRecipe recipe = matchRecipe(); + if (recipe == null) { + return Item.get(0); + } + return recipe.getFinalResult(getEquipment()); + } + + public Item getEquipment() { + return getItem(EQUIPMENT); + } + + public void setEquipment(Item equipment) { + setItem(EQUIPMENT, equipment); + } + + public Item getIngredient() { + return getItem(INGREDIENT); + } + + public void setIngredient(Item ingredient) { + setItem(INGREDIENT, ingredient); + } + + @Override + public void onOpen(Player who) { + super.onOpen(who); + who.craftingType = Player.CRAFTING_SMITHING; + } + + public Item getCurrentResult() { + return currentResult; + } +} diff --git a/src/main/java/cn/nukkit/inventory/SmithingRecipe.java b/src/main/java/cn/nukkit/inventory/SmithingRecipe.java new file mode 100644 index 00000000000..7b074dd6c37 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/SmithingRecipe.java @@ -0,0 +1,150 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * @author joserobjr + */ +@ToString +public class SmithingRecipe extends ShapelessRecipe { + private final Item equipment; + private final Item ingredient; + private final Item result; + + private final List ingredientsAggregate; + + public SmithingRecipe(String recipeId, int priority, Collection ingredients, Item result) { + super(recipeId, priority, result, ingredients); + this.equipment = (Item) ingredients.toArray()[0]; + this.ingredient = (Item) ingredients.toArray()[1]; + this.result = result; + + ArrayList aggregation = new ArrayList<>(2); + + for (Item item : new Item[]{equipment, ingredient}) { + if (item.getCount() < 1) { + throw new IllegalArgumentException("Recipe Ingredient amount was not 1 (value: " + item.getCount() + ")"); + } + boolean found = false; + for (Item existingIngredient : aggregation) { + if (existingIngredient.equals(item, item.hasMeta(), item.hasCompoundTag())) { + existingIngredient.setCount(existingIngredient.getCount() + item.getCount()); + found = true; + break; + } + } + if (!found) { + aggregation.add(item.clone()); + } + } + + aggregation.trimToSize(); + aggregation.sort(CraftingManager.recipeComparator); + this.ingredientsAggregate = Collections.unmodifiableList(aggregation); + } + + @Override + public Item getResult() { + return result; + } + + public Item getFinalResult(Item equip) { + Item finalResult = getResult().clone(); + + if (equip.hasCompoundTag()) { + finalResult.setCompoundTag(equip.getCompoundTag()); + } + + int maxDurability = finalResult.getMaxDurability(); + if (maxDurability <= 0 || equip.getMaxDurability() <= 0) { + return finalResult; + } + + int damage = equip.getDamage(); + if (damage <= 0) { + return finalResult; + } + + finalResult.setDamage(Math.min(maxDurability, damage)); + return finalResult; + } + + @Override + public void registerToCraftingManager(CraftingManager manager) { + manager.registerSmithingRecipe(this); + } + + @Override + public RecipeType getType() { + return RecipeType.SMITHING_TRANSFORM; + } + + public Item getEquipment() { + return equipment; + } + + public Item getIngredient() { + return ingredient; + } + + public List getIngredientsAggregate() { + return ingredientsAggregate; + } + + public boolean matchItems(List inputList) { + return matchItems(inputList, 1); + } + + public boolean matchItems(List inputList, int multiplier) { + List haveInputs = new ArrayList<>(); + for (Item item : inputList) { + if (item.isNull()) + continue; + haveInputs.add(item.clone()); + } + List needInputs = new ArrayList<>(); + if(multiplier != 1){ + for (Item item : ingredientsAggregate) { + if (item.isNull()) + continue; + Item itemClone = item.clone(); + itemClone.setCount(itemClone.getCount() * multiplier); + needInputs.add(itemClone); + } + } else { + for (Item item : ingredientsAggregate) { + if (item.isNull()) + continue; + needInputs.add(item.clone()); + } + } + + return matchItemList(haveInputs, needInputs); + } + + private static boolean matchItemList(List haveItems, List needItems) { + for (Item needItem : new ArrayList<>(needItems)) { + for (Item haveItem : new ArrayList<>(haveItems)) { + if (needItem.equals(haveItem, needItem.hasMeta(), needItem.hasCompoundTag())) { + int amount = Math.min(haveItem.getCount(), needItem.getCount()); + needItem.setCount(needItem.getCount() - amount); + haveItem.setCount(haveItem.getCount() - amount); + if (haveItem.getCount() == 0) { + haveItems.remove(haveItem); + } + if (needItem.getCount() == 0) { + needItems.remove(needItem); + break; + } + } + } + } + return haveItems.isEmpty() && needItems.isEmpty(); + } +} diff --git a/src/main/java/cn/nukkit/inventory/SmokerInventory.java b/src/main/java/cn/nukkit/inventory/SmokerInventory.java new file mode 100644 index 00000000000..1994b88810e --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/SmokerInventory.java @@ -0,0 +1,15 @@ +package cn.nukkit.inventory; + +import cn.nukkit.blockentity.BlockEntitySmoker; + +public class SmokerInventory extends FurnaceInventory { + + public SmokerInventory(BlockEntitySmoker smoker) { + super(smoker, InventoryType.SMOKER); + } + + @Override + public BlockEntitySmoker getHolder() { + return (BlockEntitySmoker) this.holder; + } +} diff --git a/src/main/java/cn/nukkit/inventory/TradeInventory.java b/src/main/java/cn/nukkit/inventory/TradeInventory.java new file mode 100644 index 00000000000..7f184d49ae7 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/TradeInventory.java @@ -0,0 +1,53 @@ +package cn.nukkit.inventory; + +import cn.nukkit.Player; +import cn.nukkit.entity.passive.EntityVillagerV1; +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.network.protocol.UpdateTradePacket; + +import java.io.IOException; +import java.nio.ByteOrder; + +public class TradeInventory extends BaseInventory { + + public TradeInventory(InventoryHolder holder) { + super(holder, InventoryType.TRADING); + } + + public void onOpen(Player who) { + super.onOpen(who); + + UpdateTradePacket pk = new UpdateTradePacket(); + pk.windowId = (byte) who.getWindowId(this); + pk.windowType = (byte) InventoryType.TRADING.getNetworkType(); + pk.isWilling = this.getHolder().isWilling(); + pk.screen2 = false; // use old trade screen + pk.trader = this.getHolder().getId(); + pk.tradeTier = this.getHolder().getTradeTier(); + pk.player = who.getId(); + try { + pk.offers = NBTIO.write(this.getHolder().getOffers(),ByteOrder.LITTLE_ENDIAN, true); + } catch (IOException ignored) {} + + who.dataPacket(pk); + } + + public void onClose(Player who) { + for (int i = 0; i <= 1; i++) { + Item item = getItem(i); + this.clear(i); + if (who.getInventory().canAddItem(item)) { + who.getInventory().addItem(item); + } else { + who.level.dropItem(who, item); + } + } + + super.onClose(who); + } + + public EntityVillagerV1 getHolder() { + return (EntityVillagerV1) this.holder; + } +} diff --git a/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java new file mode 100644 index 00000000000..0e042cd5202 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/TradeInventoryRecipe.java @@ -0,0 +1,114 @@ +package cn.nukkit.inventory; + +import cn.nukkit.item.Item; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import lombok.Getter; + +public class TradeInventoryRecipe { + + public static final int A_ITEM = 0; + public static final int B_ITEM = 1; + @Getter + private final Item sellItem; + @Getter + private final Item buyItem; + @Getter + private final Item secondBuyItem; + + private int tier = -1; + private int maxUses = 999; + private int buyCountA = 0; + private int buyCountB = 0; + private final int uses = 0; + private int demand = 0; + private int rewardsExp = 0; + private final int traderExp = 0; + private float priceMultiplierA = 0F; + private float priceMultiplierB = 0F; + + public TradeInventoryRecipe(Item sellItem, Item buyItem) { + this(sellItem, buyItem, Item.get(0)); + } + + public TradeInventoryRecipe(Item sellItem, Item buyItem, Item secondBuyItem) { + this.sellItem = sellItem; + this.buyItem = buyItem; + this.secondBuyItem = secondBuyItem; + } + + public TradeInventoryRecipe setTier(int tier) { + this.tier = tier; + return this; + } + + public TradeInventoryRecipe setMaxUses(int maxUses) { + this.maxUses = maxUses; + return this; + } + + public TradeInventoryRecipe setBuyCount(int count, int type) { + switch (type) { + case 0: + this.buyCountA = count; + break; + case 1: + this.buyCountB = count; + break; + } + this.buyCountA = count; + return this; + } + + public TradeInventoryRecipe setDemand(int demand) { + this.demand = demand; + return this; + } + + public TradeInventoryRecipe setMultiplier(float multiplier, int type) { + switch (type) { + case 0: + this.priceMultiplierA = multiplier; + break; + case 1: + this.priceMultiplierB = multiplier; + break; + } + return this; + } + + public TradeInventoryRecipe setRewardExp(int reward) { + this.rewardsExp = reward; + return this; + } + + public CompoundTag toNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.putCompound("buyA", NBTIO.putItemHelper(buyItem, -1)); + nbt.putCompound("buyB", NBTIO.putItemHelper(secondBuyItem,-1)); + nbt.putCompound("sell", NBTIO.putItemHelper(sellItem, -1)); + nbt.putInt("tier", tier); + nbt.putInt("buyCountA", buyCountA); + nbt.putInt("buyCountB", buyCountB); + nbt.putInt("uses", uses); + nbt.putInt("maxUses", maxUses); + nbt.putInt("rewardExp", rewardsExp); + nbt.putInt("demand", demand); + nbt.putInt("traderExp", traderExp); + nbt.putFloat("priceMultiplierA", priceMultiplierA); + nbt.putFloat("priceMultiplierB", priceMultiplierB); + return nbt; + } + + public static TradeInventoryRecipe toNBT(CompoundTag nbt) { + return new TradeInventoryRecipe(NBTIO.getItemHelper(nbt.getCompound("sell")), NBTIO.getItemHelper(nbt.getCompound("buyA")), NBTIO.getItemHelper(nbt.getCompound("buyB"))) + .setTier(nbt.getInt("tier")) + .setBuyCount(nbt.getInt("buyCountA"), A_ITEM) + .setBuyCount(nbt.getInt("buyCountB"), B_ITEM) + .setMaxUses(nbt.getInt("maxUses")) + .setMultiplier(nbt.getInt("priceMultiplierA"), A_ITEM) + .setMultiplier(nbt.getInt("priceMultiplierB"), B_ITEM) + .setDemand(nbt.getInt("demand")) + .setRewardExp(nbt.getInt("rewardExp")); + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/CraftingTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/CraftingTransaction.java index 5c041edfe24..6fa34a968f4 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/CraftingTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/CraftingTransaction.java @@ -1,10 +1,9 @@ package cn.nukkit.inventory.transaction; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.event.inventory.CraftItemEvent; -import cn.nukkit.inventory.BigCraftingGrid; -import cn.nukkit.inventory.CraftingRecipe; -import cn.nukkit.inventory.InventoryType; +import cn.nukkit.inventory.*; import cn.nukkit.inventory.transaction.action.InventoryAction; import cn.nukkit.inventory.transaction.action.SlotChangeAction; import cn.nukkit.item.Item; @@ -76,7 +75,7 @@ public void setPrimaryOutput(Item item) { if (primaryOutput == null) { primaryOutput = item.clone(); } else if (!primaryOutput.equals(item)) { - throw new RuntimeException("Primary result item has already been set and does not match the current item (expected " + primaryOutput + ", got " + item + ")"); + throw new RuntimeException("Primary result item has already been set and does not match the current item (expected " + primaryOutput + ", got " + item + ')'); } } @@ -85,7 +84,26 @@ public CraftingRecipe getRecipe() { } public boolean canExecute() { - recipe = source.getServer().getCraftingManager().matchRecipe(inputs, this.primaryOutput, this.secondaryOutputs); + if (source.craftingType == Player.CRAFTING_LOOM) { + Inventory inventory = source.getWindowById(Player.LOOM_WINDOW_ID); + if (inventory instanceof LoomInventory) { + addInventory(inventory); + } + } else if (source.craftingType == Player.CRAFTING_SMITHING) { + Inventory inventory = source.getWindowById(Player.SMITHING_WINDOW_ID); + if (inventory instanceof SmithingInventory) { + addInventory(inventory); + + SmithingInventory smithingInventory = (SmithingInventory) inventory; + SmithingRecipe smithingRecipe = smithingInventory.matchRecipe(); + if (smithingRecipe != null && this.primaryOutput.equals(smithingRecipe.getFinalResult(smithingInventory.getEquipment()), true, true)) { + return super.canExecute(); + } + return false; + } + } else { + recipe = source.getServer().getCraftingManager().matchRecipe(inputs, this.primaryOutput, this.secondaryOutputs); + } return this.recipe != null && super.canExecute(); } @@ -99,20 +117,21 @@ protected boolean callExecuteEvent() { protected void sendInventories() { super.sendInventories(); - /* + /* * TODO: HACK! - * we can't resend the contents of the crafting window, so we force the client to close it instead. - * So people don't whine about messy desync issues when someone cancels CraftItemEvent, or when a crafting - * transaction goes wrong. - */ + * we can't resend the contents of the crafting window, so we force the client to close it instead. + * So people don't whine about messy desync issues when someone cancels CraftItemEvent, or when a crafting + * transaction goes wrong. + */ ContainerClosePacket pk = new ContainerClosePacket(); pk.windowId = ContainerIds.NONE; + pk.wasServerInitiated = true; source.getServer().getScheduler().scheduleDelayedTask(new Task() { @Override public void onRun(int currentTick) { source.dataPacket(pk); } - }, 20); + }, 10); this.source.resetCraftingGridType(); } @@ -142,13 +161,22 @@ public boolean execute() { case Item.GOLDEN_PICKAXE: case Item.IRON_PICKAXE: case Item.DIAMOND_PICKAXE: + case Item.NETHERITE_PICKAXE: source.awardAchievement("buildBetterPickaxe"); break; case Item.WOODEN_SWORD: + case Item.STONE_SWORD: + case Item.GOLDEN_SWORD: + case Item.IRON_SWORD: + case Item.DIAMOND_SWORD: + case Item.NETHERITE_SWORD: source.awardAchievement("buildSword"); break; - case Item.DIAMOND: - source.awardAchievement("diamond"); + case Item.ENCHANT_TABLE: + source.awardAchievement("enchantments"); + break; + case Item.BOOKSHELF: + source.awardAchievement("bookcase"); break; } @@ -162,12 +190,28 @@ public boolean checkForCraftingPart(List actions) { for (InventoryAction action : actions) { if (action instanceof SlotChangeAction) { SlotChangeAction slotChangeAction = (SlotChangeAction) action; - if (slotChangeAction.getInventory().getType() == InventoryType.UI && slotChangeAction.getSlot() == 50 && - !slotChangeAction.getSourceItem().equals(slotChangeAction.getTargetItem())) { - return true; + if (slotChangeAction.getInventory().getType() == InventoryType.UI) { + if (slotChangeAction.getSlot() == 50) { + if (!slotChangeAction.getSourceItem().equals(slotChangeAction.getTargetItem())) { + return true; + } else { + Server.getInstance().getLogger().debug("Source equals target"); + return false; + } + } else { + Server.getInstance().getLogger().debug("Invalid slot: " + slotChangeAction.getSlot()); + return false; + } + } else { + Server.getInstance().getLogger().debug("Invalid action type: " + slotChangeAction.getInventory().getType()); + return false; } + } else { + Server.getInstance().getLogger().debug("SlotChangeAction expected, got " + action); + return false; } } + Server.getInstance().getLogger().debug("No actions on the list"); return false; } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/EnchantTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/EnchantTransaction.java index 8b828d17ebe..22d43bc8e8f 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/EnchantTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/EnchantTransaction.java @@ -16,6 +16,7 @@ @Getter @Setter public class EnchantTransaction extends InventoryTransaction { + private Item inputItem; private Item outputItem; private int cost = -1; @@ -27,7 +28,9 @@ public EnchantTransaction(Player source, List actions) { @Override public boolean canExecute() { Inventory inv = getSource().getWindowById(Player.ENCHANT_WINDOW_ID); - if (inv == null) return false; + if (!(inv instanceof EnchantInventory)) { + return false; + } EnchantInventory eInv = (EnchantInventory) inv; if (!getSource().isCreative()) { if (cost == -1 || !eInv.getReagentSlot().equals(Item.get(Item.DYE, 4), true, false) || eInv.getReagentSlot().count < cost) @@ -50,7 +53,6 @@ public boolean execute() { if (ev.isCancelled()) { source.removeAllWindows(false); this.sendInventories(); - // Cancelled by plugin, means handled OK return true; } @@ -88,9 +90,9 @@ public void addAction(InventoryAction action) { break; case NetworkInventoryAction.SOURCE_TYPE_ENCHANT_MATERIAL: if (action.getTargetItem().equals(Item.get(Item.AIR), false, false)) { - this.cost = action.getSourceItem().count; + this.cost = action.getSourceItemUnsafe().count; } else { - this.cost = action.getSourceItem().count - action.getTargetItem().count; + this.cost = action.getSourceItemUnsafe().count - action.getTargetItemUnsafe().count; } break; } diff --git a/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java index 1e4dda97451..314edf804c8 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java @@ -1,12 +1,14 @@ package cn.nukkit.inventory.transaction; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.event.inventory.InventoryClickEvent; import cn.nukkit.event.inventory.InventoryTransactionEvent; import cn.nukkit.inventory.Inventory; import cn.nukkit.inventory.transaction.action.InventoryAction; import cn.nukkit.inventory.transaction.action.SlotChangeAction; import cn.nukkit.item.Item; +import cn.nukkit.item.enchantment.Enchantment; import java.util.*; @@ -15,7 +17,7 @@ */ public class InventoryTransaction { - private long creationTime; + private boolean invalid; protected boolean hasExecuted; protected Player source; @@ -35,7 +37,7 @@ public InventoryTransaction(Player source, List actions, boolea } protected void init(Player source, List actions) { - creationTime = System.currentTimeMillis(); + //creationTime = System.currentTimeMillis(); this.source = source; for (InventoryAction action : actions) { @@ -48,7 +50,7 @@ public Player getSource() { } public long getCreationTime() { - return creationTime; + return 0; // unused } public Set getInventories() { @@ -64,8 +66,38 @@ public Set getActions() { } public void addAction(InventoryAction action) { + if (invalid) { + Server.getInstance().getLogger().debug("Failed to add InventoryAction for " + source.getName() + ": previous run was marked as invalid"); + return; + } + if (action instanceof SlotChangeAction) { - SlotChangeAction slotChangeAction = (SlotChangeAction) action; + SlotChangeAction slotChangeAction = (SlotChangeAction)action; + + Item targetItem = slotChangeAction.getTargetItemUnsafe(); + Item sourceItem = slotChangeAction.getSourceItemUnsafe(); + if (targetItem.getCount() > targetItem.getMaxStackSize() || sourceItem.getCount() > sourceItem.getMaxStackSize()) { + invalid = true; + Server.getInstance().getLogger().debug("Failed to add SlotChangeAction for " + source.getName() + ": illegal item stack size"); + return; + } + + if (!slotChangeAction.getInventory().allowedToAdd(targetItem.getId())) { + invalid = true; + Server.getInstance().getLogger().debug("Failed to add SlotChangeAction for " + source.getName() + ": " + slotChangeAction.getInventory().getTitle() + " inventory doesn't allow item " + targetItem.getId()); + return; + } + + if (!source.isCreative()) { + int slot = slotChangeAction.getSlot(); + if (slot == 36 || slot == 37 || slot == 38 || slot == 39) { + if (sourceItem.hasEnchantment(Enchantment.ID_BINDING_CURSE)) { + invalid = true; + Server.getInstance().getLogger().debug("Failed to add SlotChangeAction for " + source.getName() + ": armor has binding curse"); + return; + } + } + } ListIterator iterator = this.actions.listIterator(); @@ -77,22 +109,22 @@ public void addAction(InventoryAction action) { continue; Item existingSource = existingSlotChangeAction.getSourceItem(); Item existingTarget = existingSlotChangeAction.getTargetItem(); - if (existingSlotChangeAction.getSlot() == slotChangeAction.getSlot() - && slotChangeAction.getSourceItem().equals(existingTarget, existingTarget.hasMeta(), existingTarget.hasCompoundTag())) { + if (existingSlotChangeAction.getSlot() == slotChangeAction.getSlot() && slotChangeAction.getSourceItem().equals(existingTarget, existingTarget.hasMeta(), existingTarget.hasCompoundTag())) { iterator.set(new SlotChangeAction(existingSlotChangeAction.getInventory(), existingSlotChangeAction.getSlot(), existingSlotChangeAction.getSourceItem(), slotChangeAction.getTargetItem())); action.onAddToTransaction(this); return; } else if (existingSlotChangeAction.getSlot() == slotChangeAction.getSlot() && slotChangeAction.getSourceItem().equals(existingSource, existingSource.hasMeta(), existingSource.hasCompoundTag()) && slotChangeAction.getTargetItem().equals(existingTarget, existingTarget.hasMeta(), existingTarget.hasCompoundTag())) { - existingSource.setCount(existingSource.getCount() + slotChangeAction.getSourceItem().getCount()); - existingTarget.setCount(existingTarget.getCount() + slotChangeAction.getTargetItem().getCount()); + existingSource.setCount(existingSource.getCount() + slotChangeAction.getSourceItemUnsafe().getCount()); + existingTarget.setCount(existingTarget.getCount() + slotChangeAction.getTargetItemUnsafe().getCount()); iterator.set(new SlotChangeAction(existingSlotChangeAction.getInventory(), existingSlotChangeAction.getSlot(), existingSource, existingTarget)); return; } } } } + this.actions.add(action); action.onAddToTransaction(this); } @@ -109,15 +141,16 @@ public void addInventory(Inventory inventory) { protected boolean matchItems(List needItems, List haveItems) { for (InventoryAction action : this.actions) { - if (action.getTargetItem().getId() != Item.AIR) { + if (action.getTargetItemUnsafe().getId() != Item.AIR) { needItems.add(action.getTargetItem()); } if (!action.isValid(this.source)) { + invalid = true; return false; } - if (action.getSourceItem().getId() != Item.AIR) { + if (action.getSourceItemUnsafe().getId() != Item.AIR) { haveItems.add(action.getSourceItem()); } } @@ -146,7 +179,6 @@ protected void sendInventories() { for (InventoryAction action : this.actions) { if (action instanceof SlotChangeAction) { SlotChangeAction sca = (SlotChangeAction) action; - sca.getInventory().sendSlot(sca.getSlot(), this.source); } } @@ -155,7 +187,7 @@ protected void sendInventories() { public boolean canExecute() { List haveItems = new ArrayList<>(); List needItems = new ArrayList<>(); - return matchItems(needItems, haveItems) && this.actions.size() > 0 && haveItems.size() == 0 && needItems.size() == 0; + return matchItems(needItems, haveItems) && !this.actions.isEmpty() && haveItems.isEmpty() && needItems.isEmpty(); } protected boolean callExecuteEvent() { @@ -184,7 +216,7 @@ protected boolean callExecuteEvent() { } if (who != null && to != null) { - if (from.getTargetItem().getCount() > from.getSourceItem().getCount()) { + if (from.getTargetItemUnsafe().getCount() > from.getSourceItemUnsafe().getCount()) { from = to; } @@ -200,12 +232,11 @@ protected boolean callExecuteEvent() { } public boolean execute() { - if (this.hasExecuted() || !this.canExecute()) { + if (invalid || this.hasExecuted() || !this.canExecute()) { this.sendInventories(); return false; } - if (!callExecuteEvent()) { this.sendInventories(); return true; diff --git a/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java new file mode 100644 index 00000000000..8dda85bb7ac --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java @@ -0,0 +1,96 @@ +package cn.nukkit.inventory.transaction; + +import cn.nukkit.Player; +import cn.nukkit.event.inventory.LoomItemEvent; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.LoomInventory; +import cn.nukkit.inventory.transaction.action.InventoryAction; +import cn.nukkit.inventory.transaction.action.LoomItemAction; +import cn.nukkit.item.Item; + +import java.util.List; + +public class LoomTransaction extends InventoryTransaction { + + private Item outputItem; + + public LoomTransaction(Player source, List actions) { + super(source, actions); + } + + @Override + public void addAction(InventoryAction action) { + super.addAction(action); + + if (action instanceof LoomItemAction) { + outputItem = action.getSourceItem(); + } + } + + @Override + public boolean canExecute() { + Inventory inventory = getSource().getWindowById(Player.LOOM_WINDOW_ID); + if (!(inventory instanceof LoomInventory)) { + return false; + } + LoomInventory loomInventory = (LoomInventory) inventory; + + if (outputItem == null) { + return false; + } + + Item first = loomInventory.getFirstItem(); + Item second = loomInventory.getSecondItem(); + if (first.getId() != Item.BANNER || second.getId() != Item.DYE || first.getDamage() != outputItem.getDamage()) { + return false; + } + if (!outputItem.hasCompoundTag()) { + return false; + } + int patternCount = outputItem.getNamedTag().getList("Patterns").size(); + if (first.getNamedTag() == null) { + return patternCount == 1; + } + + if (patternCount > 6) { + return false; + } + + return first.getNamedTag().getList("Patterns").size() + 1 == patternCount; + } + + @Override + public boolean execute() { + if (this.hasExecuted() || !this.canExecute()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return false; + } + + LoomInventory inventory = (LoomInventory) getSource().getWindowById(Player.LOOM_WINDOW_ID); + LoomItemEvent event = new LoomItemEvent(inventory, this.outputItem, this.source); + this.source.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return true; + } + + for (InventoryAction action : this.actions) { + if (action.execute(this.source)) { + action.onExecuteSuccess(this.source); + } else { + action.onExecuteFail(this.source); + } + } + return true; + } + + public Item getOutputItem() { + return this.outputItem; + } + + public static boolean checkForItemPart(List actions) { + return actions.stream().anyMatch(it-> it instanceof LoomItemAction); + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java index c6c5cd0e9f1..1831c0a0cee 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java @@ -8,8 +8,8 @@ import cn.nukkit.inventory.AnvilInventory; import cn.nukkit.inventory.FakeBlockMenu; import cn.nukkit.inventory.Inventory; -import cn.nukkit.inventory.transaction.action.RepairItemAction; import cn.nukkit.inventory.transaction.action.InventoryAction; +import cn.nukkit.inventory.transaction.action.RepairItemAction; import cn.nukkit.item.Item; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.network.protocol.LevelEventPacket; @@ -35,7 +35,7 @@ public RepairItemTransaction(Player source, List actions) { @Override public boolean canExecute() { Inventory inventory = getSource().getWindowById(Player.ANVIL_WINDOW_ID); - if (inventory == null) { + if (!(inventory instanceof AnvilInventory)) { return false; } AnvilInventory anvilInventory = (AnvilInventory) inventory; @@ -54,7 +54,7 @@ public boolean execute() { AnvilInventory inventory = (AnvilInventory) getSource().getWindowById(Player.ANVIL_WINDOW_ID); if (inventory.getCost() != this.cost && !this.source.isCreative()) { - this.source.getServer().getLogger().debug("Got unexpected cost " + inventory.getCost() + " from " + this.source.getName() + "(expected " + this.cost + ")"); + this.source.getServer().getLogger().debug("Got unexpected cost " + inventory.getCost() + " from " + this.source.getName() + "(expected " + this.cost + ')'); } RepairItemEvent event = new RepairItemEvent(inventory, this.inputItem, this.outputItem, this.materialItem, this.cost, this.source); @@ -272,7 +272,7 @@ private boolean checkRecipeValid() { } } if (this.outputItem.getRepairCost() != nextBaseRepairCost) { - this.source.getServer().getLogger().debug("Got unexpected base cost " + this.outputItem.getRepairCost() + " from " + this.source.getName() + "(expected " + nextBaseRepairCost + ")"); + this.source.getServer().getLogger().debug("Got unexpected base cost " + this.outputItem.getRepairCost() + " from " + this.source.getName() + "(expected " + nextBaseRepairCost + ')'); return false; } diff --git a/src/main/java/cn/nukkit/inventory/transaction/SmithingTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/SmithingTransaction.java new file mode 100644 index 00000000000..e3f25b56d52 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/SmithingTransaction.java @@ -0,0 +1,120 @@ +package cn.nukkit.inventory.transaction; + +import cn.nukkit.Player; +import cn.nukkit.event.inventory.SmithItemEvent; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.SmithingInventory; +import cn.nukkit.inventory.transaction.action.CreativeInventoryAction; +import cn.nukkit.inventory.transaction.action.InventoryAction; +import cn.nukkit.inventory.transaction.action.SmithingItemAction; +import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; + +import java.util.List; + +/** + * @author joserobjr + */ +public class SmithingTransaction extends InventoryTransaction { + + private Item equipmentItem; + private Item ingredientItem; + private Item outputItem; + + public SmithingTransaction(Player source, List actions) { + super(source, actions); + } + + @Override + public void addAction(InventoryAction action) { + super.addAction(action); + if (action instanceof SmithingItemAction) { + switch (((SmithingItemAction) action).getType()) { + case 0: // input + this.equipmentItem = action.getTargetItem(); + break; + case 1: // ingredient + this.ingredientItem = action.getTargetItem(); + break; + case 2: // result + this.outputItem = action.getSourceItem(); + break; + } + } else if (action instanceof CreativeInventoryAction) { + CreativeInventoryAction creativeAction = (CreativeInventoryAction) action; + if (creativeAction.getActionType() == 0 + && creativeAction.getSourceItemUnsafe().isNull() + && !creativeAction.getTargetItemUnsafe().isNull() && creativeAction.getTargetItemUnsafe().getId() == ItemID.NETHERITE_INGOT) { + this.ingredientItem = action.getTargetItem(); + } + } + } + + @Override + public boolean canExecute() { + Inventory inventory = getSource().getWindowById(Player.SMITHING_WINDOW_ID); + if (!(inventory instanceof SmithingInventory)) { + return false; + } + SmithingInventory smithingInventory = (SmithingInventory) inventory; + if (outputItem == null || outputItem.isNull() || + ((equipmentItem == null || equipmentItem.isNull()) && (ingredientItem == null || ingredientItem.isNull()))) { + return false; + } + + Item air = Item.get(0); + Item equipment = equipmentItem != null ? equipmentItem : air; + Item ingredient = ingredientItem != null ? ingredientItem : air; + + return equipment.equals(smithingInventory.getEquipment(), true, true) + && ingredient.equals(smithingInventory.getIngredient(), true, true) + && outputItem.equals(smithingInventory.getResult(), true, true); + } + + @Override + public boolean execute() { + if (this.hasExecuted() || !this.canExecute()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return false; + } + + SmithingInventory inventory = (SmithingInventory) getSource().getWindowById(Player.SMITHING_WINDOW_ID); + SmithItemEvent event = new SmithItemEvent(inventory, this.equipmentItem, this.outputItem, this.ingredientItem, this.source); + this.source.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + this.source.removeAllWindows(false); + this.sendInventories(); + return true; + } + + for (InventoryAction action : this.actions) { + if (action.execute(this.source)) { + action.onExecuteSuccess(this.source); + } else { + action.onExecuteFail(this.source); + } + } + + if (inventory != null) { + inventory.sendContents(source); + } + return true; + } + + public Item getInputItem() { + return this.equipmentItem; + } + + public Item getMaterialItem() { + return this.ingredientItem; + } + + public Item getOutputItem() { + return this.outputItem; + } + + public static boolean checkForItemPart(List actions) { + return actions.stream().anyMatch(it-> it instanceof SmithingItemAction); + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTakeResultAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTakeResultAction.java index bee879d4230..b1484827cdc 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTakeResultAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTakeResultAction.java @@ -34,11 +34,9 @@ public boolean execute(Player source) { @Override public void onExecuteSuccess(Player $source) { - } @Override public void onExecuteFail(Player source) { - } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java index aa872ec9d32..46fc91701d7 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java @@ -10,12 +10,12 @@ */ public class CraftingTransferMaterialAction extends InventoryAction { - private int slot; + //private int slot; public CraftingTransferMaterialAction(Item sourceItem, Item targetItem, int slot) { super(sourceItem, targetItem); - this.slot = slot; + //this.slot = slot; } @Override @@ -45,11 +45,9 @@ public boolean execute(Player source) { @Override public void onExecuteSuccess(Player $source) { - } @Override public void onExecuteFail(Player source) { - } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/CreativeInventoryAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/CreativeInventoryAction.java index d92948d8e8b..804923111dd 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/CreativeInventoryAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/CreativeInventoryAction.java @@ -53,10 +53,8 @@ public boolean execute(Player source) { } public void onExecuteSuccess(Player source) { - } public void onExecuteFail(Player source) { - } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/DropItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/DropItemAction.java index 9f2aa207296..b46f97c5cf8 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/DropItemAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/DropItemAction.java @@ -25,11 +25,9 @@ public boolean isValid(Player source) { public boolean onPreExecute(Player source) { PlayerDropItemEvent ev; source.getServer().getPluginManager().callEvent(ev = new PlayerDropItemEvent(source, this.targetItem)); - - if(ev.isCancelled()) { + if (ev.isCancelled()) { source.stopAction(); } - return !ev.isCancelled(); } @@ -41,10 +39,8 @@ public boolean execute(Player source) { } public void onExecuteSuccess(Player source) { - } public void onExecuteFail(Player source) { - } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/EnchantingAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/EnchantingAction.java index c035bad751c..5cb41a737c4 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/EnchantingAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/EnchantingAction.java @@ -1,12 +1,14 @@ package cn.nukkit.inventory.transaction.action; import cn.nukkit.Player; +import cn.nukkit.inventory.EnchantInventory; import cn.nukkit.item.Item; import lombok.Getter; public class EnchantingAction extends InventoryAction { + @Getter - private int type; + private final int type; public EnchantingAction(Item source, Item target, int type) { super(source, target); @@ -15,7 +17,7 @@ public EnchantingAction(Item source, Item target, int type) { @Override public boolean isValid(Player source) { - return source.getWindowById(Player.ENCHANT_WINDOW_ID) != null; + return source.getWindowById(Player.ENCHANT_WINDOW_ID) instanceof EnchantInventory; } @Override diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/InventoryAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/InventoryAction.java index b1559b5dc1b..95c5454b402 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/InventoryAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/InventoryAction.java @@ -9,8 +9,7 @@ */ public abstract class InventoryAction { - - private long creationTime; + //private long creationTime; protected Item sourceItem; @@ -20,31 +19,49 @@ public InventoryAction(Item sourceItem, Item targetItem) { this.sourceItem = sourceItem; this.targetItem = targetItem; - this.creationTime = System.currentTimeMillis(); + //this.creationTime = System.currentTimeMillis(); } public long getCreationTime() { - return creationTime; + return 0; //creationTime; } /** - * Returns the item that was present before the action took place. + * Returns a clone of the item that was present before the action took place. * - * @return source item + * @return clone of the source item */ public Item getSourceItem() { return sourceItem.clone(); } /** - * Returns the item that the action attempted to replace the source item with. + * Returns the item that was present before the action took place. * - * @return target item + * @return source item + */ + public Item getSourceItemUnsafe() { + return sourceItem; + } + + /** + * Returns a clone of the item that the action attempted to replace the source item with. + * + * @return clone of the target item */ public Item getTargetItem() { return targetItem.clone(); } + /** + * Returns the item that the action attempted to replace the source item with. + * + * @return target item + */ + public Item getTargetItemUnsafe() { + return targetItem; + } + /** * Called by inventory transactions before any actions are processed. If this returns false, the transaction will * be cancelled. @@ -70,7 +87,6 @@ public boolean onPreExecute(Player source) { * @param transaction to add */ public void onAddToTransaction(InventoryTransaction transaction) { - } /** diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java new file mode 100644 index 00000000000..17956901c80 --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java @@ -0,0 +1,44 @@ +package cn.nukkit.inventory.transaction.action; + +import cn.nukkit.Player; +import cn.nukkit.inventory.LoomInventory; +import cn.nukkit.item.Item; + +public class LoomItemAction extends InventoryAction { + + private final LoomInventory inventory; + + public LoomItemAction(Item sourceItem, Item targetItem, LoomInventory inventory) { + super(sourceItem, targetItem); + this.inventory = inventory; + } + + @Override + public boolean isValid(Player source) { + return source.getWindowById(Player.LOOM_WINDOW_ID) instanceof LoomInventory; + } + + @Override + public boolean execute(Player source) { + return true; + } + + @Override + public void onExecuteSuccess(Player source) { + Item first = inventory.getFirstItem(); + Item second = inventory.getSecondItem(); + if (first != null && !first.isNull()) { + first.count--; + inventory.setFirstItem(first); + } + if (second != null && !second.isNull()) { + second.count--; + inventory.setSecondItem(second); + } + } + + @Override + public void onExecuteFail(Player source) { + inventory.sendContents(source); + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java index b126b2b2552..8b1fceb1193 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/RepairItemAction.java @@ -1,11 +1,12 @@ package cn.nukkit.inventory.transaction.action; import cn.nukkit.Player; +import cn.nukkit.inventory.AnvilInventory; import cn.nukkit.item.Item; public class RepairItemAction extends InventoryAction { - private int type; + private final int type; public RepairItemAction(Item sourceItem, Item targetItem, int type) { super(sourceItem, targetItem); @@ -14,7 +15,7 @@ public RepairItemAction(Item sourceItem, Item targetItem, int type) { @Override public boolean isValid(Player source) { - return source.getWindowById(Player.ANVIL_WINDOW_ID) != null; + return source.getWindowById(Player.ANVIL_WINDOW_ID) instanceof AnvilInventory; } @Override diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/SlotChangeAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/SlotChangeAction.java index 227eb1afda0..67ac336eb4a 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/SlotChangeAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/SlotChangeAction.java @@ -1,7 +1,11 @@ package cn.nukkit.inventory.transaction.action; import cn.nukkit.Player; -import cn.nukkit.inventory.Inventory; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityContainer; +import cn.nukkit.blockentity.BlockEntityFurnace; +import cn.nukkit.entity.Entity; +import cn.nukkit.inventory.*; import cn.nukkit.inventory.transaction.InventoryTransaction; import cn.nukkit.item.Item; @@ -14,7 +18,7 @@ public class SlotChangeAction extends InventoryAction { protected Inventory inventory; - private int inventorySlot; + private final int inventorySlot; public SlotChangeAction(Inventory inventory, int inventorySlot, Item sourceItem, Item targetItem) { super(sourceItem, targetItem); @@ -47,6 +51,30 @@ public int getSlot() { * @return valid */ public boolean isValid(Player source) { + if (inventory == null || source == null || source.closed) { + return false; + } + + if (!source.isCreative() && !(inventory instanceof PlayerUIComponent) && !(inventory instanceof PlayerUIInventory) && !inventory.getViewers().contains(source)) { + source.getServer().getLogger().debug(source.getName() + ": got SlotChangeAction but player is not a viewer of " + inventory); + return false; + } + + if (inventory instanceof PlayerOffhandInventory && !source.isInventoryOpen()) { + source.getServer().getLogger().debug(source.getName() + ": got SlotChangeAction but player has no visible inventory window"); + return false; + } + + if (inventory.getHolder() instanceof BlockEntityContainer && !((BlockEntity) inventory.getHolder()).closed && (source.distanceSquared((BlockEntity) inventory.getHolder()) > 4096 || !source.getLevel().equals(((BlockEntity) inventory.getHolder()).getLevel()))) { + source.getServer().getLogger().debug(source.getName() + ": got SlotChangeAction but player is too far away from the holder of " + inventory); + return false; + } + + if (inventory.getHolder() != source && inventory.getHolder() instanceof Entity && !((Entity) inventory.getHolder()).closed && (source.distanceSquared((Entity) inventory.getHolder()) > 4096 || !source.getLevel().equals(((Entity) inventory.getHolder()).getLevel()))) { + source.getServer().getLogger().debug(source.getName() + ": got SlotChangeAction but player is too far away from the holder of " + inventory); + return false; + } + Item check = inventory.getItem(this.inventorySlot); return check.equalsExact(this.sourceItem); @@ -72,6 +100,28 @@ public void onExecuteSuccess(Player source) { viewers.remove(source); this.inventory.sendSlot(this.inventorySlot, viewers); + + if (this.inventory instanceof FurnaceInventory && this.inventorySlot == 2) { + BlockEntityFurnace blockEntityFurnace = ((FurnaceInventory) this.inventory).getHolder(); + if (blockEntityFurnace != null && !blockEntityFurnace.closed) { + blockEntityFurnace.releaseExperience(); + } + switch (this.getSourceItemUnsafe().getId()) { + case Item.IRON_INGOT: + source.awardAchievement("acquireIron"); + break; + case Item.COOKED_FISH: + source.awardAchievement("cookFish"); + break; + } + } else if (this.inventory instanceof BrewingInventory && this.inventorySlot >= 1 && this.inventorySlot <= 3) { + int itemId = this.getSourceItemUnsafe().getId(); + if (itemId == Item.POTION || itemId == Item.SPLASH_POTION || itemId == Item.LINGERING_POTION) { + if (this.getSourceItemUnsafe().getDamage() != 0) { + source.awardAchievement("potion"); + } + } + } } /** diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/SmithingItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/SmithingItemAction.java new file mode 100644 index 00000000000..40f97cd4d8c --- /dev/null +++ b/src/main/java/cn/nukkit/inventory/transaction/action/SmithingItemAction.java @@ -0,0 +1,42 @@ +package cn.nukkit.inventory.transaction.action; + +import cn.nukkit.Player; +import cn.nukkit.inventory.SmithingInventory; +import cn.nukkit.item.Item; + +/** + * @author joserobjr + */ +public class SmithingItemAction extends InventoryAction { + + private final int type; + + public SmithingItemAction(Item sourceItem, Item targetItem, int type) { + super(sourceItem, targetItem); + this.type = type; + } + + @Override + public boolean isValid(Player source) { + return source.getWindowById(Player.SMITHING_WINDOW_ID) instanceof SmithingInventory; + } + + @Override + public boolean execute(Player source) { + return true; + } + + @Override + public void onExecuteSuccess(Player source) { + // Does nothing + } + + @Override + public void onExecuteFail(Player source) { + // Does nothing + } + + public int getType() { + return type; + } +} diff --git a/src/main/java/cn/nukkit/inventory/transaction/data/UseItemOnEntityData.java b/src/main/java/cn/nukkit/inventory/transaction/data/UseItemOnEntityData.java index 354ac0e60e7..ff959c679f2 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/data/UseItemOnEntityData.java +++ b/src/main/java/cn/nukkit/inventory/transaction/data/UseItemOnEntityData.java @@ -14,5 +14,4 @@ public class UseItemOnEntityData implements TransactionData { public Item itemInHand; public Vector3 playerPos; public Vector3 clickPos; - } diff --git a/src/main/java/cn/nukkit/item/Item.java b/src/main/java/cn/nukkit/item/Item.java index 4cff55d8563..e7ecd7cc8b5 100644 --- a/src/main/java/cn/nukkit/item/Item.java +++ b/src/main/java/cn/nukkit/item/Item.java @@ -1,55 +1,55 @@ package cn.nukkit.item; +import cn.nukkit.Nukkit; import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; +import cn.nukkit.customblock.CustomBlockManager; import cn.nukkit.entity.Entity; import cn.nukkit.inventory.Fuel; import cn.nukkit.item.RuntimeItemMapping.RuntimeEntry; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.level.Level; +import cn.nukkit.level.persistence.PersistentItemDataContainer; +import cn.nukkit.level.persistence.impl.PersistentDataContainerItemWrapper; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.IntTag; -import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.nbt.tag.StringTag; -import cn.nukkit.nbt.tag.Tag; -import cn.nukkit.utils.Binary; +import cn.nukkit.nbt.tag.*; +import cn.nukkit.network.protocol.ProtocolInfo; import cn.nukkit.utils.Utils; +import cn.nukkit.utils.material.BlockType; +import cn.nukkit.utils.material.MaterialType; import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonParser; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.Pattern; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class Item implements Cloneable, BlockID, ItemID { - //Normal Item IDs +public class Item implements Cloneable, BlockID, ItemID, ProtocolInfo { - protected static String UNKNOWN_STR = "Unknown"; - public static Class[] list = null; - - protected Block block = null; + @SuppressWarnings("rawtypes") + public static Class[] list; + protected Block block; + protected MaterialType materialType; protected final int id; protected int meta; protected boolean hasMeta = true; private byte[] tags = new byte[0]; - private CompoundTag cachedNBT = null; + private CompoundTag cachedNBT; public int count; - protected int durability = 0; protected String name; + protected static final String UNKNOWN_STR = "Unknown"; + + private PersistentItemDataContainer persistentContainer; public Item(int id) { this(id, 0, 1, UNKNOWN_STR); @@ -64,7 +64,7 @@ public Item(int id, Integer meta, int count) { } public Item(int id, Integer meta, int count, String name) { - this.id = id & 0xffff; + this.id = id; if (meta != null && meta >= 0) { this.meta = meta & 0xffff; } else { @@ -72,10 +72,6 @@ public Item(int id, Integer meta, int count, String name) { } this.count = count; this.name = name; - /*f (this.block != null && this.id <= 0xff && Block.list[id] != null) { //probably useless - this.block = Block.get(this.id, this.meta); - this.name = this.block.getName(); - }*/ } public boolean hasMeta() { @@ -89,6 +85,9 @@ public boolean canBeActivated() { public static void init() { if (list == null) { list = new Class[65535]; + list[LADDER] = ItemLadder.class; //65 + list[RAIL] = ItemRail.class; //66 + list[CACTUS] = ItemCactus.class; //81 list[IRON_SHOVEL] = ItemShovelIron.class; //256 list[IRON_PICKAXE] = ItemPickaxeIron.class; //257 list[IRON_AXE] = ItemAxeIron.class; //258 @@ -166,6 +165,7 @@ public static void init() { list[SNOWBALL] = ItemSnowball.class; //332 list[BOAT] = ItemBoat.class; //333 list[LEATHER] = ItemLeather.class; //334 + list[KELP] = ItemKelp.class; //335 list[BRICK] = ItemBrick.class; //336 list[CLAY] = ItemClay.class; //337 list[SUGARCANE] = ItemSugarcane.class; //338 @@ -288,6 +288,11 @@ public static void init() { list[TURTLE_SHELL] = ItemTurtleShell.class; //469 list[PHANTOM_MEMBRANE] = ItemPhantomMembrane.class; //470 list[CROSSBOW] = ItemCrossbow.class; //471 + list[SPRUCE_SIGN] = ItemSignSpruce.class; //472 + list[BIRCH_SIGN] = ItemSignBirch.class; //473 + list[JUNGLE_SIGN] = ItemSignJungle.class; //474 + list[ACACIA_SIGN] = ItemSignAcacia.class; //475 + list[DARKOAK_SIGN] = ItemSignDarkOak.class; //476 list[SWEET_BERRIES] = ItemSweetBerries.class; //477 list[RECORD_11] = ItemRecord11.class; //510 list[RECORD_CAT] = ItemRecordCat.class; //501 @@ -302,11 +307,28 @@ public static void init() { list[RECORD_STRAD] = ItemRecordStrad.class; //508 list[RECORD_WAIT] = ItemRecordWait.class; //511 list[SHIELD] = ItemShield.class; //513 - list[RECORD_5] = ItemRecord5.class; //643 + list[COPPER_INGOT] = ItemIngotCopper.class; // 519 + list[RAW_IRON] = ItemIronRaw.class; //520 + list[RAW_GOLD] = ItemGoldRaw.class; //521 + list[RAW_COPPER] = ItemCopperRaw.class; //522 + list[RECORD_5] = ItemRecord5.class; //636 list[RECORD_RELIC] = ItemRecordRelic.class; //701 + list[DISC_FRAGMENT_5] = ItemDiscFragment5.class; //637 + list[OAK_CHEST_BOAT] = ItemChestBoatOak.class; //638 + list[BIRCH_CHEST_BOAT] = ItemChestBoatBirch.class; //639 + list[JUNGLE_CHEST_BOAT] = ItemChestBoatJungle.class; //640 + list[SPRUCE_CHEST_BOAT] = ItemChestBoatSpruce.class; //641 + list[ACACIA_CHEST_BOAT] = ItemChestBoatAcacia.class; //642 + list[DARK_OAK_CHEST_BOAT] = ItemChestBoatDarkOak.class; //643 + list[MANGROVE_CHEST_BOAT] = ItemChestBoatMangrove.class; //644 + list[ECHO_SHARD] = ItemEchoShard.class; //647 + list[RECOVERY_COMPASS] = ItemRecoveryCompass.class; //648 + list[GLOW_BERRIES] = ItemGlowBerries.class; //654 + list[CAMPFIRE] = ItemCampfire.class; //720 list[SUSPICIOUS_STEW] = ItemSuspiciousStew.class; //734 list[HONEYCOMB] = ItemHoneycomb.class; //736 list[HONEY_BOTTLE] = ItemHoneyBottle.class; //737 + list[LODESTONE_COMPASS] = ItemLodestoneCompass.class; //741 list[NETHERITE_INGOT] = ItemIngotNetherite.class; //742 list[NETHERITE_SWORD] = ItemSwordNetherite.class; //743 list[NETHERITE_SHOVEL] = ItemShovelNetherite.class; //744 @@ -318,10 +340,20 @@ public static void init() { list[NETHERITE_LEGGINGS] = ItemLeggingsNetherite.class; //750 list[NETHERITE_BOOTS] = ItemBootsNetherite.class; //751 list[NETHERITE_SCRAP] = ItemScrapNetherite.class; //752 + list[CRIMSON_SIGN] = ItemSignCrimson.class; //753 + list[WARPED_SIGN] = ItemSignWarped.class; //754 + list[CRIMSON_DOOR] = ItemDoorCrimson.class; //755 + list[WARPED_DOOR] = ItemDoorWarped.class; //756 list[WARPED_FUNGUS_ON_A_STICK] = ItemWarpedFungusOnAStick.class; //757 + list[CHAIN] = ItemChain.class; //758 list[RECORD_PIGSTEP] = ItemRecordPigstep.class; //759 + list[NETHER_SPROUTS] = ItemNetherSprouts.class; //760 + list[GOAT_HORN] = ItemGoatHorn.class; //761 + list[AMETHYST_SHARD] = ItemAmethystShard.class; //771 list[SPYGLASS] = ItemSpyglass.class; //772 list[RECORD_OTHERSIDE] = ItemRecordOtherside.class; //773 + list[SOUL_CAMPFIRE] = ItemCampfireSoul.class; //801 + list[GLOW_ITEM_FRAME] = ItemItemFrameGlow.class; //850 for (int i = 0; i < 256; ++i) { if (Block.list[i] != null) { @@ -330,26 +362,28 @@ public static void init() { } } - initCreativeItems(); + clearCreativeItems(); } - private static final ArrayList creative = new ArrayList<>(); + private static final List creative = new ObjectArrayList<>(); - private static void initCreativeItems() { - clearCreativeItems(); + public static void initCreativeItems() { + Server.getInstance().getLogger().debug("Loading creative items..."); - JsonArray itemsArray; - try (InputStream stream = Server.class.getClassLoader().getResourceAsStream("creative_items.json")) { - itemsArray = JsonParser.parseReader(new InputStreamReader(stream, StandardCharsets.UTF_8)).getAsJsonObject().getAsJsonArray("items"); - } catch (Exception e) { - throw new AssertionError("Error loading required block states!"); + if (Nukkit.DEBUG > 1 && !creative.isEmpty()) { + Server.getInstance().getLogger().warning("registerCreativeItemsNew: creativeItems is not empty!"); + } + + JsonArray itemsArray = Utils.loadJsonResource("creative_items.json").getAsJsonObject().getAsJsonArray("items"); + if (itemsArray.isEmpty()) { + throw new IllegalStateException("Empty array"); } for (JsonElement element : itemsArray) { - Item item = RuntimeItems.parseCreativeItem(element.getAsJsonObject(), true); + Item item = RuntimeItems.getMapping().parseCreativeItem(element.getAsJsonObject(), true); if (item != null && !item.getName().equals(UNKNOWN_STR)) { // Add only implemented items - addCreativeItem(item); + creative.add(item); } } } @@ -369,12 +403,12 @@ public static void addCreativeItem(Item item) { public static void removeCreativeItem(Item item) { int index = getCreativeItemIndex(item); if (index != -1) { - Item.creative.remove(index); + getCreativeItems().remove(index); } } public static boolean isCreativeItem(Item item) { - for (Item aCreative : Item.creative) { + for (Item aCreative : getCreativeItems()) { if (item.equals(aCreative, !item.isTool())) { return true; } @@ -383,18 +417,36 @@ public static boolean isCreativeItem(Item item) { } public static Item getCreativeItem(int index) { - return (index >= 0 && index < Item.creative.size()) ? Item.creative.get(index) : null; + ArrayList items = getCreativeItems(); + return (index >= 0 && index < items.size()) ? items.get(index) : null; } public static int getCreativeItemIndex(Item item) { - for (int i = 0; i < Item.creative.size(); i++) { - if (item.equals(Item.creative.get(i), !item.isTool())) { + ArrayList items = getCreativeItems(); + for (int i = 0; i < items.size(); i++) { + if (item.equals(items.get(i), !item.isTool())) { return i; } } return -1; } + public static Item get(MaterialType type) { + return get(type, 0); + } + + public static Item get(MaterialType type, Integer meta) { + return get(type, meta, 1); + } + + public static Item get(MaterialType type, Integer meta, int count) { + int legacyId = type.getLegacyId(); + if (type instanceof BlockType && legacyId > 255) { + legacyId = 255 - legacyId; + } + return get(legacyId, meta, count); + } + public static Item get(int id) { return get(id, 0); } @@ -409,7 +461,17 @@ public static Item get(int id, Integer meta, int count) { public static Item get(int id, Integer meta, int count, byte[] tags) { try { - Class c = list[id]; + Class c; + if (id < 0) { + int blockId = 255 - id; + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + c = CustomBlockManager.get().getClassType(blockId); + } else { + c = Block.list[blockId]; + } + } else { + c = list[id]; + } Item item; if (c == null) { @@ -428,9 +490,71 @@ public static Item get(int id, Integer meta, int count, byte[] tags) { item.setCompoundTag(tags); } - return item; + return item.initItem(); + } catch (Exception e) { + return new Item(id, meta, count).setCompoundTag(tags).initItem(); + } + } + + public static Item getSaved(int id, Integer meta, int count, Tag tag) { + try { + Class c; + if (id < 0) { + int blockId = 255 - id; + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + c = CustomBlockManager.get().getClassType(blockId); + } else { + c = Block.list[blockId]; + } + } else { + c = list[id]; + } + Item item; + + if (c == null) { + item = new Item(id, meta, count); + } else if (id < 256 && id != 166) { + if (meta >= 0) { + item = new ItemBlock(Block.get(id, meta), meta, count); + } else { + item = new ItemBlock(Block.get(id), meta, count); + } + } else { + item = ((Item) c.getConstructor(Integer.class, int.class).newInstance(meta, count)); + } + + if (item.count > item.getMaxStackSize()) { + item.count = item.getMaxStackSize(); // Get rid of stacked items + + if (tag instanceof CompoundTag) { + ((CompoundTag) tag).putByte("Count", item.getMaxStackSize()); // Update count in nbt + } + } + + if (tag instanceof CompoundTag) { + if (item.id == Item.SHULKER_BOX || item.id == Item.UNDYED_SHULKER_BOX) { + Tag list = ((CompoundTag) tag).getList("Items"); + if (list != null) { + for (CompoundTag compoundTag : ((ListTag) list).getAll()) { + int itemId = compoundTag.getShort("id"); + if (itemId == Item.SHULKER_BOX || itemId == Item.UNDYED_SHULKER_BOX) { + ((CompoundTag) tag).remove("Items"); // Get rid of shulker boxes inside shulker box + break; + } + } + } + } + + item.setCompoundTag((CompoundTag) tag); + } + + return item.initItem(); } catch (Exception e) { - return new Item(id, meta, count).setCompoundTag(tags); + Item item = new Item(id, meta, count); + if (tag instanceof CompoundTag) { + item.setCompoundTag((CompoundTag) tag); + } + return item.initItem(); } } @@ -440,32 +564,34 @@ public static Item fromString(String str) { int id = 0; int meta = 0; - Pattern integerPattern = Pattern.compile("^[1-9]\\d*$"); + Pattern integerPattern = Pattern.compile("^[-1-9]\\d*$"); if (integerPattern.matcher(b[0]).matches()) { id = Integer.parseInt(b[0]); } else { try { - id = Item.class.getField(b[0].toUpperCase()).getInt(null); + id = BlockID.class.getField(b[0].toUpperCase()).getInt(null); + if (id > 255) { + id = 255 - id; + } } catch (Exception ignore) { + try { + id = ItemID.class.getField(b[0].toUpperCase()).getInt(null); + } catch (Exception ignore1) { + } } } - id = id & 0xFFFF; if (b.length != 1) meta = Integer.parseInt(b[1]) & 0xFFFF; return get(id, meta); } public static Item fromJson(Map data) { - return fromJson(data, false); - } - - public static Item fromJson(Map data, boolean ignoreUnsupported) { String nbt = (String) data.get("nbt_b64"); byte[] nbtBytes; if (nbt != null) { nbtBytes = Base64.getDecoder().decode(nbt); - } else { // Support old format for backwards compat + } else { // Support old format for backwards compatibility nbt = (String) data.getOrDefault("nbt_hex", null); if (nbt == null) { nbtBytes = new byte[0]; @@ -474,10 +600,12 @@ public static Item fromJson(Map data, boolean ignoreUnsupported) } } - int id = Utils.toInt(data.get("id")); - if (ignoreUnsupported && id < 0) return null; + return get(Utils.toInt(data.get("id")), Utils.toInt(data.getOrDefault("damage", 0)), Utils.toInt(data.getOrDefault("count", 1)), nbtBytes); + } - return get(id, Utils.toInt(data.getOrDefault("damage", 0)), Utils.toInt(data.getOrDefault("count", 1)), nbtBytes); + public static Item fromJsonOld(Map data) { + String nbt = (String) data.getOrDefault("nbt_hex", ""); + return get(Utils.toInt(data.get("id")), Utils.toInt(data.getOrDefault("damage", 0)), Utils.toInt(data.getOrDefault("count", 1)), nbt.isEmpty() ? new byte[0] : Utils.parseHexBinary(nbt)); } public static Item[] fromStringMultiple(String str) { @@ -594,7 +722,7 @@ public Enchantment getEnchantment(short id) { if (entry.getShort("id") == id) { Enchantment e = Enchantment.getEnchantment(entry.getShort("id")); if (e != null) { - e.setLevel(entry.getShort("lvl"), false); + e.setLevel(entry.getShort("lvl")); return e; } } @@ -656,7 +784,7 @@ public Enchantment[] getEnchantments() { for (CompoundTag entry : ench.getAll()) { Enchantment e = Enchantment.getEnchantment(entry.getShort("id")); if (e != null) { - e.setLevel(entry.getShort("lvl"), false); + e.setLevel(entry.getShort("lvl")); enchantments.add(e); } } @@ -665,34 +793,17 @@ public Enchantment[] getEnchantments() { } public boolean hasEnchantment(int id) { - return this.getEnchantment(id) != null; + Enchantment e = this.getEnchantment(id); + return e != null && e.getLevel() > 0; } - public int getRepairCost() { - if (this.hasCompoundTag()) { - CompoundTag tag = this.getNamedTag(); - if (tag.contains("RepairCost")) { - Tag repairCost = tag.get("RepairCost"); - if (repairCost instanceof IntTag) { - return ((IntTag) repairCost).data; - } - } - } - return 0; + public boolean hasEnchantment(short id) { + return this.getEnchantment(id) != null; } - public Item setRepairCost(int cost) { - if (cost <= 0 && this.hasCompoundTag()) { - return this.setNamedTag(this.getNamedTag().remove("RepairCost")); - } - - CompoundTag tag; - if (!this.hasCompoundTag()) { - tag = new CompoundTag(); - } else { - tag = this.getNamedTag(); - } - return this.setNamedTag(tag.putInt("RepairCost", cost)); + public int getEnchantmentLevel(int id) { + Enchantment e = this.getEnchantment(id); + return e == null ? 0 : e.getLevel(); } public boolean hasCustomName() { @@ -726,8 +837,13 @@ public String getCustomName() { } public Item setCustomName(String name) { - if (name == null || name.equals("")) { + if (name == null || name.isEmpty()) { this.clearCustomName(); + return this; + } + + if (name.length() > 100) { + name = name.substring(0, 100); } CompoundTag tag; @@ -810,7 +926,7 @@ public Item setLore(String... lines) { public Tag getNamedTagEntry(String name) { CompoundTag tag = this.getNamedTag(); if (tag != null) { - return tag.contains(name) ? tag.get(name) : null; + return tag.get(name); } return null; @@ -825,9 +941,7 @@ public CompoundTag getNamedTag() { this.cachedNBT = parseCompoundTag(this.tags); } - if (this.cachedNBT != null) { - this.cachedNBT.setName(""); - } + this.cachedNBT.setName(""); return this.cachedNBT; } @@ -897,12 +1011,23 @@ public Block getBlockUnsafe() { return this.block; } + public int getBlockId() { + return block == null ? 0 : block.getId(); + } + public int getId() { return id; } + public MaterialType getItemType() { + if (this.materialType == null) { + this.materialType = ItemTypes.getFromLegacy(this.id); + } + return this.materialType; + } + public int getDamage() { - return meta; + return meta == 0xffff ? 0 : meta; } public void setDamage(Integer meta) { @@ -979,6 +1104,10 @@ public boolean isHelmet() { return false; } + public boolean canBePutInHelmetSlot() { + return false; + } + public boolean isChestplate() { return false; } @@ -1021,11 +1150,12 @@ public boolean onRelease(Player player, int ticksUsed) { @Override final public String toString() { - return "Item " + this.name + " (" + this.id + ":" + (!this.hasMeta ? "?" : this.meta) + ")x" + this.count + (this.hasCompoundTag() ? " tags:0x" + Binary.bytesToHexString(this.getCompoundTag()) : ""); - } - - public int getDestroySpeed(Block block, Player player) { - return 1; + String out = "Item " + this.name + " (" + this.id + ':' + (!this.hasMeta ? "?" : this.meta) + ")x" + this.count; + CompoundTag tag; + if (Nukkit.DEBUG > 1 && (tag = this.getNamedTag()) != null) { + out += '\n' + tag.toString(); + } + return out; } public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { @@ -1054,7 +1184,7 @@ public final boolean equals(Item item, boolean checkDamage) { } public final boolean equals(Item item, boolean checkDamage, boolean checkCompound) { - if (this.getId() == item.getId() && (!checkDamage || this.getDamage() == item.getDamage())) { + if (this.id == item.id && (!checkDamage || this.meta == item.meta)) { if (checkCompound) { if (Arrays.equals(this.getCompoundTag(), item.getCompoundTag())) { return true; @@ -1079,21 +1209,45 @@ public final boolean equalsExact(Item other) { return this.equals(other, true, true) && this.count == other.count; } - @Deprecated public final boolean deepEquals(Item item) { return equals(item, true); } - @Deprecated public final boolean deepEquals(Item item, boolean checkDamage) { return equals(item, checkDamage, true); } - @Deprecated public final boolean deepEquals(Item item, boolean checkDamage, boolean checkCompound) { return equals(item, checkDamage, checkCompound); } + public int getRepairCost() { + if (this.hasCompoundTag()) { + CompoundTag tag = this.getNamedTag(); + if (tag.contains("RepairCost")) { + Tag repairCost = tag.get("RepairCost"); + if (repairCost instanceof IntTag) { + return ((IntTag) repairCost).data; + } + } + } + return 0; + } + + public Item setRepairCost(int cost) { + if (cost <= 0 && this.hasCompoundTag()) { + return this.setNamedTag(this.getNamedTag().remove("RepairCost")); + } + + CompoundTag tag; + if (!this.hasCompoundTag()) { + tag = new CompoundTag(); + } else { + tag = this.getNamedTag(); + } + return this.setNamedTag(tag.putInt("RepairCost", cost)); + } + @Override public Item clone() { try { @@ -1112,4 +1266,36 @@ public final RuntimeEntry getRuntimeEntry() { public final int getNetworkId() { return this.getRuntimeEntry().getRuntimeId(); } + + /** + * This code runs when the item is initialized and can be overridden to for example check the item for missing nbt + * @return current item + */ + public Item initItem() { + return this; + } + + public PersistentItemDataContainer getPersistentDataContainer() { + if (this.persistentContainer == null) { + this.persistentContainer = new PersistentDataContainerItemWrapper(this); + } + return this.persistentContainer; + } + + public boolean hasPersistentDataContainer() { + return this.hasCompoundTag() && !this.getPersistentDataContainer().isEmpty(); + } + + public final Item decrement(int amount) { + return increment(-amount); + } + + public final Item increment(int amount) { + if (this.count + amount <= 0) { + return get(0); + } + Item cloned = this.clone(); + cloned.count += amount; + return cloned; + } } diff --git a/src/main/java/cn/nukkit/item/ItemAmethystShard.java b/src/main/java/cn/nukkit/item/ItemAmethystShard.java new file mode 100644 index 00000000000..c0da083d82b --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemAmethystShard.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemAmethystShard extends Item { + + public ItemAmethystShard() { + this(0, 1); + } + + public ItemAmethystShard(Integer meta) { + this(meta, 1); + } + + public ItemAmethystShard(Integer meta, int count) { + super(AMETHYST_SHARD, meta, count, "Amethyst Shard"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemApple.java b/src/main/java/cn/nukkit/item/ItemApple.java index 62d321f8ae9..0823f6159e4 100644 --- a/src/main/java/cn/nukkit/item/ItemApple.java +++ b/src/main/java/cn/nukkit/item/ItemApple.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemApple extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemAppleGold.java b/src/main/java/cn/nukkit/item/ItemAppleGold.java index 4abaf316937..5be2aa53c98 100644 --- a/src/main/java/cn/nukkit/item/ItemAppleGold.java +++ b/src/main/java/cn/nukkit/item/ItemAppleGold.java @@ -4,7 +4,7 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAppleGold extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemAppleGoldEnchanted.java b/src/main/java/cn/nukkit/item/ItemAppleGoldEnchanted.java index 3196050edc1..00b8b1e5b1d 100644 --- a/src/main/java/cn/nukkit/item/ItemAppleGoldEnchanted.java +++ b/src/main/java/cn/nukkit/item/ItemAppleGoldEnchanted.java @@ -8,6 +8,7 @@ * Package cn.nukkit.item in project nukkit. */ public class ItemAppleGoldEnchanted extends ItemEdible { + public ItemAppleGoldEnchanted() { this(0, 1); } @@ -17,7 +18,7 @@ public ItemAppleGoldEnchanted(Integer meta) { } public ItemAppleGoldEnchanted(Integer meta, int count) { - super(GOLDEN_APPLE_ENCHANTED, meta, count, "Enchanted Golden Apple"); + super(GOLDEN_APPLE_ENCHANTED, meta, count, "Enchanted Apple"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemArmor.java b/src/main/java/cn/nukkit/item/ItemArmor.java index 910b1bfedea..6957d5ade43 100644 --- a/src/main/java/cn/nukkit/item/ItemArmor.java +++ b/src/main/java/cn/nukkit/item/ItemArmor.java @@ -7,7 +7,7 @@ import cn.nukkit.network.protocol.LevelSoundEventPacket; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ abstract public class ItemArmor extends Item implements ItemDurable { @@ -98,7 +98,7 @@ public boolean onClickAir(Player player, Vector3 directionVector) { } } - return this.getCount() == 0; + return false; // We already use setItem & clear here } @Override @@ -114,6 +114,8 @@ public int getEnchantAbility() { return 25; case TIER_IRON: return 9; + case TIER_NETHERITE: + return 10; //TODO } return 0; @@ -124,4 +126,9 @@ public boolean isUnbreakable() { Tag tag = this.getNamedTagEntry("Unbreakable"); return tag instanceof ByteTag && ((ByteTag) tag).data > 0; } + + @Override + public boolean canBePutInHelmetSlot() { + return this.isHelmet(); + } } diff --git a/src/main/java/cn/nukkit/item/ItemArmorStand.java b/src/main/java/cn/nukkit/item/ItemArmorStand.java index 845a794cbe8..3f6087abcd7 100644 --- a/src/main/java/cn/nukkit/item/ItemArmorStand.java +++ b/src/main/java/cn/nukkit/item/ItemArmorStand.java @@ -1,5 +1,18 @@ package cn.nukkit.item; +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.entity.Entity; +import cn.nukkit.entity.item.EntityArmorStand; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.network.protocol.LevelEventPacket; + public class ItemArmorStand extends Item { public ItemArmorStand() { @@ -13,4 +26,63 @@ public ItemArmorStand(Integer meta) { public ItemArmorStand(Integer meta, int count) { super(ARMOR_STAND, meta, count, "Armor Stand"); } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); + + if (chunk == null) { + return false; + } + + for (Entity e : chunk.getEntities().values()) { + if (e instanceof EntityArmorStand) { + if (e.getY() == block.getY() && e.getX() == (block.getX() + 0.5) && e.getZ() == (block.getZ() + 0.5)) { + return false; + } + } + } + + CompoundTag nbt = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY())) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", getDirection((float) player.getYaw()))) + .add(new FloatTag("", 0))); + + if (this.hasCustomName()) { + nbt.putString("CustomName", this.getCustomName()); + } + + Entity entity = Entity.createEntity("ArmorStand", chunk, nbt); + + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + } + + if (entity != null) { + entity.spawnToAll(); + player.getLevel().addLevelEvent(entity, LevelEventPacket.EVENT_SOUND_ARMOR_STAND_PLACE); + } + return true; + } + + private static float getDirection(float yaw) { + float rot = (Math.round(yaw / 22.5f / 2f) * 45f) - 180f; + if (rot < 0) { + rot += 360f; + } + return rot; + } } diff --git a/src/main/java/cn/nukkit/item/ItemArrow.java b/src/main/java/cn/nukkit/item/ItemArrow.java index 1a4107c64b5..16b24308fde 100644 --- a/src/main/java/cn/nukkit/item/ItemArrow.java +++ b/src/main/java/cn/nukkit/item/ItemArrow.java @@ -1,7 +1,9 @@ package cn.nukkit.item; +import cn.nukkit.potion.Effect; + /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemArrow extends Item { @@ -18,4 +20,89 @@ public ItemArrow(Integer meta, int count) { super(ARROW, meta, count, "Arrow"); } + public static Effect getEffect(int meta) { + switch (meta) { + case 6: + return Effect.getEffect(Effect.NIGHT_VISION).setDuration(440); + case 7: + return Effect.getEffect(Effect.NIGHT_VISION).setDuration(1200); + case 8: + return Effect.getEffect(Effect.INVISIBILITY).setDuration(440); + case 9: + return Effect.getEffect(Effect.INVISIBILITY).setDuration(1200); + case 10: + return Effect.getEffect(Effect.JUMP).setDuration(440); + case 11: + return Effect.getEffect(Effect.JUMP).setDuration(1200); + case 12: + return Effect.getEffect(Effect.JUMP).setAmplifier(1).setDuration(220); + case 13: + return Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(440); + case 14: + return Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(1200); + case 15: + return Effect.getEffect(Effect.SPEED).setDuration(440); + case 16: + return Effect.getEffect(Effect.SPEED).setDuration(1200); + case 17: + return Effect.getEffect(Effect.SPEED).setAmplifier(1).setDuration(220); + case 18: + return Effect.getEffect(Effect.SLOWNESS).setDuration(220); + case 19: + return Effect.getEffect(Effect.SLOWNESS).setDuration(600); + case 20: + return Effect.getEffect(Effect.WATER_BREATHING).setDuration(440); + case 21: + return Effect.getEffect(Effect.WATER_BREATHING).setDuration(1200); + case 22: + return Effect.getEffect(Effect.HEALING).setDuration(1); + case 23: + return Effect.getEffect(Effect.HEALING).setAmplifier(1).setDuration(1); + case 24: + return Effect.getEffect(Effect.HARMING).setDuration(1); + case 25: + return Effect.getEffect(Effect.HARMING).setAmplifier(1).setDuration(1); + case 26: + return Effect.getEffect(Effect.POISON).setDuration(100); + case 27: + return Effect.getEffect(Effect.POISON).setDuration(300); + case 28: + return Effect.getEffect(Effect.POISON).setAmplifier(1).setDuration(40); + case 29: + return Effect.getEffect(Effect.REGENERATION).setDuration(100); + case 30: + return Effect.getEffect(Effect.REGENERATION).setDuration(300); + case 31: + return Effect.getEffect(Effect.REGENERATION).setAmplifier(1).setDuration(40); + case 32: + return Effect.getEffect(Effect.STRENGTH).setDuration(440); + case 33: + return Effect.getEffect(Effect.STRENGTH).setDuration(1200); + case 34: + return Effect.getEffect(Effect.STRENGTH).setAmplifier(1).setDuration(220); + case 35: + return Effect.getEffect(Effect.WEAKNESS).setDuration(220); + case 36: + return Effect.getEffect(Effect.WEAKNESS).setDuration(600); + case 37: + return Effect.getEffect(Effect.WITHER).setAmplifier(1).setDuration(100); + case 38: + return Effect.getEffect(Effect.SLOWNESS).setAmplifier(3).setDuration(40); + //return Effect.getEffect(Effect.DAMAGE_RESISTANCE).setAmplifier(2).setDuration(40); + case 39: + return Effect.getEffect(Effect.SLOWNESS).setAmplifier(3).setDuration(100); + //return Effect.getEffect(Effect.DAMAGE_RESISTANCE).setAmplifier(2).setDuration(100); + case 40: + return Effect.getEffect(Effect.SLOWNESS).setAmplifier(5).setDuration(40); + //return Effect.getEffect(Effect.DAMAGE_RESISTANCE).setAmplifier(3).setDuration(40); + case 41: + return Effect.getEffect(Effect.SLOW_FALLING).setDuration(220); + case 42: + return Effect.getEffect(Effect.SLOW_FALLING).setDuration(600); + case 43: + return Effect.getEffect(Effect.SLOWNESS).setAmplifier(3).setDuration(40); + default: + return null; + } + } } diff --git a/src/main/java/cn/nukkit/item/ItemAxeDiamond.java b/src/main/java/cn/nukkit/item/ItemAxeDiamond.java index 5d0d2a6972d..cbe1bdfa9a1 100644 --- a/src/main/java/cn/nukkit/item/ItemAxeDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemAxeDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAxeDiamond extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemAxeGold.java b/src/main/java/cn/nukkit/item/ItemAxeGold.java index c0bf7f0f997..0007ac16ca5 100644 --- a/src/main/java/cn/nukkit/item/ItemAxeGold.java +++ b/src/main/java/cn/nukkit/item/ItemAxeGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAxeGold extends ItemTool { @@ -15,7 +15,7 @@ public ItemAxeGold(Integer meta) { } public ItemAxeGold(Integer meta, int count) { - super(GOLD_AXE, meta, count, "Gold Axe"); + super(GOLD_AXE, meta, count, "Golden Axe"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemAxeIron.java b/src/main/java/cn/nukkit/item/ItemAxeIron.java index c1157ed953b..8e60ab2d1cb 100644 --- a/src/main/java/cn/nukkit/item/ItemAxeIron.java +++ b/src/main/java/cn/nukkit/item/ItemAxeIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAxeIron extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemAxeStone.java b/src/main/java/cn/nukkit/item/ItemAxeStone.java index e3c24b2c959..7275ebdf025 100644 --- a/src/main/java/cn/nukkit/item/ItemAxeStone.java +++ b/src/main/java/cn/nukkit/item/ItemAxeStone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAxeStone extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemAxeWood.java b/src/main/java/cn/nukkit/item/ItemAxeWood.java index 78380960555..1225cafc5c1 100644 --- a/src/main/java/cn/nukkit/item/ItemAxeWood.java +++ b/src/main/java/cn/nukkit/item/ItemAxeWood.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemAxeWood extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemBanner.java b/src/main/java/cn/nukkit/item/ItemBanner.java index d44e43c49da..1c2cc561951 100644 --- a/src/main/java/cn/nukkit/item/ItemBanner.java +++ b/src/main/java/cn/nukkit/item/ItemBanner.java @@ -6,9 +6,6 @@ import cn.nukkit.utils.BannerPattern; import cn.nukkit.utils.DyeColor; -/** - * Created by PetteriM1 - */ public class ItemBanner extends Item { public ItemBanner() { @@ -65,7 +62,7 @@ public BannerPattern getPattern(int index) { public void removePattern(int index) { CompoundTag tag = this.hasCompoundTag() ? this.getNamedTag() : new CompoundTag(); ListTag patterns = tag.getList("Patterns", CompoundTag.class); - if(patterns.size() > index && index >= 0) { + if (patterns.size() > index && index >= 0) { patterns.remove(index); } this.setNamedTag(tag); @@ -75,7 +72,7 @@ public int getPatternsSize() { return (this.hasCompoundTag() ? this.getNamedTag() : new CompoundTag()).getList("Patterns").size(); } - public void correctNBT() { - + public boolean hasPattern() { + return (this.hasCompoundTag() ? this.getNamedTag() : new CompoundTag()).contains("Patterns"); } } diff --git a/src/main/java/cn/nukkit/item/ItemBed.java b/src/main/java/cn/nukkit/item/ItemBed.java index f2103b0942b..eb41df272b4 100644 --- a/src/main/java/cn/nukkit/item/ItemBed.java +++ b/src/main/java/cn/nukkit/item/ItemBed.java @@ -1,11 +1,10 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; import cn.nukkit.utils.DyeColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBed extends Item { @@ -20,7 +19,7 @@ public ItemBed(Integer meta) { public ItemBed(Integer meta, int count) { super(BED, meta, count, DyeColor.getByWoolData(meta).getName() + " Bed"); - this.block = Block.get(BlockID.BED_BLOCK); + this.block = Block.get(BED_BLOCK); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemBeefRaw.java b/src/main/java/cn/nukkit/item/ItemBeefRaw.java index 346d1777cea..435e8c3d06f 100644 --- a/src/main/java/cn/nukkit/item/ItemBeefRaw.java +++ b/src/main/java/cn/nukkit/item/ItemBeefRaw.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBeefRaw extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemBeetroot.java b/src/main/java/cn/nukkit/item/ItemBeetroot.java index f2387e2acf4..92ffd89c60d 100644 --- a/src/main/java/cn/nukkit/item/ItemBeetroot.java +++ b/src/main/java/cn/nukkit/item/ItemBeetroot.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBeetroot extends ItemEdible { @@ -17,5 +17,4 @@ public ItemBeetroot(Integer meta) { public ItemBeetroot(Integer meta, int count) { super(BEETROOT, meta, count, "Beetroot"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemBeetrootSoup.java b/src/main/java/cn/nukkit/item/ItemBeetrootSoup.java index a835e8f22db..44f13c52bd8 100644 --- a/src/main/java/cn/nukkit/item/ItemBeetrootSoup.java +++ b/src/main/java/cn/nukkit/item/ItemBeetrootSoup.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBeetrootSoup extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemBlazePowder.java b/src/main/java/cn/nukkit/item/ItemBlazePowder.java index a233d4fc8ad..348d3e1aa70 100644 --- a/src/main/java/cn/nukkit/item/ItemBlazePowder.java +++ b/src/main/java/cn/nukkit/item/ItemBlazePowder.java @@ -1,12 +1,9 @@ package cn.nukkit.item; -/** - * Created by Leonidius20 on 18.08.18. - */ public class ItemBlazePowder extends Item { public ItemBlazePowder() { - this(0); + this(0, 1); } public ItemBlazePowder(Integer meta) { @@ -14,6 +11,6 @@ public ItemBlazePowder(Integer meta) { } public ItemBlazePowder(Integer meta, int count) { - super(BLAZE_POWDER, meta, count, "Blaze Powder"); + super(BLAZE_POWDER, 0, count, "Blaze Powder"); } } diff --git a/src/main/java/cn/nukkit/item/ItemBlazeRod.java b/src/main/java/cn/nukkit/item/ItemBlazeRod.java index e02ad9ab4cc..2e50243b1b8 100644 --- a/src/main/java/cn/nukkit/item/ItemBlazeRod.java +++ b/src/main/java/cn/nukkit/item/ItemBlazeRod.java @@ -16,5 +16,4 @@ public ItemBlazeRod(Integer meta) { public ItemBlazeRod(Integer meta, int count) { super(BLAZE_ROD, meta, count, "Blaze Rod"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemBlock.java b/src/main/java/cn/nukkit/item/ItemBlock.java index f27338237a6..2aa0f6f3a76 100644 --- a/src/main/java/cn/nukkit/item/ItemBlock.java +++ b/src/main/java/cn/nukkit/item/ItemBlock.java @@ -1,12 +1,15 @@ package cn.nukkit.item; import cn.nukkit.block.Block; +import cn.nukkit.customblock.container.BlockStorageContainer; +import cn.nukkit.utils.material.MaterialType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBlock extends Item { + public ItemBlock(Block block) { this(block, 0, 1); } @@ -15,8 +18,17 @@ public ItemBlock(Block block, Integer meta) { this(block, meta, 1); } + public ItemBlock(Block block, int meta) { + this(block, meta, 1); + } + public ItemBlock(Block block, Integer meta, int count) { - super(block.getId(), meta, count, block.getName()); + super(block.getItemId(), meta, count, block.getName()); + this.block = block; + } + + public ItemBlock(Block block, int meta, int count) { + super(block.getItemId(), meta, count, block.getName()); this.block = block; } @@ -26,7 +38,12 @@ public void setDamage(Integer meta) { } else { this.hasMeta = false; } - this.block.setDamage(meta); + + if (this.block instanceof BlockStorageContainer) { + ((BlockStorageContainer) this.block).setStorageFromItem(meta == null ? 0 : meta); + } else { + this.block.setDamage(meta); + } } @Override @@ -40,14 +57,17 @@ public Block getBlock() { return this.block.clone(); } + @Override + public MaterialType getItemType() { + return this.block.getBlockType(); + } + @Override public int getMaxStackSize() { - //Shulker boxes don't stack! if (this.block.getId() == Block.SHULKER_BOX || this.block.getId() == Block.UNDYED_SHULKER_BOX) { return 1; } return super.getMaxStackSize(); } - } diff --git a/src/main/java/cn/nukkit/item/ItemBoat.java b/src/main/java/cn/nukkit/item/ItemBoat.java index 06bade567d4..8adf7e73e90 100644 --- a/src/main/java/cn/nukkit/item/ItemBoat.java +++ b/src/main/java/cn/nukkit/item/ItemBoat.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockWater; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityBoat; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -37,11 +36,11 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { if (face != BlockFace.UP || block instanceof BlockWater) return false; - EntityBoat boat = (EntityBoat) Entity.createEntity("Boat", - level.getChunk(block.getFloorX() >> 4, block.getFloorZ() >> 4), new CompoundTag("") + EntityBoat boat = new EntityBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") .putList(new ListTag("Pos") .add(new DoubleTag("", block.getX() + 0.5)) - .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.0625 : 0))) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) .add(new DoubleTag("", block.getZ() + 0.5))) .putList(new ListTag("Motion") .add(new DoubleTag("", 0)) @@ -50,17 +49,11 @@ public boolean onActivate(Level level, Player player, Block block, Block target, .putList(new ListTag("Rotation") .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) .add(new FloatTag("", 0))) - .putByte("woodID", this.getDamage()) + .putInt("Variant", this.getDamage()) ); - if (boat == null) { - return false; - } - - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); + if (!player.isCreative()) { + this.count--; } boat.spawnToAll(); diff --git a/src/main/java/cn/nukkit/item/ItemBone.java b/src/main/java/cn/nukkit/item/ItemBone.java index 2cb1d4775f8..2786ab21e7a 100644 --- a/src/main/java/cn/nukkit/item/ItemBone.java +++ b/src/main/java/cn/nukkit/item/ItemBone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBone extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemBook.java b/src/main/java/cn/nukkit/item/ItemBook.java index 83edef9fa44..3c9eb643dbd 100644 --- a/src/main/java/cn/nukkit/item/ItemBook.java +++ b/src/main/java/cn/nukkit/item/ItemBook.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBook extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemBookEnchanted.java b/src/main/java/cn/nukkit/item/ItemBookEnchanted.java index 726c95b56b1..fb5279aa823 100644 --- a/src/main/java/cn/nukkit/item/ItemBookEnchanted.java +++ b/src/main/java/cn/nukkit/item/ItemBookEnchanted.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBookEnchanted extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemBookWritable.java b/src/main/java/cn/nukkit/item/ItemBookWritable.java index 383aa5f753f..6c9ea824a47 100644 --- a/src/main/java/cn/nukkit/item/ItemBookWritable.java +++ b/src/main/java/cn/nukkit/item/ItemBookWritable.java @@ -164,7 +164,7 @@ public boolean insertPage(int pageId, String pageText) { return true; } - /** + /** * Switches the text of two pages with each other. * @return boolean indicating success */ diff --git a/src/main/java/cn/nukkit/item/ItemBookWritten.java b/src/main/java/cn/nukkit/item/ItemBookWritten.java index 9d09f1a5852..1bca8290785 100644 --- a/src/main/java/cn/nukkit/item/ItemBookWritten.java +++ b/src/main/java/cn/nukkit/item/ItemBookWritten.java @@ -36,7 +36,7 @@ public Item writeBook(String author, String title, String[] pages) { } public Item writeBook(String author, String title, ListTag pages) { - if (pages.size() > 50 || pages.size() <= 0) return this; //Minecraft does not support more than 50 pages + if (pages.size() > 50 || pages.size() <= 0) return this; // Minecraft does not support more than 50 pages CompoundTag tag = this.hasCompoundTag() ? this.getNamedTag() : new CompoundTag(); tag.putString("author", author); diff --git a/src/main/java/cn/nukkit/item/ItemBootsChain.java b/src/main/java/cn/nukkit/item/ItemBootsChain.java index dd713eacdf3..82048caccef 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsChain.java +++ b/src/main/java/cn/nukkit/item/ItemBootsChain.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBootsChain extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemBootsDiamond.java b/src/main/java/cn/nukkit/item/ItemBootsDiamond.java index d1283d0a868..6a16233c5cf 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemBootsDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBootsDiamond extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemBootsGold.java b/src/main/java/cn/nukkit/item/ItemBootsGold.java index 1cd689442bb..9df6d08c631 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsGold.java +++ b/src/main/java/cn/nukkit/item/ItemBootsGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBootsGold extends ItemArmor { @@ -15,7 +15,7 @@ public ItemBootsGold(Integer meta) { } public ItemBootsGold(Integer meta, int count) { - super(GOLD_BOOTS, meta, count, "Gold Boots"); + super(GOLD_BOOTS, meta, count, "Golden Boots"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemBootsIron.java b/src/main/java/cn/nukkit/item/ItemBootsIron.java index 0f6fed366d5..4d00db5a2f5 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsIron.java +++ b/src/main/java/cn/nukkit/item/ItemBootsIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBootsIron extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemBootsLeather.java b/src/main/java/cn/nukkit/item/ItemBootsLeather.java index f8409136edf..3c33510fc66 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsLeather.java +++ b/src/main/java/cn/nukkit/item/ItemBootsLeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBootsLeather extends ItemColorArmor { diff --git a/src/main/java/cn/nukkit/item/ItemBootsNetherite.java b/src/main/java/cn/nukkit/item/ItemBootsNetherite.java index 6761c3a26bb..961279e0256 100644 --- a/src/main/java/cn/nukkit/item/ItemBootsNetherite.java +++ b/src/main/java/cn/nukkit/item/ItemBootsNetherite.java @@ -36,6 +36,6 @@ public int getArmorPoints() { @Override public int getToughness() { - return 2; + return 3; } } diff --git a/src/main/java/cn/nukkit/item/ItemBow.java b/src/main/java/cn/nukkit/item/ItemBow.java index 019c3cf063e..41aae6d77ca 100644 --- a/src/main/java/cn/nukkit/item/ItemBow.java +++ b/src/main/java/cn/nukkit/item/ItemBow.java @@ -2,12 +2,10 @@ import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.projectile.EntityArrow; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.event.entity.EntityShootBowEvent; import cn.nukkit.event.entity.ProjectileLaunchEvent; -import cn.nukkit.inventory.Inventory; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; @@ -15,11 +13,10 @@ import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBow extends ItemTool { @@ -33,7 +30,11 @@ public ItemBow(Integer meta) { } public ItemBow(Integer meta, int count) { - super(BOW, meta, count, "Bow"); + this(BOW, meta, count, "Bow"); + } + + public ItemBow(int id, Integer meta, int count, String name) { + super(id, meta, count, name); } @Override @@ -48,23 +49,44 @@ public int getEnchantAbility() { @Override public boolean onClickAir(Player player, Vector3 directionVector) { - return player.getInventory().contains(Item.get(ItemID.ARROW)) || player.isCreative(); + return playerHasArrow(player) || player.isCreative(); + } + + private boolean playerHasArrow(Player p) { + if (p.getOffhandInventory().getItemFast(0).getId() == ItemID.ARROW) return true; + for (Item i : p.getInventory().getContents().values()) { + if (i.getId() == ItemID.ARROW) return true; + } + return false; } @Override public boolean onRelease(Player player, int ticksUsed) { - Item itemArrow = Item.get(Item.ARROW, 0, 1); - - Inventory inventory = player.getOffhandInventory(); + Item itemArrow = null; + boolean offhand = false; + if (player.getOffhandInventory().getItemFast(0).getId() == ItemID.ARROW) { + itemArrow = player.getOffhandInventory().getItemFast(0).clone(); + itemArrow.setCount(1); + offhand = true; + } else { + for (Item i : player.getInventory().getContents().values()) { + if (i.getId() == ItemID.ARROW) { + itemArrow = i.clone(); + itemArrow.setCount(1); + break; + } + } + } - if (!inventory.contains(itemArrow) && !(inventory = player.getInventory()).contains(itemArrow) && (player.isAdventure() || player.isSurvival())) { - player.getOffhandInventory().sendContents(player); - inventory.sendContents(player); - return false; + if (itemArrow == null) { + if (player.isCreative()) { + itemArrow = Item.get(Item.ARROW, 0, 1); + } else { + return false; + } } double damage = 2; - Enchantment bowDamage = this.getEnchantment(Enchantment.ID_BOW_POWER); if (bowDamage != null && bowDamage.getLevel() > 0) { damage += (double) bowDamage.getLevel() * 0.5 + 0.5; @@ -73,6 +95,12 @@ public boolean onRelease(Player player, int ticksUsed) { Enchantment flameEnchant = this.getEnchantment(Enchantment.ID_BOW_FLAME); boolean flame = flameEnchant != null && flameEnchant.getLevel() > 0; + float knockBack = 0.3f; + Enchantment knockBackEnchantment = this.getEnchantment(Enchantment.ID_BOW_KNOCKBACK); + if (knockBackEnchantment != null) { + knockBack += knockBackEnchantment.getLevel() * 0.1f; + } + CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") .add(new DoubleTag("", player.x)) @@ -85,18 +113,17 @@ public boolean onRelease(Player player, int ticksUsed) { .putList(new ListTag("Rotation") .add(new FloatTag("", (player.yaw > 180 ? 360 : 0) - (float) player.yaw)) .add(new FloatTag("", (float) -player.pitch))) - .putShort("Fire", flame ? 45 * 60 : 0) - .putDouble("damage", damage); + .putShort("Fire", flame ? 2700 : 0) + .putDouble("damage", damage) + .putFloat("knockback", knockBack); double p = (double) ticksUsed / 20; - double f = Math.min((p * p + p * 2) / 3, 1) * 2; - - EntityArrow arrow = (EntityArrow) Entity.createEntity("Arrow", player.chunk, nbt, player, f == 2); + double f = Math.min((p * p + p * 2) / 3, 1) * 2.8; - if (arrow == null) { - return false; + EntityArrow arrow = new EntityArrow(player.chunk, nbt, player, f > 2.3, false); + if (itemArrow.getDamage() > 0) { + arrow.setData(itemArrow.getDamage()); } - EntityShootBowEvent entityShootBowEvent = new EntityShootBowEvent(player, this, arrow, f); if (f < 0.1 || ticksUsed < 3) { @@ -105,25 +132,29 @@ public boolean onRelease(Player player, int ticksUsed) { Server.getInstance().getPluginManager().callEvent(entityShootBowEvent); if (entityShootBowEvent.isCancelled()) { - entityShootBowEvent.getProjectile().kill(); - player.getInventory().sendContents(player); - player.getOffhandInventory().sendContents(player); + entityShootBowEvent.getProjectile().close(); + return false; } else { entityShootBowEvent.getProjectile().setMotion(entityShootBowEvent.getProjectile().getMotion().multiply(entityShootBowEvent.getForce())); Enchantment infinityEnchant = this.getEnchantment(Enchantment.ID_BOW_INFINITY); boolean infinity = infinityEnchant != null && infinityEnchant.getLevel() > 0; EntityProjectile projectile; - if (infinity && (projectile = entityShootBowEvent.getProjectile()) instanceof EntityArrow) { + if (infinity && itemArrow.getDamage() == 0 && (projectile = entityShootBowEvent.getProjectile()) instanceof EntityArrow) { ((EntityArrow) projectile).setPickupMode(EntityArrow.PICKUP_CREATIVE); } - if (player.isAdventure() || player.isSurvival()) { - if (!infinity) { - inventory.removeItem(itemArrow); + if (!player.isCreative()) { + if (!infinity || itemArrow.getDamage() != 0) { + if (offhand) { + player.getOffhandInventory().removeItem(itemArrow); + } else { + player.getInventory().removeItem(itemArrow); + } } + if (!this.isUnbreakable()) { Enchantment durability = this.getEnchantment(Enchantment.ID_DURABILITY); - if (!(durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= new Random().nextInt(100))) { - this.setDamage(this.getDamage() + 1); + if (!(durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= Utils.random.nextInt(100))) { + this.setDamage(this.getDamage() + 2); if (this.getDamage() >= getMaxDurability()) { this.count--; } @@ -131,13 +162,15 @@ public boolean onRelease(Player player, int ticksUsed) { } } } + if (entityShootBowEvent.getProjectile() != null) { - ProjectileLaunchEvent projectev = new ProjectileLaunchEvent(entityShootBowEvent.getProjectile()); + EntityProjectile proj = entityShootBowEvent.getProjectile(); + ProjectileLaunchEvent projectev = new ProjectileLaunchEvent(proj); Server.getInstance().getPluginManager().callEvent(projectev); if (projectev.isCancelled()) { - entityShootBowEvent.getProjectile().kill(); + proj.close(); } else { - entityShootBowEvent.getProjectile().spawnToAll(); + proj.spawnToAll(); player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_BOW); } } diff --git a/src/main/java/cn/nukkit/item/ItemBowl.java b/src/main/java/cn/nukkit/item/ItemBowl.java index 886fd824cee..ea866c81cac 100644 --- a/src/main/java/cn/nukkit/item/ItemBowl.java +++ b/src/main/java/cn/nukkit/item/ItemBowl.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBowl extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemBread.java b/src/main/java/cn/nukkit/item/ItemBread.java index 49f52fc0bf7..1334d450348 100644 --- a/src/main/java/cn/nukkit/item/ItemBread.java +++ b/src/main/java/cn/nukkit/item/ItemBread.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBread extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemBrewingStand.java b/src/main/java/cn/nukkit/item/ItemBrewingStand.java index 6a70de3d858..aeadd0ca451 100644 --- a/src/main/java/cn/nukkit/item/ItemBrewingStand.java +++ b/src/main/java/cn/nukkit/item/ItemBrewingStand.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemBrewingStand extends Item { @@ -15,7 +14,6 @@ public ItemBrewingStand(Integer meta) { public ItemBrewingStand(Integer meta, int count) { super(BREWING_STAND, 0, count, "Brewing Stand"); - this.block = Block.get(BlockID.BREWING_STAND_BLOCK); + this.block = Block.get(BREWING_STAND_BLOCK); } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/ItemBrick.java b/src/main/java/cn/nukkit/item/ItemBrick.java index 0f1414b7271..0339d287d41 100644 --- a/src/main/java/cn/nukkit/item/ItemBrick.java +++ b/src/main/java/cn/nukkit/item/ItemBrick.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBrick extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemBucket.java b/src/main/java/cn/nukkit/item/ItemBucket.java index baf7eb4c70c..2c9a1580298 100644 --- a/src/main/java/cn/nukkit/item/ItemBucket.java +++ b/src/main/java/cn/nukkit/item/ItemBucket.java @@ -15,7 +15,7 @@ import cn.nukkit.network.protocol.UpdateBlockPacket; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemBucket extends Item { @@ -48,6 +48,8 @@ protected static String getName(int meta) { return "Water Bucket"; case 10: return "Lava Bucket"; + case 11: + return "Powder Snow Bucket"; case 12: return "Bucket of Axolotl"; case 13: @@ -64,15 +66,16 @@ public static int getDamageByTarget(int target) { case 4: case 5: case 8: - case 9: + case 9: // still water case 12: case 13: - return 8; + return WATER; case 10: - case 11: - return 10; + return LAVA; + case 11: // was still lava, now powder snow + return POWDER_SNOW; default: - return 0; + return AIR; } } @@ -83,7 +86,7 @@ public int getMaxStackSize() { @Override public boolean canBeActivated() { - return true; + return this.getDamage() != 1; // Not milk } @Override @@ -92,26 +95,63 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - Block targetBlock = Block.get(getDamageByTarget(this.meta)); + Block targetBlock; + if (target instanceof BlockPowderSnow && this.getDamage() == 0) { + PlayerBucketFillEvent ev = new PlayerBucketFillEvent(player, block, face, this, Item.get(BUCKET, 11, 1)); + player.getServer().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + player.getLevel().setBlock(target, BlockLayer.NORMAL, Block.get(BlockID.AIR), true, true); + + if (!player.isCreative()) { + if (this.getCount() - 1 <= 0) { + player.getInventory().setItemInHand(ev.getItem()); + } else { + Item clone = this.clone(); + clone.setCount(this.getCount() - 1); + player.getInventory().setItemInHand(clone); + if (player.getInventory().canAddItem(ev.getItem())) { + player.getInventory().addItem(ev.getItem()); + } else { + player.dropItem(ev.getItem()); + } + } + } + + level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_BUCKET_FILL_POWDER_SNOW); + return true; + } else { + player.getInventory().sendContents(player); + } + } else if ((targetBlock = Block.get(getDamageByTarget(this.meta))) instanceof BlockAir) { + if (!(target instanceof BlockLiquid) || target.getDamage() != 0) { + target = target.getLevelBlock(BlockLayer.WATERLOGGED); + } + if (!(target instanceof BlockLiquid) || target.getDamage() != 0) { + target = block; + } + if (!(target instanceof BlockLiquid) || target.getDamage() != 0) { + target = block.getLevelBlock(BlockLayer.WATERLOGGED); + } - if (targetBlock instanceof BlockAir) { if (target instanceof BlockLiquid && target.getDamage() == 0) { Item result = Item.get(BUCKET, getDamageByTarget(target.getId()), 1); PlayerBucketFillEvent ev; player.getServer().getPluginManager().callEvent(ev = new PlayerBucketFillEvent(player, block, face, this, result)); + if (!ev.isCancelled()) { - player.getLevel().setBlock(target, Block.get(BlockID.AIR), true, true); + player.getLevel().setBlock(target, target.getLayer(), Block.get(BlockID.AIR), true, true); // When water is removed ensure any adjacent still water is // replaced with water that can flow. for (BlockFace side : Plane.HORIZONTAL) { - Block b = target.getSide(side); + Block b = target.getSide(Block.LAYER_NORMAL, side); if (b.getId() == STILL_WATER) { level.setBlock(b, Block.get(BlockID.WATER)); } } - if (player.isSurvival()) { + if (!player.isCreative()) { if (this.getCount() - 1 <= 0) { player.getInventory().setItemInHand(ev.getItem()); } else { @@ -139,7 +179,28 @@ public boolean onActivate(Level level, Player player, Block block, Block target, } } else if (targetBlock instanceof BlockLiquid) { Item result = Item.get(BUCKET, 0, 1); + boolean usesWaterlogging = ((BlockLiquid) targetBlock).usesWaterLogging(); + Block placementBlock; + if (usesWaterlogging) { + if (block.getId() == BlockID.BAMBOO) { + placementBlock = block; + } else if (target.getWaterloggingType() != Block.WaterloggingType.NO_WATERLOGGING) { + placementBlock = target.getLevelBlock(BlockLayer.WATERLOGGED); + } else if (block.getWaterloggingType() != Block.WaterloggingType.NO_WATERLOGGING) { + placementBlock = block.getLevelBlock(BlockLayer.WATERLOGGED); + } else { + placementBlock = block; + } + } else { + placementBlock = block; + } PlayerBucketEmptyEvent ev = new PlayerBucketEmptyEvent(player, block, face, this, result, true); + boolean canBeFlowedInto = placementBlock.canBeFlowedInto() || placementBlock.getId() == BlockID.BAMBOO; + if (usesWaterlogging) { + ev.setCancelled(placementBlock.getWaterloggingType() == Block.WaterloggingType.NO_WATERLOGGING && !canBeFlowedInto); + } else { + ev.setCancelled(!canBeFlowedInto); + } if (!block.canBeFlowedInto()) { ev.setCancelled(true); } @@ -153,8 +214,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, player.getServer().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { - player.getLevel().setBlock(block, targetBlock, true, true); - if (player.isSurvival()) { + player.getLevel().setBlock(placementBlock, placementBlock.getLayer(), targetBlock, true, true); + if (!player.isCreative()) { if (this.getCount() - 1 <= 0) { player.getInventory().setItemInHand(ev.getItem()); } else { @@ -213,7 +274,33 @@ public boolean onActivate(Level level, Player player, Block block, Block target, player.getLevel().addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_FIZZ); player.getLevel().addParticle(new ExplodeParticle(target.add(0.5, 1, 0.5))); } else { - player.getLevel().sendBlocks(new Player[]{player}, new Block[]{Block.get(Block.AIR, 0, block)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); + player.getLevel().sendBlocks(new Player[] {player}, new Block[] {block.getLevelBlock(Block.LAYER_WATERLOGGED)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, BlockLayer.WATERLOGGED); + player.getInventory().sendContents(player); + } + } else if (targetBlock instanceof BlockPowderSnow) { + PlayerBucketEmptyEvent ev = new PlayerBucketEmptyEvent(player, block, face, this, Item.get(BUCKET, 0, 1), true); + player.getServer().getPluginManager().callEvent(ev); + + if (!ev.isCancelled()) { + player.getLevel().setBlock(block, BlockLayer.NORMAL, targetBlock, true, true); + if (!player.isCreative()) { + if (this.getCount() - 1 <= 0) { + player.getInventory().setItemInHand(ev.getItem()); + } else { + Item clone = this.clone(); + clone.setCount(this.getCount() - 1); + player.getInventory().setItemInHand(clone); + if (player.getInventory().canAddItem(ev.getItem())) { + player.getInventory().addItem(ev.getItem()); + } else { + player.dropItem(ev.getItem()); + } + } + } + + level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_BUCKET_EMPTY_POWDER_SNOW); + return true; + } else { player.getInventory().sendContents(player); } } diff --git a/src/main/java/cn/nukkit/item/ItemCactus.java b/src/main/java/cn/nukkit/item/ItemCactus.java new file mode 100644 index 00000000000..8667108640c --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemCactus.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemCactus extends Item { + + public ItemCactus() { + this(0, 1); + } + + public ItemCactus(Integer meta) { + this(meta, 1); + } + + public ItemCactus(Integer meta, int count) { + super(CACTUS, 0, count, "Cactus"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemCake.java b/src/main/java/cn/nukkit/item/ItemCake.java index d3f0021ed00..0d7037c3b38 100644 --- a/src/main/java/cn/nukkit/item/ItemCake.java +++ b/src/main/java/cn/nukkit/item/ItemCake.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemCake extends Item { @@ -19,7 +18,7 @@ public ItemCake(Integer meta) { public ItemCake(Integer meta, int count) { super(CAKE, 0, count, "Cake"); - this.block = Block.get(BlockID.CAKE_BLOCK); + this.block = Block.get(CAKE_BLOCK); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemCampfire.java b/src/main/java/cn/nukkit/item/ItemCampfire.java new file mode 100644 index 00000000000..c7ba1b41579 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemCampfire.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemCampfire extends Item { + + public ItemCampfire() { + this(0, 1); + } + + public ItemCampfire(Integer meta) { + this(meta, 1); + } + + public ItemCampfire(Integer meta, int count) { + super(CAMPFIRE, meta, count, "Campfire"); + this.block = Block.get(CAMPFIRE_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemCampfireSoul.java b/src/main/java/cn/nukkit/item/ItemCampfireSoul.java new file mode 100644 index 00000000000..5a5abd82a42 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemCampfireSoul.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemCampfireSoul extends Item { + + public ItemCampfireSoul() { + this(0, 1); + } + + public ItemCampfireSoul(Integer meta) { + this(meta, 1); + } + + public ItemCampfireSoul(Integer meta, int count) { + super(SOUL_CAMPFIRE, meta, count, "Soul Campfire"); + this.block = Block.get(Block.SOUL_CAMPFIRE_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemCarrot.java b/src/main/java/cn/nukkit/item/ItemCarrot.java index 3aeba7409be..19ceefcd61a 100644 --- a/src/main/java/cn/nukkit/item/ItemCarrot.java +++ b/src/main/java/cn/nukkit/item/ItemCarrot.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemCarrot extends ItemEdible { @@ -19,7 +18,6 @@ public ItemCarrot(Integer meta) { public ItemCarrot(Integer meta, int count) { super(CARROT, 0, count, "Carrot"); - this.block = Block.get(BlockID.CARROT_BLOCK); + this.block = Block.get(CARROT_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemCarrotGolden.java b/src/main/java/cn/nukkit/item/ItemCarrotGolden.java index d11c1a3e1b5..176e1b3af85 100644 --- a/src/main/java/cn/nukkit/item/ItemCarrotGolden.java +++ b/src/main/java/cn/nukkit/item/ItemCarrotGolden.java @@ -1,6 +1,7 @@ package cn.nukkit.item; public class ItemCarrotGolden extends ItemEdible { + public ItemCarrotGolden() { this(0, 1); } diff --git a/src/main/java/cn/nukkit/item/ItemCauldron.java b/src/main/java/cn/nukkit/item/ItemCauldron.java index 59b306e9684..e53d4163393 100644 --- a/src/main/java/cn/nukkit/item/ItemCauldron.java +++ b/src/main/java/cn/nukkit/item/ItemCauldron.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: CreeperFace + * @author CreeperFace * Nukkit Project */ public class ItemCauldron extends Item { @@ -19,6 +18,6 @@ public ItemCauldron(Integer meta) { public ItemCauldron(Integer meta, int count) { super(CAULDRON, meta, count, "Cauldron"); - this.block = Block.get(BlockID.CAULDRON_BLOCK); + this.block = Block.get(CAULDRON_BLOCK); } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/ItemChain.java b/src/main/java/cn/nukkit/item/ItemChain.java new file mode 100644 index 00000000000..e5fcae87a78 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChain.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +public class ItemChain extends Item { + + public ItemChain() { + this(0, 1); + } + + public ItemChain(Integer meta) { + this(meta, 1); + } + + public ItemChain(Integer meta, int count) { + super(CHAIN, meta, count, "Chain"); + this.block = Block.get(BlockID.CHAIN_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatAcacia.java b/src/main/java/cn/nukkit/item/ItemChestBoatAcacia.java new file mode 100644 index 00000000000..7eff773b63f --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatAcacia.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatAcacia extends Item { + + public ItemChestBoatAcacia() { + this(0, 1); + } + + public ItemChestBoatAcacia(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatAcacia(Integer meta, int count) { + super(ACACIA_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 4) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatBirch.java b/src/main/java/cn/nukkit/item/ItemChestBoatBirch.java new file mode 100644 index 00000000000..770df0b4b50 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatBirch.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatBirch extends Item { + + public ItemChestBoatBirch() { + this(0, 1); + } + + public ItemChestBoatBirch(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatBirch(Integer meta, int count) { + super(BIRCH_CHEST_BOAT, meta, count, "Birch Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 2) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatDarkOak.java b/src/main/java/cn/nukkit/item/ItemChestBoatDarkOak.java new file mode 100644 index 00000000000..74d1acce80c --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatDarkOak.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatDarkOak extends Item { + + public ItemChestBoatDarkOak() { + this(0, 1); + } + + public ItemChestBoatDarkOak(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatDarkOak(Integer meta, int count) { + super(DARK_OAK_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 5) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatJungle.java b/src/main/java/cn/nukkit/item/ItemChestBoatJungle.java new file mode 100644 index 00000000000..2436d9737eb --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatJungle.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatJungle extends Item { + + public ItemChestBoatJungle() { + this(0, 1); + } + + public ItemChestBoatJungle(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatJungle(Integer meta, int count) { + super(JUNGLE_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 3) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatMangrove.java b/src/main/java/cn/nukkit/item/ItemChestBoatMangrove.java new file mode 100644 index 00000000000..a5db0e02ff5 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatMangrove.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatMangrove extends Item { + + public ItemChestBoatMangrove() { + this(0, 1); + } + + public ItemChestBoatMangrove(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatMangrove(Integer meta, int count) { + super(MANGROVE_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 6) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatOak.java b/src/main/java/cn/nukkit/item/ItemChestBoatOak.java new file mode 100644 index 00000000000..f611fad55c9 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatOak.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatOak extends Item { + + public ItemChestBoatOak() { + this(0, 1); + } + + public ItemChestBoatOak(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatOak(Integer meta, int count) { + super(OAK_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 0) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestBoatSpruce.java b/src/main/java/cn/nukkit/item/ItemChestBoatSpruce.java new file mode 100644 index 00000000000..315b293840c --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemChestBoatSpruce.java @@ -0,0 +1,64 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; +import cn.nukkit.entity.item.EntityChestBoat; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.DoubleTag; +import cn.nukkit.nbt.tag.FloatTag; +import cn.nukkit.nbt.tag.ListTag; + +public class ItemChestBoatSpruce extends Item { + + public ItemChestBoatSpruce() { + this(0, 1); + } + + public ItemChestBoatSpruce(Integer meta) { + this(meta, 1); + } + + public ItemChestBoatSpruce(Integer meta, int count) { + super(SPRUCE_CHEST_BOAT, meta, count, "Oak Boat with Chest"); + } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (face != BlockFace.UP) return false; + EntityChestBoat boat = new EntityChestBoat( + level.getChunk(block.getChunkX(), block.getChunkZ()), new CompoundTag("") + .putList(new ListTag("Pos") + .add(new DoubleTag("", block.getX() + 0.5)) + .add(new DoubleTag("", block.getY() - (target instanceof BlockWater ? 0.1 : 0))) + .add(new DoubleTag("", block.getZ() + 0.5))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (float) ((player.yaw + 90f) % 360))) + .add(new FloatTag("", 0))) + .putInt("Variant", 1) + ); + + if (!player.isCreative()) { + this.count--; + } + + boat.spawnToAll(); + return true; + } + + @Override + public int getMaxStackSize() { + return 1; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemChestplateChain.java b/src/main/java/cn/nukkit/item/ItemChestplateChain.java index b19082e7554..609a364ec9a 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateChain.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateChain.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChestplateChain extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemChestplateDiamond.java b/src/main/java/cn/nukkit/item/ItemChestplateDiamond.java index cae253764e7..55e0fe9f9cb 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChestplateDiamond extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemChestplateGold.java b/src/main/java/cn/nukkit/item/ItemChestplateGold.java index 60856b738be..cc91d7af651 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateGold.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChestplateGold extends ItemArmor { @@ -15,7 +15,7 @@ public ItemChestplateGold(Integer meta) { } public ItemChestplateGold(Integer meta, int count) { - super(GOLD_CHESTPLATE, meta, count, "Gold Chestplate"); + super(GOLD_CHESTPLATE, meta, count, "Golden Chestplate"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemChestplateIron.java b/src/main/java/cn/nukkit/item/ItemChestplateIron.java index 69c12764628..3aefbe3f0aa 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateIron.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChestplateIron extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemChestplateLeather.java b/src/main/java/cn/nukkit/item/ItemChestplateLeather.java index c6496a7af1a..561c221f116 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateLeather.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateLeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChestplateLeather extends ItemColorArmor { diff --git a/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java b/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java index 09674778ac9..3007fa3b36c 100644 --- a/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java +++ b/src/main/java/cn/nukkit/item/ItemChestplateNetherite.java @@ -36,6 +36,6 @@ public int getArmorPoints() { @Override public int getToughness() { - return 2; + return 3; } } diff --git a/src/main/java/cn/nukkit/item/ItemChickenCooked.java b/src/main/java/cn/nukkit/item/ItemChickenCooked.java index 32771b663de..7571fa9474b 100644 --- a/src/main/java/cn/nukkit/item/ItemChickenCooked.java +++ b/src/main/java/cn/nukkit/item/ItemChickenCooked.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChickenCooked extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemChickenRaw.java b/src/main/java/cn/nukkit/item/ItemChickenRaw.java index fc510835eb4..685565db5b8 100644 --- a/src/main/java/cn/nukkit/item/ItemChickenRaw.java +++ b/src/main/java/cn/nukkit/item/ItemChickenRaw.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemChickenRaw extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemChorusFruit.java b/src/main/java/cn/nukkit/item/ItemChorusFruit.java index da98bdf478f..3481763650d 100644 --- a/src/main/java/cn/nukkit/item/ItemChorusFruit.java +++ b/src/main/java/cn/nukkit/item/ItemChorusFruit.java @@ -3,9 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.math.Vector3; -/** - * Created by Leonidius20 on 20.08.18. - */ public class ItemChorusFruit extends ItemEdible { public ItemChorusFruit() { @@ -17,7 +14,7 @@ public ItemChorusFruit(Integer meta) { } public ItemChorusFruit(Integer meta, int count) { - super(CHORUS_FRUIT, meta, count, "Chorus Fruit"); + super(CHORUS_FRUIT, 0, count, "Chorus Fruit"); } @Override @@ -27,10 +24,9 @@ public boolean onClickAir(Player player, Vector3 directionVector) { @Override public boolean onUse(Player player, int ticksUsed) { - boolean successful = super.onUse(player, ticksUsed); - if (successful) { + if (super.onUse(player, ticksUsed)) { player.onChorusFruitTeleport(); } - return successful; + return true; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/item/ItemClay.java b/src/main/java/cn/nukkit/item/ItemClay.java index 864a4bc392b..a2d3763a308 100644 --- a/src/main/java/cn/nukkit/item/ItemClay.java +++ b/src/main/java/cn/nukkit/item/ItemClay.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemClay extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemClock.java b/src/main/java/cn/nukkit/item/ItemClock.java index 193e26930a4..5234e5bc8e2 100644 --- a/src/main/java/cn/nukkit/item/ItemClock.java +++ b/src/main/java/cn/nukkit/item/ItemClock.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemClock extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemClownfish.java b/src/main/java/cn/nukkit/item/ItemClownfish.java index c512ab8f090..55aa9d88011 100644 --- a/src/main/java/cn/nukkit/item/ItemClownfish.java +++ b/src/main/java/cn/nukkit/item/ItemClownfish.java @@ -15,6 +15,6 @@ public ItemClownfish(Integer meta) { } public ItemClownfish(Integer meta, int count) { - super(CLOWNFISH, meta, count, "Clownfish"); + super(CLOWNFISH, meta, count, "Tropical Fish"); } } diff --git a/src/main/java/cn/nukkit/item/ItemCoal.java b/src/main/java/cn/nukkit/item/ItemCoal.java index c4fa1c67665..46bf7064d85 100644 --- a/src/main/java/cn/nukkit/item/ItemCoal.java +++ b/src/main/java/cn/nukkit/item/ItemCoal.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemCoal extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemColorArmor.java b/src/main/java/cn/nukkit/item/ItemColorArmor.java index 1493ed92444..692c498126b 100644 --- a/src/main/java/cn/nukkit/item/ItemColorArmor.java +++ b/src/main/java/cn/nukkit/item/ItemColorArmor.java @@ -31,7 +31,6 @@ public ItemColorArmor(int id, Integer meta, int count, String name) { * @param dyeColor - Dye color data value * @return - Return colored item */ - @Deprecated public ItemColorArmor setColor(int dyeColor) { BlockColor blockColor = DyeColor.getByDyeData(dyeColor).getColor(); return setColor(blockColor.getRed(), blockColor.getGreen(), blockColor.getBlue()); diff --git a/src/main/java/cn/nukkit/item/ItemCompass.java b/src/main/java/cn/nukkit/item/ItemCompass.java index 97b0798fb04..626935709b9 100644 --- a/src/main/java/cn/nukkit/item/ItemCompass.java +++ b/src/main/java/cn/nukkit/item/ItemCompass.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemCompass extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemCookie.java b/src/main/java/cn/nukkit/item/ItemCookie.java index a7bdf28e7e8..5088dacedf4 100644 --- a/src/main/java/cn/nukkit/item/ItemCookie.java +++ b/src/main/java/cn/nukkit/item/ItemCookie.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemCookie extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemCopperRaw.java b/src/main/java/cn/nukkit/item/ItemCopperRaw.java new file mode 100644 index 00000000000..8cdce147bf6 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemCopperRaw.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemCopperRaw extends Item { + + public ItemCopperRaw() { + this(0, 1); + } + + public ItemCopperRaw(Integer meta) { + this(meta, 1); + } + + public ItemCopperRaw(Integer meta, int count) { + super(RAW_COPPER, meta, count, "Raw Copper"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemCrossbow.java b/src/main/java/cn/nukkit/item/ItemCrossbow.java index 6abd3526271..906124fc56c 100644 --- a/src/main/java/cn/nukkit/item/ItemCrossbow.java +++ b/src/main/java/cn/nukkit/item/ItemCrossbow.java @@ -1,6 +1,18 @@ package cn.nukkit.item; -public class ItemCrossbow extends ItemTool { +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.entity.projectile.EntityProjectile; +import cn.nukkit.event.entity.EntityShootBowEvent; +import cn.nukkit.event.entity.ProjectileLaunchEvent; +import cn.nukkit.item.enchantment.Enchantment; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.tag.*; +import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.Utils; + +public class ItemCrossbow extends ItemBow { public ItemCrossbow() { this(0, 1); @@ -19,6 +31,214 @@ public int getMaxDurability() { return ItemTool.DURABILITY_CROSSBOW; } + @Override + public boolean onUse(Player player, int ticksUsed) { + int needTickUsed = 25; + Enchantment enchantment = this.getEnchantment(Enchantment.ID_CROSSBOW_QUICK_CHARGE); + if (enchantment != null) { + needTickUsed -= enchantment.getLevel() * 5; //0.25s + } + + if (ticksUsed < needTickUsed) { + return true; + } + + Item itemArrow = null; + boolean offhand = false; + Item offhandItem = player.getOffhandInventory().getItemFast(0); + if (offhandItem.getId() == ItemID.ARROW) { + itemArrow = offhandItem.clone(); + itemArrow.setCount(1); + offhand = true; + } else { + for (Item i : player.getInventory().getContents().values()) { + if (i.getId() == ItemID.ARROW) { + itemArrow = i.clone(); + itemArrow.setCount(1); + break; + } + } + } + + if (itemArrow == null) { + if (player.isCreative()) { + itemArrow = Item.get(Item.ARROW, 0, 1); + } else { + return true; + } + } + + if (!this.isLoaded()) { + if (!player.isCreative()) { + if (!this.isUnbreakable()) { + Enchantment durability = this.getEnchantment(Enchantment.ID_DURABILITY); + if (!(durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= Utils.random.nextInt(100))) { + this.setDamage(this.getDamage() + 2); + if (this.getDamage() >= DURABILITY_CROSSBOW) { + this.count--; + } + } + } + + if (offhand) { + player.getOffhandInventory().removeItem(itemArrow); + } else { + player.getInventory().removeItem(itemArrow); + } + } + + player.getInventory().setItemInHand(this.loadArrow(itemArrow)); + player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_CROSSBOW_LOADING_END); + } + + return true; + } + + @Override + public boolean onClickAir(Player player, Vector3 directionVector) { + return false; // Handled directly from Player + } + + @Override + public boolean onRelease(Player player, int ticksUsed) { + return true; + } + + public Item loadArrow(Item arrow) { + if (arrow == null) { + throw new IllegalArgumentException("null cannot be loaded into a crossbow!"); + } + if (arrow.getId() != Item.ARROW) { + throw new IllegalArgumentException(arrow + " cannot be loaded into a crossbow!"); + } + if (arrow.getCount() != 1) { + throw new IllegalArgumentException("Only one arrow per crossbow is supported!"); + } + CompoundTag tag = this.getNamedTag() == null ? new CompoundTag() : this.getNamedTag(); + CompoundTag chargedItem = new CompoundTag("chargedItem") + .putByte("Count", arrow.getCount()) + .putShort("Damage", arrow.getDamage()) + .putString("Name", "minecraft:arrow"); + CompoundTag cTag; + tag.putBoolean("Charged", true).putCompound("chargedItem", chargedItem); + this.setCompoundTag(tag); + return this; + } + + public boolean isLoaded() { + Tag itemInfo = this.getNamedTagEntry("chargedItem"); + if (itemInfo != null) { + CompoundTag tag = (CompoundTag) itemInfo; + return tag.getByte("Count") > 0 && !tag.getString("Name").isEmpty(); + } + + return false; + } + + /** + * Launch the crossbow. Assumes that isLoaded() == true. + * @param player player + * @return launched successfully + */ + public boolean launchArrow(Player player) { + CompoundTag chargedItem = ((CompoundTag) this.getNamedTagEntry("chargedItem")); + if (chargedItem == null) { + throw new IllegalArgumentException("A crossbow without charged item cannot be launched!"); + } + int arrowData = chargedItem.getShort("Damage"); + CompoundTag nbt = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", player.x)) + .add(new DoubleTag("", player.y + player.getEyeHeight())) + .add(new DoubleTag("", player.z))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", -Math.sin(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", -Math.sin(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", Math.cos(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI)))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (player.yaw > 180 ? 360 : 0) - (float) player.yaw)) + .add(new FloatTag("", (float) -player.pitch))); + EntityProjectile arrow; + { + arrow = new EntityArrow(player.chunk, nbt, player, false, true); + if (arrowData > 0) { + ((EntityArrow) arrow).setData(arrowData); + } + if (this.hasEnchantment(Enchantment.ID_CROSSBOW_PIERCING)) { + arrow.piercing = 1; + } + } + EntityShootBowEvent entityShootBowEvent = new EntityShootBowEvent(player, this, arrow, 3.5); + Server.getInstance().getPluginManager().callEvent(entityShootBowEvent); + if (entityShootBowEvent.isCancelled()) { + entityShootBowEvent.getProjectile().close(); + return false; + } else { + entityShootBowEvent.getProjectile().setMotion(entityShootBowEvent.getProjectile().getMotion().multiply(entityShootBowEvent.getForce())); + if (entityShootBowEvent.getProjectile() != null) { + EntityProjectile proj = entityShootBowEvent.getProjectile(); + ProjectileLaunchEvent projectev = new ProjectileLaunchEvent(proj); + Server.getInstance().getPluginManager().callEvent(projectev); + if (projectev.isCancelled()) { + proj.close(); + return false; + } else { + proj.spawnToAll(); + if (this.hasEnchantment(Enchantment.ID_CROSSBOW_MULTISHOT)) { + CompoundTag nbt1 = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", player.x)) + .add(new DoubleTag("", player.y + player.getEyeHeight())) + .add(new DoubleTag("", player.z))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", -Math.sin(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", -Math.sin(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", Math.cos(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI)))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (player.yaw > 180 ? 360 : 0) - (float) player.yaw - 10)) + .add(new FloatTag("", (float) -player.pitch))); + EntityArrow arrow1 = new EntityArrow(player.chunk, nbt1, player, false, true); + arrow1.setPickupMode(EntityProjectile.PICKUP_NONE_REMOVE); + if (arrowData > 0) { + arrow1.setData(arrowData); + } + if (this.hasEnchantment(Enchantment.ID_CROSSBOW_PIERCING)) { // Illegal enchantment + arrow1.piercing = 1; + } + arrow1.setMotion(arrow1.getMotion().multiply(entityShootBowEvent.getForce()).add(-0.3, 0, 0.3)); + arrow1.spawnToAll(); + CompoundTag nbt2 = new CompoundTag() + .putList(new ListTag("Pos") + .add(new DoubleTag("", player.x)) + .add(new DoubleTag("", player.y + player.getEyeHeight())) + .add(new DoubleTag("", player.z))) + .putList(new ListTag("Motion") + .add(new DoubleTag("", -Math.sin(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", -Math.sin(player.pitch / 180 * Math.PI))) + .add(new DoubleTag("", Math.cos(player.yaw / 180 * Math.PI) * Math.cos(player.pitch / 180 * Math.PI)))) + .putList(new ListTag("Rotation") + .add(new FloatTag("", (player.yaw > 180 ? 360 : 0) - (float) player.yaw + 10)) + .add(new FloatTag("", (float) -player.pitch))); + EntityArrow arrow2 = new EntityArrow(player.chunk, nbt2, player, false, true); + arrow2.setPickupMode(EntityProjectile.PICKUP_NONE_REMOVE); + if (arrowData > 0) { + arrow2.setData(arrowData); + } + if (this.hasEnchantment(Enchantment.ID_CROSSBOW_PIERCING)) { // Illegal enchantment + arrow2.piercing = 1; + } + arrow2.setMotion(arrow2.getMotion().multiply(entityShootBowEvent.getForce()).add(0.3, 0, -0.3)); + arrow2.spawnToAll(); + } + player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_CROSSBOW_SHOOT); + this.setCompoundTag(this.getNamedTag().putBoolean("Charged", false).remove("chargedItem")); + player.getInventory().setItemInHand(this); + } + } + } + return true; + } + @Override public int getEnchantAbility() { return 1; diff --git a/src/main/java/cn/nukkit/item/ItemDiamond.java b/src/main/java/cn/nukkit/item/ItemDiamond.java index b3ef6a5eedd..aa6c1e4a54c 100644 --- a/src/main/java/cn/nukkit/item/ItemDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemDiamond extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemDiscFragment5.java b/src/main/java/cn/nukkit/item/ItemDiscFragment5.java new file mode 100644 index 00000000000..96a623a47df --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemDiscFragment5.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemDiscFragment5 extends Item { + + public ItemDiscFragment5() { + this(0, 1); + } + + public ItemDiscFragment5(Integer meta) { + this(meta, 1); + } + + public ItemDiscFragment5(Integer meta, int count) { + super(DISC_FRAGMENT_5, meta, count, "Disc Fragment"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemDoorAcacia.java b/src/main/java/cn/nukkit/item/ItemDoorAcacia.java index a94538a271a..69175e8b063 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorAcacia.java +++ b/src/main/java/cn/nukkit/item/ItemDoorAcacia.java @@ -1,9 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemDoorAcacia extends Item { + public ItemDoorAcacia() { this(0, 1); } @@ -14,7 +14,6 @@ public ItemDoorAcacia(Integer meta) { public ItemDoorAcacia(Integer meta, int count) { super(ACACIA_DOOR, 0, count, "Acacia Door"); - this.block = Block.get(BlockID.ACACIA_DOOR_BLOCK); + this.block = Block.get(ACACIA_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorBirch.java b/src/main/java/cn/nukkit/item/ItemDoorBirch.java index 1da599a503a..9e0f5d5225a 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorBirch.java +++ b/src/main/java/cn/nukkit/item/ItemDoorBirch.java @@ -1,9 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemDoorBirch extends Item { + public ItemDoorBirch() { this(0, 1); } @@ -14,7 +14,6 @@ public ItemDoorBirch(Integer meta) { public ItemDoorBirch(Integer meta, int count) { super(BIRCH_DOOR, 0, count, "Birch Door"); - this.block = Block.get(BlockID.BIRCH_DOOR_BLOCK); + this.block = Block.get(BIRCH_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorCrimson.java b/src/main/java/cn/nukkit/item/ItemDoorCrimson.java new file mode 100644 index 00000000000..768b54a8693 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemDoorCrimson.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemDoorCrimson extends Item { + + public ItemDoorCrimson() { + this(0, 1); + } + + public ItemDoorCrimson(Integer meta) { + this(meta, 1); + } + + public ItemDoorCrimson(Integer meta, int count) { + super(CRIMSON_DOOR, 0, count, "Crimson Door"); + this.block = Block.get(CRIMSON_DOOR_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemDoorDarkOak.java b/src/main/java/cn/nukkit/item/ItemDoorDarkOak.java index f3259b573d9..a89a23be13b 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorDarkOak.java +++ b/src/main/java/cn/nukkit/item/ItemDoorDarkOak.java @@ -1,9 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemDoorDarkOak extends Item { + public ItemDoorDarkOak() { this(0, 1); } @@ -14,7 +14,6 @@ public ItemDoorDarkOak(Integer meta) { public ItemDoorDarkOak(Integer meta, int count) { super(DARK_OAK_DOOR, 0, count, "Dark Oak Door"); - this.block = Block.get(BlockID.DARK_OAK_DOOR_BLOCK); + this.block = Block.get(DARK_OAK_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorIron.java b/src/main/java/cn/nukkit/item/ItemDoorIron.java index a10ed1b10dd..a8af1ee0ff9 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorIron.java +++ b/src/main/java/cn/nukkit/item/ItemDoorIron.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemDoorIron extends Item { @@ -19,7 +18,6 @@ public ItemDoorIron(Integer meta) { public ItemDoorIron(Integer meta, int count) { super(IRON_DOOR, 0, count, "Iron Door"); - this.block = Block.get(BlockID.IRON_DOOR_BLOCK); + this.block = Block.get(IRON_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorJungle.java b/src/main/java/cn/nukkit/item/ItemDoorJungle.java index f5a4aba4bca..1e6bdeb7f81 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorJungle.java +++ b/src/main/java/cn/nukkit/item/ItemDoorJungle.java @@ -1,9 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemDoorJungle extends Item { + public ItemDoorJungle() { this(0, 1); } @@ -14,7 +14,6 @@ public ItemDoorJungle(Integer meta) { public ItemDoorJungle(Integer meta, int count) { super(JUNGLE_DOOR, 0, count, "Jungle Door"); - this.block = Block.get(BlockID.JUNGLE_DOOR_BLOCK); + this.block = Block.get(JUNGLE_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorSpruce.java b/src/main/java/cn/nukkit/item/ItemDoorSpruce.java index 74b4c46c259..541ed4aed10 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorSpruce.java +++ b/src/main/java/cn/nukkit/item/ItemDoorSpruce.java @@ -1,9 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; public class ItemDoorSpruce extends Item { + public ItemDoorSpruce() { this(0, 1); } @@ -14,7 +14,6 @@ public ItemDoorSpruce(Integer meta) { public ItemDoorSpruce(Integer meta, int count) { super(SPRUCE_DOOR, 0, count, "Spruce Door"); - this.block = Block.get(BlockID.SPRUCE_DOOR_BLOCK); + this.block = Block.get(SPRUCE_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDoorWarped.java b/src/main/java/cn/nukkit/item/ItemDoorWarped.java new file mode 100644 index 00000000000..6d0e3d7bfa6 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemDoorWarped.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemDoorWarped extends Item { + + public ItemDoorWarped() { + this(0, 1); + } + + public ItemDoorWarped(Integer meta) { + this(meta, 1); + } + + public ItemDoorWarped(Integer meta, int count) { + super(WARPED_DOOR, 0, count, "Warped Door"); + this.block = Block.get(WARPED_DOOR_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemDoorWood.java b/src/main/java/cn/nukkit/item/ItemDoorWood.java index e1818c39a25..0ff0bd09f7c 100644 --- a/src/main/java/cn/nukkit/item/ItemDoorWood.java +++ b/src/main/java/cn/nukkit/item/ItemDoorWood.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemDoorWood extends Item { @@ -19,7 +18,6 @@ public ItemDoorWood(Integer meta) { public ItemDoorWood(Integer meta, int count) { super(WOODEN_DOOR, 0, count, "Oak Door"); - this.block = Block.get(BlockID.WOODEN_DOOR_BLOCK); + this.block = Block.get(WOODEN_DOOR_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemDriedKelp.java b/src/main/java/cn/nukkit/item/ItemDriedKelp.java index d31f7cc76d6..ae102c76738 100644 --- a/src/main/java/cn/nukkit/item/ItemDriedKelp.java +++ b/src/main/java/cn/nukkit/item/ItemDriedKelp.java @@ -1,8 +1,5 @@ package cn.nukkit.item; -/** - * Created by PetteriM1 - */ public class ItemDriedKelp extends ItemEdible { public ItemDriedKelp() { @@ -16,4 +13,9 @@ public ItemDriedKelp(Integer meta) { public ItemDriedKelp(Integer meta, int count) { super(DRIED_KELP, 0, count, "Dried Kelp"); } + + @Override + protected int getUseTicks() { + return 15; + } } diff --git a/src/main/java/cn/nukkit/item/ItemDye.java b/src/main/java/cn/nukkit/item/ItemDye.java index 1fe86b73df7..55eb1a18dab 100644 --- a/src/main/java/cn/nukkit/item/ItemDye.java +++ b/src/main/java/cn/nukkit/item/ItemDye.java @@ -6,7 +6,7 @@ import cn.nukkit.utils.DyeColor; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemDye extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemEchoShard.java b/src/main/java/cn/nukkit/item/ItemEchoShard.java new file mode 100644 index 00000000000..4168541258a --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemEchoShard.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemEchoShard extends Item { + + public ItemEchoShard() { + this(0, 1); + } + + public ItemEchoShard(Integer meta) { + this(meta, 1); + } + + public ItemEchoShard(Integer meta, int count) { + super(ECHO_SHARD, meta, count, "Echo Shard"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemEdible.java b/src/main/java/cn/nukkit/item/ItemEdible.java index c85f579e4b7..dc9b944f847 100644 --- a/src/main/java/cn/nukkit/item/ItemEdible.java +++ b/src/main/java/cn/nukkit/item/ItemEdible.java @@ -4,12 +4,14 @@ import cn.nukkit.event.player.PlayerItemConsumeEvent; import cn.nukkit.item.food.Food; import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.LevelSoundEventPacket; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class ItemEdible extends Item { + public ItemEdible(int id, Integer meta, int count, String name) { super(id, meta, count, name); } @@ -28,27 +30,33 @@ public ItemEdible(int id, Integer meta, int count) { @Override public boolean onClickAir(Player player, Vector3 directionVector) { - if (player.getFoodData().getLevel() < player.getFoodData().getMaxLevel() || player.isCreative() || player.getServer().getDifficulty() == 0) { - return true; - } - player.getFoodData().sendFoodLevel(); - return false; + return player.canEat(true); + } + + protected int getUseTicks() { + return 30; } @Override public boolean onUse(Player player, int ticksUsed) { + if (ticksUsed < this.getUseTicks()) { + player.getServer().getLogger().debug(player.getName() + ": food ticksUsed=" + ticksUsed); + return false; + } PlayerItemConsumeEvent consumeEvent = new PlayerItemConsumeEvent(player, this); player.getServer().getPluginManager().callEvent(consumeEvent); if (consumeEvent.isCancelled()) { - player.getInventory().sendContents(player); - return false; + return false; // Inventory#sendContents is called in Player } Food food = Food.getByRelative(this); - if ((player.isAdventure() || player.isSurvival()) && food != null && food.eatenBy(player)) { - --this.count; - player.getInventory().setItemInHand(this); + if (food != null && food.eatenBy(player)) { + player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_BURP); + if (!player.isCreative() && !player.isSpectator()) { + --this.count; + player.getInventory().setItemInHand(this); + } } return true; } diff --git a/src/main/java/cn/nukkit/item/ItemEgg.java b/src/main/java/cn/nukkit/item/ItemEgg.java index 419dd087c7f..52c180674f2 100644 --- a/src/main/java/cn/nukkit/item/ItemEgg.java +++ b/src/main/java/cn/nukkit/item/ItemEgg.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemEgg extends ProjectileItem { @@ -27,7 +27,9 @@ public String getProjectileEntityType() { public float getThrowForce() { return 1.5f; } - + @Override - public int getMaxStackSize() { return 16; } + public int getMaxStackSize() { + return 16; + } } diff --git a/src/main/java/cn/nukkit/item/ItemElytra.java b/src/main/java/cn/nukkit/item/ItemElytra.java index 21d525515da..1c10b3b5468 100644 --- a/src/main/java/cn/nukkit/item/ItemElytra.java +++ b/src/main/java/cn/nukkit/item/ItemElytra.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemElytra extends ItemArmor { @@ -32,5 +32,4 @@ public boolean isArmor() { public boolean isChestplate() { return true; } - } diff --git a/src/main/java/cn/nukkit/item/ItemEmerald.java b/src/main/java/cn/nukkit/item/ItemEmerald.java index 6e82a21c3a0..204ffcc767d 100644 --- a/src/main/java/cn/nukkit/item/ItemEmerald.java +++ b/src/main/java/cn/nukkit/item/ItemEmerald.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemEmerald extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemEmptyMap.java b/src/main/java/cn/nukkit/item/ItemEmptyMap.java index 41622b635c3..5fd8314bb02 100644 --- a/src/main/java/cn/nukkit/item/ItemEmptyMap.java +++ b/src/main/java/cn/nukkit/item/ItemEmptyMap.java @@ -1,5 +1,11 @@ package cn.nukkit.item; +import cn.nukkit.Player; +import cn.nukkit.block.Block; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; +import cn.nukkit.math.Vector3; + public class ItemEmptyMap extends Item { public ItemEmptyMap() { @@ -13,4 +19,27 @@ public ItemEmptyMap(Integer meta) { public ItemEmptyMap(Integer meta, int count) { super(EMPTY_MAP, meta, count, "Empty Map"); } + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { + if (!player.isCreative()) { + this.count--; + } + player.getInventory().addItem(Item.get(Item.MAP)); + return true; + } + + @Override + public boolean onClickAir(Player player, Vector3 directionVector) { + if (!player.isCreative()) { + this.count--; + } + player.getInventory().addItem(Item.get(Item.MAP)); + return true; + } } diff --git a/src/main/java/cn/nukkit/item/ItemEndCrystal.java b/src/main/java/cn/nukkit/item/ItemEndCrystal.java index 97a6984ab7e..ea1489e716d 100644 --- a/src/main/java/cn/nukkit/item/ItemEndCrystal.java +++ b/src/main/java/cn/nukkit/item/ItemEndCrystal.java @@ -15,7 +15,7 @@ import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; public class ItemEndCrystal extends Item { @@ -40,24 +40,33 @@ public boolean canBeActivated() { public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { if (!(target instanceof BlockBedrock) && !(target instanceof BlockObsidian)) return false; FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); - Entity[] entities = level.getNearbyEntities(new SimpleAxisAlignedBB(target.x, target.y, target.z, target.x + 1, target.y + 2, target.z + 1)); - Block up = target.up(); + if (chunk == null) { + return false; + } + + int tx = (int) target.x; + int ty = (int) target.y; + int tz = (int) target.z; + if (level.getBlockIdAt(chunk, tx, ty + 1, tz) != BlockID.AIR || level.getBlockIdAt(chunk, tx, ty + 2, tz) != BlockID.AIR) { + return false; + } - if (chunk == null || up.getId() != BlockID.AIR || up.up().getId() != BlockID.AIR || entities.length != 0) { + Entity[] entities = level.getCollidingEntities(new SimpleAxisAlignedBB(target.x, target.y, target.z, target.x + 1, target.y + 2, target.z + 1)); + if (entities.length != 0) { return false; } CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") .add(new DoubleTag("", target.x + 0.5)) - .add(new DoubleTag("", up.y)) + .add(new DoubleTag("", ty + 1)) .add(new DoubleTag("", target.z + 0.5))) .putList(new ListTag("Motion") .add(new DoubleTag("", 0)) .add(new DoubleTag("", 0)) .add(new DoubleTag("", 0))) .putList(new ListTag("Rotation") - .add(new FloatTag("", new Random().nextFloat() * 360)) + .add(new FloatTag("", ThreadLocalRandom.current().nextFloat() * 360)) .add(new FloatTag("", 0))); if (this.hasCustomName()) { @@ -67,11 +76,10 @@ public boolean onActivate(Level level, Player player, Block block, Block target, Entity entity = Entity.createEntity("EndCrystal", chunk, nbt); if (entity != null) { - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); } + entity.spawnToAll(); return true; } diff --git a/src/main/java/cn/nukkit/item/ItemEnderEye.java b/src/main/java/cn/nukkit/item/ItemEnderEye.java index 0c1e55cc9d7..91262e43907 100644 --- a/src/main/java/cn/nukkit/item/ItemEnderEye.java +++ b/src/main/java/cn/nukkit/item/ItemEnderEye.java @@ -1,6 +1,6 @@ package cn.nukkit.item; -public class ItemEnderEye extends Item { +public class ItemEnderEye extends ProjectileItem { public ItemEnderEye() { this(0, 1); @@ -13,4 +13,14 @@ public ItemEnderEye(Integer meta) { public ItemEnderEye(Integer meta, int count) { super(ENDER_EYE, meta, count, "Ender Eye"); } + + @Override + public String getProjectileEntityType() { + return "EnderEye"; + } + + @Override + public float getThrowForce() { + return 1.5f; + } } diff --git a/src/main/java/cn/nukkit/item/ItemExpBottle.java b/src/main/java/cn/nukkit/item/ItemExpBottle.java index 247e2824319..6957e9162d8 100644 --- a/src/main/java/cn/nukkit/item/ItemExpBottle.java +++ b/src/main/java/cn/nukkit/item/ItemExpBottle.java @@ -27,5 +27,4 @@ public String getProjectileEntityType() { public float getThrowForce() { return 1f; } - } diff --git a/src/main/java/cn/nukkit/item/ItemFeather.java b/src/main/java/cn/nukkit/item/ItemFeather.java index b6e50659c79..d63b4187d1e 100644 --- a/src/main/java/cn/nukkit/item/ItemFeather.java +++ b/src/main/java/cn/nukkit/item/ItemFeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemFeather extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemFireCharge.java b/src/main/java/cn/nukkit/item/ItemFireCharge.java index 8182b666398..2f3dc4d4744 100644 --- a/src/main/java/cn/nukkit/item/ItemFireCharge.java +++ b/src/main/java/cn/nukkit/item/ItemFireCharge.java @@ -1,17 +1,13 @@ package cn.nukkit.item; -import cn.nukkit.block.*; -import cn.nukkit.math.BlockFace; -import cn.nukkit.level.Level; import cn.nukkit.Player; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.block.*; import cn.nukkit.event.block.BlockIgniteEvent; -import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; +import cn.nukkit.math.BlockFace; +import cn.nukkit.utils.Utils; -/** - * Created by PetteriM1 - */ public class ItemFireCharge extends Item { public ItemFireCharge() { @@ -37,32 +33,32 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta)) { + if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta || target instanceof BlockLeaves)) { if (target.getId() == OBSIDIAN) { - if (level.createPortal(target)) { + if (level.createPortal(target, true)) { return true; } } - BlockFire fire = (BlockFire) Block.get(BlockID.FIRE); + int did; + BlockFire fire = (BlockFire) Block.get(((did = block.down().getId()) == SOUL_SAND || did == SOUL_SOIL) ? BlockID.SOUL_FIRE : BlockID.FIRE); fire.x = block.x; fire.y = block.y; fire.z = block.z; fire.level = level; if (fire.isBlockTopFacingSurfaceSolid(fire.down()) || fire.canNeighborBurn()) { - BlockIgniteEvent e = new BlockIgniteEvent(block, null, player, BlockIgniteEvent.BlockIgniteCause.FLINT_AND_STEEL); + BlockIgniteEvent e = new BlockIgniteEvent(block, null, player, BlockIgniteEvent.BlockIgniteCause.FIREBALL); block.getLevel().getServer().getPluginManager().callEvent(e); if (!e.isCancelled()) { level.setBlock(fire, fire, true); - level.addLevelEvent(block, LevelEventPacket.EVENT_SOUND_GHAST_SHOOT, 78642); - level.scheduleUpdate(fire, fire.tickRate() + ThreadLocalRandom.current().nextInt(10)); - } - if (player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); + level.scheduleUpdate(fire, (fire.tickRate() + Utils.random.nextInt(10))); + level.addSound(block, Sound.MOB_GHAST_FIREBALL); + + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); + } } return true; } diff --git a/src/main/java/cn/nukkit/item/ItemFirework.java b/src/main/java/cn/nukkit/item/ItemFirework.java index 2bc7e4ca8f5..2fdccf968b3 100644 --- a/src/main/java/cn/nukkit/item/ItemFirework.java +++ b/src/main/java/cn/nukkit/item/ItemFirework.java @@ -2,16 +2,12 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityFirework; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.DoubleTag; -import cn.nukkit.nbt.tag.FloatTag; -import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.nbt.tag.*; import cn.nukkit.utils.DyeColor; import java.util.ArrayList; @@ -81,18 +77,22 @@ public boolean onActivate(Level level, Player player, Block block, Block target, @Override public boolean onClickAir(Player player, Vector3 directionVector) { - if (player.getInventory().getChestplate() instanceof ItemElytra && player.isGliding()) { + if (player.getInventory().getChestplateFast() instanceof ItemElytra && player.isGliding()) { this.spawnFirework(player.getLevel(), player); - player.setMotion(new Vector3( - -Math.sin(Math.toRadians(player.yaw)) * Math.cos(Math.toRadians(player.pitch)) * 2, - -Math.sin(Math.toRadians(player.pitch)) * 2, - Math.cos(Math.toRadians(player.yaw)) * Math.cos(Math.toRadians(player.pitch)) * 2)); - if (!player.isCreative()) { this.count--; } + player.onFireworkBoost(); + + int level = this.getFlight(); + double multiplier = 1 + 0.25 * (level < 1 ? 0.25 : level); + player.setMotion(new Vector3( + -Math.sin(Math.toRadians(player.yaw)) * Math.cos(Math.toRadians(player.pitch)) * multiplier, + -Math.sin(Math.toRadians(player.pitch)) * multiplier, + Math.cos(Math.toRadians(player.yaw)) * Math.cos(Math.toRadians(player.pitch)) * multiplier)); + return true; } @@ -146,16 +146,32 @@ private void spawnFirework(Level level, Vector3 pos) { .add(new FloatTag("", 0))) .putCompound("FireworkItem", NBTIO.putItemHelper(this)); - EntityFirework entity = (EntityFirework) Entity.createEntity("Firework", level.getChunk(pos.getFloorX() >> 4, pos.getFloorZ() >> 4), nbt); - if (entity != null) { - entity.spawnToAll(); + EntityFirework entity = new EntityFirework(level.getChunk(pos.getChunkX(), pos.getChunkZ()), nbt); + entity.spawnToAll(); + } + + public int getFlight() { + int level = 0; + Tag nbt = this.getNamedTag(); + if (nbt != null) { + nbt = ((CompoundTag) nbt).get("Fireworks"); + if (nbt instanceof CompoundTag) { + level = ((CompoundTag) nbt).getByte("Flight"); + } } + return level; + } + + public void setFlight(int flight) { + CompoundTag tag = this.getNamedTag(); + tag.putCompound("Fireworks", tag.getCompound("Fireworks").putByte("Flight", flight)); + this.setNamedTag(tag); } public static class FireworkExplosion { - private List colors = new ArrayList<>(); - private List fades = new ArrayList<>(); + private final List colors = new ArrayList<>(); + private final List fades = new ArrayList<>(); private boolean flicker = false; private boolean trail = false; private ExplosionType type = ExplosionType.CREEPER_SHAPED; @@ -213,4 +229,4 @@ public enum ExplosionType { BURST } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/ItemFish.java b/src/main/java/cn/nukkit/item/ItemFish.java index ad874c5d565..f9c7c380a94 100644 --- a/src/main/java/cn/nukkit/item/ItemFish.java +++ b/src/main/java/cn/nukkit/item/ItemFish.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemFish extends ItemEdible { @@ -21,5 +21,4 @@ public ItemFish(Integer meta, int count) { protected ItemFish(int id, Integer meta, int count, String name) { super(id, meta, count, name); } - } diff --git a/src/main/java/cn/nukkit/item/ItemFishCooked.java b/src/main/java/cn/nukkit/item/ItemFishCooked.java index aef3dd90548..fe4a29c3f50 100644 --- a/src/main/java/cn/nukkit/item/ItemFishCooked.java +++ b/src/main/java/cn/nukkit/item/ItemFishCooked.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemFishCooked extends ItemFish { @@ -17,5 +17,4 @@ public ItemFishCooked(Integer meta) { public ItemFishCooked(Integer meta, int count) { super(COOKED_FISH, meta, count, "Cooked Fish"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemFishingRod.java b/src/main/java/cn/nukkit/item/ItemFishingRod.java index 431a1e11289..c6fea5d8dd9 100644 --- a/src/main/java/cn/nukkit/item/ItemFishingRod.java +++ b/src/main/java/cn/nukkit/item/ItemFishingRod.java @@ -3,10 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.math.Vector3; -/** - * Created by Snake1999 on 2016/1/14. - * Package cn.nukkit.item in project nukkit. - */ public class ItemFishingRod extends ItemTool { public ItemFishingRod() { @@ -29,11 +25,17 @@ public int getEnchantAbility() { @Override public boolean onClickAir(Player player, Vector3 directionVector) { if (player.fishing != null) { + if (player.fishing.getTarget() > 0) { + this.meta = this.meta + 2; + } else { + this.meta++; + } + player.stopFishing(true); - } else { - player.startFishing(this); - this.meta++; + } else { + player.startFishing(this); } + return true; } @@ -51,4 +53,9 @@ public boolean noDamageOnAttack() { public boolean noDamageOnBreak() { return true; } + + @Override + public boolean onUse(Player player, int ticksUsed) { + return true; + } } diff --git a/src/main/java/cn/nukkit/item/ItemFlint.java b/src/main/java/cn/nukkit/item/ItemFlint.java index 9bd87411660..c78a7444dd3 100644 --- a/src/main/java/cn/nukkit/item/ItemFlint.java +++ b/src/main/java/cn/nukkit/item/ItemFlint.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemFlint extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemFlintSteel.java b/src/main/java/cn/nukkit/item/ItemFlintSteel.java index 0a55252fb28..a5dd0473275 100644 --- a/src/main/java/cn/nukkit/item/ItemFlintSteel.java +++ b/src/main/java/cn/nukkit/item/ItemFlintSteel.java @@ -4,17 +4,18 @@ import cn.nukkit.block.*; import cn.nukkit.event.block.BlockIgniteEvent; import cn.nukkit.level.Level; +import cn.nukkit.level.Sound; +import cn.nukkit.level.particle.ItemBreakParticle; import cn.nukkit.math.BlockFace; import cn.nukkit.network.protocol.LevelSoundEventPacket; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemFlintSteel extends ItemTool { - + public ItemFlintSteel() { this(0, 1); } @@ -38,14 +39,16 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } - if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta)) { + // 1.18 vanilla allows flint & steel to be used even if fire exists + if ((block.getId() == AIR || block.getId() == FIRE || block.getId() == SOUL_FIRE) && (target instanceof BlockSolid || target instanceof BlockSolidMeta || target instanceof BlockLeaves)) { if (target.getId() == OBSIDIAN) { - if (level.createPortal(target)) { + if (level.createPortal(target, false)) { return true; } } - BlockFire fire = (BlockFire) Block.get(BlockID.FIRE); + int did; + BlockFire fire = (BlockFire) Block.get(((did = block.down().getId()) == SOUL_SAND || did == SOUL_SOIL) ? BlockID.SOUL_FIRE : BlockID.FIRE); fire.x = block.x; fire.y = block.y; fire.z = block.z; @@ -57,18 +60,23 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (!e.isCancelled()) { level.setBlock(fire, fire, true); + level.scheduleUpdate(fire, fire.tickRate() + Utils.random.nextInt(10)); level.addLevelSoundEvent(block, LevelSoundEventPacket.SOUND_IGNITE); - level.scheduleUpdate(fire, fire.tickRate() + ThreadLocalRandom.current().nextInt(10)); - if ((player.gamemode & 0x01) == 0 && this.useOn(block)) { - if (this.getDamage() >= this.getMaxDurability()) { + if (!player.isCreative()) { + this.useOn(block); + if (this.getDamage() >= DURABILITY_FLINT_STEEL) { this.count = 0; + + player.level.addSound(player, Sound.RANDOM_BREAK); + player.level.addParticle(new ItemBreakParticle(player, this)); } + player.getInventory().setItemInHand(this); } } - return true; } + return true; } return false; } diff --git a/src/main/java/cn/nukkit/item/ItemFlowerPot.java b/src/main/java/cn/nukkit/item/ItemFlowerPot.java index 99c9f7ea4e8..7e84f8186c3 100644 --- a/src/main/java/cn/nukkit/item/ItemFlowerPot.java +++ b/src/main/java/cn/nukkit/item/ItemFlowerPot.java @@ -20,5 +20,4 @@ public ItemFlowerPot(Integer meta, int count) { super(FLOWER_POT, meta, count, "Flower Pot"); this.block = Block.get(Block.FLOWER_POT_BLOCK); } - } diff --git a/src/main/java/cn/nukkit/item/ItemGhastTear.java b/src/main/java/cn/nukkit/item/ItemGhastTear.java index 4a12bf08316..b754438ca60 100644 --- a/src/main/java/cn/nukkit/item/ItemGhastTear.java +++ b/src/main/java/cn/nukkit/item/ItemGhastTear.java @@ -1,8 +1,5 @@ package cn.nukkit.item; -/** - * Created by Leonidius20 on 18.08.18. - */ public class ItemGhastTear extends Item { public ItemGhastTear() { @@ -14,7 +11,6 @@ public ItemGhastTear(Integer meta) { } public ItemGhastTear(Integer meta, int count) { - super(GHAST_TEAR, meta, count, "Ghast Tear"); + super(GHAST_TEAR, 0, count, "Ghast Tear"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemGlassBottle.java b/src/main/java/cn/nukkit/item/ItemGlassBottle.java index d73db93b83d..903df002ff8 100644 --- a/src/main/java/cn/nukkit/item/ItemGlassBottle.java +++ b/src/main/java/cn/nukkit/item/ItemGlassBottle.java @@ -28,7 +28,7 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { if (target.getId() == WATER || target.getId() == STILL_WATER) { - Item potion = new ItemPotion(); + Item potion = Item.get(Item.POTION); if (this.count == 1) { player.getInventory().setItemInHand(potion); diff --git a/src/main/java/cn/nukkit/item/ItemGlowBerries.java b/src/main/java/cn/nukkit/item/ItemGlowBerries.java new file mode 100644 index 00000000000..3c1a0bc2902 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemGlowBerries.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemGlowBerries extends ItemEdible { + + public ItemGlowBerries() { + this(0, 1); + } + + public ItemGlowBerries(Integer meta) { + this(meta, 1); + } + + public ItemGlowBerries(Integer meta, int count) { + super(GLOW_BERRIES, meta, count, "Glow Berries"); + this.block = Block.get(CAVE_VINES); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemGlowstoneDust.java b/src/main/java/cn/nukkit/item/ItemGlowstoneDust.java index 9eee2a76096..ae23ef5023c 100644 --- a/src/main/java/cn/nukkit/item/ItemGlowstoneDust.java +++ b/src/main/java/cn/nukkit/item/ItemGlowstoneDust.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemGlowstoneDust extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemGoatHorn.java b/src/main/java/cn/nukkit/item/ItemGoatHorn.java new file mode 100644 index 00000000000..f1effaf1ab3 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemGoatHorn.java @@ -0,0 +1,63 @@ +package cn.nukkit.item; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.level.Sound; +import cn.nukkit.math.Vector3; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ItemGoatHorn extends Item { + + private final AtomicBoolean cooldown = new AtomicBoolean(false); + + public ItemGoatHorn() { + this(0, 1); + } + + public ItemGoatHorn(Integer meta) { + this(meta, 1); + } + + public ItemGoatHorn(Integer meta, int count) { + super(GOAT_HORN, meta, count, "Goat Horn"); + } + + @Override + public int getMaxStackSize() { + return 1; + } + + @Override + public boolean onClickAir(Player player, Vector3 directionVector) { + if (!cooldown.getAndSet(true)) { + this.playSound(player); + Server.getInstance().getScheduler().scheduleDelayedTask(null, () -> cooldown.set(false), 140); + player.startItemCooldown(140, "goat_horn"); + return true; + } else { + return false; + } + } + + public void playSound(Player player) { + switch (this.getDamage()) { + case 0: player.getLevel().addSound(player, Sound.HORN_CALL_0); + break; + case 1: player.getLevel().addSound(player, Sound.HORN_CALL_1); + break; + case 2: player.getLevel().addSound(player, Sound.HORN_CALL_2); + break; + case 3: player.getLevel().addSound(player, Sound.HORN_CALL_3); + break; + case 4: player.getLevel().addSound(player, Sound.HORN_CALL_4); + break; + case 5: player.getLevel().addSound(player, Sound.HORN_CALL_5); + break; + case 6: player.getLevel().addSound(player, Sound.HORN_CALL_6); + break; + case 7: player.getLevel().addSound(player, Sound.HORN_CALL_7); + break; + } + } +} diff --git a/src/main/java/cn/nukkit/item/ItemGoldRaw.java b/src/main/java/cn/nukkit/item/ItemGoldRaw.java new file mode 100644 index 00000000000..b5d86ecf927 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemGoldRaw.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemGoldRaw extends Item { + + public ItemGoldRaw() { + this(0, 1); + } + + public ItemGoldRaw(Integer meta) { + this(meta, 1); + } + + public ItemGoldRaw(Integer meta, int count) { + super(RAW_GOLD, meta, count, "Raw Gold"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemGunpowder.java b/src/main/java/cn/nukkit/item/ItemGunpowder.java index b02786db0e9..a2225cfc3e2 100644 --- a/src/main/java/cn/nukkit/item/ItemGunpowder.java +++ b/src/main/java/cn/nukkit/item/ItemGunpowder.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemGunpowder extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemHelmetChain.java b/src/main/java/cn/nukkit/item/ItemHelmetChain.java index 1cc3d1dec74..c1b5abdcb59 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetChain.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetChain.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHelmetChain extends ItemArmor { @@ -15,7 +15,7 @@ public ItemHelmetChain(Integer meta) { } public ItemHelmetChain(Integer meta, int count) { - super(CHAIN_HELMET, meta, count, "Chainmail Helmet"); + super(CHAIN_HELMET, meta, count, "Chain Helmet"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemHelmetDiamond.java b/src/main/java/cn/nukkit/item/ItemHelmetDiamond.java index a9e2fe52d06..ba759c055ea 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHelmetDiamond extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemHelmetGold.java b/src/main/java/cn/nukkit/item/ItemHelmetGold.java index d68866c19fc..b08e69e4d19 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetGold.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHelmetGold extends ItemArmor { @@ -15,7 +15,7 @@ public ItemHelmetGold(Integer meta) { } public ItemHelmetGold(Integer meta, int count) { - super(GOLD_HELMET, meta, count, "Gold Helmet"); + super(GOLD_HELMET, meta, count, "Golden Helmet"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemHelmetIron.java b/src/main/java/cn/nukkit/item/ItemHelmetIron.java index d31f0b3eac0..aad54957f6a 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetIron.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHelmetIron extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemHelmetLeather.java b/src/main/java/cn/nukkit/item/ItemHelmetLeather.java index fb4403479da..e3ed24f71cb 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetLeather.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetLeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHelmetLeather extends ItemColorArmor { diff --git a/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java b/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java index 4f6b45146fb..29340e46f8e 100644 --- a/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java +++ b/src/main/java/cn/nukkit/item/ItemHelmetNetherite.java @@ -36,6 +36,6 @@ public int getArmorPoints() { @Override public int getToughness() { - return 2; + return 3; } } diff --git a/src/main/java/cn/nukkit/item/ItemHoeDiamond.java b/src/main/java/cn/nukkit/item/ItemHoeDiamond.java index 02a97921238..0f117442ef8 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemHoeDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHoeDiamond extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemHoeGold.java b/src/main/java/cn/nukkit/item/ItemHoeGold.java index 8d08a946737..ade2b243e4e 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeGold.java +++ b/src/main/java/cn/nukkit/item/ItemHoeGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHoeGold extends ItemTool { @@ -15,7 +15,7 @@ public ItemHoeGold(Integer meta) { } public ItemHoeGold(Integer meta, int count) { - super(GOLD_HOE, meta, count, "Gold Hoe"); + super(GOLD_HOE, meta, count, "Golden Hoe"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemHoeIron.java b/src/main/java/cn/nukkit/item/ItemHoeIron.java index e0beeb7d1b2..0411990f0a0 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeIron.java +++ b/src/main/java/cn/nukkit/item/ItemHoeIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHoeIron extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemHoeNetherite.java b/src/main/java/cn/nukkit/item/ItemHoeNetherite.java index a808c6c80b5..69de33522e2 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeNetherite.java +++ b/src/main/java/cn/nukkit/item/ItemHoeNetherite.java @@ -19,11 +19,6 @@ public boolean isHoe() { return true; } - @Override - public int getAttackDamage() { - return 6; - } - @Override public int getTier() { return ItemTool.TIER_NETHERITE; diff --git a/src/main/java/cn/nukkit/item/ItemHoeStone.java b/src/main/java/cn/nukkit/item/ItemHoeStone.java index 747d9fc030c..ea195ed0dfb 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeStone.java +++ b/src/main/java/cn/nukkit/item/ItemHoeStone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHoeStone extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemHoeWood.java b/src/main/java/cn/nukkit/item/ItemHoeWood.java index edeaa4a1c18..98c30e01a32 100644 --- a/src/main/java/cn/nukkit/item/ItemHoeWood.java +++ b/src/main/java/cn/nukkit/item/ItemHoeWood.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemHoeWood extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemHoneyBottle.java b/src/main/java/cn/nukkit/item/ItemHoneyBottle.java index bfb42f82a43..839a4f73b67 100644 --- a/src/main/java/cn/nukkit/item/ItemHoneyBottle.java +++ b/src/main/java/cn/nukkit/item/ItemHoneyBottle.java @@ -5,22 +5,21 @@ /** * @author Kaooot - * @version 1.0 */ public class ItemHoneyBottle extends ItemEdible { - + public ItemHoneyBottle() { this(0, 1); } - + public ItemHoneyBottle(Integer meta) { this(meta, 1); } - + public ItemHoneyBottle(Integer meta, int count) { super(HONEY_BOTTLE, meta, count, "Honey Bottle"); } - + @Override public int getMaxStackSize() { return 16; @@ -28,17 +27,14 @@ public int getMaxStackSize() { @Override public boolean onUse(Player player, int ticksUsed) { - super.onUse(player, ticksUsed); - - if (player.hasEffect(Effect.POISON)) { + if (super.onUse(player, ticksUsed)) { player.removeEffect(Effect.POISON); - } - - player.getInventory().setItemInHand(this); - if (!player.isCreative()) { - player.getInventory().addItem(new Item(ItemID.BOTTLE, 0, 1)); + if (!player.isCreative()) { + this.count--; + player.getInventory().addItem(Item.get(Item.BOTTLE, 0, 1)); + } } return true; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/item/ItemHoneycomb.java b/src/main/java/cn/nukkit/item/ItemHoneycomb.java index 69cc77f3640..9566c7cc891 100644 --- a/src/main/java/cn/nukkit/item/ItemHoneycomb.java +++ b/src/main/java/cn/nukkit/item/ItemHoneycomb.java @@ -2,7 +2,6 @@ /** * @author Kaooot - * @version 1.0 */ public class ItemHoneycomb extends Item { @@ -17,4 +16,4 @@ public ItemHoneycomb(Integer meta) { public ItemHoneycomb(Integer meta, int count) { super(HONEYCOMB, meta, count, "Honeycomb"); } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/item/ItemHopper.java b/src/main/java/cn/nukkit/item/ItemHopper.java index 74127f79843..95c76cbc787 100644 --- a/src/main/java/cn/nukkit/item/ItemHopper.java +++ b/src/main/java/cn/nukkit/item/ItemHopper.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** * Created by CreeperFace on 13.5.2017. @@ -18,6 +17,6 @@ public ItemHopper(Integer meta) { public ItemHopper(Integer meta, int count) { super(HOPPER, 0, count, "Hopper"); - this.block = Block.get(BlockID.HOPPER_BLOCK); + this.block = Block.get(HOPPER_BLOCK); } } diff --git a/src/main/java/cn/nukkit/item/ItemHorseArmorDiamond.java b/src/main/java/cn/nukkit/item/ItemHorseArmorDiamond.java index d59da4e4429..adc81255ef9 100644 --- a/src/main/java/cn/nukkit/item/ItemHorseArmorDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemHorseArmorDiamond.java @@ -1,6 +1,7 @@ package cn.nukkit.item; public class ItemHorseArmorDiamond extends Item { + public ItemHorseArmorDiamond() { this(0, 0); } diff --git a/src/main/java/cn/nukkit/item/ItemHorseArmorGold.java b/src/main/java/cn/nukkit/item/ItemHorseArmorGold.java index d88c44db6f1..dfbf503964f 100644 --- a/src/main/java/cn/nukkit/item/ItemHorseArmorGold.java +++ b/src/main/java/cn/nukkit/item/ItemHorseArmorGold.java @@ -1,6 +1,7 @@ package cn.nukkit.item; public class ItemHorseArmorGold extends Item { + public ItemHorseArmorGold() { this(0, 0); } @@ -10,7 +11,7 @@ public ItemHorseArmorGold(Integer meta) { } public ItemHorseArmorGold(Integer meta, int count) { - super(GOLD_HORSE_ARMOR, meta, count, "Gold Horse Armor"); + super(GOLD_HORSE_ARMOR, meta, count, "Golden Horse Armor"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemHorseArmorIron.java b/src/main/java/cn/nukkit/item/ItemHorseArmorIron.java index dc9cec7da05..90d31071a7f 100644 --- a/src/main/java/cn/nukkit/item/ItemHorseArmorIron.java +++ b/src/main/java/cn/nukkit/item/ItemHorseArmorIron.java @@ -1,6 +1,7 @@ package cn.nukkit.item; public class ItemHorseArmorIron extends Item { + public ItemHorseArmorIron() { this(0, 0); } diff --git a/src/main/java/cn/nukkit/item/ItemHorseArmorLeather.java b/src/main/java/cn/nukkit/item/ItemHorseArmorLeather.java index 47d5a4ee7de..99c0437f3ce 100644 --- a/src/main/java/cn/nukkit/item/ItemHorseArmorLeather.java +++ b/src/main/java/cn/nukkit/item/ItemHorseArmorLeather.java @@ -1,6 +1,7 @@ package cn.nukkit.item; public class ItemHorseArmorLeather extends Item { + public ItemHorseArmorLeather() { this(0, 1); } diff --git a/src/main/java/cn/nukkit/item/ItemID.java b/src/main/java/cn/nukkit/item/ItemID.java index 28a0b52808f..97d5b1c5ff2 100644 --- a/src/main/java/cn/nukkit/item/ItemID.java +++ b/src/main/java/cn/nukkit/item/ItemID.java @@ -1,6 +1,10 @@ package cn.nukkit.item; +/** + * List of item IDs + */ public interface ItemID { + int IRON_SHOVEL = 256; int IRON_PICKAXE = 257; int IRON_AXE = 258; @@ -59,9 +63,13 @@ public interface ItemID { int LEATHER_LEGGINGS = 300; int LEATHER_BOOTS = 301; int CHAIN_HELMET = 302; + int CHAINMAIL_HELMET = 302; int CHAIN_CHESTPLATE = 303; + int CHAINMAIL_CHESTPLATE = 303; int CHAIN_LEGGINGS = 304; + int CHAINMAIL_LEGGINGS = 304; int CHAIN_BOOTS = 305; + int CHAINMAIL_BOOTS = 305; int IRON_HELMET = 306; int IRON_CHESTPLATE = 307; int IRON_LEGGINGS = 308; @@ -86,6 +94,7 @@ public interface ItemID { int SIGN = 323; int WOODEN_DOOR = 324; int BUCKET = 325; + // int MINECART = 328; int SADDLE = 329; int IRON_DOOR = 330; @@ -107,6 +116,7 @@ public interface ItemID { int SLIME_BALL = 341; int CHEST_MINECART = 342; int MINECART_WITH_CHEST = 342; + // int EGG = 344; int COMPASS = 345; int FISHING_ROD = 346; @@ -220,16 +230,26 @@ public interface ItemID { int CHORUS_FRUIT = 432; int POPPED_CHORUS_FRUIT = 433; int BANNER_PATTERN = 434; + // int DRAGON_BREATH = 437; int SPLASH_POTION = 438; + // int LINGERING_POTION = 441; + int SPARKLER = 442; int COMMAND_BLOCK_MINECART = 443; int ELYTRA = 444; int SHULKER_SHELL = 445; int BANNER = 446; + int EYE_DROP = 447; + int BALLOON = 448; + int SUPER_FERTILIZER = 449; int TOTEM = 450; + int BLEACH = 451; int IRON_NUGGET = 452; + int ICE_BOMB = 453; + // int TRIDENT = 455; + // int BEETROOT = 457; int BEETROOT_SEEDS = 458; int BEETROOT_SEED = 458; @@ -250,7 +270,15 @@ public interface ItemID { int TURTLE_HELMET = 469; int PHANTOM_MEMBRANE = 470; int CROSSBOW = 471; + int SPRUCE_SIGN = 472; + int BIRCH_SIGN = 473; + int JUNGLE_SIGN = 474; + int ACACIA_SIGN = 475; + int DARKOAK_SIGN = 476; int SWEET_BERRIES = 477; + // + int CAMERA = 498; + int COMPOUND = 499; int RECORD_13 = 500; int RECORD_CAT = 501; int RECORD_BLOCKS = 502; @@ -263,12 +291,39 @@ public interface ItemID { int RECORD_WARD = 509; int RECORD_11 = 510; int RECORD_WAIT = 511; + // int SHIELD = 513; - int RECORD_5 = 643; + // + int COPPER_INGOT = 519; + int RAW_IRON = 520; + int RAW_GOLD = 521; + int RAW_COPPER = 522; + // + int RECORD_5 = 636; //TODO: 643 + int DISC_FRAGMENT_5 = 637; + // + int OAK_CHEST_BOAT = 638; + int BIRCH_CHEST_BOAT = 639; + int JUNGLE_CHEST_BOAT = 640; + int SPRUCE_CHEST_BOAT = 641; + int ACACIA_CHEST_BOAT = 642; + int DARK_OAK_CHEST_BOAT = 643; + int MANGROVE_CHEST_BOAT = 644; + // + int ECHO_SHARD = 647; + int RECOVERY_COMPASS = 648; + // + int GLOW_BERRIES = 654; + // int RECORD_RELIC = 701; + // + int CAMPFIRE = 720; + // int SUSPICIOUS_STEW = 734; + // int HONEYCOMB = 736; int HONEY_BOTTLE = 737; + // int LODESTONECOMPASS = 741; int LODESTONE_COMPASS = 741; int NETHERITE_INGOT = 742; @@ -282,8 +337,23 @@ public interface ItemID { int NETHERITE_LEGGINGS = 750; int NETHERITE_BOOTS = 751; int NETHERITE_SCRAP = 752; + int CRIMSON_SIGN = 753; + int WARPED_SIGN = 754; + int CRIMSON_DOOR = 755; + int WARPED_DOOR = 756; + // int WARPED_FUNGUS_ON_A_STICK = 757; + // + int CHAIN = 758; int RECORD_PIGSTEP = 759; + int NETHER_SPROUTS = 760; + int GOAT_HORN = 761; + // + int AMETHYST_SHARD = 771; int SPYGLASS = 772; int RECORD_OTHERSIDE = 773; + // + int SOUL_CAMPFIRE = 801; + // + int GLOW_ITEM_FRAME = 850; } diff --git a/src/main/java/cn/nukkit/item/ItemIngotCopper.java b/src/main/java/cn/nukkit/item/ItemIngotCopper.java new file mode 100644 index 00000000000..07378d6fe03 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemIngotCopper.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemIngotCopper extends Item { + + public ItemIngotCopper() { + this(0, 1); + } + + public ItemIngotCopper(Integer meta) { + this(meta, 1); + } + + public ItemIngotCopper(Integer meta, int count) { + super(COPPER_INGOT, 0, count, "Copper Ingot"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemIngotGold.java b/src/main/java/cn/nukkit/item/ItemIngotGold.java index cd3150f599c..bb0ade3ab95 100644 --- a/src/main/java/cn/nukkit/item/ItemIngotGold.java +++ b/src/main/java/cn/nukkit/item/ItemIngotGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemIngotGold extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemIngotIron.java b/src/main/java/cn/nukkit/item/ItemIngotIron.java index ec6306afd54..f1fc33a7769 100644 --- a/src/main/java/cn/nukkit/item/ItemIngotIron.java +++ b/src/main/java/cn/nukkit/item/ItemIngotIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemIngotIron extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemIronRaw.java b/src/main/java/cn/nukkit/item/ItemIronRaw.java new file mode 100644 index 00000000000..c6fca03c650 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemIronRaw.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemIronRaw extends Item { + + public ItemIronRaw() { + this(0, 1); + } + + public ItemIronRaw(Integer meta) { + this(meta, 1); + } + + public ItemIronRaw(Integer meta, int count) { + super(RAW_IRON, meta, count, "Raw Iron"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemItemFrame.java b/src/main/java/cn/nukkit/item/ItemItemFrame.java index b8e5eb1935b..b32df67479a 100644 --- a/src/main/java/cn/nukkit/item/ItemItemFrame.java +++ b/src/main/java/cn/nukkit/item/ItemItemFrame.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** * Created by Pub4Game on 03.07.2016. @@ -18,6 +17,6 @@ public ItemItemFrame(Integer meta) { public ItemItemFrame(Integer meta, int count) { super(ITEM_FRAME, meta, count, "Item Frame"); - this.block = Block.get(BlockID.ITEM_FRAME_BLOCK); + this.block = Block.get(ITEM_FRAME_BLOCK); } } diff --git a/src/main/java/cn/nukkit/item/ItemItemFrameGlow.java b/src/main/java/cn/nukkit/item/ItemItemFrameGlow.java new file mode 100644 index 00000000000..826f625ffb4 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemItemFrameGlow.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +public class ItemItemFrameGlow extends Item { + + public ItemItemFrameGlow() { + this(0, 1); + } + + public ItemItemFrameGlow(Integer meta) { + this(meta, 1); + } + + public ItemItemFrameGlow(Integer meta, int count) { + super(GLOW_ITEM_FRAME, meta, count, "Glow Item Frame"); + this.block = Block.get(BlockID.GLOW_FRAME); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/ItemKelp.java b/src/main/java/cn/nukkit/item/ItemKelp.java new file mode 100644 index 00000000000..96b36b205d4 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemKelp.java @@ -0,0 +1,19 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +public class ItemKelp extends Item { + + public ItemKelp() { + this(0, 1); + } + + public ItemKelp(Integer meta) { + this(meta, 1); + } + + public ItemKelp(Integer meta, int count) { + super(KELP, meta, count, "Kelp"); + this.block = Block.get(Block.BLOCK_KELP); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemLadder.java b/src/main/java/cn/nukkit/item/ItemLadder.java new file mode 100644 index 00000000000..96a338fadd8 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemLadder.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemLadder extends Item { + + public ItemLadder() { + this(0, 1); + } + + public ItemLadder(Integer meta) { + this(meta, 1); + } + + public ItemLadder(Integer meta, int count) { + super(LADDER, 0, count, "Ladder"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemLeather.java b/src/main/java/cn/nukkit/item/ItemLeather.java index d676ed64700..8addfb1ad5f 100644 --- a/src/main/java/cn/nukkit/item/ItemLeather.java +++ b/src/main/java/cn/nukkit/item/ItemLeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeather extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsChain.java b/src/main/java/cn/nukkit/item/ItemLeggingsChain.java index 687ad8c764e..7ba7910719a 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsChain.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsChain.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeggingsChain extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsDiamond.java b/src/main/java/cn/nukkit/item/ItemLeggingsDiamond.java index 82f3f995fb2..d7648af679c 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeggingsDiamond extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsGold.java b/src/main/java/cn/nukkit/item/ItemLeggingsGold.java index a34e2dd6e83..b653688be19 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsGold.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeggingsGold extends ItemArmor { @@ -15,7 +15,7 @@ public ItemLeggingsGold(Integer meta) { } public ItemLeggingsGold(Integer meta, int count) { - super(GOLD_LEGGINGS, meta, count, "Gold Leggings"); + super(GOLD_LEGGINGS, meta, count, "Golden Leggings"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsIron.java b/src/main/java/cn/nukkit/item/ItemLeggingsIron.java index 58b83d0a589..3c591e629d7 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsIron.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeggingsIron extends ItemArmor { diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsLeather.java b/src/main/java/cn/nukkit/item/ItemLeggingsLeather.java index fd25d1f6d31..d107fb1b5b9 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsLeather.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsLeather.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemLeggingsLeather extends ItemColorArmor { diff --git a/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java b/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java index a7e0298fec9..77ced531669 100644 --- a/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java +++ b/src/main/java/cn/nukkit/item/ItemLeggingsNetherite.java @@ -36,6 +36,6 @@ public int getArmorPoints() { @Override public int getToughness() { - return 2; + return 3; } } diff --git a/src/main/java/cn/nukkit/item/ItemLodestoneCompass.java b/src/main/java/cn/nukkit/item/ItemLodestoneCompass.java new file mode 100644 index 00000000000..1c12d9880b9 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemLodestoneCompass.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemLodestoneCompass extends Item { + + public ItemLodestoneCompass() { + this(0, 1); + } + + public ItemLodestoneCompass(Integer meta) { + this(meta, 1); + } + + public ItemLodestoneCompass(Integer meta, int count) { + super(LODESTONE_COMPASS, meta, count, "Lodestone Compass"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemMagmaCream.java b/src/main/java/cn/nukkit/item/ItemMagmaCream.java index 0cf5fdcbd9a..1010904d179 100644 --- a/src/main/java/cn/nukkit/item/ItemMagmaCream.java +++ b/src/main/java/cn/nukkit/item/ItemMagmaCream.java @@ -6,14 +6,14 @@ public class ItemMagmaCream extends Item { public ItemMagmaCream() { - this(0); + this(0, 1); } public ItemMagmaCream(Integer meta) { - this(0, 1); + this(meta, 1); } public ItemMagmaCream(Integer meta, int count) { - super(MAGMA_CREAM, meta, count, "Magma Cream"); + super(MAGMA_CREAM, 0, count, "Magma Cream"); } } diff --git a/src/main/java/cn/nukkit/item/ItemMap.java b/src/main/java/cn/nukkit/item/ItemMap.java index 5ed327ea55b..054507cc541 100644 --- a/src/main/java/cn/nukkit/item/ItemMap.java +++ b/src/main/java/cn/nukkit/item/ItemMap.java @@ -1,12 +1,13 @@ package cn.nukkit.item; import cn.nukkit.Player; +import cn.nukkit.Server; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.network.protocol.ClientboundMapItemDataPacket; import cn.nukkit.utils.MainLogger; import javax.imageio.ImageIO; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -18,9 +19,8 @@ */ public class ItemMap extends Item { - public static int mapCount = 0; + public static long mapCount = 0; - // not very pretty but definitely better than before. private BufferedImage image; public ItemMap() { @@ -33,12 +33,6 @@ public ItemMap(Integer meta) { public ItemMap(Integer meta, int count) { super(MAP, meta, count, "Map"); - - if (!hasCompoundTag() || !getNamedTag().contains("map_uuid")) { - CompoundTag tag = new CompoundTag(); - tag.putLong("map_uuid", mapCount++); - this.setNamedTag(tag); - } } public void setImage(File file) throws IOException { @@ -47,7 +41,12 @@ public void setImage(File file) throws IOException { public void setImage(BufferedImage image) { try { - if (image.getHeight() != 128 || image.getWidth() != 128) { //resize + if (this.getMapId() == 0) { + Server.getInstance().getLogger().debug("Uninitialized map", new Throwable()); + this.initItem(); + } + + if (image.getHeight() != 128 || image.getWidth() != 128) { this.image = new BufferedImage(128, 128, image.getType()); Graphics2D g = this.image.createGraphics(); g.drawImage(image, 0, 0, 128, 128, null); @@ -59,7 +58,8 @@ public void setImage(BufferedImage image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(this.image, "png", baos); - this.getNamedTag().putByteArray("Colors", baos.toByteArray()); + this.setNamedTag(this.getNamedTag().putByteArray("Colors", baos.toByteArray())); + baos.close(); } catch (IOException e) { MainLogger.getLogger().logException(e); } @@ -78,24 +78,49 @@ protected BufferedImage loadImageFromNBT() { } public long getMapId() { - return getNamedTag().getLong("map_uuid"); + CompoundTag tag = this.getNamedTag(); + if (tag == null) return 0; + return tag.getLong("map_uuid"); } public void sendImage(Player p) { - // don't load the image from NBT if it has been done before. + // Don't load the image from NBT if it has been done before + BufferedImage image = this.image != null ? this.image : loadImageFromNBT(); + + ClientboundMapItemDataPacket pk = new ClientboundMapItemDataPacket(); + pk.mapId = getMapId(); + pk.update = ClientboundMapItemDataPacket.TEXTURE_UPDATE; + pk.scale = 0; + pk.width = 128; + pk.height = 128; + pk.offsetX = 0; + pk.offsetZ = 0; + pk.image = image; + pk.eids = new long[]{pk.mapId}; + + p.dataPacket(pk); + } + + public boolean trySendImage(Player p) { + // Don't load the image from NBT if it has been done before BufferedImage image = this.image != null ? this.image : loadImageFromNBT(); + if (image == null) { + return false; + } ClientboundMapItemDataPacket pk = new ClientboundMapItemDataPacket(); pk.mapId = getMapId(); - pk.update = 2; + pk.update = ClientboundMapItemDataPacket.TEXTURE_UPDATE; pk.scale = 0; pk.width = 128; pk.height = 128; pk.offsetX = 0; pk.offsetZ = 0; pk.image = image; + pk.eids = new long[]{pk.mapId}; p.dataPacket(pk); + return true; } @Override @@ -105,6 +130,28 @@ public boolean canBeActivated() { @Override public int getMaxStackSize() { - return 1; + return 1; // TODO: 64 when map copying is implemented + } + + @Override + public Item initItem() { + CompoundTag compoundTag = this.getNamedTag(); + if (compoundTag == null || !compoundTag.contains("map_uuid")) { + CompoundTag tag = new CompoundTag(); + mapCount++; + tag.putLong("map_uuid", mapCount); + tag.putInt("map_name_index", (int) mapCount); + this.setNamedTag(tag); + } else { + long id; + if ((id = getMapId()) > mapCount) { + mapCount = id; + } + if (!(compoundTag = this.getNamedTag()).contains("map_name_index")) { + compoundTag.putInt("map_name_index", (int) id); + this.setNamedTag(compoundTag); + } + } + return super.initItem(); } } diff --git a/src/main/java/cn/nukkit/item/ItemMelon.java b/src/main/java/cn/nukkit/item/ItemMelon.java index 64dae4da6ad..cfcc63bad8f 100644 --- a/src/main/java/cn/nukkit/item/ItemMelon.java +++ b/src/main/java/cn/nukkit/item/ItemMelon.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemMelon extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemMelonGlistering.java b/src/main/java/cn/nukkit/item/ItemMelonGlistering.java index ad1ef673539..235b7ae0bb5 100644 --- a/src/main/java/cn/nukkit/item/ItemMelonGlistering.java +++ b/src/main/java/cn/nukkit/item/ItemMelonGlistering.java @@ -6,7 +6,7 @@ public class ItemMelonGlistering extends Item { public ItemMelonGlistering() { - this(0); + this(0, 1); } public ItemMelonGlistering(Integer meta) { @@ -14,6 +14,6 @@ public ItemMelonGlistering(Integer meta) { } public ItemMelonGlistering(Integer meta, int count) { - super(GLISTERING_MELON, meta, count, "Glistering Melon"); + super(GLISTERING_MELON, 0, count, "Glistering Melon"); } } diff --git a/src/main/java/cn/nukkit/item/ItemMinecart.java b/src/main/java/cn/nukkit/item/ItemMinecart.java index 5a523cd88cd..561dd11d0fb 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecart.java +++ b/src/main/java/cn/nukkit/item/ItemMinecart.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockRail; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityMinecartEmpty; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -14,7 +13,7 @@ import cn.nukkit.utils.Rail; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemMinecart extends Item { @@ -44,8 +43,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (type.isAscending()) { adjacent = 0.5D; } - EntityMinecartEmpty minecart = (EntityMinecartEmpty) Entity.createEntity("MinecartRideable", - level.getChunk(target.getFloorX() >> 4, target.getFloorZ() >> 4), new CompoundTag("") + EntityMinecartEmpty minecart = new EntityMinecartEmpty( + level.getChunk(target.getChunkX(), target.getChunkZ()), new CompoundTag("") .putList(new ListTag<>("Pos") .add(new DoubleTag("", target.getX() + 0.5)) .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) @@ -58,18 +57,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, .add(new FloatTag("", 0)) .add(new FloatTag("", 0))) ); - - if(minecart == null) { - return false; - } - - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - } - minecart.spawnToAll(); + count -= 1; return true; } return false; diff --git a/src/main/java/cn/nukkit/item/ItemMinecartChest.java b/src/main/java/cn/nukkit/item/ItemMinecartChest.java index 847a0feb6d6..d925f8f15ed 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartChest.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartChest.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockRail; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityMinecartChest; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -40,32 +39,22 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (type.isAscending()) { adjacent = 0.5D; } - EntityMinecartChest minecart = (EntityMinecartChest) Entity.createEntity("MinecartChest", - level.getChunk(target.getFloorX() >> 4, target.getFloorZ() >> 4), new CompoundTag("") - .putList(new ListTag<>("Pos") - .add(new DoubleTag("", target.getX() + 0.5)) - .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) - .add(new DoubleTag("", target.getZ() + 0.5))) - .putList(new ListTag<>("Motion") - .add(new DoubleTag("", 0)) - .add(new DoubleTag("", 0)) - .add(new DoubleTag("", 0))) - .putList(new ListTag<>("Rotation") - .add(new FloatTag("", 0)) - .add(new FloatTag("", 0))) + EntityMinecartChest minecart = new EntityMinecartChest( + level.getChunk(target.getChunkX(), target.getChunkZ()), new CompoundTag("") + .putList(new ListTag<>("Pos") + .add(new DoubleTag("", target.getX() + 0.5)) + .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) + .add(new DoubleTag("", target.getZ() + 0.5))) + .putList(new ListTag<>("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag<>("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0))) ); - - if(minecart == null) { - return false; - } - - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - } - minecart.spawnToAll(); + count -= 1; return true; } return false; diff --git a/src/main/java/cn/nukkit/item/ItemMinecartHopper.java b/src/main/java/cn/nukkit/item/ItemMinecartHopper.java index 84c052b3376..8684223f1a1 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartHopper.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartHopper.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockRail; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityMinecartHopper; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -40,32 +39,22 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (type.isAscending()) { adjacent = 0.5D; } - EntityMinecartHopper minecart = (EntityMinecartHopper) Entity.createEntity("MinecartHopper", - level.getChunk(target.getFloorX() >> 4, target.getFloorZ() >> 4), new CompoundTag("") - .putList(new ListTag<>("Pos") - .add(new DoubleTag("", target.getX() + 0.5)) - .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) - .add(new DoubleTag("", target.getZ() + 0.5))) - .putList(new ListTag<>("Motion") - .add(new DoubleTag("", 0)) - .add(new DoubleTag("", 0)) - .add(new DoubleTag("", 0))) - .putList(new ListTag<>("Rotation") - .add(new FloatTag("", 0)) - .add(new FloatTag("", 0))) + EntityMinecartHopper minecart = new EntityMinecartHopper( + level.getChunk(target.getChunkX(), target.getChunkZ()), new CompoundTag("") + .putList(new ListTag<>("Pos") + .add(new DoubleTag("", target.getX() + 0.5)) + .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) + .add(new DoubleTag("", target.getZ() + 0.5))) + .putList(new ListTag<>("Motion") + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0)) + .add(new DoubleTag("", 0))) + .putList(new ListTag<>("Rotation") + .add(new FloatTag("", 0)) + .add(new FloatTag("", 0))) ); - - if(minecart == null) { - return false; - } - - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - } - minecart.spawnToAll(); + count -= 1; return true; } return false; diff --git a/src/main/java/cn/nukkit/item/ItemMinecartTNT.java b/src/main/java/cn/nukkit/item/ItemMinecartTNT.java index 20f0e6eb2fe..a4810898e70 100644 --- a/src/main/java/cn/nukkit/item/ItemMinecartTNT.java +++ b/src/main/java/cn/nukkit/item/ItemMinecartTNT.java @@ -3,7 +3,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockRail; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityMinecartTNT; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; @@ -40,8 +39,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (type.isAscending()) { adjacent = 0.5D; } - EntityMinecartTNT minecart = (EntityMinecartTNT) Entity.createEntity("MinecartTnt", - level.getChunk(target.getFloorX() >> 4, target.getFloorZ() >> 4), new CompoundTag("") + EntityMinecartTNT minecart = new EntityMinecartTNT( + level.getChunk(target.getChunkX(), target.getChunkZ()), new CompoundTag("") .putList(new ListTag<>("Pos") .add(new DoubleTag("", target.getX() + 0.5)) .add(new DoubleTag("", target.getY() + 0.0625D + adjacent)) @@ -54,18 +53,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, .add(new FloatTag("", 0)) .add(new FloatTag("", 0))) ); - - if(minecart == null) { - return false; - } - - if (player.isAdventure() || player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - } - minecart.spawnToAll(); + count -= 1; return true; } return false; diff --git a/src/main/java/cn/nukkit/item/ItemMushroomStew.java b/src/main/java/cn/nukkit/item/ItemMushroomStew.java index 808f68f81d1..579c190ba88 100644 --- a/src/main/java/cn/nukkit/item/ItemMushroomStew.java +++ b/src/main/java/cn/nukkit/item/ItemMushroomStew.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemMushroomStew extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemNameTag.java b/src/main/java/cn/nukkit/item/ItemNameTag.java index ef898d71a5c..917cfff3c33 100644 --- a/src/main/java/cn/nukkit/item/ItemNameTag.java +++ b/src/main/java/cn/nukkit/item/ItemNameTag.java @@ -11,7 +11,6 @@ public ItemNameTag(Integer meta) { } public ItemNameTag(Integer meta, int count) { - super(NAME_TAG, meta, count, "Name Tag"); + super(NAME_TAG, 0, count, "Name Tag"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemNetherBrick.java b/src/main/java/cn/nukkit/item/ItemNetherBrick.java index c354c8a194f..b3facadd8eb 100644 --- a/src/main/java/cn/nukkit/item/ItemNetherBrick.java +++ b/src/main/java/cn/nukkit/item/ItemNetherBrick.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemNetherBrick extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemNetherSprouts.java b/src/main/java/cn/nukkit/item/ItemNetherSprouts.java new file mode 100644 index 00000000000..a3205c2b91d --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemNetherSprouts.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +public class ItemNetherSprouts extends Item { + + public ItemNetherSprouts() { + this(0, 1); + } + + public ItemNetherSprouts(Integer meta) { + this(meta, 1); + } + + public ItemNetherSprouts(Integer meta, int count) { + super(NETHER_SPROUTS, 0, count, "Nether Sprouts"); + this.block = Block.get(BlockID.NETHER_SPROUTS_BLOCK); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemNetherWart.java b/src/main/java/cn/nukkit/item/ItemNetherWart.java index d0221314b12..fecf4d918ef 100644 --- a/src/main/java/cn/nukkit/item/ItemNetherWart.java +++ b/src/main/java/cn/nukkit/item/ItemNetherWart.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** * Created by Leonidius20 on 22.03.17. @@ -18,7 +17,6 @@ public ItemNetherWart(Integer meta) { public ItemNetherWart(Integer meta, int count) { super(NETHER_WART, meta, count, "Nether Wart"); - this.block = Block.get(BlockID.NETHER_WART_BLOCK, meta); + this.block = Block.get(NETHER_WART_BLOCK, meta); } - } diff --git a/src/main/java/cn/nukkit/item/ItemNuggetGold.java b/src/main/java/cn/nukkit/item/ItemNuggetGold.java index 2d82ea9857e..ec7819572aa 100644 --- a/src/main/java/cn/nukkit/item/ItemNuggetGold.java +++ b/src/main/java/cn/nukkit/item/ItemNuggetGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemNuggetGold extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemPainting.java b/src/main/java/cn/nukkit/item/ItemPainting.java index 3419bfc2086..4c58358afce 100644 --- a/src/main/java/cn/nukkit/item/ItemPainting.java +++ b/src/main/java/cn/nukkit/item/ItemPainting.java @@ -2,7 +2,6 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; -import cn.nukkit.entity.Entity; import cn.nukkit.entity.item.EntityPainting; import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; @@ -12,16 +11,18 @@ import cn.nukkit.nbt.tag.DoubleTag; import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.network.protocol.LevelEventPacket; +import cn.nukkit.utils.Utils; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPainting extends Item { + private static final int[] DIRECTION = {2, 3, 4, 5}; private static final int[] RIGHT = {4, 5, 3, 2}; private static final double OFFSET = 0.53125; @@ -45,13 +46,12 @@ public boolean canBeActivated() { @Override public boolean onActivate(Level level, Player player, Block block, Block target, BlockFace face, double fx, double fy, double fz) { - if (player.isAdventure()) { + if (player.isAdventure() || target.isTransparent() || face.getHorizontalIndex() == -1 || block.isSolid()) { return false; } FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); - - if (chunk == null || target.isTransparent() || face.getHorizontalIndex() == -1 || block.isSolid()) { + if (chunk == null) { return false; } @@ -74,7 +74,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, } } int direction = DIRECTION[face.getIndex() - 2]; - EntityPainting.Motive motive = validMotives.get(ThreadLocalRandom.current().nextInt(validMotives.size())); + EntityPainting.Motive motive = validMotives.get(Utils.random.nextInt(validMotives.size())); Vector3 position = new Vector3(target.x + 0.5, target.y + 0.5, target.z + 0.5); double widthOffset = offset(motive.width); @@ -114,19 +114,14 @@ public boolean onActivate(Level level, Player player, Block block, Block target, .add(new FloatTag("0", direction * 90)) .add(new FloatTag("1", 0))); - EntityPainting entity = (EntityPainting) Entity.createEntity("Painting", chunk, nbt); + EntityPainting entity = new EntityPainting(chunk, nbt); - if (entity == null) { - return false; + if (!player.isCreative()) { + player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); } - - if (player.isSurvival()) { - Item item = player.getInventory().getItemInHand(); - item.setCount(item.getCount() - 1); - player.getInventory().setItemInHand(item); - } - entity.spawnToAll(); + + level.addLevelEvent(block, LevelEventPacket.EVENT_SOUND_ITEM_FRAME_PLACED); return true; } diff --git a/src/main/java/cn/nukkit/item/ItemPaper.java b/src/main/java/cn/nukkit/item/ItemPaper.java index 59185957fc3..5d420125307 100644 --- a/src/main/java/cn/nukkit/item/ItemPaper.java +++ b/src/main/java/cn/nukkit/item/ItemPaper.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPaper extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeDiamond.java b/src/main/java/cn/nukkit/item/ItemPickaxeDiamond.java index ac7cecf769b..e0072afea74 100644 --- a/src/main/java/cn/nukkit/item/ItemPickaxeDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemPickaxeDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPickaxeDiamond extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeGold.java b/src/main/java/cn/nukkit/item/ItemPickaxeGold.java index 993ff1b3272..654c3f18e61 100644 --- a/src/main/java/cn/nukkit/item/ItemPickaxeGold.java +++ b/src/main/java/cn/nukkit/item/ItemPickaxeGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPickaxeGold extends ItemTool { @@ -15,7 +15,7 @@ public ItemPickaxeGold(Integer meta) { } public ItemPickaxeGold(Integer meta, int count) { - super(GOLD_PICKAXE, meta, count, "Gold Pickaxe"); + super(GOLD_PICKAXE, meta, count, "Golden Pickaxe"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeIron.java b/src/main/java/cn/nukkit/item/ItemPickaxeIron.java index 62a01a72c06..77d636a29ea 100644 --- a/src/main/java/cn/nukkit/item/ItemPickaxeIron.java +++ b/src/main/java/cn/nukkit/item/ItemPickaxeIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPickaxeIron extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeStone.java b/src/main/java/cn/nukkit/item/ItemPickaxeStone.java index 8bfa75ef14d..72ad598e481 100644 --- a/src/main/java/cn/nukkit/item/ItemPickaxeStone.java +++ b/src/main/java/cn/nukkit/item/ItemPickaxeStone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPickaxeStone extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemPickaxeWood.java b/src/main/java/cn/nukkit/item/ItemPickaxeWood.java index 97fe4125d69..ab4e339b8fd 100644 --- a/src/main/java/cn/nukkit/item/ItemPickaxeWood.java +++ b/src/main/java/cn/nukkit/item/ItemPickaxeWood.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPickaxeWood extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemPorkchopCooked.java b/src/main/java/cn/nukkit/item/ItemPorkchopCooked.java index 46df30e00cc..09004762524 100644 --- a/src/main/java/cn/nukkit/item/ItemPorkchopCooked.java +++ b/src/main/java/cn/nukkit/item/ItemPorkchopCooked.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPorkchopCooked extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemPorkchopRaw.java b/src/main/java/cn/nukkit/item/ItemPorkchopRaw.java index 93797753e01..c40c2bb5aba 100644 --- a/src/main/java/cn/nukkit/item/ItemPorkchopRaw.java +++ b/src/main/java/cn/nukkit/item/ItemPorkchopRaw.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPorkchopRaw extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemPotato.java b/src/main/java/cn/nukkit/item/ItemPotato.java index bde98e930d6..915f32e0511 100644 --- a/src/main/java/cn/nukkit/item/ItemPotato.java +++ b/src/main/java/cn/nukkit/item/ItemPotato.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPotato extends ItemEdible { @@ -19,7 +18,7 @@ public ItemPotato(Integer meta) { public ItemPotato(Integer meta, int count) { super(POTATO, meta, count, "Potato"); - this.block = Block.get(BlockID.POTATO_BLOCK); + this.block = Block.get(POTATO_BLOCK); } protected ItemPotato(int id, Integer meta, int count, String name) { diff --git a/src/main/java/cn/nukkit/item/ItemPotatoBaked.java b/src/main/java/cn/nukkit/item/ItemPotatoBaked.java index b4f23bac68c..6c0d9f1fc96 100644 --- a/src/main/java/cn/nukkit/item/ItemPotatoBaked.java +++ b/src/main/java/cn/nukkit/item/ItemPotatoBaked.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPotatoBaked extends ItemEdible { @@ -17,5 +17,4 @@ public ItemPotatoBaked(Integer meta) { public ItemPotatoBaked(Integer meta, int count) { super(BAKED_POTATO, meta, count, "Baked Potato"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemPotion.java b/src/main/java/cn/nukkit/item/ItemPotion.java index 70d9bb6d6b9..417094b2a7a 100644 --- a/src/main/java/cn/nukkit/item/ItemPotion.java +++ b/src/main/java/cn/nukkit/item/ItemPotion.java @@ -69,6 +69,10 @@ public boolean onClickAir(Player player, Vector3 directionVector) { @Override public boolean onUse(Player player, int ticksUsed) { + if (ticksUsed < 30) { + player.getServer().getLogger().debug(player.getName() + ": potion ticksUsed=" + ticksUsed); + return false; + } PlayerItemConsumeEvent consumeEvent = new PlayerItemConsumeEvent(player, this); player.getServer().getPluginManager().callEvent(consumeEvent); if (consumeEvent.isCancelled()) { @@ -76,10 +80,10 @@ public boolean onUse(Player player, int ticksUsed) { } Potion potion = Potion.getPotion(this.getDamage()).setSplash(false); - if (player.isAdventure() || player.isSurvival()) { + if (!player.isCreative()) { --this.count; player.getInventory().setItemInHand(this); - player.getInventory().addItem(new ItemGlassBottle()); + player.getInventory().addItem(Item.get(Item.GLASS_BOTTLE)); } if (potion != null) { diff --git a/src/main/java/cn/nukkit/item/ItemPotionLingering.java b/src/main/java/cn/nukkit/item/ItemPotionLingering.java index d2df4441ed7..e3ab5a8a902 100644 --- a/src/main/java/cn/nukkit/item/ItemPotionLingering.java +++ b/src/main/java/cn/nukkit/item/ItemPotionLingering.java @@ -1,6 +1,8 @@ package cn.nukkit.item; -public class ItemPotionLingering extends Item { +import cn.nukkit.nbt.tag.CompoundTag; + +public class ItemPotionLingering extends ProjectileItem { public ItemPotionLingering() { this(0, 1); @@ -11,11 +13,31 @@ public ItemPotionLingering(Integer meta) { } public ItemPotionLingering(Integer meta, int count) { - super(POTION, meta, count, "Lingering Potion"); + super(LINGERING_POTION, meta, count, "Lingering Potion"); } @Override public int getMaxStackSize() { return 1; } -} \ No newline at end of file + + @Override + public boolean canBeActivated() { + return true; + } + + @Override + public String getProjectileEntityType() { + return "ThrownLingeringPotion"; + } + + @Override + public float getThrowForce() { + return 0.50f; + } + + @Override + protected void correctNBT(CompoundTag nbt) { + nbt.putInt("PotionId", this.meta); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemPotionSplash.java b/src/main/java/cn/nukkit/item/ItemPotionSplash.java index 96373bd1e16..4e463b72d9b 100644 --- a/src/main/java/cn/nukkit/item/ItemPotionSplash.java +++ b/src/main/java/cn/nukkit/item/ItemPotionSplash.java @@ -33,7 +33,7 @@ public String getProjectileEntityType() { @Override public float getThrowForce() { - return 0.5f; + return 0.50f; } @Override diff --git a/src/main/java/cn/nukkit/item/ItemPufferfish.java b/src/main/java/cn/nukkit/item/ItemPufferfish.java index 1b275409814..1d76a6d1c46 100644 --- a/src/main/java/cn/nukkit/item/ItemPufferfish.java +++ b/src/main/java/cn/nukkit/item/ItemPufferfish.java @@ -17,5 +17,4 @@ public ItemPufferfish(Integer meta) { public ItemPufferfish(Integer meta, int count) { super(PUFFERFISH, meta, count, "Pufferfish"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemPumpkinPie.java b/src/main/java/cn/nukkit/item/ItemPumpkinPie.java index 4ae92c2d21c..8919b54fcaf 100644 --- a/src/main/java/cn/nukkit/item/ItemPumpkinPie.java +++ b/src/main/java/cn/nukkit/item/ItemPumpkinPie.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemPumpkinPie extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemQuartz.java b/src/main/java/cn/nukkit/item/ItemQuartz.java index 18850835eab..ae8722ef3a8 100644 --- a/src/main/java/cn/nukkit/item/ItemQuartz.java +++ b/src/main/java/cn/nukkit/item/ItemQuartz.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemQuartz extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemRabbitRaw.java b/src/main/java/cn/nukkit/item/ItemRabbitRaw.java index 18e3ba2fe5a..395c9864ecb 100644 --- a/src/main/java/cn/nukkit/item/ItemRabbitRaw.java +++ b/src/main/java/cn/nukkit/item/ItemRabbitRaw.java @@ -5,6 +5,7 @@ * Package cn.nukkit.item in project nukkit. */ public class ItemRabbitRaw extends ItemEdible { + public ItemRabbitRaw() { this(0, 1); } @@ -16,5 +17,4 @@ public ItemRabbitRaw(Integer meta) { public ItemRabbitRaw(Integer meta, int count) { super(RAW_RABBIT, meta, count, "Raw Rabbit"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemRail.java b/src/main/java/cn/nukkit/item/ItemRail.java new file mode 100644 index 00000000000..4bbcf317ad3 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemRail.java @@ -0,0 +1,20 @@ +package cn.nukkit.item; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemRail extends Item { + + public ItemRail() { + this(0, 1); + } + + public ItemRail(Integer meta) { + this(meta, 1); + } + + public ItemRail(Integer meta, int count) { + super(RAIL, 0, count, "Rail"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemRecord.java b/src/main/java/cn/nukkit/item/ItemRecord.java index 5bf652e4105..b15f7487056 100644 --- a/src/main/java/cn/nukkit/item/ItemRecord.java +++ b/src/main/java/cn/nukkit/item/ItemRecord.java @@ -15,4 +15,8 @@ public int getMaxStackSize() { } public abstract String getSoundId(); + + public String getDiscName() { + return "Unknown"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecord11.java b/src/main/java/cn/nukkit/item/ItemRecord11.java index 56f3b8f7a50..f2732c3f100 100644 --- a/src/main/java/cn/nukkit/item/ItemRecord11.java +++ b/src/main/java/cn/nukkit/item/ItemRecord11.java @@ -21,4 +21,9 @@ public ItemRecord11(Integer meta, int count) { public String getSoundId() { return "record.11"; } + + @Override + public String getDiscName() { + return "C418 - 11"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecord13.java b/src/main/java/cn/nukkit/item/ItemRecord13.java index 173e8b029fb..bb90dbb5c43 100644 --- a/src/main/java/cn/nukkit/item/ItemRecord13.java +++ b/src/main/java/cn/nukkit/item/ItemRecord13.java @@ -21,4 +21,9 @@ public ItemRecord13(Integer meta, int count) { public String getSoundId() { return "record.13"; } + + @Override + public String getDiscName() { + return "C418 - 13"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecord5.java b/src/main/java/cn/nukkit/item/ItemRecord5.java index 7d85876f1f4..b143ceaea05 100644 --- a/src/main/java/cn/nukkit/item/ItemRecord5.java +++ b/src/main/java/cn/nukkit/item/ItemRecord5.java @@ -18,4 +18,9 @@ public ItemRecord5(Integer meta, int count) { public String getSoundId() { return "record.5"; } + + @Override + public String getDiscName() { + return "Samuel Åberg - 5"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordBlocks.java b/src/main/java/cn/nukkit/item/ItemRecordBlocks.java index 00be58db636..4fa73c559c1 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordBlocks.java +++ b/src/main/java/cn/nukkit/item/ItemRecordBlocks.java @@ -21,4 +21,9 @@ public ItemRecordBlocks(Integer meta, int count) { public String getSoundId() { return "record.blocks"; } + + @Override + public String getDiscName() { + return "C418 - blocks"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordCat.java b/src/main/java/cn/nukkit/item/ItemRecordCat.java index 8f4eef89fb9..dd680a4adf1 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordCat.java +++ b/src/main/java/cn/nukkit/item/ItemRecordCat.java @@ -21,4 +21,9 @@ public ItemRecordCat(Integer meta, int count) { public String getSoundId() { return "record.cat"; } + + @Override + public String getDiscName() { + return "C418 - cat"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordChirp.java b/src/main/java/cn/nukkit/item/ItemRecordChirp.java index 82bd15366a9..6d97c5955d1 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordChirp.java +++ b/src/main/java/cn/nukkit/item/ItemRecordChirp.java @@ -21,4 +21,9 @@ public ItemRecordChirp(Integer meta, int count) { public String getSoundId() { return "record.chirp"; } + + @Override + public String getDiscName() { + return "C418 - chirp"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordFar.java b/src/main/java/cn/nukkit/item/ItemRecordFar.java index 9f8d1a29c1c..51b975b2cb5 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordFar.java +++ b/src/main/java/cn/nukkit/item/ItemRecordFar.java @@ -21,4 +21,9 @@ public ItemRecordFar(Integer meta, int count) { public String getSoundId() { return "record.far"; } + + @Override + public String getDiscName() { + return "C418 - far"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordMall.java b/src/main/java/cn/nukkit/item/ItemRecordMall.java index aa49939e6b6..589bcb266fa 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordMall.java +++ b/src/main/java/cn/nukkit/item/ItemRecordMall.java @@ -21,4 +21,9 @@ public ItemRecordMall(Integer meta, int count) { public String getSoundId() { return "record.mall"; } + + @Override + public String getDiscName() { + return "C418 - mall"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordMellohi.java b/src/main/java/cn/nukkit/item/ItemRecordMellohi.java index 9c5119d950b..1875bdce0e5 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordMellohi.java +++ b/src/main/java/cn/nukkit/item/ItemRecordMellohi.java @@ -21,4 +21,9 @@ public ItemRecordMellohi(Integer meta, int count) { public String getSoundId() { return "record.mellohi"; } + + @Override + public String getDiscName() { + return "C418 - mellohi"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordOtherside.java b/src/main/java/cn/nukkit/item/ItemRecordOtherside.java index b662b60b51b..6c8dd7fa922 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordOtherside.java +++ b/src/main/java/cn/nukkit/item/ItemRecordOtherside.java @@ -18,4 +18,9 @@ public ItemRecordOtherside(Integer meta, int count) { public String getSoundId() { return "record.otherside"; } + + @Override + public String getDiscName() { + return "Lena Raine - otherside"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordPigstep.java b/src/main/java/cn/nukkit/item/ItemRecordPigstep.java index 2fb3c523b7e..eb3dc3574c8 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordPigstep.java +++ b/src/main/java/cn/nukkit/item/ItemRecordPigstep.java @@ -1,8 +1,5 @@ package cn.nukkit.item; -/** - * @author PetteriM1 - */ public class ItemRecordPigstep extends ItemRecord { public ItemRecordPigstep() { @@ -21,4 +18,9 @@ public ItemRecordPigstep(Integer meta, int count) { public String getSoundId() { return "record.pigstep"; } + + @Override + public String getDiscName() { + return "Lena Raine - Pigstep"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordRelic.java b/src/main/java/cn/nukkit/item/ItemRecordRelic.java index 2cfede0867c..135610678df 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordRelic.java +++ b/src/main/java/cn/nukkit/item/ItemRecordRelic.java @@ -18,4 +18,9 @@ public ItemRecordRelic(Integer meta, int count) { public String getSoundId() { return "record.relic"; } + + @Override + public String getDiscName() { + return "Aaron Cherof - Relic"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordStal.java b/src/main/java/cn/nukkit/item/ItemRecordStal.java index ec3b623f8d2..0bcdac73cb4 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordStal.java +++ b/src/main/java/cn/nukkit/item/ItemRecordStal.java @@ -21,4 +21,9 @@ public ItemRecordStal(Integer meta, int count) { public String getSoundId() { return "record.stal"; } + + @Override + public String getDiscName() { + return "C418 - stal"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordStrad.java b/src/main/java/cn/nukkit/item/ItemRecordStrad.java index 2edf6eb9b6c..d5f3b879ee2 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordStrad.java +++ b/src/main/java/cn/nukkit/item/ItemRecordStrad.java @@ -21,4 +21,9 @@ public ItemRecordStrad(Integer meta, int count) { public String getSoundId() { return "record.strad"; } + + @Override + public String getDiscName() { + return "C418 - strad"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordWait.java b/src/main/java/cn/nukkit/item/ItemRecordWait.java index 8816d6d8e2e..f1460bc10ad 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordWait.java +++ b/src/main/java/cn/nukkit/item/ItemRecordWait.java @@ -21,4 +21,9 @@ public ItemRecordWait(Integer meta, int count) { public String getSoundId() { return "record.wait"; } + + @Override + public String getDiscName() { + return "C418 - wait"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecordWard.java b/src/main/java/cn/nukkit/item/ItemRecordWard.java index 0d65d8fb8df..20cfc64d1f7 100644 --- a/src/main/java/cn/nukkit/item/ItemRecordWard.java +++ b/src/main/java/cn/nukkit/item/ItemRecordWard.java @@ -21,4 +21,9 @@ public ItemRecordWard(Integer meta, int count) { public String getSoundId() { return "record.ward"; } + + @Override + public String getDiscName() { + return "C418 - ward"; + } } diff --git a/src/main/java/cn/nukkit/item/ItemRecoveryCompass.java b/src/main/java/cn/nukkit/item/ItemRecoveryCompass.java new file mode 100644 index 00000000000..1908f883f5c --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemRecoveryCompass.java @@ -0,0 +1,16 @@ +package cn.nukkit.item; + +public class ItemRecoveryCompass extends Item { + + public ItemRecoveryCompass() { + this(0, 1); + } + + public ItemRecoveryCompass(Integer meta) { + this(meta, 1); + } + + public ItemRecoveryCompass(Integer meta, int count) { + super(RECOVERY_COMPASS, meta, count, "Recovery Compass"); + } +} diff --git a/src/main/java/cn/nukkit/item/ItemRedstone.java b/src/main/java/cn/nukkit/item/ItemRedstone.java index 50269f92933..4aae1c8f4a1 100644 --- a/src/main/java/cn/nukkit/item/ItemRedstone.java +++ b/src/main/java/cn/nukkit/item/ItemRedstone.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemRedstone extends Item { @@ -19,7 +18,6 @@ public ItemRedstone(Integer meta) { public ItemRedstone(Integer meta, int count) { super(REDSTONE, meta, count, "Redstone Dust"); - this.block = Block.get(BlockID.REDSTONE_WIRE); + this.block = Block.get(REDSTONE_WIRE); } - } diff --git a/src/main/java/cn/nukkit/item/ItemRedstoneComparator.java b/src/main/java/cn/nukkit/item/ItemRedstoneComparator.java index 93e97f24fd4..a6fa2b69f45 100644 --- a/src/main/java/cn/nukkit/item/ItemRedstoneComparator.java +++ b/src/main/java/cn/nukkit/item/ItemRedstoneComparator.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** * @author CreeperFace @@ -18,6 +17,6 @@ public ItemRedstoneComparator(Integer meta) { public ItemRedstoneComparator(Integer meta, int count) { super(COMPARATOR, meta, count, "Redstone Comparator"); - this.block = Block.get(BlockID.UNPOWERED_COMPARATOR); + this.block = Block.get(UNPOWERED_COMPARATOR); } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/ItemRedstoneRepeater.java b/src/main/java/cn/nukkit/item/ItemRedstoneRepeater.java index 43edb2dcb55..2f3b3bec0de 100644 --- a/src/main/java/cn/nukkit/item/ItemRedstoneRepeater.java +++ b/src/main/java/cn/nukkit/item/ItemRedstoneRepeater.java @@ -1,7 +1,6 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** * @author CreeperFace @@ -18,6 +17,6 @@ public ItemRedstoneRepeater(Integer meta) { public ItemRedstoneRepeater(Integer meta, int count) { super(REPEATER, meta, count, "Redstone Repeater"); - this.block = Block.get(BlockID.UNPOWERED_REPEATER); + this.block = Block.get(UNPOWERED_REPEATER); } } diff --git a/src/main/java/cn/nukkit/item/ItemRottenFlesh.java b/src/main/java/cn/nukkit/item/ItemRottenFlesh.java index 58cbc7f2aad..cf8a3cdfdf9 100644 --- a/src/main/java/cn/nukkit/item/ItemRottenFlesh.java +++ b/src/main/java/cn/nukkit/item/ItemRottenFlesh.java @@ -17,5 +17,4 @@ public ItemRottenFlesh(Integer meta) { public ItemRottenFlesh(Integer meta, int count) { super(ROTTEN_FLESH, meta, count, "Rotten Flesh"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemSaddle.java b/src/main/java/cn/nukkit/item/ItemSaddle.java index 7251554acb4..e83635e1635 100644 --- a/src/main/java/cn/nukkit/item/ItemSaddle.java +++ b/src/main/java/cn/nukkit/item/ItemSaddle.java @@ -1,12 +1,13 @@ package cn.nukkit.item; public class ItemSaddle extends Item { + public ItemSaddle() { - this(0, 0); + this(0, 1); } public ItemSaddle(Integer meta) { - this(meta, 0); + this(meta, 1); } public ItemSaddle(Integer meta, int count) { diff --git a/src/main/java/cn/nukkit/item/ItemSalmon.java b/src/main/java/cn/nukkit/item/ItemSalmon.java index 59a1a7a0488..6a58bf85b6b 100644 --- a/src/main/java/cn/nukkit/item/ItemSalmon.java +++ b/src/main/java/cn/nukkit/item/ItemSalmon.java @@ -17,5 +17,4 @@ public ItemSalmon(Integer meta) { public ItemSalmon(Integer meta, int count) { super(RAW_SALMON, meta, count, "Raw Salmon"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemSalmonCooked.java b/src/main/java/cn/nukkit/item/ItemSalmonCooked.java index e1277102226..c9a4509dae8 100644 --- a/src/main/java/cn/nukkit/item/ItemSalmonCooked.java +++ b/src/main/java/cn/nukkit/item/ItemSalmonCooked.java @@ -17,5 +17,4 @@ public ItemSalmonCooked(Integer meta) { public ItemSalmonCooked(Integer meta, int count) { super(COOKED_SALMON, meta, count, "Cooked Salmon"); } - } diff --git a/src/main/java/cn/nukkit/item/ItemSeedsBeetroot.java b/src/main/java/cn/nukkit/item/ItemSeedsBeetroot.java index ef6d9d9c51c..94f91a3cc00 100644 --- a/src/main/java/cn/nukkit/item/ItemSeedsBeetroot.java +++ b/src/main/java/cn/nukkit/item/ItemSeedsBeetroot.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSeedsBeetroot extends Item { @@ -19,6 +18,6 @@ public ItemSeedsBeetroot(Integer meta) { public ItemSeedsBeetroot(Integer meta, int count) { super(BEETROOT_SEEDS, 0, count, "Beetroot Seeds"); - this.block = Block.get(BlockID.BEETROOT_BLOCK); + this.block = Block.get(BEETROOT_BLOCK); } } diff --git a/src/main/java/cn/nukkit/item/ItemSeedsMelon.java b/src/main/java/cn/nukkit/item/ItemSeedsMelon.java index 44b2fa2f8d4..773f93e0a8e 100644 --- a/src/main/java/cn/nukkit/item/ItemSeedsMelon.java +++ b/src/main/java/cn/nukkit/item/ItemSeedsMelon.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSeedsMelon extends Item { @@ -19,6 +18,6 @@ public ItemSeedsMelon(Integer meta) { public ItemSeedsMelon(Integer meta, int count) { super(MELON_SEEDS, 0, count, "Melon Seeds"); - this.block = Block.get(BlockID.MELON_STEM); + this.block = Block.get(MELON_STEM); } } diff --git a/src/main/java/cn/nukkit/item/ItemSeedsPumpkin.java b/src/main/java/cn/nukkit/item/ItemSeedsPumpkin.java index 8f3452b5419..416c89a7f73 100644 --- a/src/main/java/cn/nukkit/item/ItemSeedsPumpkin.java +++ b/src/main/java/cn/nukkit/item/ItemSeedsPumpkin.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSeedsPumpkin extends Item { @@ -19,6 +18,6 @@ public ItemSeedsPumpkin(Integer meta) { public ItemSeedsPumpkin(Integer meta, int count) { super(PUMPKIN_SEEDS, 0, count, "Pumpkin Seeds"); - this.block = Block.get(BlockID.PUMPKIN_STEM); + this.block = Block.get(PUMPKIN_STEM); } } diff --git a/src/main/java/cn/nukkit/item/ItemSeedsWheat.java b/src/main/java/cn/nukkit/item/ItemSeedsWheat.java index cf37b8b46a1..d4f18ede800 100644 --- a/src/main/java/cn/nukkit/item/ItemSeedsWheat.java +++ b/src/main/java/cn/nukkit/item/ItemSeedsWheat.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSeedsWheat extends Item { @@ -18,7 +17,7 @@ public ItemSeedsWheat(Integer meta) { } public ItemSeedsWheat(Integer meta, int count) { - super(WHEAT_SEEDS, 0, count, "Wheat Seeds"); - this.block = Block.get(BlockID.WHEAT_BLOCK); + super(WHEAT_SEEDS, 0, count, "Seeds"); + this.block = Block.get(WHEAT_BLOCK); } } diff --git a/src/main/java/cn/nukkit/item/ItemShears.java b/src/main/java/cn/nukkit/item/ItemShears.java index f09a5b9b6d6..708874a8cf0 100644 --- a/src/main/java/cn/nukkit/item/ItemShears.java +++ b/src/main/java/cn/nukkit/item/ItemShears.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShears extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemShield.java b/src/main/java/cn/nukkit/item/ItemShield.java index 621d18d3906..a10a2ac8123 100644 --- a/src/main/java/cn/nukkit/item/ItemShield.java +++ b/src/main/java/cn/nukkit/item/ItemShield.java @@ -1,6 +1,6 @@ package cn.nukkit.item; -public class ItemShield extends Item { +public class ItemShield extends ItemTool { public ItemShield() { this(0, 1); @@ -15,7 +15,12 @@ public ItemShield(Integer meta, int count) { } @Override - public int getMaxStackSize() { - return 1; + public int getMaxDurability() { + return 337; + } + + @Override + public boolean noDamageOnAttack() { + return true; } } diff --git a/src/main/java/cn/nukkit/item/ItemShovelDiamond.java b/src/main/java/cn/nukkit/item/ItemShovelDiamond.java index d4b35b25bcb..cc44d0f0519 100644 --- a/src/main/java/cn/nukkit/item/ItemShovelDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemShovelDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShovelDiamond extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemShovelGold.java b/src/main/java/cn/nukkit/item/ItemShovelGold.java index 1b4f3e351b7..e31d0ae2c40 100644 --- a/src/main/java/cn/nukkit/item/ItemShovelGold.java +++ b/src/main/java/cn/nukkit/item/ItemShovelGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShovelGold extends ItemTool { @@ -15,7 +15,7 @@ public ItemShovelGold(Integer meta) { } public ItemShovelGold(Integer meta, int count) { - super(GOLD_SHOVEL, meta, count, "Gold Shovel"); + super(GOLD_SHOVEL, meta, count, "Golden Shovel"); } @Override @@ -32,9 +32,4 @@ public boolean isShovel() { public int getTier() { return ItemTool.TIER_GOLD; } - - @Override - public int getAttackDamage() { - return 1; - } } diff --git a/src/main/java/cn/nukkit/item/ItemShovelIron.java b/src/main/java/cn/nukkit/item/ItemShovelIron.java index d0ae0b630d8..a3b7c327db7 100644 --- a/src/main/java/cn/nukkit/item/ItemShovelIron.java +++ b/src/main/java/cn/nukkit/item/ItemShovelIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShovelIron extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemShovelStone.java b/src/main/java/cn/nukkit/item/ItemShovelStone.java index f7465c1803d..b331669309e 100644 --- a/src/main/java/cn/nukkit/item/ItemShovelStone.java +++ b/src/main/java/cn/nukkit/item/ItemShovelStone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShovelStone extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemShovelWood.java b/src/main/java/cn/nukkit/item/ItemShovelWood.java index e202dfd4f32..01676b54310 100644 --- a/src/main/java/cn/nukkit/item/ItemShovelWood.java +++ b/src/main/java/cn/nukkit/item/ItemShovelWood.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemShovelWood extends ItemTool { @@ -32,9 +32,4 @@ public boolean isShovel() { public int getTier() { return ItemTool.TIER_WOODEN; } - - @Override - public int getAttackDamage() { - return 1; - } } diff --git a/src/main/java/cn/nukkit/item/ItemSign.java b/src/main/java/cn/nukkit/item/ItemSign.java index 7063c55b055..6cbe86c6085 100644 --- a/src/main/java/cn/nukkit/item/ItemSign.java +++ b/src/main/java/cn/nukkit/item/ItemSign.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSign extends Item { @@ -19,7 +18,7 @@ public ItemSign(Integer meta) { public ItemSign(Integer meta, int count) { super(SIGN, 0, count, "Sign"); - this.block = Block.get(BlockID.SIGN_POST); + this.block = Block.get(SIGN_POST); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemSignAcacia.java b/src/main/java/cn/nukkit/item/ItemSignAcacia.java new file mode 100644 index 00000000000..cfb45c73dcc --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignAcacia.java @@ -0,0 +1,29 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemSignAcacia extends Item { + + public ItemSignAcacia() { + this(0, 1); + } + + public ItemSignAcacia(Integer meta) { + this(meta, 1); + } + + public ItemSignAcacia(Integer meta, int count) { + super(ACACIA_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.ACACIA_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignBirch.java b/src/main/java/cn/nukkit/item/ItemSignBirch.java new file mode 100644 index 00000000000..16653be49f5 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignBirch.java @@ -0,0 +1,29 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemSignBirch extends Item { + + public ItemSignBirch() { + this(0, 1); + } + + public ItemSignBirch(Integer meta) { + this(meta, 1); + } + + public ItemSignBirch(Integer meta, int count) { + super(BIRCH_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.BIRCH_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignCrimson.java b/src/main/java/cn/nukkit/item/ItemSignCrimson.java new file mode 100644 index 00000000000..db96e4e9bc9 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignCrimson.java @@ -0,0 +1,25 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +public class ItemSignCrimson extends Item { + + public ItemSignCrimson() { + this(0, 1); + } + + public ItemSignCrimson(Integer meta) { + this(meta, 1); + } + + public ItemSignCrimson(Integer meta, int count) { + super(CRIMSON_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.CRIMSON_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignDarkOak.java b/src/main/java/cn/nukkit/item/ItemSignDarkOak.java new file mode 100644 index 00000000000..ea35afd3222 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignDarkOak.java @@ -0,0 +1,28 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemSignDarkOak extends Item { + + public ItemSignDarkOak() { + this(0, 1); + } + + public ItemSignDarkOak(Integer meta) { + this(meta, 1); + } + + public ItemSignDarkOak(Integer meta, int count) { + super(DARKOAK_SIGN, 0, count, "Sign"); + this.block = Block.get(DARK_OAK_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignJungle.java b/src/main/java/cn/nukkit/item/ItemSignJungle.java new file mode 100644 index 00000000000..875564422fa --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignJungle.java @@ -0,0 +1,29 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemSignJungle extends Item { + + public ItemSignJungle() { + this(0, 1); + } + + public ItemSignJungle(Integer meta) { + this(meta, 1); + } + + public ItemSignJungle(Integer meta, int count) { + super(JUNGLE_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.JUNGLE_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignSpruce.java b/src/main/java/cn/nukkit/item/ItemSignSpruce.java new file mode 100644 index 00000000000..af9d7e6854b --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignSpruce.java @@ -0,0 +1,29 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +/** + * @author MagicDroidX + * Nukkit Project + */ +public class ItemSignSpruce extends Item { + + public ItemSignSpruce() { + this(0, 1); + } + + public ItemSignSpruce(Integer meta) { + this(meta, 1); + } + + public ItemSignSpruce(Integer meta, int count) { + super(SPRUCE_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.SPRUCE_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSignWarped.java b/src/main/java/cn/nukkit/item/ItemSignWarped.java new file mode 100644 index 00000000000..dad835c282a --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemSignWarped.java @@ -0,0 +1,25 @@ +package cn.nukkit.item; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; + +public class ItemSignWarped extends Item { + + public ItemSignWarped() { + this(0, 1); + } + + public ItemSignWarped(Integer meta) { + this(meta, 1); + } + + public ItemSignWarped(Integer meta, int count) { + super(WARPED_SIGN, 0, count, "Sign"); + this.block = Block.get(BlockID.WARPED_STANDING_SIGN); + } + + @Override + public int getMaxStackSize() { + return 16; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemSkull.java b/src/main/java/cn/nukkit/item/ItemSkull.java index ee79602f8d5..982e23cee9f 100644 --- a/src/main/java/cn/nukkit/item/ItemSkull.java +++ b/src/main/java/cn/nukkit/item/ItemSkull.java @@ -1,6 +1,10 @@ package cn.nukkit.item; import cn.nukkit.block.Block; +import cn.nukkit.entity.mob.EntityCreeper; +import cn.nukkit.entity.mob.EntitySkeleton; +import cn.nukkit.entity.mob.EntityWitherSkeleton; +import cn.nukkit.entity.mob.EntityZombie; /** * Created by Snake1999 on 2016/2/3. @@ -48,4 +52,24 @@ public static String getItemSkullName(int meta) { return "Skeleton Skull"; } } + + @Override + public boolean canBePutInHelmetSlot() { + return true; + } + + public static Item getMobHead(int networkId) { + switch (networkId) { + case EntitySkeleton.NETWORK_ID: + return Item.get(SKULL, SKELETON_SKULL, 1); + case EntityWitherSkeleton.NETWORK_ID: + return Item.get(SKULL, WITHER_SKELETON_SKULL, 1); + case EntityZombie.NETWORK_ID: + return Item.get(SKULL, ZOMBIE_HEAD, 1); + case EntityCreeper.NETWORK_ID: + return Item.get(SKULL, CREEPER_HEAD, 1); + default: + return null; + } + } } diff --git a/src/main/java/cn/nukkit/item/ItemSlimeball.java b/src/main/java/cn/nukkit/item/ItemSlimeball.java index 1bfd0969261..1546ed99ebd 100644 --- a/src/main/java/cn/nukkit/item/ItemSlimeball.java +++ b/src/main/java/cn/nukkit/item/ItemSlimeball.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSlimeball extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemSnowball.java b/src/main/java/cn/nukkit/item/ItemSnowball.java index ae6a5a2740e..83023ee88bb 100644 --- a/src/main/java/cn/nukkit/item/ItemSnowball.java +++ b/src/main/java/cn/nukkit/item/ItemSnowball.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSnowball extends ProjectileItem { diff --git a/src/main/java/cn/nukkit/item/ItemSpawnEgg.java b/src/main/java/cn/nukkit/item/ItemSpawnEgg.java index 3afd9eb6440..32348d97082 100644 --- a/src/main/java/cn/nukkit/item/ItemSpawnEgg.java +++ b/src/main/java/cn/nukkit/item/ItemSpawnEgg.java @@ -2,9 +2,15 @@ import cn.nukkit.Player; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockMobSpawner; +import cn.nukkit.entity.BaseEntity; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.mob.EntityZombie; +import cn.nukkit.entity.passive.EntityChicken; +import cn.nukkit.entity.passive.EntityCow; +import cn.nukkit.entity.passive.EntityPig; +import cn.nukkit.entity.passive.EntitySheep; import cn.nukkit.event.entity.CreatureSpawnEvent; -import cn.nukkit.event.entity.CreatureSpawnEvent.SpawnReason; import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.BlockFace; @@ -12,11 +18,12 @@ import cn.nukkit.nbt.tag.DoubleTag; import cn.nukkit.nbt.tag.FloatTag; import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.Utils; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSpawnEgg extends Item { @@ -30,7 +37,7 @@ public ItemSpawnEgg(Integer meta) { } public ItemSpawnEgg(Integer meta, int count) { - super(SPAWN_EGG, meta, count, "Spawn EntityEgg"); + super(SPAWN_EGG, meta, count, "Spawn Entity Egg"); } @Override @@ -44,6 +51,10 @@ public boolean onActivate(Level level, Player player, Block block, Block target, return false; } + if (target instanceof BlockMobSpawner) { + return false; + } + FullChunk chunk = level.getChunk((int) block.getX() >> 4, (int) block.getZ() >> 4); if (chunk == null) { @@ -60,14 +71,14 @@ public boolean onActivate(Level level, Player player, Block block, Block target, .add(new DoubleTag("", 0)) .add(new DoubleTag("", 0))) .putList(new ListTag("Rotation") - .add(new FloatTag("", new Random().nextFloat() * 360)) + .add(new FloatTag("", ThreadLocalRandom.current().nextFloat() * 360)) .add(new FloatTag("", 0))); if (this.hasCustomName()) { nbt.putString("CustomName", this.getCustomName()); } - CreatureSpawnEvent ev = new CreatureSpawnEvent(this.meta, block, nbt, SpawnReason.SPAWN_EGG); + CreatureSpawnEvent ev = new CreatureSpawnEvent(this.meta, block, nbt, CreatureSpawnEvent.SpawnReason.SPAWN_EGG); level.getServer().getPluginManager().callEvent(ev); if (ev.isCancelled()) { @@ -77,13 +88,24 @@ public boolean onActivate(Level level, Player player, Block block, Block target, Entity entity = Entity.createEntity(this.meta, chunk, nbt); if (entity != null) { - if (player.isSurvival()) { + if (!player.isCreative()) { player.getInventory().decreaseCount(player.getInventory().getHeldItemIndex()); } + entity.spawnToAll(); + + if (Utils.rand(1, 20) == 1 && + (entity instanceof EntityCow || + entity instanceof EntityChicken || + entity instanceof EntityPig || + entity instanceof EntitySheep || + entity instanceof EntityZombie)) { + + ((BaseEntity) entity).setBaby(true); + } + return true; } - return false; } } diff --git a/src/main/java/cn/nukkit/item/ItemSpiderEye.java b/src/main/java/cn/nukkit/item/ItemSpiderEye.java index 470087ddbdf..df40c18ecbb 100644 --- a/src/main/java/cn/nukkit/item/ItemSpiderEye.java +++ b/src/main/java/cn/nukkit/item/ItemSpiderEye.java @@ -1,4 +1,5 @@ package cn.nukkit.item; + /** * Created by Snake1999 on 2016/1/14. * Package cn.nukkit.item in project nukkit. diff --git a/src/main/java/cn/nukkit/item/ItemSpiderEyeFermented.java b/src/main/java/cn/nukkit/item/ItemSpiderEyeFermented.java index 0c28ea55c30..56b6cfd2f76 100644 --- a/src/main/java/cn/nukkit/item/ItemSpiderEyeFermented.java +++ b/src/main/java/cn/nukkit/item/ItemSpiderEyeFermented.java @@ -6,14 +6,14 @@ public class ItemSpiderEyeFermented extends Item { public ItemSpiderEyeFermented() { - this(0); + this(0, 1); } public ItemSpiderEyeFermented(Integer meta) { - this(0, 1); + this(meta, 1); } public ItemSpiderEyeFermented(Integer meta, int count) { - super(FERMENTED_SPIDER_EYE, meta, count, "Fermented Spider Eye"); + super(FERMENTED_SPIDER_EYE, 0, count, "Fermented Spider Eye"); } } diff --git a/src/main/java/cn/nukkit/item/ItemSteak.java b/src/main/java/cn/nukkit/item/ItemSteak.java index d35dc202976..295804dfe1a 100644 --- a/src/main/java/cn/nukkit/item/ItemSteak.java +++ b/src/main/java/cn/nukkit/item/ItemSteak.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSteak extends ItemEdible { diff --git a/src/main/java/cn/nukkit/item/ItemStick.java b/src/main/java/cn/nukkit/item/ItemStick.java index da3ffd88286..6b667805f47 100644 --- a/src/main/java/cn/nukkit/item/ItemStick.java +++ b/src/main/java/cn/nukkit/item/ItemStick.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemStick extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemString.java b/src/main/java/cn/nukkit/item/ItemString.java index b619a255d18..7d57b8c4612 100644 --- a/src/main/java/cn/nukkit/item/ItemString.java +++ b/src/main/java/cn/nukkit/item/ItemString.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemString extends Item { @@ -19,6 +18,6 @@ public ItemString(Integer meta) { public ItemString(Integer meta, int count) { super(STRING, meta, count, "String"); - this.block = Block.get(BlockID.TRIPWIRE); + this.block = Block.get(TRIPWIRE); } } diff --git a/src/main/java/cn/nukkit/item/ItemSugar.java b/src/main/java/cn/nukkit/item/ItemSugar.java index d3476eb47d2..e9f8866e17f 100644 --- a/src/main/java/cn/nukkit/item/ItemSugar.java +++ b/src/main/java/cn/nukkit/item/ItemSugar.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSugar extends Item { diff --git a/src/main/java/cn/nukkit/item/ItemSugarcane.java b/src/main/java/cn/nukkit/item/ItemSugarcane.java index f87632ea105..9111f9ea44a 100644 --- a/src/main/java/cn/nukkit/item/ItemSugarcane.java +++ b/src/main/java/cn/nukkit/item/ItemSugarcane.java @@ -1,10 +1,9 @@ package cn.nukkit.item; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSugarcane extends Item { @@ -18,7 +17,7 @@ public ItemSugarcane(Integer meta) { } public ItemSugarcane(Integer meta, int count) { - super(SUGARCANE, 0, count, "Sugar Cane"); - this.block = Block.get(BlockID.SUGARCANE_BLOCK); + super(SUGARCANE, 0, count, "Sugar Canes"); + this.block = Block.get(SUGARCANE_BLOCK); } } diff --git a/src/main/java/cn/nukkit/item/ItemSweetBerries.java b/src/main/java/cn/nukkit/item/ItemSweetBerries.java index 181322bc64f..80510fe301e 100644 --- a/src/main/java/cn/nukkit/item/ItemSweetBerries.java +++ b/src/main/java/cn/nukkit/item/ItemSweetBerries.java @@ -1,5 +1,7 @@ package cn.nukkit.item; +import cn.nukkit.block.Block; + public class ItemSweetBerries extends ItemEdible { public ItemSweetBerries() { @@ -12,5 +14,6 @@ public ItemSweetBerries(Integer meta) { public ItemSweetBerries(Integer meta, int count) { super(SWEET_BERRIES, meta, count, "Sweet Berries"); + this.block = Block.get(SWEET_BERRY_BUSH); } } diff --git a/src/main/java/cn/nukkit/item/ItemSwordDiamond.java b/src/main/java/cn/nukkit/item/ItemSwordDiamond.java index fab4b7c33bc..9ed70071dda 100644 --- a/src/main/java/cn/nukkit/item/ItemSwordDiamond.java +++ b/src/main/java/cn/nukkit/item/ItemSwordDiamond.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSwordDiamond extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemSwordGold.java b/src/main/java/cn/nukkit/item/ItemSwordGold.java index 5c39ec59b41..d9248c2a764 100644 --- a/src/main/java/cn/nukkit/item/ItemSwordGold.java +++ b/src/main/java/cn/nukkit/item/ItemSwordGold.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSwordGold extends ItemTool { @@ -15,7 +15,7 @@ public ItemSwordGold(Integer meta) { } public ItemSwordGold(Integer meta, int count) { - super(GOLD_SWORD, meta, count, "Gold Sword"); + super(GOLD_SWORD, meta, count, "Golden Sword"); } @Override diff --git a/src/main/java/cn/nukkit/item/ItemSwordIron.java b/src/main/java/cn/nukkit/item/ItemSwordIron.java index 508a206f2bc..282e7eaee07 100644 --- a/src/main/java/cn/nukkit/item/ItemSwordIron.java +++ b/src/main/java/cn/nukkit/item/ItemSwordIron.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSwordIron extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemSwordStone.java b/src/main/java/cn/nukkit/item/ItemSwordStone.java index cbe9d85f7ec..34c1fa0146e 100644 --- a/src/main/java/cn/nukkit/item/ItemSwordStone.java +++ b/src/main/java/cn/nukkit/item/ItemSwordStone.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSwordStone extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemSwordWood.java b/src/main/java/cn/nukkit/item/ItemSwordWood.java index 8dba4ad4c09..7725fe6d5c3 100644 --- a/src/main/java/cn/nukkit/item/ItemSwordWood.java +++ b/src/main/java/cn/nukkit/item/ItemSwordWood.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemSwordWood extends ItemTool { diff --git a/src/main/java/cn/nukkit/item/ItemTool.java b/src/main/java/cn/nukkit/item/ItemTool.java index 8e8602857d1..e67a2322227 100644 --- a/src/main/java/cn/nukkit/item/ItemTool.java +++ b/src/main/java/cn/nukkit/item/ItemTool.java @@ -5,14 +5,14 @@ import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.nbt.tag.ByteTag; import cn.nukkit.nbt.tag.Tag; - -import java.util.Random; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class ItemTool extends Item implements ItemDurable { + public static final int TIER_WOODEN = 1; public static final int TIER_GOLD = 2; public static final int TIER_STONE = 3; @@ -28,6 +28,7 @@ public abstract class ItemTool extends Item implements ItemDurable { public static final int TYPE_SHEARS = 5; public static final int TYPE_HOE = 6; + // Using vanilla durability + 1 here public static final int DURABILITY_WOODEN = 60; public static final int DURABILITY_GOLD = 33; public static final int DURABILITY_STONE = 132; @@ -37,10 +38,10 @@ public abstract class ItemTool extends Item implements ItemDurable { public static final int DURABILITY_FLINT_STEEL = 65; public static final int DURABILITY_SHEARS = 239; public static final int DURABILITY_BOW = 385; - public static final int DURABILITY_TRIDENT = 251; - public static final int DURABILITY_FISHING_ROD = 65; public static final int DURABILITY_CROSSBOW = 465; - public static final int DURABILITY_CARROT_ON_A_STICK = 25; + public static final int DURABILITY_TRIDENT = 251; + public static final int DURABILITY_FISHING_ROD = 385; + public static final int DURABILITY_CARROT_ON_A_STICK = 26; public static final int DURABILITY_WARPED_FUNGUS_ON_A_STICK = 100; public ItemTool(int id) { @@ -78,7 +79,7 @@ public boolean useOn(Block block) { block.getToolType() == ItemTool.TYPE_SHEARS && this.isShears() ) { this.meta++; - } else if (!this.isShears() && block.getBreakTime(this) > 0) { + } else if (this.isSword() && block.getHardness() > 0) { this.meta += 2; } else if (this.isHoe()) { if (block.getId() == GRASS || block.getId() == DIRT) { @@ -106,12 +107,8 @@ public boolean useOn(Entity entity) { } private boolean isDurable() { - if (!hasEnchantments()) { - return false; - } - Enchantment durability = getEnchantment(Enchantment.ID_DURABILITY); - return durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= new Random().nextInt(100); + return durability != null && durability.getLevel() > 0 && (100 / (durability.getLevel() + 1)) <= Utils.random.nextInt(100); } @Override @@ -120,36 +117,6 @@ public boolean isUnbreakable() { return tag instanceof ByteTag && ((ByteTag) tag).data > 0; } - @Override - public boolean isPickaxe() { - return false; - } - - @Override - public boolean isAxe() { - return false; - } - - @Override - public boolean isSword() { - return false; - } - - @Override - public boolean isShovel() { - return false; - } - - @Override - public boolean isHoe() { - return false; - } - - @Override - public boolean isShears() { - return (this.id == SHEARS); - } - @Override public boolean isTool() { return true; @@ -168,6 +135,8 @@ public int getEnchantAbility() { return 22; case TIER_IRON: return 14; + case TIER_NETHERITE: + return 10; //TODO } return 0; diff --git a/src/main/java/cn/nukkit/item/ItemTotem.java b/src/main/java/cn/nukkit/item/ItemTotem.java index e7330a41bd9..0a12915ff6f 100644 --- a/src/main/java/cn/nukkit/item/ItemTotem.java +++ b/src/main/java/cn/nukkit/item/ItemTotem.java @@ -1,7 +1,7 @@ package cn.nukkit.item; public class ItemTotem extends Item { - + public ItemTotem(Integer meta) { this(meta, 1); } diff --git a/src/main/java/cn/nukkit/item/ItemTrident.java b/src/main/java/cn/nukkit/item/ItemTrident.java index 99b23ca2862..2e285a8b7e3 100644 --- a/src/main/java/cn/nukkit/item/ItemTrident.java +++ b/src/main/java/cn/nukkit/item/ItemTrident.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.entity.projectile.EntityThrownTrident; import cn.nukkit.event.entity.EntityShootBowEvent; import cn.nukkit.event.entity.ProjectileLaunchEvent; @@ -13,9 +14,6 @@ import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.network.protocol.LevelSoundEventPacket; -/** - * Created by PetteriM1 - */ public class ItemTrident extends ItemTool { public ItemTrident() { @@ -34,12 +32,12 @@ public ItemTrident(Integer meta, int count) { public int getMaxDurability() { return ItemTool.DURABILITY_TRIDENT; } - + @Override public boolean isSword() { return true; } - + @Override public int getAttackDamage() { return 9; @@ -74,6 +72,14 @@ public boolean onRelease(Player player, int ticksUsed) { EntityThrownTrident trident = new EntityThrownTrident(player.chunk, nbt, player); trident.setItem(this); + if (player.isCreative()) { + trident.setPickupMode(EntityProjectile.PICKUP_CREATIVE); + } + + if (this.hasEnchantment(Enchantment.ID_TRIDENT_LOYALTY)) { + trident.setFavoredSlot(player.getInventory().getHeldItemIndex()); + } + double p = (double) ticksUsed / 20; double f = Math.min((p * p + p * 2) / 3, 1) * 2.5; diff --git a/src/main/java/cn/nukkit/item/ItemTurtleShell.java b/src/main/java/cn/nukkit/item/ItemTurtleShell.java index d2c2d1c9cd8..5d4fd03b0df 100644 --- a/src/main/java/cn/nukkit/item/ItemTurtleShell.java +++ b/src/main/java/cn/nukkit/item/ItemTurtleShell.java @@ -1,8 +1,5 @@ package cn.nukkit.item; -/** - * Created by PetteriM1 - */ public class ItemTurtleShell extends ItemArmor { public ItemTurtleShell() { @@ -36,9 +33,4 @@ public int getArmorPoints() { public int getMaxDurability() { return 276; } - - @Override - public int getToughness() { - return 2; - } } diff --git a/src/main/java/cn/nukkit/item/ItemTypes.java b/src/main/java/cn/nukkit/item/ItemTypes.java new file mode 100644 index 00000000000..87ca603f699 --- /dev/null +++ b/src/main/java/cn/nukkit/item/ItemTypes.java @@ -0,0 +1,364 @@ +package cn.nukkit.item; + +import cn.nukkit.utils.material.ItemType; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Data; + + +public class ItemTypes { + private static final Int2ObjectMap types = new Int2ObjectOpenHashMap<>(); + private static final Object2ObjectMap identifiers = new Object2ObjectOpenHashMap<>(); + + + public static final ItemType IRON_SHOVEL = register("minecraft:iron_shovel", ItemID.IRON_SHOVEL); + public static final ItemType IRON_PICKAXE = register("minecraft:iron_pickaxe", ItemID.IRON_PICKAXE); + public static final ItemType IRON_AXE = register("minecraft:iron_axe", ItemID.IRON_AXE); + public static final ItemType FLINT_STEEL = register("minecraft:flint_and_steel", ItemID.FLINT_STEEL); + public static final ItemType FLINT_AND_STEEL = register("minecraft:flint_and_steel", ItemID.FLINT_AND_STEEL); + public static final ItemType APPLE = register("minecraft:apple", ItemID.APPLE); + public static final ItemType BOW = register("minecraft:bow", ItemID.BOW); + public static final ItemType ARROW = register("minecraft:arrow", ItemID.ARROW); + public static final ItemType COAL = register("minecraft:coal", ItemID.COAL); + public static final ItemType DIAMOND = register("minecraft:diamond", ItemID.DIAMOND); + public static final ItemType IRON_INGOT = register("minecraft:iron_ingot", ItemID.IRON_INGOT); + public static final ItemType GOLD_INGOT = register("minecraft:gold_ingot", ItemID.GOLD_INGOT); + public static final ItemType IRON_SWORD = register("minecraft:iron_sword", ItemID.IRON_SWORD); + public static final ItemType WOODEN_SWORD = register("minecraft:wooden_sword", ItemID.WOODEN_SWORD); + public static final ItemType WOODEN_SHOVEL = register("minecraft:wooden_shovel", ItemID.WOODEN_SHOVEL); + public static final ItemType WOODEN_PICKAXE = register("minecraft:wooden_pickaxe", ItemID.WOODEN_PICKAXE); + public static final ItemType WOODEN_AXE = register("minecraft:wooden_axe", ItemID.WOODEN_AXE); + public static final ItemType STONE_SWORD = register("minecraft:stone_sword", ItemID.STONE_SWORD); + public static final ItemType STONE_SHOVEL = register("minecraft:stone_shovel", ItemID.STONE_SHOVEL); + public static final ItemType STONE_PICKAXE = register("minecraft:stone_pickaxe", ItemID.STONE_PICKAXE); + public static final ItemType STONE_AXE = register("minecraft:stone_axe", ItemID.STONE_AXE); + public static final ItemType DIAMOND_SWORD = register("minecraft:diamond_sword", ItemID.DIAMOND_SWORD); + public static final ItemType DIAMOND_SHOVEL = register("minecraft:diamond_shovel", ItemID.DIAMOND_SHOVEL); + public static final ItemType DIAMOND_PICKAXE = register("minecraft:diamond_pickaxe", ItemID.DIAMOND_PICKAXE); + public static final ItemType DIAMOND_AXE = register("minecraft:diamond_axe", ItemID.DIAMOND_AXE); + public static final ItemType STICK = register("minecraft:stick", ItemID.STICK); + public static final ItemType STICKS = register("minecraft:stick", ItemID.STICKS); + public static final ItemType BOWL = register("minecraft:bowl", ItemID.BOWL); + public static final ItemType MUSHROOM_STEW = register("minecraft:mushroom_stew", ItemID.MUSHROOM_STEW); + public static final ItemType GOLD_SWORD = register("minecraft:golden_sword", ItemID.GOLD_SWORD); + public static final ItemType GOLDEN_SWORD = register("minecraft:golden_sword", ItemID.GOLDEN_SWORD); + public static final ItemType GOLD_SHOVEL = register("minecraft:golden_shovel", ItemID.GOLD_SHOVEL); + public static final ItemType GOLDEN_SHOVEL = register("minecraft:golden_shovel", ItemID.GOLDEN_SHOVEL); + public static final ItemType GOLD_PICKAXE = register("minecraft:golden_pickaxe", ItemID.GOLD_PICKAXE); + public static final ItemType GOLDEN_PICKAXE = register("minecraft:golden_pickaxe", ItemID.GOLDEN_PICKAXE); + public static final ItemType GOLD_AXE = register("minecraft:golden_axe", ItemID.GOLD_AXE); + public static final ItemType GOLDEN_AXE = register("minecraft:golden_axe", ItemID.GOLDEN_AXE); + public static final ItemType STRING = register("minecraft:string", ItemID.STRING); + public static final ItemType FEATHER = register("minecraft:feather", ItemID.FEATHER); + public static final ItemType GUNPOWDER = register("minecraft:gunpowder", ItemID.GUNPOWDER); + public static final ItemType WOODEN_HOE = register("minecraft:wooden_hoe", ItemID.WOODEN_HOE); + public static final ItemType STONE_HOE = register("minecraft:stone_hoe", ItemID.STONE_HOE); + public static final ItemType IRON_HOE = register("minecraft:iron_hoe", ItemID.IRON_HOE); + public static final ItemType DIAMOND_HOE = register("minecraft:diamond_hoe", ItemID.DIAMOND_HOE); + public static final ItemType GOLD_HOE = register("minecraft:golden_hoe", ItemID.GOLD_HOE); + public static final ItemType GOLDEN_HOE = register("minecraft:golden_hoe", ItemID.GOLDEN_HOE); + public static final ItemType SEEDS = register("minecraft:wheat_seeds", ItemID.SEEDS); + public static final ItemType WHEAT_SEEDS = register("minecraft:wheat_seeds", ItemID.WHEAT_SEEDS); + public static final ItemType WHEAT = register("minecraft:wheat", ItemID.WHEAT); + public static final ItemType BREAD = register("minecraft:bread", ItemID.BREAD); + public static final ItemType LEATHER_CAP = register("minecraft:leather_helmet", ItemID.LEATHER_CAP); + public static final ItemType LEATHER_HELMET = register("minecraft:leather_helmet", ItemID.LEATHER_HELMET); + public static final ItemType LEATHER_TUNIC = register("minecraft:leather_chestplate", ItemID.LEATHER_TUNIC); + public static final ItemType LEATHER_CHESTPLATE = register("minecraft:leather_chestplate", ItemID.LEATHER_CHESTPLATE); + public static final ItemType LEATHER_PANTS = register("minecraft:leather_leggings", ItemID.LEATHER_PANTS); + public static final ItemType LEATHER_LEGGINGS = register("minecraft:leather_leggings", ItemID.LEATHER_LEGGINGS); + public static final ItemType LEATHER_BOOTS = register("minecraft:leather_boots", ItemID.LEATHER_BOOTS); + public static final ItemType CHAIN_HELMET = register("minecraft:chainmail_helmet", ItemID.CHAIN_HELMET); + public static final ItemType CHAIN_CHESTPLATE = register("minecraft:chainmail_chestplate", ItemID.CHAIN_CHESTPLATE); + public static final ItemType CHAIN_LEGGINGS = register("minecraft:chainmail_leggings", ItemID.CHAIN_LEGGINGS); + public static final ItemType CHAIN_BOOTS = register("minecraft:chainmail_boots", ItemID.CHAIN_BOOTS); + public static final ItemType IRON_HELMET = register("minecraft:iron_helmet", ItemID.IRON_HELMET); + public static final ItemType IRON_CHESTPLATE = register("minecraft:iron_chestplate", ItemID.IRON_CHESTPLATE); + public static final ItemType IRON_LEGGINGS = register("minecraft:iron_leggings", ItemID.IRON_LEGGINGS); + public static final ItemType IRON_BOOTS = register("minecraft:iron_boots", ItemID.IRON_BOOTS); + public static final ItemType DIAMOND_HELMET = register("minecraft:diamond_helmet", ItemID.DIAMOND_HELMET); + public static final ItemType DIAMOND_CHESTPLATE = register("minecraft:diamond_chestplate", ItemID.DIAMOND_CHESTPLATE); + public static final ItemType DIAMOND_LEGGINGS = register("minecraft:diamond_leggings", ItemID.DIAMOND_LEGGINGS); + public static final ItemType DIAMOND_BOOTS = register("minecraft:diamond_boots", ItemID.DIAMOND_BOOTS); + public static final ItemType GOLD_HELMET = register("minecraft:golden_helmet", ItemID.GOLD_HELMET); + public static final ItemType GOLDEN_HELMET = register("minecraft:golden_helmet", ItemID.GOLDEN_HELMET); + public static final ItemType GOLD_CHESTPLATE = register("minecraft:golden_chestplate", ItemID.GOLD_CHESTPLATE); + public static final ItemType GOLDEN_CHESTPLATE = register("minecraft:golden_chestplate", ItemID.GOLDEN_CHESTPLATE); + public static final ItemType GOLD_LEGGINGS = register("minecraft:golden_leggings", ItemID.GOLD_LEGGINGS); + public static final ItemType GOLDEN_LEGGINGS = register("minecraft:golden_leggings", ItemID.GOLDEN_LEGGINGS); + public static final ItemType GOLD_BOOTS = register("minecraft:golden_boots", ItemID.GOLD_BOOTS); + public static final ItemType GOLDEN_BOOTS = register("minecraft:golden_boots", ItemID.GOLDEN_BOOTS); + public static final ItemType FLINT = register("minecraft:flint", ItemID.FLINT); + public static final ItemType RAW_PORKCHOP = register("minecraft:porkchop", ItemID.RAW_PORKCHOP); + public static final ItemType COOKED_PORKCHOP = register("minecraft:cooked_porkchop", ItemID.COOKED_PORKCHOP); + public static final ItemType PAINTING = register("minecraft:painting", ItemID.PAINTING); + public static final ItemType GOLDEN_APPLE = register("minecraft:golden_apple", ItemID.GOLDEN_APPLE); + public static final ItemType SIGN = register("minecraft:oak_sign", ItemID.SIGN); + public static final ItemType WOODEN_DOOR = register("minecraft:wooden_door", ItemID.WOODEN_DOOR); + public static final ItemType BUCKET = register("minecraft:bucket", ItemID.BUCKET); + public static final ItemType MINECART = register("minecraft:minecart", ItemID.MINECART); + public static final ItemType SADDLE = register("minecraft:saddle", ItemID.SADDLE); + public static final ItemType IRON_DOOR = register("minecraft:iron_door", ItemID.IRON_DOOR); + public static final ItemType REDSTONE = register("minecraft:redstone", ItemID.REDSTONE); + public static final ItemType REDSTONE_DUST = register("minecraft:redstone", ItemID.REDSTONE_DUST); + public static final ItemType SNOWBALL = register("minecraft:snowball", ItemID.SNOWBALL); + public static final ItemType BOAT = register("minecraft:boat", ItemID.BOAT); + public static final ItemType LEATHER = register("minecraft:leather", ItemID.LEATHER); + public static final ItemType KELP = register("minecraft:kelp", ItemID.KELP); + public static final ItemType BRICK = register("minecraft:brick", ItemID.BRICK); + public static final ItemType CLAY = register("minecraft:clay_ball", ItemID.CLAY); + public static final ItemType CLAY_BALL = register("minecraft:clay_ball", ItemID.CLAY_BALL); + public static final ItemType SUGARCANE = register("minecraft:sugar_cane", ItemID.SUGARCANE); + public static final ItemType SUGAR_CANE = register("minecraft:sugar_cane", ItemID.SUGAR_CANE); + public static final ItemType SUGAR_CANES = register("minecraft:sugar_cane", ItemID.SUGAR_CANES); + public static final ItemType PAPER = register("minecraft:paper", ItemID.PAPER); + public static final ItemType BOOK = register("minecraft:book", ItemID.BOOK); + public static final ItemType SLIMEBALL = register("minecraft:slime_ball", ItemID.SLIMEBALL); + public static final ItemType SLIME_BALL = register("minecraft:slime_ball", ItemID.SLIME_BALL); + public static final ItemType CHEST_MINECART = register("minecraft:chest_minecart", ItemID.CHEST_MINECART); + public static final ItemType MINECART_WITH_CHEST = register("minecraft:chest_minecart", ItemID.MINECART_WITH_CHEST); + public static final ItemType EGG = register("minecraft:egg", ItemID.EGG); + public static final ItemType COMPASS = register("minecraft:compass", ItemID.COMPASS); + public static final ItemType FISHING_ROD = register("minecraft:fishing_rod", ItemID.FISHING_ROD); + public static final ItemType CLOCK = register("minecraft:clock", ItemID.CLOCK); + public static final ItemType GLOWSTONE_DUST = register("minecraft:glowstone_dust", ItemID.GLOWSTONE_DUST); + public static final ItemType RAW_FISH = register("minecraft:cod", ItemID.RAW_FISH); + public static final ItemType COOKED_FISH = register("minecraft:cooked_cod", ItemID.COOKED_FISH); + public static final ItemType DYE = register("minecraft:dye", ItemID.DYE); + public static final ItemType BONE = register("minecraft:bone", ItemID.BONE); + public static final ItemType SUGAR = register("minecraft:sugar", ItemID.SUGAR); + public static final ItemType CAKE = register("minecraft:cake", ItemID.CAKE); + public static final ItemType BED = register("minecraft:bed", ItemID.BED); + public static final ItemType REPEATER = register("minecraft:repeater", ItemID.REPEATER); + public static final ItemType COOKIE = register("minecraft:cookie", ItemID.COOKIE); + public static final ItemType MAP = register("minecraft:filled_map", ItemID.MAP); + public static final ItemType SHEARS = register("minecraft:shears", ItemID.SHEARS); + public static final ItemType MELON = register("minecraft:melon_slice", ItemID.MELON); + public static final ItemType MELON_SLICE = register("minecraft:melon_slice", ItemID.MELON_SLICE); + public static final ItemType PUMPKIN_SEEDS = register("minecraft:pumpkin_seeds", ItemID.PUMPKIN_SEEDS); + public static final ItemType MELON_SEEDS = register("minecraft:melon_seeds", ItemID.MELON_SEEDS); + public static final ItemType BEEF = register("minecraft:beef", ItemID.BEEF); + public static final ItemType RAW_BEEF = register("minecraft:beef", ItemID.RAW_BEEF); + public static final ItemType STEAK = register("minecraft:cooked_beef", ItemID.STEAK); + public static final ItemType COOKED_BEEF = register("minecraft:cooked_beef", ItemID.COOKED_BEEF); + public static final ItemType RAW_CHICKEN = register("minecraft:chicken", ItemID.RAW_CHICKEN); + public static final ItemType CHICKEN = register("minecraft:chicken", ItemID.CHICKEN); + public static final ItemType COOKED_CHICKEN = register("minecraft:cooked_chicken", ItemID.COOKED_CHICKEN); + public static final ItemType ROTTEN_FLESH = register("minecraft:rotten_flesh", ItemID.ROTTEN_FLESH); + public static final ItemType ENDER_PEARL = register("minecraft:ender_pearl", ItemID.ENDER_PEARL); + public static final ItemType BLAZE_ROD = register("minecraft:blaze_rod", ItemID.BLAZE_ROD); + public static final ItemType GHAST_TEAR = register("minecraft:ghast_tear", ItemID.GHAST_TEAR); + public static final ItemType GOLD_NUGGET = register("minecraft:gold_nugget", ItemID.GOLD_NUGGET); + public static final ItemType GOLDEN_NUGGET = register("minecraft:gold_nugget", ItemID.GOLDEN_NUGGET); + public static final ItemType NETHER_WART = register("minecraft:nether_wart", ItemID.NETHER_WART); + public static final ItemType POTION = register("minecraft:potion", ItemID.POTION); + public static final ItemType GLASS_BOTTLE = register("minecraft:glass_bottle", ItemID.GLASS_BOTTLE); + public static final ItemType BOTTLE = register("minecraft:glass_bottle", ItemID.BOTTLE); + public static final ItemType SPIDER_EYE = register("minecraft:spider_eye", ItemID.SPIDER_EYE); + public static final ItemType FERMENTED_SPIDER_EYE = register("minecraft:fermented_spider_eye", ItemID.FERMENTED_SPIDER_EYE); + public static final ItemType BLAZE_POWDER = register("minecraft:blaze_powder", ItemID.BLAZE_POWDER); + public static final ItemType MAGMA_CREAM = register("minecraft:magma_cream", ItemID.MAGMA_CREAM); + public static final ItemType BREWING_STAND = register("minecraft:brewing_stand", ItemID.BREWING_STAND); + public static final ItemType BREWING = register("minecraft:brewing_stand", ItemID.BREWING); + public static final ItemType CAULDRON = register("minecraft:cauldron", ItemID.CAULDRON); + public static final ItemType ENDER_EYE = register("minecraft:ender_eye", ItemID.ENDER_EYE); + public static final ItemType GLISTERING_MELON = register("minecraft:glistering_melon_slice", ItemID.GLISTERING_MELON); + public static final ItemType GLISTERING_MELON_SLICE = register("minecraft:glistering_melon_slice", ItemID.GLISTERING_MELON_SLICE); + public static final ItemType SPAWN_EGG = register("minecraft:spawn_egg", ItemID.SPAWN_EGG); + public static final ItemType EXPERIENCE_BOTTLE = register("minecraft:experience_bottle", ItemID.EXPERIENCE_BOTTLE); + public static final ItemType FIRE_CHARGE = register("minecraft:fire_charge", ItemID.FIRE_CHARGE); + public static final ItemType BOOK_AND_QUILL = register("minecraft:writable_book", ItemID.BOOK_AND_QUILL); + public static final ItemType WRITTEN_BOOK = register("minecraft:written_book", ItemID.WRITTEN_BOOK); + public static final ItemType EMERALD = register("minecraft:emerald", ItemID.EMERALD); + public static final ItemType ITEM_FRAME = register("minecraft:frame", ItemID.ITEM_FRAME); + public static final ItemType FRAME = register("minecraft:frame", ItemID.FRAME); + public static final ItemType FLOWER_POT = register("minecraft:flower_pot", ItemID.FLOWER_POT); + public static final ItemType CARROT = register("minecraft:carrot", ItemID.CARROT); + public static final ItemType CARROTS = register("minecraft:carrot", ItemID.CARROTS); + public static final ItemType POTATO = register("minecraft:potato", ItemID.POTATO); + public static final ItemType POTATOES = register("minecraft:potato", ItemID.POTATOES); + public static final ItemType BAKED_POTATO = register("minecraft:baked_potato", ItemID.BAKED_POTATO); + public static final ItemType BAKED_POTATOES = register("minecraft:baked_potato", ItemID.BAKED_POTATOES); + public static final ItemType POISONOUS_POTATO = register("minecraft:poisonous_potato", ItemID.POISONOUS_POTATO); + public static final ItemType EMPTY_MAP = register("minecraft:empty_map", ItemID.EMPTY_MAP); + public static final ItemType EMPTYMAP = register("minecraft:empty_map", ItemID.EMPTYMAP); + public static final ItemType GOLDEN_CARROT = register("minecraft:golden_carrot", ItemID.GOLDEN_CARROT); + public static final ItemType SKULL = register("minecraft:skull", ItemID.SKULL); + public static final ItemType CARROTONASTICK = register("minecraft:carrot_on_a_stick", ItemID.CARROTONASTICK); + public static final ItemType CARROT_ON_A_STICK = register("minecraft:carrot_on_a_stick", ItemID.CARROT_ON_A_STICK); + public static final ItemType NETHER_STAR = register("minecraft:nether_star", ItemID.NETHER_STAR); + public static final ItemType PUMPKIN_PIE = register("minecraft:pumpkin_pie", ItemID.PUMPKIN_PIE); + public static final ItemType FIREWORKS = register("minecraft:firework_rocket", ItemID.FIREWORKS); + public static final ItemType FIREWORKSCHARGE = register("minecraft:firework_star", ItemID.FIREWORKSCHARGE); + public static final ItemType ENCHANTED_BOOK = register("minecraft:enchanted_book", ItemID.ENCHANTED_BOOK); + public static final ItemType ENCHANT_BOOK = register("minecraft:enchanted_book", ItemID.ENCHANT_BOOK); + public static final ItemType COMPARATOR = register("minecraft:comparator", ItemID.COMPARATOR); + public static final ItemType NETHER_BRICK = register("minecraft:netherbrick", ItemID.NETHER_BRICK); + public static final ItemType QUARTZ = register("minecraft:quartz", ItemID.QUARTZ); + public static final ItemType NETHER_QUARTZ = register("minecraft:quartz", ItemID.NETHER_QUARTZ); + public static final ItemType TNT_MINECART = register("minecraft:tnt_minecart", ItemID.TNT_MINECART); + public static final ItemType MINECART_WITH_TNT = register("minecraft:tnt_minecart", ItemID.MINECART_WITH_TNT); + public static final ItemType HOPPER_MINECART = register("minecraft:hopper_minecart", ItemID.HOPPER_MINECART); + public static final ItemType MINECART_WITH_HOPPER = register("minecraft:hopper_minecart", ItemID.MINECART_WITH_HOPPER); + public static final ItemType PRISMARINE_SHARD = register("minecraft:prismarine_shard", ItemID.PRISMARINE_SHARD); + public static final ItemType HOPPER = register("minecraft:hopper", ItemID.HOPPER); + public static final ItemType RAW_RABBIT = register("minecraft:rabbit", ItemID.RAW_RABBIT); + public static final ItemType RABBIT = register("minecraft:rabbit", ItemID.RABBIT); + public static final ItemType COOKED_RABBIT = register("minecraft:cooked_rabbit", ItemID.COOKED_RABBIT); + public static final ItemType RABBIT_STEW = register("minecraft:rabbit_stew", ItemID.RABBIT_STEW); + public static final ItemType RABBIT_FOOT = register("minecraft:rabbit_foot", ItemID.RABBIT_FOOT); + public static final ItemType RABBIT_HIDE = register("minecraft:rabbit_hide", ItemID.RABBIT_HIDE); + public static final ItemType LEATHER_HORSE_ARMOR = register("minecraft:leather_horse_armor", ItemID.LEATHER_HORSE_ARMOR); + public static final ItemType IRON_HORSE_ARMOR = register("minecraft:iron_horse_armor", ItemID.IRON_HORSE_ARMOR); + public static final ItemType GOLD_HORSE_ARMOR = register("minecraft:golden_horse_armor", ItemID.GOLD_HORSE_ARMOR); + public static final ItemType GOLDEN_HORSE_ARMOR = register("minecraft:golden_horse_armor", ItemID.GOLDEN_HORSE_ARMOR); + public static final ItemType DIAMOND_HORSE_ARMOR = register("minecraft:diamond_horse_armor", ItemID.DIAMOND_HORSE_ARMOR); + public static final ItemType LEAD = register("minecraft:lead", ItemID.LEAD); + public static final ItemType NAME_TAG = register("minecraft:name_tag", ItemID.NAME_TAG); + public static final ItemType NAMETAG = register("minecraft:name_tag", ItemID.NAMETAG); + public static final ItemType PRISMARINE_CRYSTALS = register("minecraft:prismarine_crystals", ItemID.PRISMARINE_CRYSTALS); + public static final ItemType RAW_MUTTON = register("minecraft:mutton", ItemID.RAW_MUTTON); + public static final ItemType MUTTONRAW = register("minecraft:mutton", ItemID.MUTTONRAW); + public static final ItemType COOKED_MUTTON = register("minecraft:cooked_mutton", ItemID.COOKED_MUTTON); + public static final ItemType ARMOR_STAND = register("minecraft:armor_stand", ItemID.ARMOR_STAND); + public static final ItemType END_CRYSTAL = register("minecraft:end_crystal", ItemID.END_CRYSTAL); + public static final ItemType SPRUCE_DOOR = register("minecraft:spruce_door", ItemID.SPRUCE_DOOR); + public static final ItemType BIRCH_DOOR = register("minecraft:birch_door", ItemID.BIRCH_DOOR); + public static final ItemType JUNGLE_DOOR = register("minecraft:jungle_door", ItemID.JUNGLE_DOOR); + public static final ItemType ACACIA_DOOR = register("minecraft:acacia_door", ItemID.ACACIA_DOOR); + public static final ItemType DARK_OAK_DOOR = register("minecraft:dark_oak_door", ItemID.DARK_OAK_DOOR); + public static final ItemType CHORUS_FRUIT = register("minecraft:chorus_fruit", ItemID.CHORUS_FRUIT); + public static final ItemType POPPED_CHORUS_FRUIT = register("minecraft:popped_chorus_fruit", ItemID.POPPED_CHORUS_FRUIT); + public static final ItemType BANNER_PATTERN = register("minecraft:banner_pattern", ItemID.BANNER_PATTERN); + public static final ItemType DRAGON_BREATH = register("minecraft:dragon_breath", ItemID.DRAGON_BREATH); + public static final ItemType SPLASH_POTION = register("minecraft:splash_potion", ItemID.SPLASH_POTION); + public static final ItemType LINGERING_POTION = register("minecraft:lingering_potion", ItemID.LINGERING_POTION); + public static final ItemType SPARKLER = register("minecraft:sparkler", ItemID.SPARKLER); + public static final ItemType COMMAND_BLOCK_MINECART = register("minecraft:command_block_minecart", ItemID.COMMAND_BLOCK_MINECART); + public static final ItemType ELYTRA = register("minecraft:elytra", ItemID.ELYTRA); + public static final ItemType SHULKER_SHELL = register("minecraft:shulker_shell", ItemID.SHULKER_SHELL); + public static final ItemType BANNER = register("minecraft:banner", ItemID.BANNER); + public static final ItemType EYE_DROP = register("minecraft:medicine", ItemID.EYE_DROP); + public static final ItemType BALLOON = register("minecraft:balloon", ItemID.BALLOON); + public static final ItemType SUPER_FERTILIZER = register("minecraft:rapid_fertilizer", ItemID.SUPER_FERTILIZER); + public static final ItemType TOTEM = register("minecraft:totem_of_undying", ItemID.TOTEM); + public static final ItemType BLEACH = register("minecraft:bleach", ItemID.BLEACH); + public static final ItemType IRON_NUGGET = register("minecraft:iron_nugget", ItemID.IRON_NUGGET); + public static final ItemType ICE_BOMB = register("minecraft:ice_bomb", ItemID.ICE_BOMB); + public static final ItemType TRIDENT = register("minecraft:trident", ItemID.TRIDENT); + public static final ItemType BEETROOT = register("minecraft:beetroot", ItemID.BEETROOT); + public static final ItemType BEETROOT_SEEDS = register("minecraft:beetroot_seeds", ItemID.BEETROOT_SEEDS); + public static final ItemType BEETROOT_SEED = register("minecraft:beetroot_seeds", ItemID.BEETROOT_SEED); + public static final ItemType BEETROOT_SOUP = register("minecraft:beetroot_soup", ItemID.BEETROOT_SOUP); + public static final ItemType RAW_SALMON = register("minecraft:salmon", ItemID.RAW_SALMON); + public static final ItemType CLOWNFISH = register("minecraft:tropical_fish", ItemID.CLOWNFISH); + public static final ItemType PUFFERFISH = register("minecraft:pufferfish", ItemID.PUFFERFISH); + public static final ItemType COOKED_SALMON = register("minecraft:cooked_salmon", ItemID.COOKED_SALMON); + public static final ItemType DRIED_KELP = register("minecraft:dried_kelp", ItemID.DRIED_KELP); + public static final ItemType NAUTILUS_SHELL = register("minecraft:nautilus_shell", ItemID.NAUTILUS_SHELL); + public static final ItemType GOLDEN_APPLE_ENCHANTED = register("minecraft:enchanted_golden_apple", ItemID.GOLDEN_APPLE_ENCHANTED); + public static final ItemType ENCHANTED_GOLDEN_APPLE = register("minecraft:enchanted_golden_apple", ItemID.ENCHANTED_GOLDEN_APPLE); + public static final ItemType APPLEENCHANTED = register("minecraft:enchanted_golden_apple", ItemID.APPLEENCHANTED); + public static final ItemType HEART_OF_THE_SEA = register("minecraft:heart_of_the_sea", ItemID.HEART_OF_THE_SEA); + public static final ItemType SCUTE = register("minecraft:scute", ItemID.SCUTE); + public static final ItemType TURTLE_SHELL = register("minecraft:turtle_helmet", ItemID.TURTLE_SHELL); + public static final ItemType TURTLE_HELMET = register("minecraft:turtle_helmet", ItemID.TURTLE_HELMET); + public static final ItemType PHANTOM_MEMBRANE = register("minecraft:phantom_membrane", ItemID.PHANTOM_MEMBRANE); + public static final ItemType CROSSBOW = register("minecraft:crossbow", ItemID.CROSSBOW); + public static final ItemType SPRUCE_SIGN = register("minecraft:spruce_sign", ItemID.SPRUCE_SIGN); + public static final ItemType BIRCH_SIGN = register("minecraft:birch_sign", ItemID.BIRCH_SIGN); + public static final ItemType JUNGLE_SIGN = register("minecraft:jungle_sign", ItemID.JUNGLE_SIGN); + public static final ItemType ACACIA_SIGN = register("minecraft:acacia_sign", ItemID.ACACIA_SIGN); + public static final ItemType DARKOAK_SIGN = register("minecraft:dark_oak_sign", ItemID.DARKOAK_SIGN); + public static final ItemType SWEET_BERRIES = register("minecraft:sweet_berries", ItemID.SWEET_BERRIES); + public static final ItemType CAMERA = register("minecraft:camera", ItemID.CAMERA); + public static final ItemType COMPOUND = register("minecraft:compound", ItemID.COMPOUND); + public static final ItemType RECORD_13 = register("minecraft:music_disc_13", ItemID.RECORD_13); + public static final ItemType RECORD_CAT = register("minecraft:music_disc_cat", ItemID.RECORD_CAT); + public static final ItemType RECORD_BLOCKS = register("minecraft:music_disc_blocks", ItemID.RECORD_BLOCKS); + public static final ItemType RECORD_CHIRP = register("minecraft:music_disc_chirp", ItemID.RECORD_CHIRP); + public static final ItemType RECORD_FAR = register("minecraft:music_disc_far", ItemID.RECORD_FAR); + public static final ItemType RECORD_MALL = register("minecraft:music_disc_mall", ItemID.RECORD_MALL); + public static final ItemType RECORD_MELLOHI = register("minecraft:music_disc_mellohi", ItemID.RECORD_MELLOHI); + public static final ItemType RECORD_STAL = register("minecraft:music_disc_stal", ItemID.RECORD_STAL); + public static final ItemType RECORD_STRAD = register("minecraft:music_disc_strad", ItemID.RECORD_STRAD); + public static final ItemType RECORD_WARD = register("minecraft:music_disc_ward", ItemID.RECORD_WARD); + public static final ItemType RECORD_11 = register("minecraft:music_disc_11", ItemID.RECORD_11); + public static final ItemType RECORD_WAIT = register("minecraft:music_disc_wait", ItemID.RECORD_WAIT); + public static final ItemType SHIELD = register("minecraft:shield", ItemID.SHIELD); + public static final ItemType RAW_IRON = register("minecraft:raw_iron", ItemID.RAW_IRON); + public static final ItemType RAW_GOLD = register("minecraft:raw_gold", ItemID.RAW_GOLD); + public static final ItemType RAW_COPPER = register("minecraft:raw_copper", ItemID.RAW_COPPER); + public static final ItemType RECORD_5 = register("minecraft:music_disc_5", ItemID.RECORD_5); + public static final ItemType DISC_FRAGMENT_5 = register("minecraft:disc_fragment_5", ItemID.DISC_FRAGMENT_5); + public static final ItemType OAK_CHEST_BOAT = register("minecraft:oak_chest_boat", ItemID.OAK_CHEST_BOAT); + public static final ItemType BIRCH_CHEST_BOAT = register("minecraft:birch_chest_boat", ItemID.BIRCH_CHEST_BOAT); + public static final ItemType JUNGLE_CHEST_BOAT = register("minecraft:jungle_chest_boat", ItemID.JUNGLE_CHEST_BOAT); + public static final ItemType SPRUCE_CHEST_BOAT = register("minecraft:spruce_chest_boat", ItemID.SPRUCE_CHEST_BOAT); + public static final ItemType ACACIA_CHEST_BOAT = register("minecraft:acacia_chest_boat", ItemID.ACACIA_CHEST_BOAT); + public static final ItemType DARK_OAK_CHEST_BOAT = register("minecraft:dark_oak_chest_boat", ItemID.DARK_OAK_CHEST_BOAT); + public static final ItemType MANGROVE_CHEST_BOAT = register("minecraft:mangrove_chest_boat", ItemID.MANGROVE_CHEST_BOAT); + public static final ItemType ECHO_SHARD = register("minecraft:echo_shard", ItemID.ECHO_SHARD); + public static final ItemType GLOW_BERRIES = register("minecraft:glow_berries", ItemID.GLOW_BERRIES); + public static final ItemType RECORD_RELIC = register("minecraft:music_disc_relic", ItemID.RECORD_RELIC); + public static final ItemType CAMPFIRE = register("minecraft:campfire", ItemID.CAMPFIRE); + public static final ItemType SUSPICIOUS_STEW = register("minecraft:suspicious_stew", ItemID.SUSPICIOUS_STEW); + public static final ItemType HONEYCOMB = register("minecraft:honeycomb", ItemID.HONEYCOMB); + public static final ItemType HONEY_BOTTLE = register("minecraft:honey_bottle", ItemID.HONEY_BOTTLE); + public static final ItemType LODESTONECOMPASS = register("minecraft:lodestone_compass", ItemID.LODESTONECOMPASS); + public static final ItemType LODESTONE_COMPASS = register("minecraft:lodestone_compass", ItemID.LODESTONE_COMPASS); + public static final ItemType NETHERITE_INGOT = register("minecraft:netherite_ingot", ItemID.NETHERITE_INGOT); + public static final ItemType NETHERITE_SWORD = register("minecraft:netherite_sword", ItemID.NETHERITE_SWORD); + public static final ItemType NETHERITE_SHOVEL = register("minecraft:netherite_shovel", ItemID.NETHERITE_SHOVEL); + public static final ItemType NETHERITE_PICKAXE = register("minecraft:netherite_pickaxe", ItemID.NETHERITE_PICKAXE); + public static final ItemType NETHERITE_AXE = register("minecraft:netherite_axe", ItemID.NETHERITE_AXE); + public static final ItemType NETHERITE_HOE = register("minecraft:netherite_hoe", ItemID.NETHERITE_HOE); + public static final ItemType NETHERITE_HELMET = register("minecraft:netherite_helmet", ItemID.NETHERITE_HELMET); + public static final ItemType NETHERITE_CHESTPLATE = register("minecraft:netherite_chestplate", ItemID.NETHERITE_CHESTPLATE); + public static final ItemType NETHERITE_LEGGINGS = register("minecraft:netherite_leggings", ItemID.NETHERITE_LEGGINGS); + public static final ItemType NETHERITE_BOOTS = register("minecraft:netherite_boots", ItemID.NETHERITE_BOOTS); + public static final ItemType NETHERITE_SCRAP = register("minecraft:netherite_scrap", ItemID.NETHERITE_SCRAP); + public static final ItemType CRIMSON_SIGN = register("minecraft:crimson_sign", ItemID.CRIMSON_SIGN); + public static final ItemType WARPED_SIGN = register("minecraft:warped_sign", ItemID.WARPED_SIGN); + public static final ItemType CRIMSON_DOOR = register("minecraft:crimson_door", ItemID.CRIMSON_DOOR); + public static final ItemType WARPED_DOOR = register("minecraft:warped_door", ItemID.WARPED_DOOR); + public static final ItemType WARPED_FUNGUS_ON_A_STICK = register("minecraft:warped_fungus_on_a_stick", ItemID.WARPED_FUNGUS_ON_A_STICK); + public static final ItemType CHAIN = register("minecraft:chain", ItemID.CHAIN); + public static final ItemType RECORD_PIGSTEP = register("minecraft:music_disc_pigstep", ItemID.RECORD_PIGSTEP); + public static final ItemType NETHER_SPROUTS = register("minecraft:nether_sprouts", ItemID.NETHER_SPROUTS); + public static final ItemType GOAT_HORN = register("minecraft:goat_horn", ItemID.GOAT_HORN); + public static final ItemType AMETHYST_SHARD = register("minecraft:amethyst_shard", ItemID.AMETHYST_SHARD); + public static final ItemType SPYGLASS = register("minecraft:spyglass", ItemID.SPYGLASS); + public static final ItemType RECORD_OTHERSIDE = register("minecraft:music_disc_otherside", ItemID.RECORD_OTHERSIDE); + public static final ItemType SOUL_CAMPFIRE = register("minecraft:soul_campfire", ItemID.SOUL_CAMPFIRE); + + + private static ItemType register(String identifier, int legacyId) { + return register(new ItemTypeImpl(identifier, legacyId)); + } + + private static ItemType register(ItemType itemType) { + ItemType old = types.putIfAbsent(itemType.getLegacyId(), itemType); + /*if (old != null) { // TODO: there are alternate names for some items + throw new IllegalArgumentException("Item type with id " + itemType.getLegacyId() + " already exists: " + old); + }*/ + identifiers.putIfAbsent(itemType.getIdentifier(), itemType); // TODO: using identifiers.put() would be better + return old == null ? itemType : old; + } + + public static ItemType getFromLegacy(int legacyId) { + return types.get(legacyId); + } + + public static ItemType get(String identifier) { + return identifiers.get(identifier); + } + + @Data + private static class ItemTypeImpl implements ItemType { + private final String identifier; + private final int legacyId; + } +} diff --git a/src/main/java/cn/nukkit/item/ItemWarpedFungusOnAStick.java b/src/main/java/cn/nukkit/item/ItemWarpedFungusOnAStick.java index e8c0378c113..dae721c19f0 100644 --- a/src/main/java/cn/nukkit/item/ItemWarpedFungusOnAStick.java +++ b/src/main/java/cn/nukkit/item/ItemWarpedFungusOnAStick.java @@ -1,8 +1,5 @@ package cn.nukkit.item; -/** - * @author PetteriM1 - */ public class ItemWarpedFungusOnAStick extends ItemTool { public ItemWarpedFungusOnAStick() { diff --git a/src/main/java/cn/nukkit/item/ItemWheat.java b/src/main/java/cn/nukkit/item/ItemWheat.java index d04bcb6ab12..d46ed0ddfef 100644 --- a/src/main/java/cn/nukkit/item/ItemWheat.java +++ b/src/main/java/cn/nukkit/item/ItemWheat.java @@ -1,7 +1,7 @@ package cn.nukkit.item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ItemWheat extends Item { diff --git a/src/main/java/cn/nukkit/item/ProjectileItem.java b/src/main/java/cn/nukkit/item/ProjectileItem.java index 5daac1d2756..bdcc4a30090 100644 --- a/src/main/java/cn/nukkit/item/ProjectileItem.java +++ b/src/main/java/cn/nukkit/item/ProjectileItem.java @@ -2,10 +2,14 @@ import cn.nukkit.Player; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.projectile.EntityEnderEye; import cn.nukkit.entity.projectile.EntityEnderPearl; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.event.entity.ProjectileLaunchEvent; +import cn.nukkit.level.Level; +import cn.nukkit.level.Position; import cn.nukkit.math.Vector3; +import cn.nukkit.math.Vector3f; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.DoubleTag; import cn.nukkit.nbt.tag.FloatTag; @@ -26,10 +30,14 @@ public ProjectileItem(int id, Integer meta, int count, String name) { abstract public float getThrowForce(); public boolean onClickAir(Player player, Vector3 directionVector) { + if (this instanceof ItemEnderEye && player.getLevel().getDimension() != Level.DIMENSION_OVERWORLD) { + return false; + } + CompoundTag nbt = new CompoundTag() .putList(new ListTag("Pos") .add(new DoubleTag("", player.x)) - .add(new DoubleTag("", player.y + player.getEyeHeight() - 0.30000000149011612)) + .add(new DoubleTag("", player.y + player.getEyeHeight())) .add(new DoubleTag("", player.z))) .putList(new ListTag("Motion") .add(new DoubleTag("", directionVector.x)) @@ -41,41 +49,67 @@ public boolean onClickAir(Player player, Vector3 directionVector) { this.correctNBT(nbt); - Entity projectile = Entity.createEntity(this.getProjectileEntityType(), player.getLevel().getChunk(player.getFloorX() >> 4, player.getFloorZ() >> 4), nbt, player); + Entity projectile = Entity.createEntity(this.getProjectileEntityType(), player.getLevel().getChunk(player.getChunkX(), player.getChunkZ()), nbt, player); if (projectile != null) { - if (projectile instanceof EntityEnderPearl) { + if (projectile instanceof EntityEnderPearl || projectile instanceof EntityEnderEye) { if (player.getServer().getTick() - player.getLastEnderPearlThrowingTick() < 20) { - projectile.kill(); + projectile.close(); return false; } } - projectile.setMotion(projectile.getMotion().multiply(this.getThrowForce())); + if (projectile instanceof EntityEnderEye) { + if (player.getServer().getTick() - player.getLastEnderPearlThrowingTick() < 20) { + projectile.close(); + return false; + } + + Position strongholdPosition = this.getStrongholdPosition(player); + if (strongholdPosition == null) { + projectile.close(); + return false; + } + + Vector3f vector = strongholdPosition + .subtract(player.getPosition()) + .asVector3f() + .normalize() + .multiply(1f); + + vector.y = 0.55f; + + projectile.setMotion(vector.asVector3().divide(this.getThrowForce())); + } else { + projectile.setMotion(projectile.getMotion().multiply(this.getThrowForce())); + } if (projectile instanceof EntityProjectile) { ProjectileLaunchEvent ev = new ProjectileLaunchEvent((EntityProjectile) projectile); player.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { - projectile.kill(); + projectile.close(); } else { if (!player.isCreative()) { this.count--; } - if (projectile instanceof EntityEnderPearl) { + if (projectile instanceof EntityEnderPearl || projectile instanceof EntityEnderEye) { player.onThrowEnderPearl(); } projectile.spawnToAll(); player.getLevel().addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_BOW); } } - } else { - return false; } + return true; } protected void correctNBT(CompoundTag nbt) { + } + private Position getStrongholdPosition(Player p) { + return p.level.getDimension() == Level.DIMENSION_OVERWORLD ? p : null; // TODO } } diff --git a/src/main/java/cn/nukkit/item/RuntimeItemMapping.java b/src/main/java/cn/nukkit/item/RuntimeItemMapping.java index 6b7e4f3cf01..bc3a7308bcf 100644 --- a/src/main/java/cn/nukkit/item/RuntimeItemMapping.java +++ b/src/main/java/cn/nukkit/item/RuntimeItemMapping.java @@ -1,19 +1,20 @@ package cn.nukkit.item; +import cn.nukkit.Nukkit; +import cn.nukkit.block.Block; import cn.nukkit.item.RuntimeItems.MappingEntry; -import cn.nukkit.Server; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.Utils; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.Data; import lombok.extern.log4j.Log4j2; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -27,11 +28,10 @@ public class RuntimeItemMapping { private byte[] itemPalette; public RuntimeItemMapping(Map mappings) { - InputStream stream = Server.class.getClassLoader().getResourceAsStream("runtime_item_states.json"); - if (stream == null) { - throw new AssertionError("Unable to load runtime_item_states.json"); + JsonArray json = Utils.loadJsonResource("runtime_item_states.json").getAsJsonArray(); + if (json.isEmpty()) { + throw new IllegalStateException("Empty array"); } - JsonArray json = JsonParser.parseReader(new InputStreamReader(stream)).getAsJsonArray(); for (JsonElement element : json) { if (!element.isJsonObject()) { @@ -49,7 +49,7 @@ public RuntimeItemMapping(Map mappings) { MappingEntry mapping = mappings.get(identifier); legacyId = RuntimeItems.getLegacyIdFromLegacyString(mapping.getLegacyName()); if (legacyId == -1) { - throw new IllegalStateException("Unable to match " + mapping + " with legacyId"); + throw new IllegalStateException("Unable to match " + mapping + " with legacyId"); } damage = mapping.getDamage(); hasDamage = true; @@ -61,22 +61,36 @@ public RuntimeItemMapping(Map mappings) { } } - int fullId = this.getFullId(legacyId, damage); - LegacyEntry legacyEntry = new LegacyEntry(legacyId, hasDamage, damage); + this.registerItem(identifier, runtimeId, legacyId, damage, hasDamage); + } - this.runtime2Legacy.put(runtimeId, legacyEntry); - this.identifier2Legacy.put(identifier, legacyEntry); - if (!hasDamage && this.legacy2Runtime.containsKey(fullId)) { - Server.getInstance().getLogger().debug("RuntimeItemMapping contains duplicated legacy item state runtimeId=" + runtimeId + " identifier=" + identifier); - } else { - this.legacy2Runtime.put(fullId, new RuntimeEntry(identifier, runtimeId, hasDamage)); + this.generatePalette(); + } + + public void registerItem(String identifier, int runtimeId, int legacyId, int damage) { + this.registerItem(identifier, runtimeId, legacyId, damage, false); + } + + public void registerItem(String identifier, int runtimeId, int legacyId, int damage, boolean hasDamage) { + int fullId = this.getFullId(legacyId, damage); + LegacyEntry legacyEntry = new LegacyEntry(legacyId, hasDamage, damage); + + if (Nukkit.DEBUG > 1) { + if (this.runtime2Legacy.containsKey(runtimeId)) { + log.warn("RuntimeItemMapping: Registering " + identifier + " but runtime id " + runtimeId + " is already used"); } } - this.generatePalette(); + this.runtime2Legacy.put(runtimeId, legacyEntry); + this.identifier2Legacy.put(identifier, legacyEntry); + if (!hasDamage && this.legacy2Runtime.containsKey(fullId)) { + log.debug("RuntimeItemMapping contains duplicated legacy item state runtimeId=" + runtimeId + " identifier=" + identifier); + } else { + this.legacy2Runtime.put(fullId, new RuntimeEntry(identifier, runtimeId, hasDamage)); + } } - private void generatePalette() { + public void generatePalette() { BinaryStream paletteBuffer = new BinaryStream(); paletteBuffer.putUnsignedVarInt(this.legacy2Runtime.size()); for (RuntimeEntry entry : this.legacy2Runtime.values()) { @@ -90,7 +104,9 @@ private void generatePalette() { public LegacyEntry fromRuntime(int runtimeId) { LegacyEntry legacyEntry = this.runtime2Legacy.get(runtimeId); if (legacyEntry == null) { - throw new IllegalArgumentException("Unknown runtime2Legacy mapping: " + runtimeId); + //throw new IllegalArgumentException("Unknown runtime2Legacy mapping: " + runtimeId); + log.warn("Unknown runtime2Legacy mapping: " + runtimeId); + return new LegacyEntry(0, false, 0); } return legacyEntry; } @@ -102,11 +118,59 @@ public RuntimeEntry toRuntime(int id, int meta) { } if (runtimeEntry == null) { - throw new IllegalArgumentException("Unknown legacy2Runtime mapping: id=" + id + " meta=" + meta); + log.warn("Unknown legacy2Runtime mapping: id=" + id + " meta=" + meta); + runtimeEntry = this.legacy2Runtime.get(this.getFullId(Item.INFO_UPDATE, 0)); + if (runtimeEntry == null) throw new RuntimeException("Runtime ID for Item.INFO_UPDATE must exist!"); + //throw new IllegalArgumentException("Unknown legacy2Runtime mapping: id=" + id + " meta=" + meta); } return runtimeEntry; } + public Item parseCreativeItem(JsonObject json, boolean ignoreUnknown) { + String identifier = json.get("id").getAsString(); + LegacyEntry legacyEntry = this.fromIdentifier(identifier); + if (legacyEntry == null) { + log.trace("Can not find legacyEntry for " + identifier); + if (!ignoreUnknown) { + throw new IllegalStateException("Can not find legacyEntry for " + identifier); + } + return null; + } + + byte[] nbtBytes; + if (json.has("nbt_b64")) { + nbtBytes = Base64.getDecoder().decode(json.get("nbt_b64").getAsString()); + } else if (json.has("nbt_hex")) { + nbtBytes = Utils.parseHexBinary(json.get("nbt_hex").getAsString()); + } else { + nbtBytes = new byte[0]; + } + + int legacyId = legacyEntry.getLegacyId(); + int damage = 0; + if (json.has("damage")) { + damage = json.get("damage").getAsInt(); + } else if (legacyEntry.isHasDamage()) { + damage = legacyEntry.getDamage(); + } else if (json.has("blockRuntimeId")) { + int runtimeId = json.get("blockRuntimeId").getAsInt(); + int fullId = GlobalBlockPalette.getLegacyFullId(runtimeId); + if (fullId == -1) { + if (ignoreUnknown) { + return null; + } else { + throw new IllegalStateException("Can not find blockRuntimeId for " + identifier + " (" + runtimeId + ")"); + } + } + + damage = fullId & Block.DATA_MASK; + } + + int count = json.has("count") ? json.get("count").getAsInt() : 1; + return Item.get(legacyId, damage, count, nbtBytes); + } + + public LegacyEntry fromIdentifier(String identifier) { return this.identifier2Legacy.get(identifier); } diff --git a/src/main/java/cn/nukkit/item/RuntimeItems.java b/src/main/java/cn/nukkit/item/RuntimeItems.java index 5e09049b3c0..944ba5cea0b 100644 --- a/src/main/java/cn/nukkit/item/RuntimeItems.java +++ b/src/main/java/cn/nukkit/item/RuntimeItems.java @@ -1,17 +1,12 @@ package cn.nukkit.item; -import cn.nukkit.item.RuntimeItemMapping.LegacyEntry; -import cn.nukkit.Server; -import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.utils.Utils; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import lombok.Data; import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -20,7 +15,11 @@ public class RuntimeItems { private static final Map legacyString2LegacyInt = new HashMap<>(); - private static RuntimeItemMapping itemPalette; + private static final Map legacyInt2LegacyString = new HashMap<>(); + private static final Map latestIdentifierMapping = new HashMap<>(); + + private static RuntimeItemMapping mapping; + private static boolean initialized; public static void init() { @@ -28,89 +27,76 @@ public static void init() { throw new IllegalStateException("RuntimeItems were already generated!"); } initialized = true; - log.info("Loading runtime items..."); - - InputStream itemIdsStream = Server.class.getClassLoader().getResourceAsStream("legacy_item_ids.json"); - if (itemIdsStream == null) { - throw new AssertionError("Unable to load legacy_item_ids.json"); - } + log.debug("Loading runtime items..."); - JsonObject json = JsonParser.parseReader(new InputStreamReader(itemIdsStream)).getAsJsonObject(); - for (String identifier : json.keySet()) { - legacyString2LegacyInt.put(identifier, json.get(identifier).getAsInt()); + JsonObject json = Utils.loadJsonResource("legacy_item_ids.json").getAsJsonObject(); + for (Map.Entry identifierToId : json.entrySet()) { + int legacyId = identifierToId.getValue().getAsInt(); + legacyString2LegacyInt.put(identifierToId.getKey(), legacyId); + legacyInt2LegacyString.put(legacyId, identifierToId.getKey()); } - InputStream mappingStream = Server.class.getClassLoader().getResourceAsStream("item_mappings.json"); - if (mappingStream == null) { - throw new AssertionError("Unable to load item_mappings.json"); - } - JsonObject itemMapping = JsonParser.parseReader(new InputStreamReader(mappingStream)).getAsJsonObject(); - - Map mappingEntries = new HashMap<>(); + JsonObject itemMapping = Utils.loadJsonResource("item_mappings.json").getAsJsonObject(); + Map mappingEntries = latestIdentifierMapping; // keep it same with master + Map mappingEntriesLegacy = new HashMap<>(); for (String legacyName : itemMapping.keySet()) { JsonObject convertData = itemMapping.getAsJsonObject(legacyName); + boolean isLegacyStone = "minecraft:stone".equals(legacyName); + for (String damageStr : convertData.keySet()) { String identifier = convertData.get(damageStr).getAsString(); int damage = Integer.parseInt(damageStr); mappingEntries.put(identifier, new MappingEntry(legacyName, damage)); - } - } - itemPalette = new RuntimeItemMapping(mappingEntries); - } - - public static Item parseCreativeItem(JsonObject json, boolean ignoreUnknown) { - String identifier = json.get("id").getAsString(); - LegacyEntry legacyEntry = itemPalette.fromIdentifier(identifier); - if (legacyEntry == null) { - if (!ignoreUnknown) { - throw new IllegalStateException("Can not find legacyEntry for " + identifier); - } - log.debug("Can not find legacyEntry for " + identifier); - return null; - } - - byte[] nbtBytes; - if (json.has("nbt_b64")) { - nbtBytes = Base64.getDecoder().decode(json.get("nbt_b64").getAsString()); - } else { - nbtBytes = new byte[0]; - } - int legacyId = legacyEntry.getLegacyId(); - int damage = 0; - if (json.has("damage")) { - damage = json.get("damage").getAsInt(); - } else if (legacyEntry.isHasDamage()) { - damage = legacyEntry.getDamage(); - } else if (json.has("blockRuntimeId")) { - int runtimeId = json.get("blockRuntimeId").getAsInt(); - int fullId = GlobalBlockPalette.getLegacyFullId(runtimeId); - if (fullId == -1) { - if (ignoreUnknown) { - return null; - } else { - throw new IllegalStateException("Can not find blockRuntimeId for " + runtimeId); + if (!isLegacyStone) { + mappingEntriesLegacy.put(identifier, new MappingEntry(legacyName, damage)); } } - - damage = fullId & 0xf; } - int count = json.has("count") ? json.get("count").getAsInt() : 1; - return Item.get(legacyId, damage, count, nbtBytes); + mapping = new RuntimeItemMapping(mappingEntries); } public static RuntimeItemMapping getMapping() { - return itemPalette; + return mapping; } public static int getLegacyIdFromLegacyString(String identifier) { return legacyString2LegacyInt.getOrDefault(identifier, -1); } + public static String getLegacyStringFromLegacyId(int legacyId) { + return legacyInt2LegacyString.getOrDefault(legacyId, "minecraft:info_update"); + } + + public static String getLegacyStringFromNew(String identifier) { + MappingEntry entry = latestIdentifierMapping.get(identifier); + return entry == null ? identifier : entry.getLegacyName(); + } + @Data public static class MappingEntry { private final String legacyName; private final int damage; } + + public static int getId(int fullId) { + return (short) (fullId >> 16); + } + + public static int getData(int fullId) { + return ((fullId >> 1) & 0x7fff); + } + + public static int getFullId(int id, int data) { + return (((short) id) << 16) | ((data & 0x7fff) << 1); + } + + public static int getNetworkId(int networkFullId) { + return networkFullId >> 1; + } + + public static boolean hasData(int id) { + return (id & 0x1) != 0; + } } diff --git a/src/main/java/cn/nukkit/item/enchantment/Enchantment.java b/src/main/java/cn/nukkit/item/enchantment/Enchantment.java index 5f5753ff7b5..4596d59950c 100644 --- a/src/main/java/cn/nukkit/item/enchantment/Enchantment.java +++ b/src/main/java/cn/nukkit/item/enchantment/Enchantment.java @@ -21,22 +21,19 @@ import cn.nukkit.item.enchantment.trident.EnchantmentTridentImpaling; import cn.nukkit.item.enchantment.trident.EnchantmentTridentLoyalty; import cn.nukkit.item.enchantment.trident.EnchantmentTridentRiptide; -import cn.nukkit.math.NukkitMath; +import cn.nukkit.utils.Utils; import java.util.ArrayList; import java.util.HashSet; -import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Enchantment implements Cloneable { protected static Enchantment[] enchantments; - //http://minecraft.gamepedia.com/Enchanting#Aqua_Affinity - public static final int ID_PROTECTION_ALL = 0; public static final int ID_PROTECTION_FIRE = 1; public static final int ID_PROTECTION_FALL = 2; @@ -109,8 +106,8 @@ public static void init() { enchantments[ID_BINDING_CURSE] = new EnchantmentBindingCurse(); enchantments[ID_VANISHING_CURSE] = new EnchantmentVanishingCurse(); enchantments[ID_TRIDENT_IMPALING] = new EnchantmentTridentImpaling(); - enchantments[ID_TRIDENT_RIPTIDE] = new EnchantmentTridentRiptide(); enchantments[ID_TRIDENT_LOYALTY] = new EnchantmentTridentLoyalty(); + enchantments[ID_TRIDENT_RIPTIDE] = new EnchantmentTridentRiptide(); enchantments[ID_TRIDENT_CHANNELING] = new EnchantmentTridentChanneling(); enchantments[ID_CROSSBOW_MULTISHOT] = new EnchantmentCrossbowMultishot(); enchantments[ID_CROSSBOW_PIERCING] = new EnchantmentCrossbowPiercing(); @@ -177,7 +174,9 @@ public Enchantment setLevel(int level, boolean safe) { return this; } - this.level = NukkitMath.clamp(level, this.getMinLevel(), this.getMaxLevel()); + if (level > this.getMaxLevel()) { + this.level = this.getMaxLevel(); + } else this.level = Math.max(level, this.getMinLevel()); return this; } @@ -190,10 +189,6 @@ public Rarity getRarity() { return this.rarity; } - /** - * @deprecated use {@link Rarity#getWeight()} instead - */ - @Deprecated public int getWeight() { return this.rarity.getWeight(); } @@ -258,6 +253,10 @@ public boolean isMajor() { return false; } + public boolean isTreasure() { + return false; + } + @Override protected Enchantment clone() { try { @@ -270,10 +269,9 @@ protected Enchantment clone() { public static final String[] words = {"the", "elder", "scrolls", "klaatu", "berata", "niktu", "xyzzy", "bless", "curse", "light", "darkness", "fire", "air", "earth", "water", "hot", "dry", "cold", "wet", "ignite", "snuff", "embiggen", "twist", "shorten", "stretch", "fiddle", "destroy", "imbue", "galvanize", "enchant", "free", "limited", "range", "of", "towards", "inside", "sphere", "cube", "self", "other", "ball", "mental", "physical", "grow", "shrink", "demon", "elemental", "spirit", "animal", "creature", "beast", "humanoid", "undead", "fresh", "stale"}; public static String getRandomName() { - int count = ThreadLocalRandom.current().nextInt(3, 6); HashSet set = new HashSet<>(); - while (set.size() < count) { - set.add(Enchantment.words[ThreadLocalRandom.current().nextInt(0, Enchantment.words.length)]); + while (set.size() < Utils.random.nextInt(3, 6)) { + set.add(Enchantment.words[Utils.random.nextInt(0, Enchantment.words.length)]); } String[] words = set.toArray(new String[0]); diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java index 7d511415002..e2d0b1876be 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentBindingCurse.java @@ -1,17 +1,23 @@ -package cn.nukkit.item.enchantment; - -public class EnchantmentBindingCurse extends Enchantment { - protected EnchantmentBindingCurse() { - super(ID_BINDING_CURSE, "curse.binding", Rarity.VERY_RARE, EnchantmentType.WEARABLE); - } - - @Override - public int getMinEnchantAbility(int level) { - return 25; - } - - @Override - public int getMaxEnchantAbility(int level) { - return 30; - } -} +package cn.nukkit.item.enchantment; + +public class EnchantmentBindingCurse extends Enchantment { + + protected EnchantmentBindingCurse() { + super(ID_BINDING_CURSE, "curse.binding", Rarity.VERY_RARE, EnchantmentType.WEARABLE); + } + + @Override + public int getMinEnchantAbility(int level) { + return 25; + } + + @Override + public int getMaxEnchantAbility(int level) { + return 30; + } + + @Override + public boolean isTreasure() { + return true; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java index 8cd4140069a..1f3e533e306 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentDurability.java @@ -5,17 +5,18 @@ import java.util.Random; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentDurability extends Enchantment { + protected EnchantmentDurability() { super(ID_DURABILITY, "durability", Rarity.UNCOMMON, EnchantmentType.BREAKABLE); } @Override public int getMinEnchantAbility(int level) { - return 5 + (level - 1) * 8; + return 5 + ((level - 1) << 3); } @Override @@ -28,11 +29,6 @@ public int getMaxLevel() { return 3; } - @Override - public boolean checkCompatibility(Enchantment enchantment) { - return super.checkCompatibility(enchantment) && enchantment.id != ID_FORTUNE_DIGGING; - } - @Override public boolean canEnchant(Item item) { return item.getMaxDurability() >= 0 || super.canEnchant(item); diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java index 35581c0cf3f..289c601d9ed 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEfficiency.java @@ -3,10 +3,11 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentEfficiency extends Enchantment { + protected EnchantmentEfficiency() { super(ID_EFFICIENCY, "digging", Rarity.COMMON, EnchantmentType.DIGGER); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEntry.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEntry.java index 1ff6fb9d8d4..52ca6b7c2b6 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentEntry.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentEntry.java @@ -26,5 +26,4 @@ public int getCost() { public String getRandomName() { return randomName; } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java index 4ebb94500cc..e76d5ed56d6 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFireAspect.java @@ -6,10 +6,11 @@ import cn.nukkit.event.entity.EntityCombustByEntityEvent; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentFireAspect extends Enchantment { + protected EnchantmentFireAspect() { super(ID_FIRE_ASPECT, "fire", Rarity.RARE, EnchantmentType.SWORD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java index 7f75d0e2d35..8bd9fac1487 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentFrostWalker.java @@ -1,17 +1,28 @@ -package cn.nukkit.item.enchantment; - -public class EnchantmentFrostWalker extends Enchantment { - protected EnchantmentFrostWalker() { - super(ID_FROST_WALKER, "frostwalker", Rarity.RARE, EnchantmentType.ARMOR_FEET); - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 15; - } - - @Override - public int getMaxLevel() { - return 2; - } -} +package cn.nukkit.item.enchantment; + +public class EnchantmentFrostWalker extends Enchantment { + + protected EnchantmentFrostWalker() { + super(ID_FROST_WALKER, "frostwalker", Rarity.RARE, EnchantmentType.ARMOR_FEET); + } + + @Override + public int getMaxEnchantAbility(int level) { + return this.getMinEnchantAbility(level) + 15; + } + + @Override + public int getMaxLevel() { + return 2; + } + + @Override + public boolean isTreasure() { + return true; + } + + @Override + public boolean checkCompatibility(Enchantment enchantment) { + return super.checkCompatibility(enchantment) && enchantment.id != ID_WATER_WALKER; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java index e920eb91f67..bd4d47e0174 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentKnockback.java @@ -1,10 +1,11 @@ package cn.nukkit.item.enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentKnockback extends Enchantment { + protected EnchantmentKnockback() { super(ID_KNOCKBACK, "knockback", Rarity.UNCOMMON, EnchantmentType.SWORD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentList.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentList.java index 5723e969d28..e890348087b 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentList.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentList.java @@ -28,5 +28,4 @@ public EnchantmentEntry getSlot(int slot) { public int getSize() { return enchantments.length; } - } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java index d8890a6e053..51cd8fa3ef6 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentLure.java @@ -1,10 +1,11 @@ package cn.nukkit.item.enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentLure extends Enchantment { + protected EnchantmentLure() { super(ID_LURE, "fishingSpeed", Rarity.RARE, EnchantmentType.FISHING_ROD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java index 2b9e9fc3b48..c07de608377 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentMending.java @@ -4,6 +4,7 @@ * @author Rover656 */ public class EnchantmentMending extends Enchantment { + protected EnchantmentMending() { super(ID_MENDING, "mending", Rarity.RARE, EnchantmentType.BREAKABLE); } @@ -18,6 +19,11 @@ public int getMaxEnchantAbility(int level) { return this.getMinEnchantAbility(level) + 50; } + @Override + public boolean isTreasure() { + return true; + } + @Override public boolean checkCompatibility(Enchantment enchantment) { return super.checkCompatibility(enchantment) && enchantment.id != ID_BOW_INFINITY; diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java index 550dea32eba..740ee115f0a 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSilkTouch.java @@ -3,10 +3,11 @@ import cn.nukkit.item.Item; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentSilkTouch extends Enchantment { + protected EnchantmentSilkTouch() { super(ID_SILK_TOUCH, "untouching", Rarity.VERY_RARE, EnchantmentType.DIGGER); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java index cf85ed2cb97..14b0d90e5ab 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentThorns.java @@ -9,10 +9,11 @@ import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentThorns extends Enchantment { + protected EnchantmentThorns() { super(ID_THORNS, "thorns", Rarity.RARE, EnchantmentType.ARMOR); } @@ -34,7 +35,7 @@ public int getMaxLevel() { @Override public void doPostAttack(Entity attacker, Entity entity) { - if (!(entity instanceof EntityHumanType)) { + if (!(entity instanceof EntityHumanType) || attacker == entity) { return; } @@ -52,7 +53,7 @@ public void doPostAttack(Entity attacker, Entity entity) { ThreadLocalRandom random = ThreadLocalRandom.current(); if (shouldHit(random, thornsLevel)) { - attacker.attack(new EntityDamageByEntityEvent(entity, attacker, EntityDamageEvent.DamageCause.ENTITY_ATTACK, getDamage(random, level), 0f)); + attacker.attack(new EntityDamageByEntityEvent(entity, attacker, EntityDamageEvent.DamageCause.THORNS, getDamage(random, level), 0f)); } } @@ -63,4 +64,4 @@ private static boolean shouldHit(ThreadLocalRandom random, int level) { private static int getDamage(ThreadLocalRandom random, int level) { return level > 10 ? level - 10 : random.nextInt(1, 5); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java index 676a15cb08f..037c4c8865e 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentType.java @@ -1,15 +1,10 @@ package cn.nukkit.item.enchantment; -import cn.nukkit.item.Item; -import cn.nukkit.item.ItemArmor; -import cn.nukkit.item.ItemBow; -import cn.nukkit.item.ItemCrossbow; -import cn.nukkit.item.ItemFishingRod; -import cn.nukkit.item.ItemSkull; -import cn.nukkit.item.ItemTrident; +import cn.nukkit.block.BlockID; +import cn.nukkit.item.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public enum EnchantmentType { @@ -52,11 +47,10 @@ public boolean canEnchantItem(Item item) { default: return false; } - } else { switch (this) { case SWORD: - return item.isSword(); + return item.isSword() && item.getId() != ItemID.TRIDENT; case DIGGER: return item.isPickaxe() || item.isShovel() || item.isAxe() || item.isHoe(); case BOW: @@ -64,7 +58,7 @@ public boolean canEnchantItem(Item item) { case FISHING_ROD: return item instanceof ItemFishingRod; case WEARABLE: - return item instanceof ItemSkull; + return item instanceof ItemSkull || item.getId() == (255 - BlockID.CARVED_PUMPKIN); case TRIDENT: return item instanceof ItemTrident; case CROSSBOW: diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java index 928398ee5fb..40ad778af19 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentVanishingCurse.java @@ -1,14 +1,19 @@ -package cn.nukkit.item.enchantment; - -import cn.nukkit.item.Item; - -public class EnchantmentVanishingCurse extends Enchantment { - protected EnchantmentVanishingCurse() { - super(ID_VANISHING_CURSE, "curse.vanishing", Rarity.VERY_RARE, EnchantmentType.BREAKABLE); - } - - @Override - public boolean canEnchant(Item item) { - return item.getId() == Item.SKULL || item.getId() == Item.COMPASS || super.canEnchant(item); - } -} +package cn.nukkit.item.enchantment; + +import cn.nukkit.item.Item; + +public class EnchantmentVanishingCurse extends Enchantment { + + protected EnchantmentVanishingCurse() { + super(ID_VANISHING_CURSE, "curse.vanishing", Rarity.VERY_RARE, EnchantmentType.BREAKABLE); + } + + @Override + public boolean isTreasure() { + return true; + } + + public boolean canEnchant(Item item) { + return item.getId() == Item.SKULL || item.getId() == Item.COMPASS || super.canEnchant(item); + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java index 694b59ee54d..7e1fd5e3f61 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterBreath.java @@ -1,10 +1,11 @@ package cn.nukkit.item.enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentWaterBreath extends Enchantment { + protected EnchantmentWaterBreath() { super(ID_WATER_BREATHING, "oxygen", Rarity.RARE, EnchantmentType.ARMOR_HEAD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java index 4a6afdba2fd..36a0b4f79da 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWalker.java @@ -1,10 +1,11 @@ package cn.nukkit.item.enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentWaterWalker extends Enchantment { + protected EnchantmentWaterWalker() { super(ID_WATER_WALKER, "waterWalker", Rarity.RARE, EnchantmentType.ARMOR_FEET); } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java index baa9e4542c0..b8e45b52a95 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentWaterWorker.java @@ -1,10 +1,11 @@ package cn.nukkit.item.enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentWaterWorker extends Enchantment { + protected EnchantmentWaterWorker() { super(ID_WATER_WORKER, "waterWorker", Rarity.RARE, EnchantmentType.ARMOR_HEAD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java index 062eaf35df2..30581eab182 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBow.java @@ -4,10 +4,11 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EnchantmentBow extends Enchantment { + protected EnchantmentBow(int id, String name, Rarity rarity) { super(id, name, rarity, EnchantmentType.BOW); } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java index 62b9f48d54a..6bb109edc3d 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowFlame.java @@ -3,7 +3,7 @@ import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentBowFlame extends EnchantmentBow { diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java index f630c80af95..7848e7b75b0 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowInfinity.java @@ -3,10 +3,11 @@ import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentBowInfinity extends EnchantmentBow { + public EnchantmentBowInfinity() { super(Enchantment.ID_BOW_INFINITY, "arrowInfinite", Rarity.VERY_RARE); } diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java index 457e14715f3..51cf980f9aa 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowKnockback.java @@ -3,7 +3,7 @@ import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentBowKnockback extends EnchantmentBow { @@ -13,7 +13,7 @@ public EnchantmentBowKnockback() { @Override public int getMinEnchantAbility(int level) { - return 12 + (level - 1) * 20; + return this.getMinEnchantAbility(level) + 25; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java index 77b25b7172b..3c832fffcab 100644 --- a/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java +++ b/src/main/java/cn/nukkit/item/enchantment/bow/EnchantmentBowPower.java @@ -3,10 +3,11 @@ import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentBowPower extends EnchantmentBow { + public EnchantmentBowPower() { super(Enchantment.ID_BOW_POWER, "arrowDamage", Rarity.COMMON); } diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java index a2a1e5a612b..9f864befdcf 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamage.java @@ -5,7 +5,7 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EnchantmentDamage extends Enchantment { @@ -16,11 +16,8 @@ public enum TYPE { ARTHROPODS } - protected final TYPE damageType; - protected EnchantmentDamage(int id, String name, Rarity rarity, TYPE type) { super(id, name, rarity, EnchantmentType.SWORD); - this.damageType = type; } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java index 0cf32eb95c9..7121b211e54 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageAll.java @@ -3,7 +3,7 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentDamageAll extends EnchantmentDamage { @@ -29,6 +29,6 @@ public int getMaxEnchantableLevel() { @Override public double getDamageBonus(Entity entity) { - return this.getLevel() * 1.25; //https://minecraft.fandom.com/wiki/Sharpness#Usage + return this.getLevel() * 1.25; } } diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java index 3966e261d23..909c6ed5e5f 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageArthropods.java @@ -3,11 +3,10 @@ import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityArthropod; import cn.nukkit.potion.Effect; - -import java.util.concurrent.ThreadLocalRandom; +import cn.nukkit.utils.Utils; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentDamageArthropods extends EnchantmentDamage { @@ -18,7 +17,7 @@ public EnchantmentDamageArthropods() { @Override public int getMinEnchantAbility(int level) { - return 5 + (level - 1) * 8; + return 5 + ((level - 1) << 3); } @Override @@ -38,8 +37,7 @@ public double getDamageBonus(Entity entity) { @Override public void doPostAttack(Entity attacker, Entity entity) { if (entity instanceof EntityArthropod) { - int duration = 20 + ThreadLocalRandom.current().nextInt(10 * this.level); - entity.addEffect(Effect.getEffect(Effect.SLOWNESS).setDuration(duration).setAmplifier(3)); + entity.addEffect(Effect.getEffect(Effect.SLOWNESS).setDuration(20 + Utils.random.nextInt(10 * this.level)).setAmplifier(3)); } } } diff --git a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java index ec45ba265ec..de0ed9e9ad4 100644 --- a/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java +++ b/src/main/java/cn/nukkit/item/enchantment/damage/EnchantmentDamageSmite.java @@ -4,7 +4,7 @@ import cn.nukkit.entity.EntitySmite; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentDamageSmite extends EnchantmentDamage { @@ -15,7 +15,7 @@ public EnchantmentDamageSmite() { @Override public int getMinEnchantAbility(int level) { - return 5 + (level - 1) * 8; + return 5 + ((level - 1) << 3); } @Override @@ -25,7 +25,7 @@ public int getMaxEnchantAbility(int level) { @Override public double getDamageBonus(Entity entity) { - if(entity instanceof EntitySmite) { + if (entity instanceof EntitySmite) { return getLevel() * 2.5; } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java index 3be23fe9686..3c002d52086 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLoot.java @@ -4,7 +4,7 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EnchantmentLoot extends Enchantment { diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java index 83d116a69e0..6e5fa3da8a4 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootDigging.java @@ -4,10 +4,11 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentLootDigging extends EnchantmentLoot { + public EnchantmentLootDigging() { super(Enchantment.ID_FORTUNE_DIGGING, "lootBonusDigger", Rarity.RARE, EnchantmentType.DIGGER); } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java index 1016b6f3137..077bad043ef 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootFishing.java @@ -4,10 +4,11 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentLootFishing extends EnchantmentLoot { + public EnchantmentLootFishing() { super(Enchantment.ID_FORTUNE_FISHING, "lootBonusFishing", Rarity.RARE, EnchantmentType.FISHING_ROD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java index d0f438b2794..8bb5a1834b4 100644 --- a/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java +++ b/src/main/java/cn/nukkit/item/enchantment/loot/EnchantmentLootWeapon.java @@ -4,10 +4,11 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentLootWeapon extends EnchantmentLoot { + public EnchantmentLootWeapon() { super(Enchantment.ID_LOOTING, "lootBonus", Rarity.RARE, EnchantmentType.SWORD); } diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java index 57949b75d1c..7096d26894f 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtection.java @@ -4,7 +4,7 @@ import cn.nukkit.item.enchantment.EnchantmentType; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class EnchantmentProtection extends Enchantment { diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java index 4db1e0f9616..bb1ad5d21f6 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionAll.java @@ -5,7 +5,7 @@ import cn.nukkit.item.enchantment.Enchantment; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentProtectionAll extends EnchantmentProtection { @@ -33,7 +33,7 @@ public double getTypeModifier() { public float getProtectionFactor(EntityDamageEvent e) { DamageCause cause = e.getCause(); - if (level <= 0 || cause == DamageCause.VOID || cause == DamageCause.CUSTOM || cause == DamageCause.MAGIC || cause == DamageCause.HUNGER) { + if (level <= 0 || cause == DamageCause.VOID || cause == DamageCause.SUICIDE || cause == DamageCause.CUSTOM || cause == DamageCause.HUNGER) { // Apparently protection reduces potion damage now return 0; } diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java index ca2d74693e2..d9e54c2dd28 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionExplosion.java @@ -4,7 +4,7 @@ import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentProtectionExplosion extends EnchantmentProtection { @@ -15,7 +15,7 @@ public EnchantmentProtectionExplosion() { @Override public int getMinEnchantAbility(int level) { - return 5 + (level - 1) * 8; + return 5 + ((level - 1) << 3); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java index 2fc1d91803d..d8cac97d0f5 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFall.java @@ -4,7 +4,7 @@ import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentProtectionFall extends EnchantmentProtection { diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java index f5aeac6a93b..378a4db250e 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionFire.java @@ -4,7 +4,7 @@ import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentProtectionFire extends EnchantmentProtection { @@ -15,7 +15,7 @@ public EnchantmentProtectionFire() { @Override public int getMinEnchantAbility(int level) { - return 10 + (level - 1) * 8; + return 10 + ((level - 1) << 3); } @Override diff --git a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java index 5a05696bd7e..9674dd98ce1 100644 --- a/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java +++ b/src/main/java/cn/nukkit/item/enchantment/protection/EnchantmentProtectionProjectile.java @@ -4,7 +4,7 @@ import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EnchantmentProtectionProjectile extends EnchantmentProtection { diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java index 4e9b8e066a4..78d3d667f96 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTrident.java @@ -4,6 +4,7 @@ import cn.nukkit.item.enchantment.EnchantmentType; public abstract class EnchantmentTrident extends Enchantment { + protected EnchantmentTrident(int id, String name, Rarity rarity) { super(id, name, rarity, EnchantmentType.TRIDENT); } diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java index 61956f8db4f..f95939a5afb 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentChanneling.java @@ -1,9 +1,10 @@ -package cn.nukkit.item.enchantment.trident; - -import cn.nukkit.item.enchantment.Enchantment; - -public class EnchantmentTridentChanneling extends EnchantmentTrident { - public EnchantmentTridentChanneling() { - super(Enchantment.ID_TRIDENT_CHANNELING, "tridentChanneling", Rarity.VERY_RARE); - } -} +package cn.nukkit.item.enchantment.trident; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentTridentChanneling extends EnchantmentTrident { + + public EnchantmentTridentChanneling() { + super(Enchantment.ID_TRIDENT_CHANNELING, "tridentChanneling", Rarity.VERY_RARE); + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java index 384ed212cd2..488217a98b1 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentImpaling.java @@ -1,34 +1,35 @@ -package cn.nukkit.item.enchantment.trident; - -import cn.nukkit.entity.Entity; -import cn.nukkit.item.enchantment.Enchantment; - -public class EnchantmentTridentImpaling extends EnchantmentTrident { - public EnchantmentTridentImpaling() { - super(Enchantment.ID_TRIDENT_IMPALING, "tridentImpaling", Rarity.RARE); - } - - @Override - public int getMinEnchantAbility(int level) { - return 8 * level - 7; - } - - @Override - public int getMaxEnchantAbility(int level) { - return this.getMinEnchantAbility(level) + 20; - } - - @Override - public int getMaxLevel() { - return 5; - } - - @Override - public double getDamageBonus(Entity entity) { - if (entity.isInsideOfWater() || (entity.getLevel().isRaining() && entity.getLevel().canBlockSeeSky(entity))) { - return 2.5 * getLevel(); - } - - return 0; - } -} +package cn.nukkit.item.enchantment.trident; + +import cn.nukkit.entity.Entity; +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentTridentImpaling extends EnchantmentTrident { + + public EnchantmentTridentImpaling() { + super(Enchantment.ID_TRIDENT_IMPALING, "tridentImpaling", Rarity.RARE); + } + + @Override + public int getMinEnchantAbility(int level) { + return 8 * level - 7; + } + + @Override + public int getMaxEnchantAbility(int level) { + return this.getMinEnchantAbility(level) + 20; + } + + @Override + public int getMaxLevel() { + return 5; + } + + @Override + public double getDamageBonus(Entity entity) { + if (entity.isInsideOfWater() || (entity.getLevel().isRaining() && entity.canSeeSky())) { + return 2.5 * getLevel(); + } + + return 0; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java index be37f596a15..ee61f5d2b52 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentLoyalty.java @@ -1,19 +1,20 @@ -package cn.nukkit.item.enchantment.trident; - -import cn.nukkit.item.enchantment.Enchantment; - -public class EnchantmentTridentLoyalty extends EnchantmentTrident { - public EnchantmentTridentLoyalty() { - super(Enchantment.ID_TRIDENT_LOYALTY, "tridentLoyalty", Rarity.UNCOMMON); - } - - @Override - public int getMinEnchantAbility(int level) { - return 7 * level + 5; - } - - @Override - public int getMaxLevel() { - return 3; - } -} +package cn.nukkit.item.enchantment.trident; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentTridentLoyalty extends EnchantmentTrident { + + public EnchantmentTridentLoyalty() { + super(Enchantment.ID_TRIDENT_LOYALTY, "tridentLoyalty", Rarity.UNCOMMON); + } + + @Override + public int getMinEnchantAbility(int level) { + return 7 * level + 5; + } + + @Override + public int getMaxLevel() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java index e226fb7d766..65c2bf161a4 100644 --- a/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java +++ b/src/main/java/cn/nukkit/item/enchantment/trident/EnchantmentTridentRiptide.java @@ -1,19 +1,20 @@ -package cn.nukkit.item.enchantment.trident; - -import cn.nukkit.item.enchantment.Enchantment; - -public class EnchantmentTridentRiptide extends EnchantmentTrident { - public EnchantmentTridentRiptide() { - super(Enchantment.ID_TRIDENT_RIPTIDE, "tridentRiptide", Rarity.RARE); - } - - @Override - public int getMinEnchantAbility(int level) { - return 7 * level + 10; - } - - @Override - public int getMaxLevel() { - return 3; - } -} +package cn.nukkit.item.enchantment.trident; + +import cn.nukkit.item.enchantment.Enchantment; + +public class EnchantmentTridentRiptide extends EnchantmentTrident { + + public EnchantmentTridentRiptide() { + super(Enchantment.ID_TRIDENT_RIPTIDE, "tridentRiptide", Rarity.RARE); + } + + @Override + public int getMinEnchantAbility(int level) { + return 7 * level + 10; + } + + @Override + public int getMaxLevel() { + return 3; + } +} diff --git a/src/main/java/cn/nukkit/item/food/Food.java b/src/main/java/cn/nukkit/item/food/Food.java index a52aa5b0151..7a6d82dfb2a 100644 --- a/src/main/java/cn/nukkit/item/food/Food.java +++ b/src/main/java/cn/nukkit/item/food/Food.java @@ -20,14 +20,14 @@ public abstract class Food { public static final Food apple = registerDefaultFood(new FoodNormal(4, 2.4F).addRelative(Item.APPLE)); public static final Food apple_golden = registerDefaultFood(new FoodEffective(4, 9.6F) - .addEffect(Effect.getEffect(Effect.REGENERATION).setAmplifier(1).setDuration(5 * 20)) - .addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(2 * 60 * 20)) + .addEffect(Effect.getEffect(Effect.REGENERATION).setAmplifier(1).setDuration(100)) + .addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(2400)) .addRelative(Item.GOLDEN_APPLE)); public static final Food apple_golden_enchanted = registerDefaultFood(new FoodEffective(4, 9.6F) - .addEffect(Effect.getEffect(Effect.REGENERATION).setAmplifier(4).setDuration(30 * 20)) - .addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(2 * 60 * 20).setAmplifier(3)) - .addEffect(Effect.getEffect(Effect.DAMAGE_RESISTANCE).setDuration(5 * 60 * 20)) - .addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(5 * 60 * 20)) + .addEffect(Effect.getEffect(Effect.REGENERATION).setAmplifier(4).setDuration(600)) + .addEffect(Effect.getEffect(Effect.ABSORPTION).setDuration(2400).setAmplifier(3)) + .addEffect(Effect.getEffect(Effect.DAMAGE_RESISTANCE).setDuration(6000)) + .addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setDuration(6000)) .addRelative(Item.GOLDEN_APPLE_ENCHANTED)); public static final Food beef_raw = registerDefaultFood(new FoodNormal(3, 1.8F).addRelative(Item.RAW_BEEF)); public static final Food beetroot = registerDefaultFood(new FoodNormal(1, 1.2F).addRelative(Item.BEETROOT)); @@ -40,7 +40,7 @@ public abstract class Food { public static final Food carrot = registerDefaultFood(new FoodNormal(3, 4.8F).addRelative(Item.CARROT)); public static final Food carrot_golden = registerDefaultFood(new FoodNormal(6, 14.4F).addRelative(Item.GOLDEN_CARROT)); public static final Food chicken_raw = registerDefaultFood(new FoodEffective(2, 1.2F) - .addChanceEffect(0.3F, Effect.getEffect(Effect.HUNGER).setDuration(30 * 20)) + .addChanceEffect(0.3F, Effect.getEffect(Effect.HUNGER).setDuration(600)) .addRelative(Item.RAW_CHICKEN)); public static final Food chicken_cooked = registerDefaultFood(new FoodNormal(6, 7.2F).addRelative(Item.COOKED_CHICKEN)); public static final Food chorus_fruit = registerDefaultFood(new FoodChorusFruit()); @@ -55,35 +55,52 @@ public abstract class Food { public static final Food potato_raw = registerDefaultFood(new FoodNormal(1, 0.6F).addRelative(Item.POTATO)); public static final Food potato_baked = registerDefaultFood(new FoodNormal(5, 7.2F).addRelative(Item.BAKED_POTATO)); public static final Food potato_poisonous = registerDefaultFood(new FoodEffective(2, 1.2F) - .addChanceEffect(0.6F, Effect.getEffect(Effect.POISON).setDuration(4 * 20)) + .addChanceEffect(0.6F, Effect.getEffect(Effect.POISON).setDuration(80)) .addRelative(Item.POISONOUS_POTATO)); public static final Food pumpkin_pie = registerDefaultFood(new FoodNormal(8, 4.8F).addRelative(Item.PUMPKIN_PIE)); public static final Food rabbit_cooked = registerDefaultFood(new FoodNormal(5, 6F).addRelative(Item.COOKED_RABBIT)); public static final Food rabbit_raw = registerDefaultFood(new FoodNormal(3, 1.8F).addRelative(Item.RAW_RABBIT)); public static final Food rabbit_stew = registerDefaultFood(new FoodInBowl(10, 12F).addRelative(Item.RABBIT_STEW)); public static final Food rotten_flesh = registerDefaultFood(new FoodEffective(4, 0.8F) - .addChanceEffect(0.8F, Effect.getEffect(Effect.HUNGER).setDuration(30 * 20)) + .addChanceEffect(0.8F, Effect.getEffect(Effect.HUNGER).setDuration(600)) .addRelative(Item.ROTTEN_FLESH)); public static final Food spider_eye = registerDefaultFood(new FoodEffective(2, 3.2F) - .addEffect(Effect.getEffect(Effect.POISON).setDuration(4 * 20)) + .addEffect(Effect.getEffect(Effect.POISON).setDuration(80)) .addRelative(Item.SPIDER_EYE)); public static final Food steak = registerDefaultFood(new FoodNormal(8, 12.8F).addRelative(Item.COOKED_BEEF)); - //different kinds of fishes public static final Food clownfish = registerDefaultFood(new FoodNormal(1, 0.2F).addRelative(Item.CLOWNFISH)); public static final Food fish_cooked = registerDefaultFood(new FoodNormal(5, 6F).addRelative(Item.COOKED_FISH)); public static final Food fish_raw = registerDefaultFood(new FoodNormal(2, 0.4F).addRelative(Item.RAW_FISH)); public static final Food salmon_cooked = registerDefaultFood(new FoodNormal(6, 9.6F).addRelative(Item.COOKED_SALMON)); public static final Food salmon_raw = registerDefaultFood(new FoodNormal(2, 0.4F).addRelative(Item.RAW_SALMON)); public static final Food pufferfish = registerDefaultFood(new FoodEffective(1, 0.2F) - .addEffect(Effect.getEffect(Effect.HUNGER).setAmplifier(2).setDuration(15 * 20)) - .addEffect(Effect.getEffect(Effect.NAUSEA).setAmplifier(1).setDuration(15 * 20)) - .addEffect(Effect.getEffect(Effect.POISON).setAmplifier(3).setDuration(60 * 20)) + .addEffect(Effect.getEffect(Effect.HUNGER).setAmplifier(2).setDuration(300)) + .addEffect(Effect.getEffect(Effect.NAUSEA).setAmplifier(1).setDuration(300)) + .addEffect(Effect.getEffect(Effect.POISON).setAmplifier(3).setDuration(1200)) .addRelative(Item.PUFFERFISH)); public static final Food dried_kelp = registerDefaultFood(new FoodNormal(1, 0.6F).addRelative(Item.DRIED_KELP)); public static final Food sweet_berries = registerDefaultFood(new FoodNormal(2, 0.4F).addRelative(Item.SWEET_BERRIES)); + public static final Food glow_berries = registerDefaultFood(new FoodNormal(2, 0.4F).addRelative(Item.GLOW_BERRIES)); + public static final Food suspicious_stew_night_vision = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.NIGHT_VISION).setAmplifier(1).setDuration(80)).addRelative(Item.SUSPICIOUS_STEW, 0)); + public static final Food suspicious_stew_jump = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.JUMP).setAmplifier(1).setDuration(80)).addRelative(Item.SUSPICIOUS_STEW, 1)); + public static final Food suspicious_stew_weakness = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.WEAKNESS).setAmplifier(1).setDuration(140)).addRelative(Item.SUSPICIOUS_STEW, 2)); + public static final Food suspicious_stew_blindness = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.BLINDNESS).setAmplifier(1).setDuration(120)).addRelative(Item.SUSPICIOUS_STEW, 3)); + public static final Food suspicious_stew_poison = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.POISON).setAmplifier(1).setDuration(220)).addRelative(Item.SUSPICIOUS_STEW, 4)); + public static final Food suspicious_stew_saturation = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.SATURATION).setAmplifier(1).setDuration(7)).addRelative(Item.SUSPICIOUS_STEW, 6)); + public static final Food suspicious_stew_fire_resistance = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.FIRE_RESISTANCE).setAmplifier(1).setDuration(40)).addRelative(Item.SUSPICIOUS_STEW, 7)); + public static final Food suspicious_stew_regeneration = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.REGENERATION).setAmplifier(1).setDuration(120)).addRelative(Item.SUSPICIOUS_STEW, 8)); + public static final Food suspicious_stew_wither = registerDefaultFood(new FoodEffectiveInBow(6, 7.2F) + .addEffect(Effect.getEffect(Effect.WITHER).setAmplifier(1).setDuration(120)).addRelative(Item.SUSPICIOUS_STEW, 9)); public static final Food honey_bottle = registerDefaultFood(new FoodNormal(6, 1.2F).addRelative(Item.HONEY_BOTTLE)); - //Opened API for plugins public static Food registerFood(Food food, Plugin plugin) { Objects.requireNonNull(food); Objects.requireNonNull(plugin); @@ -185,5 +202,4 @@ static class NodeIDMetaPlugin extends NodeIDMeta { this.plugin = plugin; } } - } diff --git a/src/main/java/cn/nukkit/item/food/FoodChorusFruit.java b/src/main/java/cn/nukkit/item/food/FoodChorusFruit.java index 44f9ec0866a..2d494ea4067 100644 --- a/src/main/java/cn/nukkit/item/food/FoodChorusFruit.java +++ b/src/main/java/cn/nukkit/item/food/FoodChorusFruit.java @@ -6,9 +6,10 @@ import cn.nukkit.event.player.PlayerTeleportEvent; import cn.nukkit.item.Item; import cn.nukkit.level.Level; -import cn.nukkit.math.NukkitRandom; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.LevelSoundEventPacket; +import cn.nukkit.utils.Utils; /** * Created by Leonidius20 on 20.08.18. @@ -23,7 +24,6 @@ public FoodChorusFruit() { @Override protected boolean onEatenBy(Player player) { super.onEatenBy(player); - // Teleportation int minX = player.getFloorX() - 8; int minY = player.getFloorY() - 8; int minZ = player.getFloorZ() - 8; @@ -31,39 +31,39 @@ protected boolean onEatenBy(Player player) { int maxY = minY + 16; int maxZ = minZ + 16; + int minBlockY = player.getLevel().getMinBlockY(); + Level level = player.getLevel(); if (level == null) return false; - NukkitRandom random = new NukkitRandom(); - for (int attempts = 0; attempts < 128; attempts++) { - int x = random.nextRange(minX, maxX); - int y = random.nextRange(minY, maxY); - int z = random.nextRange(minZ, maxZ); + for (int attempts = 0; attempts < 16; attempts++) { + int x = Utils.rand(minX, maxX); + int y = Utils.rand(minY, maxY); + int z = Utils.rand(minZ, maxZ); - if (y < 0) continue; + if (y < minBlockY) continue; - while (y >= 0 && !level.getBlock(new Vector3(x, y + 1, z)).isSolid()) { + FullChunk chunk = level.getChunk(x >> 4, z >> 4); + while (y >= minBlockY && !level.getBlock(chunk, x, y + 1, z, true).isSolid()) { y--; } - y++; // Back up to non solid - Block blockUp = level.getBlock(new Vector3(x, y + 1, z)); - Block blockUp2 = level.getBlock(new Vector3(x, y + 2, z)); + y++; + + Block blockUp = level.getBlock(chunk, x, y + 1, z, true); + Block blockUp2 = level.getBlock(chunk, x, y + 2, z, true); - if (blockUp.isSolid() || blockUp instanceof BlockLiquid || - blockUp2.isSolid() || blockUp2 instanceof BlockLiquid) { + if (blockUp.isSolid() || blockUp instanceof BlockLiquid || blockUp2.isSolid() || blockUp2 instanceof BlockLiquid) { continue; } - // Sounds are broadcast at both source and destination - level.addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_TELEPORT); - player.teleport(new Vector3(x + 0.5, y + 1, z + 0.5), PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT); level.addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_TELEPORT); - + if (player.teleport(new Vector3(x + 0.5, y + 1, z + 0.5), PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT)) { + level.addLevelSoundEvent(player, LevelSoundEventPacket.SOUND_TELEPORT); + } break; } return true; } - -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/item/food/FoodEffective.java b/src/main/java/cn/nukkit/item/food/FoodEffective.java index 612c2e7390d..8a77a85adec 100644 --- a/src/main/java/cn/nukkit/item/food/FoodEffective.java +++ b/src/main/java/cn/nukkit/item/food/FoodEffective.java @@ -1,10 +1,11 @@ package cn.nukkit.item.food; import cn.nukkit.Player; +import cn.nukkit.item.Item; import cn.nukkit.potion.Effect; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -35,11 +36,15 @@ public FoodEffective addChanceEffect(float chance, Effect effect) { @Override protected boolean onEatenBy(Player player) { super.onEatenBy(player); - List toApply = new LinkedList<>(); + List toApply = new ObjectArrayList<>(); effects.forEach((effect, chance) -> { if (chance >= Math.random()) toApply.add(effect.clone()); }); toApply.forEach(player::addEffect); + NodeIDMeta id = relativeIDs.get(0); + if (id != null && id.id == Item.GOLDEN_APPLE_ENCHANTED) { + player.awardAchievement("overpowered"); + } return true; } } diff --git a/src/main/java/cn/nukkit/item/food/FoodEffectiveInBow.java b/src/main/java/cn/nukkit/item/food/FoodEffectiveInBow.java new file mode 100644 index 00000000000..a5c17ee7e44 --- /dev/null +++ b/src/main/java/cn/nukkit/item/food/FoodEffectiveInBow.java @@ -0,0 +1,18 @@ +package cn.nukkit.item.food; + +import cn.nukkit.Player; +import cn.nukkit.item.Item; + +public class FoodEffectiveInBow extends FoodEffective { + + public FoodEffectiveInBow(int restoreFood, float restoreSaturation) { + super(restoreFood, restoreSaturation); + } + + @Override + protected boolean onEatenBy(Player player) { + super.onEatenBy(player); + player.getInventory().addItem(Item.get(Item.BOWL));; // TODO: set to same slot but don't have it replaced + return true; + } +} diff --git a/src/main/java/cn/nukkit/item/food/FoodInBowl.java b/src/main/java/cn/nukkit/item/food/FoodInBowl.java index 81427500e55..a3850e02e78 100644 --- a/src/main/java/cn/nukkit/item/food/FoodInBowl.java +++ b/src/main/java/cn/nukkit/item/food/FoodInBowl.java @@ -1,7 +1,7 @@ package cn.nukkit.item.food; import cn.nukkit.Player; -import cn.nukkit.item.ItemBowl; +import cn.nukkit.item.Item; /** * Created by Snake1999 on 2016/1/14. @@ -17,8 +17,7 @@ public FoodInBowl(int restoreFood, float restoreSaturation) { @Override protected boolean onEatenBy(Player player) { super.onEatenBy(player); - player.getInventory().addItem(new ItemBowl()); + player.getInventory().addItem(Item.get(Item.BOWL)); // TODO: set to same slot but don't have it replaced return true; } - } diff --git a/src/main/java/cn/nukkit/item/food/FoodMilk.java b/src/main/java/cn/nukkit/item/food/FoodMilk.java index 2d2cd7c3ec9..98379d4f2cb 100644 --- a/src/main/java/cn/nukkit/item/food/FoodMilk.java +++ b/src/main/java/cn/nukkit/item/food/FoodMilk.java @@ -1,17 +1,18 @@ package cn.nukkit.item.food; import cn.nukkit.Player; -import cn.nukkit.item.ItemBucket; +import cn.nukkit.item.Item; /** * Created by Snake1999 on 2016/1/21. * Package cn.nukkit.item.food in project nukkit. */ public class FoodMilk extends Food { + @Override protected boolean onEatenBy(Player player) { super.onEatenBy(player); - player.getInventory().addItem(new ItemBucket()); + player.getInventory().addItem(Item.get(Item.BUCKET)); player.removeAllEffects(); return true; } diff --git a/src/main/java/cn/nukkit/item/randomitem/ConstantItemSelector.java b/src/main/java/cn/nukkit/item/randomitem/ConstantItemSelector.java index 950981b19da..51cf91411e9 100644 --- a/src/main/java/cn/nukkit/item/randomitem/ConstantItemSelector.java +++ b/src/main/java/cn/nukkit/item/randomitem/ConstantItemSelector.java @@ -18,10 +18,18 @@ public ConstantItemSelector(int id, Integer meta, Selector parent) { this(id, meta, 1, parent); } + public ConstantItemSelector(int id, int meta, Selector parent) { + this(id, meta, 1, parent); + } + public ConstantItemSelector(int id, Integer meta, int count, Selector parent) { this(Item.get(id, meta, count), parent); } + public ConstantItemSelector(int id, int meta, int count, Selector parent) { + this(Item.get(id, meta, count), parent); + } + public ConstantItemSelector(Item item, Selector parent) { super(parent); this.item = item; @@ -32,6 +40,6 @@ public Item getItem() { } public Object select() { - return getItem(); + return item; } } diff --git a/src/main/java/cn/nukkit/item/randomitem/Fishing.java b/src/main/java/cn/nukkit/item/randomitem/Fishing.java index f328a0e3d6e..f1cadaac1fd 100644 --- a/src/main/java/cn/nukkit/item/randomitem/Fishing.java +++ b/src/main/java/cn/nukkit/item/randomitem/Fishing.java @@ -2,7 +2,6 @@ import cn.nukkit.item.Item; import cn.nukkit.item.enchantment.Enchantment; -import cn.nukkit.math.NukkitMath; import cn.nukkit.potion.Potion; import cn.nukkit.utils.DyeColor; @@ -45,19 +44,20 @@ public static Item getFishingResult(Item rod) { int fortuneLevel = 0; int lureLevel = 0; if (rod != null) { - if (rod.getEnchantment(Enchantment.ID_FORTUNE_FISHING) != null) { - fortuneLevel = rod.getEnchantment(Enchantment.ID_FORTUNE_FISHING).getLevel(); - } else if (rod.getEnchantment(Enchantment.ID_LURE) != null) { - lureLevel = rod.getEnchantment(Enchantment.ID_LURE).getLevel(); + Enchantment ench; + if ((ench = rod.getEnchantment(Enchantment.ID_FORTUNE_FISHING)) != null) { + fortuneLevel = ench.getLevel(); + } else if ((ench = rod.getEnchantment(Enchantment.ID_LURE)) != null) { + lureLevel = ench.getLevel(); } } return getFishingResult(fortuneLevel, lureLevel); } public static Item getFishingResult(int fortuneLevel, int lureLevel) { - float treasureChance = NukkitMath.clamp(0.05f + 0.01f * fortuneLevel - 0.01f * lureLevel, 0, 1); - float junkChance = NukkitMath.clamp(0.05f - 0.025f * fortuneLevel - 0.01f * lureLevel, 0, 1); - float fishChance = NukkitMath.clamp(1 - treasureChance - junkChance, 0, 1); + float treasureChance = limitRange(0.05f + 0.01f * fortuneLevel - 0.01f * lureLevel); + float junkChance = limitRange(0.05f - 0.025f * fortuneLevel - 0.01f * lureLevel); + float fishChance = limitRange(1 - treasureChance - junkChance); putSelector(FISHES, fishChance); putSelector(TREASURES, treasureChance); putSelector(JUNKS, junkChance); @@ -65,4 +65,9 @@ public static Item getFishingResult(int fortuneLevel, int lureLevel) { if (result instanceof Item) return (Item) result; return null; } + + private static float limitRange(float value) { + if (value >= 1f) return 1f; + return Math.max(value, 0f); + } } diff --git a/src/main/java/cn/nukkit/item/randomitem/RandomItem.java b/src/main/java/cn/nukkit/item/randomitem/RandomItem.java index 93ab7933810..598d707edd9 100644 --- a/src/main/java/cn/nukkit/item/randomitem/RandomItem.java +++ b/src/main/java/cn/nukkit/item/randomitem/RandomItem.java @@ -9,6 +9,7 @@ * Package cn.nukkit.item.randomitem in project nukkit. */ public final class RandomItem { + private static final Map selectors = new HashMap<>(); public static final Selector ROOT = new Selector(null); @@ -29,8 +30,7 @@ static Object selectFrom(Selector selector) { selectors.forEach((s, f) -> { if (s.getParent() == selector) child.put(s, f); }); - if (child.size() == 0) return selector.select(); + if (child.isEmpty()) return selector.select(); return selectFrom(Selector.selectRandom(child)); } - } diff --git a/src/main/java/cn/nukkit/lang/BaseLang.java b/src/main/java/cn/nukkit/lang/BaseLang.java index b8b31948285..59d6a5ecfd4 100644 --- a/src/main/java/cn/nukkit/lang/BaseLang.java +++ b/src/main/java/cn/nukkit/lang/BaseLang.java @@ -10,10 +10,11 @@ import java.util.Objects; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BaseLang { + public static final String FALLBACK_LANGUAGE = "eng"; protected final String langName; @@ -21,7 +22,6 @@ public class BaseLang { protected Map lang = new HashMap<>(); protected Map fallbackLang = new HashMap<>(); - public BaseLang(String lang) { this(lang, null); } @@ -43,8 +43,6 @@ public BaseLang(String lang, String path, String fallback) { if (useFallback) this.fallbackLang = this.loadLang(path + fallback + "/lang.ini"); } if (this.fallbackLang == null) this.fallbackLang = this.lang; - - } public Map getLangMap() { @@ -69,7 +67,7 @@ protected Map loadLang(String path) { Map d = new HashMap<>(); for (String line : content.split("\n")) { line = line.trim(); - if (line.equals("") || line.charAt(0) == '#') { + if (line.isEmpty() || line.charAt(0) == '#') { continue; } String[] t = line.split("="); @@ -77,15 +75,15 @@ protected Map loadLang(String path) { continue; } String key = t[0]; - String value = ""; + StringBuilder value = new StringBuilder(); for (int i = 1; i < t.length - 1; i++) { - value += t[i] + "="; + value.append(t[i]).append("="); } - value += t[t.length - 1]; - if (value.equals("")) { + value.append(t[t.length - 1]); + if (value.length() == 0) { continue; } - d.put(key, value); + d.put(key, value.toString()); } return d; } catch (IOException e) { @@ -100,7 +98,7 @@ protected Map loadLang(InputStream stream) { Map d = new HashMap<>(); for (String line : content.split("\n")) { line = line.trim(); - if (line.equals("") || line.charAt(0) == '#') { + if (line.isEmpty() || line.charAt(0) == '#') { continue; } String[] t = line.split("="); @@ -108,15 +106,15 @@ protected Map loadLang(InputStream stream) { continue; } String key = t[0]; - String value = ""; + StringBuilder value = new StringBuilder(); for (int i = 1; i < t.length - 1; i++) { - value += t[i] + "="; + value.append(t[i]).append("="); } - value += t[t.length - 1]; - if (value.equals("")) { + value.append(t[t.length - 1]); + if (value.length() == 0) { continue; } - d.put(key, value); + d.put(key, value.toString()); } return d; } catch (IOException e) { @@ -199,19 +197,18 @@ protected String parseTranslation(String text, String onlyPrefix) { StringBuilder newString = new StringBuilder(); text = String.valueOf(text); - String replaceString = null; + StringBuilder replaceString = null; int len = text.length(); for (int i = 0; i < len; ++i) { char c = text.charAt(i); if (replaceString != null) { - int ord = c; - if ((ord >= 0x30 && ord <= 0x39) // 0-9 - || (ord >= 0x41 && ord <= 0x5a) // A-Z - || (ord >= 0x61 && ord <= 0x7a) || // a-z + if (((int) c >= 0x30 && (int) c <= 0x39) // 0-9 + || ((int) c >= 0x41 && (int) c <= 0x5a) // A-Z + || ((int) c >= 0x61 && (int) c <= 0x7a) || // a-z c == '.' || c == '-') { - replaceString += String.valueOf(c); + replaceString.append(c); } else { String t = this.internalGet(replaceString.substring(1)); if (t != null && (onlyPrefix == null || replaceString.indexOf(onlyPrefix) == 1)) { @@ -221,13 +218,13 @@ protected String parseTranslation(String text, String onlyPrefix) { } replaceString = null; if (c == '%') { - replaceString = String.valueOf(c); + replaceString = new StringBuilder(String.valueOf(c)); } else { newString.append(c); } } } else if (c == '%') { - replaceString = String.valueOf(c); + replaceString = new StringBuilder(String.valueOf(c)); } else { newString.append(c); } diff --git a/src/main/java/cn/nukkit/lang/TextContainer.java b/src/main/java/cn/nukkit/lang/TextContainer.java index a6051907985..61d47814361 100644 --- a/src/main/java/cn/nukkit/lang/TextContainer.java +++ b/src/main/java/cn/nukkit/lang/TextContainer.java @@ -3,10 +3,11 @@ import cn.nukkit.Server; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class TextContainer implements Cloneable { + protected String text; public TextContainer(String text) { @@ -23,7 +24,7 @@ public String getText() { @Override public String toString() { - return this.getText(); + return this.text; } @Override diff --git a/src/main/java/cn/nukkit/lang/TranslationContainer.java b/src/main/java/cn/nukkit/lang/TranslationContainer.java index 5bf0f089b15..e938edb1b46 100644 --- a/src/main/java/cn/nukkit/lang/TranslationContainer.java +++ b/src/main/java/cn/nukkit/lang/TranslationContainer.java @@ -1,7 +1,7 @@ package cn.nukkit.lang; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class TranslationContainer extends TextContainer implements Cloneable { diff --git a/src/main/java/cn/nukkit/level/AsyncChunkData.java b/src/main/java/cn/nukkit/level/AsyncChunkData.java new file mode 100644 index 00000000000..0451510b6f1 --- /dev/null +++ b/src/main/java/cn/nukkit/level/AsyncChunkData.java @@ -0,0 +1,20 @@ +package cn.nukkit.level; + +class AsyncChunkData { + + final long timestamp; + final int x; + final int z; + final long hash; + final byte[] data; + final int count; + + AsyncChunkData(long timestamp, int x, int z, long hash, byte[] data, int count) { + this.timestamp = timestamp; + this.x = x; + this.z = z; + this.hash = hash; + this.data = data; + this.count = count; + } +} diff --git a/src/main/java/cn/nukkit/level/AsyncChunkThread.java b/src/main/java/cn/nukkit/level/AsyncChunkThread.java new file mode 100644 index 00000000000..f95d96a523c --- /dev/null +++ b/src/main/java/cn/nukkit/level/AsyncChunkThread.java @@ -0,0 +1,41 @@ +package cn.nukkit.level; + +import cn.nukkit.level.format.generic.BaseChunk; +import cn.nukkit.Server; +import cn.nukkit.level.format.generic.serializer.NetworkChunkData; +import cn.nukkit.level.format.generic.serializer.NetworkChunkSerializer; +import cn.nukkit.utils.BinaryStream; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.BiConsumer; + +class AsyncChunkThread { + + private final ExecutorService threadedExecutor; + final Queue out = new ConcurrentLinkedQueue<>(); + + AsyncChunkThread(String levelName) { + ThreadFactoryBuilder builder = new ThreadFactoryBuilder(); + builder.setNameFormat("AsyncChunkThread for " + levelName); + builder.setUncaughtExceptionHandler((thread, ex) -> Server.getInstance().getLogger().error("Exception in " + thread.getName(), ex)); + this.threadedExecutor = Executors.newFixedThreadPool(2, builder.build()); + } + + void queue(BaseChunk chunk, long timestamp, int x, int z, DimensionData dimensionData) { + this.threadedExecutor.execute(() -> this.run(chunk, timestamp, x, z, dimensionData)); + } + + private void run(BaseChunk chunk, long timestamp, int x, int z, DimensionData dimensionData) { + BiConsumer callback = (stream, data) -> + this.out.add(new AsyncChunkData(timestamp, x, z, Level.chunkHash(x, z), stream.getBuffer(), data.getChunkSections())); + NetworkChunkSerializer.serialize(chunk, callback, dimensionData); + } + + void shutdown() { + this.threadedExecutor.shutdownNow(); + } +} diff --git a/src/main/java/cn/nukkit/level/BlockPalette.java b/src/main/java/cn/nukkit/level/BlockPalette.java new file mode 100644 index 00000000000..ea1d77cf425 --- /dev/null +++ b/src/main/java/cn/nukkit/level/BlockPalette.java @@ -0,0 +1,104 @@ +package cn.nukkit.level; + +import cn.nukkit.block.BlockID; +import cn.nukkit.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntMaps; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import lombok.Data; +import lombok.extern.log4j.Log4j2; + +import java.util.HashMap; +import java.util.Map; + +@Data +@Log4j2 +public class BlockPalette { + + private final int protocol; + private final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap(); + private final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap(); + private final Map stateToLegacy = new HashMap<>(); + private volatile boolean locked; + + public BlockPalette(int protocol) { + this.protocol = protocol; + this.legacyToRuntimeId.defaultReturnValue(-1); + this.runtimeIdToLegacy.defaultReturnValue(-1); + } + + public void clearStates() { + this.locked = false; + this.legacyToRuntimeId.clear(); + this.runtimeIdToLegacy.clear(); + this.stateToLegacy.clear(); + } + + public void registerState(int blockId, int data, int runtimeId, CompoundTag blockState) { + if (this.locked) { + throw new IllegalStateException("Block palette is already locked!"); + } + + int legacyId = blockId << 6 | data; + this.legacyToRuntimeId.put(legacyId, runtimeId); + this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); + this.stateToLegacy.putIfAbsent(blockState, legacyId); + + // Hack: Map IDs for item frame up & down states + if (blockId == BlockID.ITEM_FRAME_BLOCK || blockId == BlockID.GLOW_FRAME) { + if (data == 7) { + int offset = 5; + + runtimeId = runtimeId + offset; + legacyId = blockId << 6 | 5; // Up + this.legacyToRuntimeId.put(legacyId, runtimeId); + this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); + + int offset2 = 0; + + runtimeId = runtimeId + offset + offset2; + legacyId = blockId << 6 | 4; // Down + this.legacyToRuntimeId.put(legacyId, runtimeId); + this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); + } + } + } + + public void lock() { + this.locked = true; + } + + public int getRuntimeId(int id, int meta) { + int legacyId = id << 6 | meta; + int runtimeId = this.legacyToRuntimeId.get(legacyId); + if (runtimeId == -1) { + runtimeId = legacyToRuntimeId.get(id << 6); + if (runtimeId == -1) { + runtimeId = legacyToRuntimeId.get(BlockID.INFO_UPDATE << 6); + log.info("Missing block runtime id mappings for {}:{}", id, meta); + } + } + return runtimeId; + } + + public int getRuntimeId(int legacyId) { + int runtimeId = this.legacyToRuntimeId.get(legacyId); + if (runtimeId == -1) { + log.info("Missing block runtime id mappings for {}", legacyId); + return legacyToRuntimeId.get(BlockID.INFO_UPDATE << 6); + } + return runtimeId; + } + + public int getLegacyFullId(int runtimeId) { + return this.runtimeIdToLegacy.get(runtimeId); + } + + public int getLegacyFullId(CompoundTag state) { + return this.stateToLegacy.getOrDefault(state, -1); + } + + public Int2IntMap getLegacyToRuntimeIdMap() { + return Int2IntMaps.unmodifiable(this.legacyToRuntimeId); + } +} diff --git a/src/main/java/cn/nukkit/level/ChunkLoader.java b/src/main/java/cn/nukkit/level/ChunkLoader.java index dd46de977a9..1e1d8fd273c 100644 --- a/src/main/java/cn/nukkit/level/ChunkLoader.java +++ b/src/main/java/cn/nukkit/level/ChunkLoader.java @@ -4,7 +4,7 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface ChunkLoader { diff --git a/src/main/java/cn/nukkit/level/ChunkManager.java b/src/main/java/cn/nukkit/level/ChunkManager.java index a7edf2b897e..503b43390cc 100644 --- a/src/main/java/cn/nukkit/level/ChunkManager.java +++ b/src/main/java/cn/nukkit/level/ChunkManager.java @@ -1,18 +1,32 @@ package cn.nukkit.level; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.format.generic.BaseFullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface ChunkManager { - int getBlockIdAt(int x, int y, int z); + default int getBlockIdAt(int x, int y, int z) { + return this.getBlockIdAt(x, y, z, Block.LAYER_NORMAL); + } + + int getBlockIdAt(int x, int y, int z, BlockLayer layer); + + default void setBlockFullIdAt(int x, int y, int z, int fullId) { + this.setBlockFullIdAt(x, y, z, Block.LAYER_NORMAL, fullId); + } + + void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, int fullId); - void setBlockFullIdAt(int x, int y, int z, int fullId); + default void setBlockIdAt(int x, int y, int z, int id) { + this.setBlockIdAt(x, y, z, Block.LAYER_NORMAL, id); + } - void setBlockIdAt(int x, int y, int z, int id); + void setBlockIdAt(int x, int y, int z, BlockLayer layer, int id); default void setBlockAt(int x, int y, int z, int id) { setBlockAt(x, y, z, id, 0); @@ -20,9 +34,20 @@ default void setBlockAt(int x, int y, int z, int id) { void setBlockAt(int x, int y, int z, int id, int data); - int getBlockDataAt(int x, int y, int z); + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id); + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id, int data); + + default int getBlockDataAt(int x, int y, int z) { + return this.getBlockDataAt(x, y, z, Block.LAYER_NORMAL); + } + + int getBlockDataAt(int x, int y, int z, BlockLayer layer); + + default void setBlockDataAt(int x, int y, int z, int data) { + this.setBlockDataAt(x, y, z, Block.LAYER_NORMAL, data); + } - void setBlockDataAt(int x, int y, int z, int data); + void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data); BaseFullChunk getChunk(int chunkX, int chunkZ); @@ -31,4 +56,4 @@ default void setBlockAt(int x, int y, int z, int id) { void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk); long getSeed(); -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/ChunkPosition.java b/src/main/java/cn/nukkit/level/ChunkPosition.java index 4a8282dc54c..6f1e43c9eec 100644 --- a/src/main/java/cn/nukkit/level/ChunkPosition.java +++ b/src/main/java/cn/nukkit/level/ChunkPosition.java @@ -4,8 +4,8 @@ import cn.nukkit.math.Vector3; /** - * Author: Adam Matthew - *

+ * @author Adam Matthew + * * Nukkit Project */ public class ChunkPosition { diff --git a/src/main/java/cn/nukkit/level/DimensionData.java b/src/main/java/cn/nukkit/level/DimensionData.java index c25ab2ed65e..3c3c02205d0 100644 --- a/src/main/java/cn/nukkit/level/DimensionData.java +++ b/src/main/java/cn/nukkit/level/DimensionData.java @@ -4,6 +4,9 @@ @Data public class DimensionData { + + public static final DimensionData LEGACY_DIMENSION = new LegacyDimensionData(); + private final int dimensionId; private final int minHeight; private final int maxHeight; @@ -20,4 +23,19 @@ public DimensionData(int dimensionId, int minHeight, int maxHeight) { } this.height = height; } + + public int getSectionOffset() { + return (-this.minHeight) >> 4; + } + + private static class LegacyDimensionData extends DimensionData { + public LegacyDimensionData() { + super(0, 0, 255); + } + + @Override + public int getHeight() { + return DimensionEnum.OVERWORLD.getDimensionData().getHeight(); + } + } } diff --git a/src/main/java/cn/nukkit/level/DimensionEnum.java b/src/main/java/cn/nukkit/level/DimensionEnum.java index 52bcd8cabc8..5f8f902bb67 100644 --- a/src/main/java/cn/nukkit/level/DimensionEnum.java +++ b/src/main/java/cn/nukkit/level/DimensionEnum.java @@ -1,6 +1,7 @@ package cn.nukkit.level; public enum DimensionEnum { + OVERWORLD(new DimensionData(Level.DIMENSION_OVERWORLD, -64, 319)), NETHER(new DimensionData(Level.DIMENSION_NETHER, 0, 127)), END(new DimensionData(Level.DIMENSION_THE_END, 0, 255)); diff --git a/src/main/java/cn/nukkit/level/EnumLevel.java b/src/main/java/cn/nukkit/level/EnumLevel.java index 3423d84c9bd..2054016a25d 100644 --- a/src/main/java/cn/nukkit/level/EnumLevel.java +++ b/src/main/java/cn/nukkit/level/EnumLevel.java @@ -2,76 +2,50 @@ import cn.nukkit.Server; import cn.nukkit.level.generator.Generator; -import cn.nukkit.math.NukkitMath; +/** + * Default dimensions and their Levels + */ public enum EnumLevel { + OVERWORLD, NETHER, - //THE_END - ; + THE_END; Level level; + /** + * Get Level + * + * @return Level or null if the dimension is not enabled + */ public Level getLevel() { return level; } + /** + * Internal: Initialize default overworld, nether and the end Levels + */ public static void initLevels() { - OVERWORLD.level = Server.getInstance().getDefaultLevel(); - - // attempt to load the nether world if it is allowed in server properties - if (Server.getInstance().isNetherAllowed() && !Server.getInstance().loadLevel("nether")) { - - // Nether is allowed, and not found, create the default nether world - Server.getInstance().getLogger().info("No level called \"nether\" found, creating default nether level."); - - // Generate seed for nether and get nether generator - long seed = System.currentTimeMillis(); - Class generator = Generator.getGenerator("nether"); - - // Generate the nether world - Server.getInstance().generateLevel("nether", seed, generator); - - // Finally, load the level if not already loaded and set the level - if (!Server.getInstance().isLevelLoaded("nether")) { - Server.getInstance().loadLevel("nether"); + Server server = Server.getInstance(); + OVERWORLD.level = server.getDefaultLevel(); + if (server.netherEnabled) { + if (server.getLevelByName("nether") == null) { + server.generateLevel("nether", System.currentTimeMillis(), Generator.getGenerator(Generator.TYPE_NETHER)); + server.loadLevel("nether"); } - + NETHER.level = server.getLevelByName("nether"); } - - NETHER.level = Server.getInstance().getLevelByName("nether"); - - if (NETHER.level == null) { - // Nether is not found or disabled - Server.getInstance().getLogger().alert("No level called \"nether\" found or nether is disabled in server properties! Nether functionality will be disabled."); - } - } - - public static Level getOtherNetherPair(Level current) { - if (current == OVERWORLD.level) { - return NETHER.level; - } else if (current == NETHER.level) { - return OVERWORLD.level; - } else { - throw new IllegalArgumentException("Neither overworld nor nether given!"); - } - } - - public static Position moveToNether(Position current) { - if (NETHER.level == null) { - return null; - } else { - if (current.level == OVERWORLD.level) { - return new Position(mRound(current.getFloorX() >> 3, 128), NukkitMath.clamp(mRound(current.getFloorY(), 32), 70, 128 - 10), mRound(current.getFloorZ() >> 3, 128), NETHER.level); - } else if (current.level == NETHER.level) { - return new Position(mRound(current.getFloorX() << 3, 1024), NukkitMath.clamp(mRound(current.getFloorY(), 32), 70, 256 - 10), mRound(current.getFloorZ() << 3, 1024), OVERWORLD.level); - } else { - throw new IllegalArgumentException("Neither overworld nor nether given!"); + if (server.endEnabled) { + if (server.getLevelByName("the_end") == null) { + server.generateLevel("the_end", System.currentTimeMillis(), Generator.getGenerator(Generator.TYPE_THE_END)); + server.loadLevel("the_end"); } + THE_END.level = server.getLevelByName("the_end"); } } - private static final int mRound(int value, int factor) { - return Math.round((float) value / factor) * factor; + static int mRound32(int value) { + return Math.round((float) value / 32) * 32; } } diff --git a/src/main/java/cn/nukkit/level/Explosion.java b/src/main/java/cn/nukkit/level/Explosion.java index 8155dcb806b..66ecfdb0ef6 100644 --- a/src/main/java/cn/nukkit/level/Explosion.java +++ b/src/main/java/cn/nukkit/level/Explosion.java @@ -1,50 +1,54 @@ package cn.nukkit.level; +import cn.nukkit.Player; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; import cn.nukkit.block.BlockTNT; -import cn.nukkit.block.BlockWater; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntityShulkerBox; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityExplosive; +import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.item.EntityItem; import cn.nukkit.entity.item.EntityXPOrb; +import cn.nukkit.entity.mob.EntityEnderDragon; +import cn.nukkit.event.block.BlockExplodeEvent; import cn.nukkit.event.block.BlockUpdateEvent; import cn.nukkit.event.entity.EntityDamageByBlockEvent; import cn.nukkit.event.entity.EntityDamageByEntityEvent; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.EntityExplodeEvent; +import cn.nukkit.inventory.Inventory; import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.item.Item; -import cn.nukkit.item.ItemBlock; +import cn.nukkit.item.ItemArmor; import cn.nukkit.level.particle.HugeExplodeSeedParticle; import cn.nukkit.math.*; import cn.nukkit.network.protocol.LevelSoundEventPacket; import cn.nukkit.utils.Hash; +import cn.nukkit.utils.Utils; import it.unimi.dsi.fastutil.longs.LongArraySet; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class Explosion { - private final int rays = 16; //Rays + private static final int rays = 16; private final Level level; private final Position source; private final double size; private List affectedBlocks = new ArrayList<>(); - private final double stepLen = 0.3d; + private static final double stepLen = 0.3d; private final Object what; private boolean doesDamage = true; + private double minHeight = Integer.MIN_VALUE; public Explosion(Position center, double size, Entity what) { this.level = center.getLevel(); @@ -53,9 +57,15 @@ public Explosion(Position center, double size, Entity what) { this.what = what; } + public Explosion(Position center, double size, Object what) { + this.level = center.getLevel(); + this.source = center; + this.size = Math.max(size, 0); + this.what = what; + } + /** * @return bool - * @deprecated */ public boolean explode() { if (explodeA()) { @@ -68,48 +78,42 @@ public boolean explode() { * @return bool */ public boolean explodeA() { - if (what instanceof EntityExplosive) { - Entity entity = (Entity) what; - if (entity.getLevelBlock() instanceof BlockWater) { - this.doesDamage = false; - return true; - } - } - - if (this.size < 0.1) { - return false; + if (what instanceof EntityExplosive && ((Entity) what).isInsideOfWater()) { + this.doesDamage = false; + return true; } + if (this.size < 0.1) return false; Vector3 vector = new Vector3(0, 0, 0); Vector3 vBlock = new Vector3(0, 0, 0); - int mRays = this.rays - 1; - for (int i = 0; i < this.rays; ++i) { - for (int j = 0; j < this.rays; ++j) { - for (int k = 0; k < this.rays; ++k) { + int mRays = 15; + for (int i = 0; i < rays; ++i) { + for (int j = 0; j < rays; ++j) { + for (int k = 0; k < rays; ++k) { if (i == 0 || i == mRays || j == 0 || j == mRays || k == 0 || k == mRays) { vector.setComponents((double) i / (double) mRays * 2d - 1, (double) j / (double) mRays * 2d - 1, (double) k / (double) mRays * 2d - 1); double len = vector.length(); - vector.setComponents((vector.x / len) * this.stepLen, (vector.y / len) * this.stepLen, (vector.z / len) * this.stepLen); + vector.setComponents((vector.x / len) * stepLen, (vector.y / len) * stepLen, (vector.z / len) * stepLen); double pointerX = this.source.x; double pointerY = this.source.y; double pointerZ = this.source.z; - for (double blastForce = this.size * (ThreadLocalRandom.current().nextInt(700, 1301)) / 1000d; blastForce > 0; blastForce -= this.stepLen * 0.75d) { + for (double blastForce = this.size * (Utils.random.nextInt(700, 1301)) / 1000d; blastForce > 0; blastForce -= 0.22499999999999998) { int x = (int) pointerX; int y = (int) pointerY; int 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 || vBlock.y > 255) { + if (vBlock.y < this.level.getMinBlockY() || vBlock.y > this.level.getMaxBlockY()) { break; } - Block block = this.level.getBlock(vBlock); - if (block.getId() != 0) { - blastForce -= (block.getResistance() / 5 + 0.3d) * this.stepLen; - if (blastForce > 0) { + Block block = this.level.getBlock(vBlock); + if (block.getId() != Block.AIR) { + blastForce -= (block.getResistance() / 5 + 0.3d) * stepLen; + if (blastForce > 0 && block.y >= this.minHeight) { if (!this.affectedBlocks.contains(block)) { this.affectedBlocks.add(block); } @@ -128,11 +132,6 @@ public boolean explodeA() { } public boolean explodeB() { - - LongArraySet updateBlocks = new LongArraySet(); - List send = new ArrayList<>(); - - Vector3 source = (new Vector3(this.source.x, this.source.y, this.source.z)).floor(); double yield = (1d / this.size) * 100d; if (this.what instanceof Entity) { @@ -144,8 +143,18 @@ public boolean explodeB() { yield = ev.getYield(); this.affectedBlocks = ev.getBlockList(); } + } else if (this.what instanceof Block) { + BlockExplodeEvent ev = new BlockExplodeEvent((Block) this.what, this.source, this.affectedBlocks, yield); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return false; + } else { + yield = ev.getYield(); + this.affectedBlocks = ev.getBlockList(); + } } + LongArraySet updateBlocks = new LongArraySet(); double explosionSize = this.size * 2d; double minX = NukkitMath.floorDouble(this.source.x - explosionSize - 1); double maxX = NukkitMath.ceilDouble(this.source.x + explosionSize + 1); @@ -161,10 +170,10 @@ public boolean explodeB() { if (distance <= 1) { Vector3 motion = entity.subtract(this.source).normalize(); - int exposure = 1; + double exposure = this.getSeenPercent(this.source, entity); double impact = (1 - distance) * exposure; - int damage = this.doesDamage ? (int) (((impact * impact + impact) / 2) * 8 * explosionSize + 1) : 0; + int damage = this.doesDamage ? Math.max((int) (((impact * impact + impact) / 2) * 8 * explosionSize + 1), 0) : 0; if (this.what instanceof Entity) { entity.attack(new EntityDamageByEntityEvent((Entity) this.what, entity, DamageCause.ENTITY_EXPLOSION, damage)); @@ -175,43 +184,67 @@ public boolean explodeB() { } if (!(entity instanceof EntityItem || entity instanceof EntityXPOrb)) { - entity.setMotion(motion.multiply(impact)); + if (entity instanceof Player) { + int netheritePieces = 0; + for (Item armor : ((Player) entity).getInventory().getArmorContents()) { + if (armor.getTier() == ItemArmor.TIER_NETHERITE) { + netheritePieces++; + } + } + if (netheritePieces > 0) { + impact *= 1 - 0.1 * netheritePieces; + } + } + entity.setMotion(entity.getMotion().add(motion.multiply(impact))); } } } - ItemBlock air = new ItemBlock(Block.get(BlockID.AIR)); + Item air = Item.get(Item.AIR); BlockEntity container; - - //Iterator iter = this.affectedBlocks.entrySet().iterator(); for (Block block : this.affectedBlocks) { - //Block block = (Block) ((HashMap.Entry) iter.next()).getValue(); if (block.getId() == Block.TNT) { - ((BlockTNT) block).prime(new NukkitRandom().nextRange(10, 30), this.what instanceof Entity ? (Entity) this.what : null); - } else if ((container = block.getLevel().getBlockEntity(block)) instanceof InventoryHolder) { - if (container instanceof BlockEntityShulkerBox) { - this.level.dropItem(block.add(0.5, 0.5, 0.5), block.toItem()); - ((InventoryHolder) container).getInventory().clearAll(); - } else { - for (Item drop : ((InventoryHolder) container).getInventory().getContents().values()) { - this.level.dropItem(block.add(0.5, 0.5, 0.5), drop); + ((BlockTNT) block).prime(Utils.rand(10, 30), this.what instanceof Entity ? (Entity) this.what : null); + } else if (block.getId() == Block.BED_BLOCK && (block.getDamage() & 0x08) == 0x08) { + this.level.setBlockAt((int) block.x, (int) block.y, (int) block.z, Block.AIR); + continue; // We don't want drops from both bed parts + } else if ((container = this.level.getBlockEntity(block)) instanceof InventoryHolder && !container.closed) { + if (this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { + Inventory inv = ((InventoryHolder) container).getInventory(); + if (inv != null) { + inv.getViewers().clear(); } - ((InventoryHolder) container).getInventory().clearAll(); + if (container instanceof BlockEntityShulkerBox) { + this.level.dropItem(block.add(0.5, 0.5, 0.5), block.toItem()); + ((BlockEntityShulkerBox) container).getInventory().clearAll(); + } else { + container.onBreak(); + } + } + container.close(); + } else if (block.alwaysDropsOnExplosion() || Math.random() * 100 < yield) { + if (this.level.getBlockIdAt((int) block.x, (int) block.y, (int) block.z) == Block.AIR) { + continue; // Block broken by another explosion (end crystals) } - } else if (Math.random() * 100 < yield) { for (Item drop : block.getDrops(air)) { this.level.dropItem(block.add(0.5, 0.5, 0.5), drop); } } - this.level.setBlockAt((int) block.x, (int) block.y, (int) block.z, BlockID.AIR); + this.level.setBlockAt((int) block.x, (int) block.y, (int) block.z, Block.AIR); Vector3 pos = new Vector3(block.x, block.y, block.z); + sideBlocks: for (BlockFace side : BlockFace.values()) { Vector3 sideBlock = pos.getSide(side); long index = Hash.hashBlock((int) sideBlock.x, (int) sideBlock.y, (int) sideBlock.z); - if (!this.affectedBlocks.contains(sideBlock) && !updateBlocks.contains(index)) { + if (!updateBlocks.contains(index)) { + for (Block affected : this.affectedBlocks) { + if (affected.x == sideBlock.x && affected.y == sideBlock.y && affected.z == sideBlock.z) { + continue sideBlocks; + } + } BlockUpdateEvent ev = new BlockUpdateEvent(this.level.getBlock(sideBlock)); this.level.getServer().getPluginManager().callEvent(ev); if (!ev.isCancelled()) { @@ -220,13 +253,195 @@ public boolean explodeB() { updateBlocks.add(index); } } - send.add(new Vector3(block.x - source.x, block.y - source.y, block.z - source.z)); } this.level.addParticle(new HugeExplodeSeedParticle(this.source)); this.level.addLevelSoundEvent(source, LevelSoundEventPacket.SOUND_EXPLODE); - return true; } + public void explodeEntity() { + if (this.what instanceof Entity) { + EntityExplodeEvent ev = new EntityExplodeEvent((Entity) this.what, this.source, this.affectedBlocks, 0); + this.level.getServer().getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; + } + } + + double explosionSize = this.size * 2d; + double minX = NukkitMath.floorDouble(this.source.x - explosionSize - 1); + double maxX = NukkitMath.ceilDouble(this.source.x + explosionSize + 1); + double minY = NukkitMath.floorDouble(this.source.y - explosionSize - 1); + double maxY = NukkitMath.ceilDouble(this.source.y + explosionSize + 1); + double minZ = NukkitMath.floorDouble(this.source.z - explosionSize - 1); + double maxZ = NukkitMath.ceilDouble(this.source.z + explosionSize + 1); + + AxisAlignedBB explosionBB = new SimpleAxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + Entity[] list = this.level.getNearbyEntities(explosionBB, this.what instanceof Entity ? (Entity) this.what : null); + for (Entity entity : list) { + if (!(entity instanceof EntityLiving) || (entity instanceof EntityEnderDragon)) { + continue; + } + + double distance = entity.distance(this.source) / explosionSize; + + if (distance <= 1) { + Vector3 motion = entity.subtract(this.source).normalize(); + double exposure = this.getSeenPercent(this.source, entity); + double impact = (1 - distance) * exposure; + + int damage = this.doesDamage ? Math.max((int) (((impact * impact + impact) / 2) * 5 * this.size + 1), 0) : 0; // * 8 * explosionSize + + if (this.what instanceof Entity) { + entity.attack(new EntityDamageByEntityEvent((Entity) this.what, entity, DamageCause.ENTITY_EXPLOSION, damage)); + } else if (this.what instanceof Block) { + entity.attack(new EntityDamageByBlockEvent((Block) this.what, entity, DamageCause.BLOCK_EXPLOSION, damage)); + } else { + entity.attack(new EntityDamageEvent(entity, DamageCause.BLOCK_EXPLOSION, damage)); + } + + if (entity instanceof Player) { + int netheritePieces = 0; + for (Item armor : ((Player) entity).getInventory().getArmorContents()) { + if (armor.getTier() == ItemArmor.TIER_NETHERITE) { + netheritePieces++; + } + } + if (netheritePieces > 0) { + impact *= 1 - 0.1 * netheritePieces; + } + } + entity.setMotion(entity.getMotion().add(motion.multiply(impact))); + } + } + } + + private double getSeenPercent(Vector3 source, Entity entity) { + AxisAlignedBB bb = entity.getBoundingBox(); + + if (bb.isVectorInside(source)) { + return 1; + } + + double x = 1 / ((bb.getMaxX() - bb.getMinX()) * 2 + 1); + double y = 1 / ((bb.getMaxY() - bb.getMinY()) * 2 + 1); + double z = 1 / ((bb.getMaxZ() - bb.getMinZ()) * 2 + 1); + + double xOffset = (1 - Math.floor(1 / x) * x) / 2; + double yOffset = (1 - Math.floor(1 / y) * y) / 2; + double zOffset = (1 - Math.floor(1 / z) * z) / 2; + + int misses = 0; + int total = 0; + + for (double i = 0; i <= 1; i += x) { + for (double j = 0; j <= 1; j += y) { + for (double k = 0; k <= 1; k += z) { + Vector3 target = new Vector3( + bb.getMinX() + i * (bb.getMaxX() - bb.getMinX()) + xOffset, + bb.getMinY() + j * (bb.getMaxY() - bb.getMinY()) + yOffset, + bb.getMinZ() + k * (bb.getMaxZ() - bb.getMinZ()) + zOffset + ); + + if (!this.raycast(source, target)) { + ++misses; + } + + total++; + } + } + } + + return total != 0 ? (double) misses / (double) total : 0; + } + + private boolean raycast(Vector3 start, Vector3 end) { + Vector3 current = new Vector3(start.x, start.y, start.z); + Vector3 direction = end.subtract(start).normalize(); + + double stepX = sign(direction.getX()); + double stepY = sign(direction.getY()); + double stepZ = sign(direction.getZ()); + + double tMaxX = boundary(start.getX(), direction.getX()); + double tMaxY = boundary(start.getY(), direction.getY()); + double tMaxZ = boundary(start.getZ(), direction.getZ()); + + double tDeltaX = direction.getX() == 0 ? 0 : stepX / direction.getX(); + double tDeltaY = direction.getY() == 0 ? 0 : stepY / direction.getY(); + double tDeltaZ = direction.getZ() == 0 ? 0 : stepZ / direction.getZ(); + + double radius = start.distance(end); + + while (true) { + Block block = level.getBlock(current); + + if (block.isSolid() && block.calculateIntercept(current, end) != null) { + return true; + } + + if (tMaxX < tMaxY && tMaxX < tMaxZ) { + if (tMaxX > radius) { + break; + } + + current.x += stepX; + tMaxX += tDeltaX; + } else if (tMaxY < tMaxZ) { + if (tMaxY > radius) { + break; + } + + current.y += stepY; + tMaxY += tDeltaY; + } else { + if (tMaxZ > radius) { + break; + } + + current.z += stepZ; + tMaxZ += tDeltaZ; + } + } + + return false; + } + + private static double sign(double d) { + if (d > 0) { + return 1; + } + + if (d < 0) { + return -1; + } + + return 0; + } + + private static double boundary(double start, double distance) { + if (distance == 0) { + return Double.POSITIVE_INFINITY; + } + + if (distance < 0) { + start = -start; + distance = -distance; + + if (Math.floor(start) == start) { + return 0; + } + } + + return (1 - (start - Math.floor(start))) / distance; + } + + /** + * Set minimum height at which the explosion can break blocks + * @param minHeight min y coordinate + */ + public void setMinHeight(double minHeight) { + this.minHeight = minHeight; + } } diff --git a/src/main/java/cn/nukkit/level/GameRule.java b/src/main/java/cn/nukkit/level/GameRule.java index aeabe6d4836..d8f2a3e88fa 100644 --- a/src/main/java/cn/nukkit/level/GameRule.java +++ b/src/main/java/cn/nukkit/level/GameRule.java @@ -26,13 +26,19 @@ public enum GameRule { NATURAL_REGENERATION("naturalRegeneration"), PVP("pvp"), RANDOM_TICK_SPEED("randomTickSpeed"), + RESPAWN_BLOCKS_EXPLODE("respawnBlocksExplode"), SEND_COMMAND_FEEDBACK("sendCommandFeedback"), SHOW_COORDINATES("showCoordinates"), SHOW_DEATH_MESSAGES("showDeathMessages"), + SHOW_TAGS("showTags"), SPAWN_RADIUS("spawnRadius"), TNT_EXPLODES("tntExplodes"), - SHOW_TAGS("showTags"), - PLAYERS_SLEEPING_PERCENTAGE("playersSleepingPercentage"); + + SHOW_BORDER_EFFECT("showBorderEffect"), + PLAYERS_SLEEPING_PERCENTAGE("playersSleepingPercentage"), + RECIPES_UNLOCK("recipesUnlock"), + DO_LIMITED_CRAFTING("doLimitedCrafting"), + ; private final String name; @@ -42,7 +48,7 @@ public enum GameRule { public static Optional parseString(String gameRuleString) { for (GameRule gameRule: values()) { - if (gameRule.getName().equalsIgnoreCase(gameRuleString)) { + if (gameRule.name.equalsIgnoreCase(gameRuleString)) { return Optional.of(gameRule); } } @@ -51,9 +57,16 @@ public static Optional parseString(String gameRuleString) { public static String[] getNames() { String[] stringValues = new String[values().length]; + for (int i = 0; i < values().length; i++) { + stringValues[i] = values()[i].name; + } + return stringValues; + } + public static String[] getNamesLowerCase() { + String[] stringValues = new String[values().length]; for (int i = 0; i < values().length; i++) { - stringValues[i] = values()[i].getName(); + stringValues[i] = values()[i].name.toLowerCase(); } return stringValues; } diff --git a/src/main/java/cn/nukkit/level/GameRules.java b/src/main/java/cn/nukkit/level/GameRules.java index 21036427862..6e3a670ad4d 100644 --- a/src/main/java/cn/nukkit/level/GameRules.java +++ b/src/main/java/cn/nukkit/level/GameRules.java @@ -12,25 +12,22 @@ import static cn.nukkit.level.GameRule.*; -@SuppressWarnings({"unchecked"}) +@SuppressWarnings("unchecked") public class GameRules { + private final EnumMap gameRules = new EnumMap<>(GameRule.class); private boolean stale; - private GameRules() { - } - - public static GameRules getDefault() { GameRules gameRules = new GameRules(); - gameRules.gameRules.put(COMMAND_BLOCKS_ENABLED, new Value<>(Type.BOOLEAN, true)); + gameRules.gameRules.put(COMMAND_BLOCKS_ENABLED, new Value<>(Type.BOOLEAN, false)); // Vanilla: default true gameRules.gameRules.put(COMMAND_BLOCK_OUTPUT, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(DO_DAYLIGHT_CYCLE, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(DO_ENTITY_DROPS, new Value<>(Type.BOOLEAN, true)); - gameRules.gameRules.put(DO_FIRE_TICK, new Value(Type.BOOLEAN, true)); - gameRules.gameRules.put(DO_INSOMNIA, new Value(Type.BOOLEAN, true)); - gameRules.gameRules.put(DO_IMMEDIATE_RESPAWN, new Value(Type.BOOLEAN, false)); + gameRules.gameRules.put(DO_FIRE_TICK, new Value<>(Type.BOOLEAN, true)); + gameRules.gameRules.put(DO_INSOMNIA, new Value<>(Type.BOOLEAN, false)); // Vanilla: default true + gameRules.gameRules.put(DO_IMMEDIATE_RESPAWN, new Value<>(Type.BOOLEAN, false)); gameRules.gameRules.put(DO_MOB_LOOT, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(DO_MOB_SPAWNING, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(DO_TILE_DROPS, new Value<>(Type.BOOLEAN, true)); @@ -45,14 +42,19 @@ public static GameRules getDefault() { gameRules.gameRules.put(MOB_GRIEFING, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(NATURAL_REGENERATION, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(PVP, new Value<>(Type.BOOLEAN, true)); - gameRules.gameRules.put(RANDOM_TICK_SPEED, new Value<>(Type.INTEGER, 3)); + gameRules.gameRules.put(RANDOM_TICK_SPEED, new Value<>(Type.INTEGER, 3)); // Vanilla: default 1 + gameRules.gameRules.put(RESPAWN_BLOCKS_EXPLODE, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(SEND_COMMAND_FEEDBACK, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(SHOW_COORDINATES, new Value<>(Type.BOOLEAN, false)); gameRules.gameRules.put(SHOW_DEATH_MESSAGES, new Value<>(Type.BOOLEAN, true)); + gameRules.gameRules.put(SHOW_TAGS, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(SPAWN_RADIUS, new Value<>(Type.INTEGER, 5)); gameRules.gameRules.put(TNT_EXPLODES, new Value<>(Type.BOOLEAN, true)); - gameRules.gameRules.put(SHOW_TAGS, new Value<>(Type.BOOLEAN, true)); + + gameRules.gameRules.put(SHOW_BORDER_EFFECT, new Value<>(Type.BOOLEAN, true)); gameRules.gameRules.put(PLAYERS_SLEEPING_PERCENTAGE, new Value<>(Type.INTEGER, 100)); + gameRules.gameRules.put(RECIPES_UNLOCK, new Value<>(Type.BOOLEAN, false)); + gameRules.gameRules.put(DO_LIMITED_CRAFTING, new Value<>(Type.BOOLEAN, false)); return gameRules; } @@ -99,9 +101,9 @@ public void setGameRules(GameRule gameRule, String value) throws IllegalArgument switch (getGameRuleType(gameRule)) { case BOOLEAN: - if (value.equalsIgnoreCase("true")) { + if (value.equalsIgnoreCase("true") || value.equals("1")) { setGameRule(gameRule, true); - } else if (value.equalsIgnoreCase("false")) { + } else if (value.equalsIgnoreCase("false") || value.equals("0")) { setGameRule(gameRule, false); } else { throw new IllegalArgumentException("Was not a boolean"); @@ -120,22 +122,18 @@ public boolean getBoolean(GameRule gameRule) { } public int getInteger(GameRule gameRule) { - Preconditions.checkNotNull(gameRule, "gameRule"); return gameRules.get(gameRule).getValueAsInteger(); } public float getFloat(GameRule gameRule) { - Preconditions.checkNotNull(gameRule, "gameRule"); return gameRules.get(gameRule).getValueAsFloat(); } public String getString(GameRule gameRule) { - Preconditions.checkNotNull(gameRule, "gameRule"); return gameRules.get(gameRule).value.toString(); } public Type getGameRuleType(GameRule gameRule) { - Preconditions.checkNotNull(gameRule, "gameRule"); return gameRules.get(gameRule).getType(); } @@ -147,7 +145,6 @@ public GameRule[] getRules() { return gameRules.keySet().toArray(new GameRule[0]); } - // TODO: This needs to be moved out since there is not a separate compound tag in the LevelDB format for Game Rules. public CompoundTag writeNBT() { CompoundTag nbt = new CompoundTag(); @@ -159,7 +156,7 @@ public CompoundTag writeNBT() { } public void readNBT(CompoundTag nbt) { - Preconditions.checkNotNull(nbt); + Preconditions.checkNotNull(nbt, "nbt"); for (String key : nbt.getTags().keySet()) { Optional gameRule = GameRule.parseString(key); if (!gameRule.isPresent()) { @@ -170,6 +167,7 @@ public void readNBT(CompoundTag nbt) { } } + public enum Type { UNKNOWN { @Override @@ -198,6 +196,7 @@ void write(BinaryStream pk, Value value) { abstract void write(BinaryStream pk, Value value); } + public static class Value { private final Type type; private T value; @@ -248,10 +247,10 @@ private float getValueAsFloat() { return (Float) value; } - public void write(BinaryStream stream) { - stream.putBoolean(this.canBeChanged); - stream.putUnsignedVarInt(type.ordinal()); - type.write(stream, this); + public void write(BinaryStream pk) { + pk.putBoolean(this.canBeChanged); + pk.putUnsignedVarInt(type.ordinal()); + type.write(pk, this); } } } diff --git a/src/main/java/cn/nukkit/level/GlobalBlockPalette.java b/src/main/java/cn/nukkit/level/GlobalBlockPalette.java index 8b56333d0af..34f41090171 100644 --- a/src/main/java/cn/nukkit/level/GlobalBlockPalette.java +++ b/src/main/java/cn/nukkit/level/GlobalBlockPalette.java @@ -1,59 +1,59 @@ package cn.nukkit.level; -import cn.nukkit.Server; -import cn.nukkit.block.BlockID; -import cn.nukkit.nbt.NBTIO; +import cn.nukkit.level.format.leveldb.LevelDBConstants; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import cn.nukkit.network.protocol.ProtocolInfo; +import cn.nukkit.utils.Utils; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.Getter; import lombok.extern.log4j.Log4j2; -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteOrder; import java.util.List; import java.util.NoSuchElementException; -import java.util.zip.GZIPInputStream; @Log4j2 public class GlobalBlockPalette { - private static final Int2IntMap legacyToRuntimeId = new Int2IntOpenHashMap(); - private static final Int2IntMap runtimeIdToLegacy = new Int2IntOpenHashMap(); - static { - legacyToRuntimeId.defaultReturnValue(-1); - runtimeIdToLegacy.defaultReturnValue(-1); + private static boolean initialized; - ListTag tag; - try (InputStream stream = Server.class.getClassLoader().getResourceAsStream("runtime_block_states.dat")) { - if (stream == null) { - throw new AssertionError("Unable to locate block state nbt"); - } - //noinspection unchecked - tag = (ListTag) NBTIO.readTag(new BufferedInputStream(new GZIPInputStream(stream)), ByteOrder.BIG_ENDIAN, false); - } catch (IOException e) { - throw new AssertionError("Unable to load block palette", e); + @Getter + private static final BlockPalette currentBlockPalette = new BlockPalette(ProtocolInfo.CURRENT_PROTOCOL); + @Getter + private static final BlockPalette leveldbBlockPalette = new BlockPalette(LevelDBConstants.PALETTE_VERSION); + + public static void init() { + if (initialized) { + throw new IllegalStateException("GlobalBlockPalette was already generated!"); } + initialized = true; + log.debug("Loading block palette..."); + + //noinspection unchecked + loadBlockStates((ListTag) Utils.loadTagResource("runtime_block_states.dat"), getCurrentBlockPalette()); + //noinspection unchecked + loadBlockStates((ListTag) Utils.loadTagResource("runtime_block_states_" + LevelDBConstants.PALETTE_VERSION + ".dat"), getLeveldbBlockPalette()); + } + private static void loadBlockStates(ListTag blockStates, BlockPalette blockPalette) { List stateOverloads = new ObjectArrayList<>(); - for (CompoundTag state : tag.getAll()) { - if (!registerBlockState(state, false)) { + for (CompoundTag state : blockStates.getAll()) { + if (!registerBlockState(blockPalette, state, false)) { stateOverloads.add(state); } } for (CompoundTag state : stateOverloads) { - log.debug("Registering block palette overload: {}", state.getString("name")); - registerBlockState(state, true); + log.debug("[{}] Registering block palette overload: {}", blockPalette.getProtocol(), state.getString("name")); + registerBlockState(blockPalette, state, true); } + + blockPalette.lock(); // prevent adding new states } - private static boolean registerBlockState(CompoundTag state, boolean force) { - int blockId = state.getInt("id"); - int meta = state.getShort("data"); + private static boolean registerBlockState(BlockPalette blockPalette, CompoundTag state, boolean force) { + int id = state.getInt("id"); + int data = state.getShort("data"); int runtimeId = state.getInt("runtimeId"); boolean stateOverload = state.getBoolean("stateOverload"); @@ -61,32 +61,24 @@ private static boolean registerBlockState(CompoundTag state, boolean force) { return false; } - int legacyId = blockId << 6 | meta; - legacyToRuntimeId.put(legacyId, runtimeId); - runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); + CompoundTag vanillaState = state + .remove("id") + .remove("data") + .remove("runtimeId") + .remove("stateOverload"); + blockPalette.registerState(id, data, runtimeId, vanillaState); return true; } public static int getOrCreateRuntimeId(int id, int meta) { - int legacyId = id << 6 | meta; - int runtimeId = legacyToRuntimeId.get(legacyId); - if (runtimeId == -1) { - runtimeId = legacyToRuntimeId.get(id << 6); - if (runtimeId == -1 && id != BlockID.INFO_UPDATE) { - log.info("Unable to find runtime id for {}", id); - return getOrCreateRuntimeId(BlockID.INFO_UPDATE, 0); - } else if (id == BlockID.INFO_UPDATE){ - throw new IllegalStateException("InfoUpdate state is missing!"); - } - } - return runtimeId; + return getCurrentBlockPalette().getRuntimeId(id, meta); } public static int getOrCreateRuntimeId(int legacyId) throws NoSuchElementException { - return getOrCreateRuntimeId(legacyId >> 4, legacyId & 0xf); + return getCurrentBlockPalette().getRuntimeId(legacyId); } public static int getLegacyFullId(int runtimeId) { - return runtimeIdToLegacy.get(runtimeId); + return getCurrentBlockPalette().getLegacyFullId(runtimeId); } } diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 02e8f50d092..aecc770e5a0 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -2,14 +2,16 @@ import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; -import cn.nukkit.block.BlockRedstoneDiode; +import cn.nukkit.block.*; import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityContainer; +import cn.nukkit.customblock.CustomBlockManager; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.custom.EntityDefinition; +import cn.nukkit.entity.custom.EntityManager; import cn.nukkit.entity.item.EntityItem; import cn.nukkit.entity.item.EntityXPOrb; -import cn.nukkit.entity.projectile.EntityArrow; +import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.entity.weather.EntityLightning; import cn.nukkit.event.block.BlockBreakEvent; import cn.nukkit.event.block.BlockPlaceEvent; @@ -18,6 +20,8 @@ import cn.nukkit.event.player.PlayerInteractEvent; import cn.nukkit.event.player.PlayerInteractEvent.Action; import cn.nukkit.event.weather.LightningStrikeEvent; +import cn.nukkit.inventory.Inventory; +import cn.nukkit.inventory.InventoryHolder; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; import cn.nukkit.item.ItemBucket; @@ -29,18 +33,19 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.format.LevelProvider; import cn.nukkit.level.format.anvil.Anvil; +import cn.nukkit.level.format.generic.BaseChunk; import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.format.generic.BaseLevelProvider; import cn.nukkit.level.format.generic.EmptyChunkSection; -import cn.nukkit.level.format.leveldb.LevelDB; -import cn.nukkit.level.format.mcregion.McRegion; import cn.nukkit.level.generator.Generator; +import cn.nukkit.level.generator.GeneratorTaskFactory; import cn.nukkit.level.generator.PopChunkManager; import cn.nukkit.level.generator.task.GenerationTask; -import cn.nukkit.level.generator.task.LightPopulationTask; import cn.nukkit.level.generator.task.PopulationTask; import cn.nukkit.level.particle.DestroyBlockParticle; +import cn.nukkit.level.particle.ItemBreakParticle; import cn.nukkit.level.particle.Particle; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.level.persistence.impl.DelegatePersistentDataContainer; import cn.nukkit.math.*; import cn.nukkit.math.BlockFace.Plane; import cn.nukkit.metadata.BlockMetadataStore; @@ -53,32 +58,26 @@ import cn.nukkit.potion.Effect; import cn.nukkit.scheduler.AsyncTask; import cn.nukkit.scheduler.BlockUpdateScheduler; -import cn.nukkit.timings.LevelTimings; import cn.nukkit.utils.*; -import co.aikar.timings.Timings; -import co.aikar.timings.TimingsHistory; -import com.google.common.base.Preconditions; -import it.unimi.dsi.fastutil.ints.Int2IntMap; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.longs.*; import it.unimi.dsi.fastutil.objects.ObjectIterator; +import lombok.Getter; +import lombok.Setter; -import java.io.File; -import java.io.IOException; import java.lang.ref.SoftReference; import java.util.*; import java.util.concurrent.*; +import java.util.function.Predicate; + /** - * author: MagicDroidX Nukkit Project + * @author MagicDroidX Nukkit Project */ -public class Level implements ChunkManager, Metadatable { +public class Level implements ChunkManager, Metadatable, GeneratorTaskFactory { private static int levelIdCounter = 1; private static int chunkLoaderCounter = 1; - public static int COMPRESSION_LEVEL = 8; public static final int BLOCK_UPDATE_NORMAL = 1; public static final int BLOCK_UPDATE_RANDOM = 2; @@ -102,10 +101,12 @@ public class Level implements ChunkManager, Metadatable { public static final int DIMENSION_THE_END = 2; // Lower values use less memory - public static final int MAX_BLOCK_CACHE = 512; + public static final int MAX_BLOCK_CACHE = 1024; - // The blocks that can randomly tick - private static final boolean[] randomTickBlocks = new boolean[256]; + /** + * The blocks that can be randomly ticked + */ + private static final boolean[] randomTickBlocks = new boolean[Block.MAX_BLOCK_ID]; static { randomTickBlocks[Block.GRASS] = true; @@ -126,34 +127,63 @@ public class Level implements ChunkManager, Metadatable { randomTickBlocks[Block.PUMPKIN_STEM] = true; randomTickBlocks[Block.WHEAT_BLOCK] = true; randomTickBlocks[Block.SUGARCANE_BLOCK] = true; - randomTickBlocks[Block.RED_MUSHROOM] = true; - randomTickBlocks[Block.BROWN_MUSHROOM] = true; randomTickBlocks[Block.NETHER_WART_BLOCK] = true; randomTickBlocks[Block.FIRE] = true; randomTickBlocks[Block.GLOWING_REDSTONE_ORE] = true; randomTickBlocks[Block.COCOA_BLOCK] = true; + randomTickBlocks[Block.ICE_FROSTED] = true; randomTickBlocks[Block.VINE] = true; + randomTickBlocks[Block.WATER] = true; + randomTickBlocks[Block.CAULDRON_BLOCK] = true; + randomTickBlocks[Block.CHORUS_FLOWER] = true; + randomTickBlocks[Block.SWEET_BERRY_BUSH] = true; + randomTickBlocks[Block.BAMBOO_SAPLING] = true; + randomTickBlocks[Block.BAMBOO] = true; + randomTickBlocks[Block.CORAL_FAN] = true; + randomTickBlocks[Block.CORAL_FAN_DEAD] = true; + randomTickBlocks[Block.BLOCK_KELP] = true; + randomTickBlocks[BlockID.COPPER_BLOCK] = true; + randomTickBlocks[BlockID.EXPOSED_COPPER] = true; + randomTickBlocks[BlockID.WEATHERED_COPPER] = true; + randomTickBlocks[BlockID.WAXED_COPPER] = true; + randomTickBlocks[BlockID.CUT_COPPER] = true; + randomTickBlocks[BlockID.EXPOSED_CUT_COPPER] = true; + randomTickBlocks[BlockID.WEATHERED_CUT_COPPER] = true; + randomTickBlocks[BlockID.CUT_COPPER_STAIRS] = true; + randomTickBlocks[BlockID.EXPOSED_CUT_COPPER_STAIRS] = true; + randomTickBlocks[BlockID.WEATHERED_CUT_COPPER_STAIRS] = true; + randomTickBlocks[BlockID.CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.EXPOSED_CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.WEATHERED_CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.DOUBLE_CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.EXPOSED_DOUBLE_CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.WEATHERED_DOUBLE_CUT_COPPER_SLAB] = true; + randomTickBlocks[BlockID.BUDDING_AMETHYST] = true; + randomTickBlocks[BlockID.CAVE_VINES] = true; + randomTickBlocks[BlockID.CAVE_VINES_BODY_WITH_BERRIES] = true; + randomTickBlocks[BlockID.CAVE_VINES_HEAD_WITH_BERRIES] = true; + randomTickBlocks[BlockID.AZALEA_LEAVES] = true; + randomTickBlocks[BlockID.AZALEA_LEAVES_FLOWERED] = true; + randomTickBlocks[BlockID.POINTED_DRIPSTONE] = true; } private final Long2ObjectOpenHashMap blockEntities = new Long2ObjectOpenHashMap<>(); private final Long2ObjectOpenHashMap players = new Long2ObjectOpenHashMap<>(); - private final Long2ObjectOpenHashMap entities = new Long2ObjectOpenHashMap<>(); + public final Long2ObjectOpenHashMap entities = new Long2ObjectOpenHashMap<>(); public final Long2ObjectOpenHashMap updateEntities = new Long2ObjectOpenHashMap<>(); private final ConcurrentLinkedQueue updateBlockEntities = new ConcurrentLinkedQueue<>(); - private boolean cacheChunks = false; - private final Server server; - + private final int levelId; private LevelProvider provider; - private final Int2ObjectOpenHashMap loaders = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap loaders = new Int2ObjectOpenHashMap<>(); private final Int2IntMap loaderCounter = new Int2IntOpenHashMap(); @@ -165,140 +195,121 @@ public class Level implements ChunkManager, Metadatable { private final Long2LongMap unloadQueue = Long2LongMaps.synchronize(new Long2LongOpenHashMap()); - private float time; + private int time; + public boolean stopTime; public float skyLightSubtracted; - private String folderName; - - private Vector3 mutableBlock; + private final String folderName; // Avoid OOM, gc'd references result in whole chunk being sent (possibly higher cpu) - private final Long2ObjectOpenHashMap>> changedBlocks = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectOpenHashMap>> changedBlocks = new Long2ObjectOpenHashMap<>(); // Storing the vector is redundant private final Object changeBlocksPresent = new Object(); // Storing extra blocks past 512 is redundant - private final Map changeBlocksFullMap = new HashMap() { - @Override - public int size() { - return Character.MAX_VALUE; - } - }; - + private final Int2ObjectOpenHashMap changeBlocksFullMap = new Int2ObjectOpenHashMap<>(); private final BlockUpdateScheduler updateQueue; private final Queue normalUpdateQueue = new ConcurrentLinkedDeque<>(); -// private final TreeSet updateQueue = new TreeSet<>(); -// private final List nextTickUpdates = Lists.newArrayList(); - //private final Map updateQueueIndex = new HashMap<>(); private final ConcurrentMap> chunkSendQueue = new ConcurrentHashMap<>(); private final LongSet chunkSendTasks = new LongOpenHashSet(); - private final Long2ObjectOpenHashMap chunkPopulationQueue = new Long2ObjectOpenHashMap<>(); - private final Long2ObjectOpenHashMap chunkPopulationLock = new Long2ObjectOpenHashMap<>(); - private final Long2ObjectOpenHashMap chunkGenerationQueue = new Long2ObjectOpenHashMap<>(); - private int chunkGenerationQueueSize = 8; - private int chunkPopulationQueueSize = 2; + private final LongOpenHashSet chunkPopulationQueue = new LongOpenHashSet(); + private final LongOpenHashSet chunkPopulationLock = new LongOpenHashSet(); + private final LongOpenHashSet chunkGenerationQueue = new LongOpenHashSet(); + private final int chunkGenerationQueueSize; + private final int chunkPopulationQueueSize; private boolean autoSave; + @Getter + @Setter + private boolean saveOnUnloadEnabled = true; + public boolean isBeingConverted; private BlockMetadataStore blockMetadata; - private boolean useSections; + private final boolean useSections; - private Position temporalPosition; - private Vector3 temporalVector; + private boolean useChunkLoaderApi; + private final Vector3 temporalVector; public int sleepTicks = 0; - private int chunkTickRadius; + private final int chunkTickRadius; private final Long2IntMap chunkTickList = new Long2IntOpenHashMap(); - private int chunksPerTicks; - private boolean clearChunksOnTick; + private final int chunksPerTicks; + private final boolean clearChunksOnTick; private int updateLCG = ThreadLocalRandom.current().nextInt(); private static final int LCG_CONSTANT = 1013904223; - public LevelTimings timings; - - private int tickRate; + private int tickRate = 1; public int tickRateTime = 0; public int tickRateCounter = 0; - private Class generatorClass; - private ThreadLocal generators = new ThreadLocal() { + @SuppressWarnings("FieldMayBeFinal") private Class generatorClass; + @SuppressWarnings("FieldMayBeFinal") private ThreadLocal generators = new ThreadLocal() { + @Override public Generator initialValue() { try { Generator generator = generatorClass.getConstructor(Map.class).newInstance(provider.getGeneratorOptions()); NukkitRandom rand = new NukkitRandom(getSeed()); - ChunkManager manager; if (Server.getInstance().isPrimaryThread()) { generator.init(Level.this, rand); } - generator.init(new PopChunkManager(getSeed()), rand); + generator.init(new PopChunkManager(getSeed(), Level.this::getDimensionData), rand); return generator; } catch (Throwable e) { - e.printStackTrace(); + Server.getInstance().getLogger().logException(e); return null; } } }; - private boolean raining = false; - private int rainTime = 0; - private boolean thundering = false; - private int thunderTime = 0; + private boolean raining; + private int rainTime; + private boolean thundering; + private int thunderTime; - private long levelCurrentTick = 0; + private long levelCurrentTick; + @Getter + @Setter private DimensionData dimensionData; public GameRules gameRules; + private final AsyncChunkThread asyncChunkThread; + private Vector3 cachedSpawnPos; + + private GeneratorTaskFactory generatorTaskFactory = this; + public Level(Server server, String name, String path, Class provider) { this.levelId = levelIdCounter++; - this.blockMetadata = new BlockMetadataStore(this); + this.server = server; this.autoSave = server.getAutoSave(); - boolean convert = provider == McRegion.class || provider == LevelDB.class; + this.blockMetadata = new BlockMetadataStore(this); + try { - if (convert) { - String newPath = new File(path).getParent() + "/" + name + ".old/"; - new File(path).renameTo(new File(newPath)); - this.provider = provider.getConstructor(Level.class, String.class).newInstance(this, newPath); - } else { - this.provider = provider.getConstructor(Level.class, String.class).newInstance(this, path); - } + this.provider = provider.getConstructor(Level.class, String.class).newInstance(this, path); } catch (Exception e) { throw new LevelException("Caused by " + Utils.getExceptionMessage(e)); } - this.timings = new LevelTimings(this); - - if (convert) { - this.server.getLogger().info(this.server.getLanguage().translateString("nukkit.level.updating", - TextFormat.GREEN + this.provider.getName() + TextFormat.WHITE)); - LevelProvider old = this.provider; - try { - this.provider = new LevelProviderConverter(this, path) - .from(old) - .to(Anvil.class) - .perform(); - } catch (IOException e) { - throw new RuntimeException(e); - } - old.close(); - } - this.provider.updateLevelName(name); this.server.getLogger().info(this.server.getLanguage().translateString("nukkit.level.preparing", - TextFormat.GREEN + this.provider.getName() + TextFormat.WHITE)); + TextFormat.GREEN + name + TextFormat.WHITE)); + + if (this.provider instanceof Anvil) { + this.server.getLogger().warning("Level \"" + name + "\" is in old format. Convert it to enable new features. Type 'convert " + name + "' to get started."); + } this.generatorClass = Generator.getGenerator(this.provider.getGenerator()); @@ -309,66 +320,65 @@ public Level(Server server, String name, String path, Class= 256) { - throw new IllegalArgumentException("Y coordinate y is out of range!"); - } - return (((long) x & (long) 0xFFFFFFF) << 36) | (((long) y & (long) 0xFF) << 28) | ((long) z & (long) 0xFFFFFFF); + public static long blockHash(int x, int y, int z, DimensionData dimensionData) { + return (((long) x & (long) 0xFFFFFFF) << 36) | ((long) (capWorldY(y, dimensionData) - dimensionData.getMinHeight()) << 28) | ((long) z & (long) 0xFFFFFFF); } - public static char localBlockHash(double x, double y, double z) { + public static int localBlockHash(double x, double y, double z, DimensionData dimensionData) { byte hi = (byte) (((int) x & 15) + (((int) z & 15) << 4)); - byte lo = (byte) y; - return (char) (((hi & 0xFF) << 8) | (lo & 0xFF)); + short lo = (short) (capWorldY((int) y, dimensionData) - dimensionData.getMinHeight()); + return (hi & 0xFF) << 16 | lo; } - public static Vector3 getBlockXYZ(long chunkHash, char blockHash) { - int hi = (byte) (blockHash >>> 8); - int lo = (byte) blockHash; - int y = lo & 0xFF; + public static Vector3 getBlockXYZ(long chunkHash, int blockHash, DimensionData dimensionData) { + int hi = (byte) (blockHash >>> 16); + int lo = (short) blockHash; + int y = capWorldY(lo + dimensionData.getMinHeight(), dimensionData); int x = (hi & 0xF) + (getHashX(chunkHash) << 4); int z = ((hi >> 4) & 0xF) + (getHashZ(chunkHash) << 4); return new Vector3(x, y, z); } + public static BlockVector3 blockHash(double x, double y, double z) { + return new BlockVector3((int) x, (int) y, (int) z); + } + public static int chunkBlockHash(int x, int y, int z) { - return (x << 12) | (z << 8) | y; + return (x << 13) | (z << 9) | (y + 64); } public static int getHashX(long hash) { @@ -387,6 +397,14 @@ public static Chunk.Entry getChunkXZ(long hash) { return new Chunk.Entry(getHashX(hash), getHashZ(hash)); } + public static int capWorldY(int y, DimensionData dimensionData) { + return Math.max(Math.min(y, dimensionData.getMaxHeight()), dimensionData.getMinHeight()); + } + + public int capWorldY(int y) { + return capWorldY(y, this.getDimensionData()); + } + public static int generateChunkLoaderId(ChunkLoader loader) { if (loader.getLoaderId() == 0) { return chunkLoaderCounter++; @@ -410,11 +428,12 @@ public void setTickRate(int tickRate) { public void initLevel() { Generator generator = generators.get(); this.dimensionData = generator.getDimensionData(); - this.gameRules = this.provider.getGamerules(); + if (this.dimensionData.getDimensionId() == DIMENSION_OVERWORLD && + this.provider instanceof Anvil) { + this.dimensionData = DimensionData.LEGACY_DIMENSION; + } - this.server.getLogger().info("Preparing start region for level \"" + this.getFolderName() + "\""); - Position spawn = this.getSpawnLocation(); - this.populateChunk(spawn.getChunkX(), spawn.getChunkZ(), true); + this.gameRules = this.provider.getGamerules(); } public Generator getGenerator() { @@ -438,14 +457,14 @@ final public int getId() { } public void close() { - if (this.getAutoSave()) { - this.save(true); + if (this.asyncChunkThread != null) { + this.asyncChunkThread.shutdown(); } - this.provider.close(); + this.saveLevelData(); + this.provider.close(); // Also saves chunks on unload this.provider = null; this.blockMetadata = null; - this.temporalPosition = null; this.server.getLevels().remove(this.levelId); } @@ -462,8 +481,8 @@ public void addSound(Vector3 pos, Sound sound, float volume, float pitch, Collec } public void addSound(Vector3 pos, Sound sound, float volume, float pitch, Player... players) { - Preconditions.checkArgument(volume >= 0 && volume <= 1, "Sound volume must be between 0 and 1"); - Preconditions.checkArgument(pitch >= 0, "Sound pitch must be higher than 0"); + if (volume < 0 || volume > 1) throw new IllegalArgumentException("Sound volume must be between 0 and 1"); + if (pitch < 0) throw new IllegalArgumentException("Sound pitch must be higher than 0"); PlaySoundPacket packet = new PlaySoundPacket(); packet.name = sound.getSound(); @@ -474,7 +493,38 @@ public void addSound(Vector3 pos, Sound sound, float volume, float pitch, Player packet.z = pos.getFloorZ(); if (players == null || players.length == 0) { - addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, packet); + this.addChunkPacket(pos.getChunkX(), pos.getChunkZ(), packet); + } else { + Server.broadcastPacket(players, packet); + } + } + + /** + * Play sound + * @param pos position + * @param sound sound identifier + */ + public void addSound(Vector3 pos, String sound) { + this.addSound(pos, sound, (Player[]) null); + } + + /** + * Play sound + * @param pos position + * @param sound sound identifier + * @param players target players + */ + public void addSound(Vector3 pos, String sound, Player... players) { + PlaySoundPacket packet = new PlaySoundPacket(); + packet.name = sound; + packet.volume = 1; + packet.pitch = 1; + packet.x = pos.getFloorX(); + packet.y = pos.getFloorY(); + packet.z = pos.getFloorZ(); + + if (players == null || players.length == 0) { + addChunkPacket(pos.getChunkX(), pos.getChunkZ(), packet); } else { Server.broadcastPacket(players, packet); } @@ -491,17 +541,24 @@ public void addLevelEvent(Vector3 pos, int event, int data) { pk.y = (float) pos.y; pk.z = (float) pos.z; pk.data = data; - - addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); + this.addChunkPacket(pos.getChunkX(), pos.getChunkZ(), pk); } public void addLevelSoundEvent(Vector3 pos, int type, int data, int entityType) { - addLevelSoundEvent(pos, type, data, entityType, false, false); + this.addLevelSoundEvent(pos, type, data, entityType, false, false); } public void addLevelSoundEvent(Vector3 pos, int type, int data, int entityType, boolean isBaby, boolean isGlobal) { - String identifier = AddEntityPacket.LEGACY_IDS.getOrDefault(entityType, ":"); - addLevelSoundEvent(pos, type, data, identifier, isBaby, isGlobal); + String identifier = AddEntityPacket.LEGACY_IDS.get(entityType); + if (identifier == null) { + EntityDefinition definition = EntityManager.get().getDefinition(entityType); + if (definition == null) { + identifier = ":"; + } else { + identifier = definition.getIdentifier(); + } + } + this.addLevelSoundEvent(pos, type, data, identifier, isBaby, isGlobal); } public void addLevelSoundEvent(Vector3 pos, int type) { @@ -516,7 +573,7 @@ public void addLevelSoundEvent(Vector3 pos, int type) { * @param data generic data that can affect sound */ public void addLevelSoundEvent(Vector3 pos, int type, int data) { - this.addLevelSoundEvent(pos, type, data, ":", false, false); + this.addLevelSoundEvent(pos, type, data, "", false, false); } public void addLevelSoundEvent(Vector3 pos, int type, int data, String identifier, boolean isBaby, boolean isGlobal) { @@ -530,7 +587,7 @@ public void addLevelSoundEvent(Vector3 pos, int type, int data, String identifie pk.isGlobal = isGlobal; pk.isBabyMob = isBaby; - this.addChunkPacket(pos.getFloorX() >> 4, pos.getFloorZ() >> 4, pk); + this.addChunkPacket(pos.getChunkX(), pos.getChunkZ(), pk); } public void addParticle(Particle particle) { @@ -542,18 +599,19 @@ public void addParticle(Particle particle, Player player) { } public void addParticle(Particle particle, Player[] players) { - DataPacket[] packets = particle.encode(); + this.addParticle(particle, players, 1); + } + public void addParticle(Particle particle, Player[] players, int count) { if (players == null) { - if (packets != null) { - for (DataPacket packet : packets) { - this.addChunkPacket((int) particle.x >> 4, (int) particle.z >> 4, packet); - } - } - } else { - if (packets != null) { - for (DataPacket packet : packets) { - Server.broadcastPacket(players, packet); + players = this.getChunkPlayers(particle.getChunkX(), particle.getChunkZ()).values().toArray(new Player[0]); + } + + DataPacket[] packets = particle.encode(); + if (packets != null) { + for (int i = 0; i < count; i++) { + for (DataPacket pk : packets) { + Server.broadcastPacket(players, pk); } } } @@ -613,7 +671,7 @@ public boolean unload(boolean force) { LevelUnloadEvent ev = new LevelUnloadEvent(this); if (this == this.server.getDefaultLevel() && !force) { - ev.setCancelled(); + ev.setCancelled(true); } this.server.getPluginManager().callEvent(ev); @@ -645,8 +703,9 @@ public boolean unload(boolean force) { public Map getChunkPlayers(int chunkX, int chunkZ) { long index = Level.chunkHash(chunkX, chunkZ); - if (this.playerLoaders.containsKey(index)) { - return new HashMap<>(this.playerLoaders.get(index)); + Map map = this.playerLoaders.get(index); + if (map != null) { + return new HashMap<>(map); } else { return new HashMap<>(); } @@ -654,8 +713,9 @@ public Map getChunkPlayers(int chunkX, int chunkZ) { public ChunkLoader[] getChunkLoaders(int chunkX, int chunkZ) { long index = Level.chunkHash(chunkX, chunkZ); - if (this.chunkLoaders.containsKey(index)) { - return this.chunkLoaders.get(index).values().toArray(new ChunkLoader[0]); + Map map = this.chunkLoaders.get(index); + if (map != null) { + return map.values().toArray(new ChunkLoader[0]); } else { return new ChunkLoader[0]; } @@ -674,18 +734,31 @@ public void registerChunkLoader(ChunkLoader loader, int chunkX, int chunkZ) { } public void registerChunkLoader(ChunkLoader loader, int chunkX, int chunkZ, boolean autoLoad) { + if (!(loader instanceof Player) && !this.useChunkLoaderApi) { + this.server.getLogger().debug("registerChunkLoader: full ChunkLoader API enabled"); + this.useChunkLoaderApi = true; + } + int hash = loader.getLoaderId(); long index = Level.chunkHash(chunkX, chunkZ); - if (!this.chunkLoaders.containsKey(index)) { - this.chunkLoaders.put(index, new HashMap<>()); - this.playerLoaders.put(index, new HashMap<>()); - } else if (this.chunkLoaders.get(index).containsKey(hash)) { - return; - } - this.chunkLoaders.get(index).put(hash, loader); - if (loader instanceof Player) { - this.playerLoaders.get(index).put(hash, (Player) loader); + Map map = this.chunkLoaders.get(index); + if (map == null) { + Map newChunkLoader = new HashMap<>(); + newChunkLoader.put(hash, loader); + this.chunkLoaders.put(index, newChunkLoader); + Map newPlayerLoader = new HashMap<>(); + if (loader instanceof Player) { + newPlayerLoader.put(hash, (Player) loader); + } + this.playerLoaders.put(index, newPlayerLoader); + } else if (map.containsKey(hash)) { + return; + } else { + map.put(hash, loader); + if (loader instanceof Player) { + this.playerLoaders.get(index).put(hash, (Player) loader); + } } if (!this.loaders.containsKey(hash)) { @@ -719,7 +792,7 @@ public void unregisterChunkLoader(ChunkLoader loader, int chunkX, int chunkZ) { } int count = this.loaderCounter.get(hash); - if (--count == 0) { + if (--count <= 0) { this.loaderCounter.remove(hash); this.loaders.remove(hash); } else { @@ -731,13 +804,14 @@ public void unregisterChunkLoader(ChunkLoader loader, int chunkX, int chunkZ) { public void checkTime() { if (!this.stopTime && this.gameRules.getBoolean(GameRule.DO_DAYLIGHT_CYCLE)) { - this.time += tickRate; + this.time = this.time + tickRate; + if (this.time < 0) this.time = 0; } } public void sendTime(Player... players) { SetTimePacket pk = new SetTimePacket(); - pk.time = (int) this.time; + pk.time = this.time; Server.broadcastPacket(players, pk); } @@ -751,24 +825,31 @@ public GameRules getGameRules() { } public void doTick(int currentTick) { - this.timings.doTick.startTiming(); + int sendCount = (this.players.size() + 1) * server.chunksPerTick; + for (int i = 0; i < sendCount; i++) { + AsyncChunkData data = this.asyncChunkThread.out.poll(); + if (data == null) { + break; + } + this.chunkRequestCallback(data.timestamp, data.x, data.z, data.count, data.data, data.hash); + } updateBlockLight(lightQueue); this.checkTime(); - if (currentTick % 1200 == 0) { // Send time to client every 60 seconds to make sure it stay in sync + if (/*stopTime || !this.gameRules.getBoolean(GameRule.DO_DAYLIGHT_CYCLE) ||*/ currentTick % 6000 == 0) { // Keep the time in sync this.sendTime(); } // Tick Weather - if (this.getDimension() != DIMENSION_NETHER && this.getDimension() != DIMENSION_THE_END && gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) { + if (this.getDimension() != DIMENSION_NETHER && this.getDimension() != DIMENSION_THE_END && this.gameRules.getBoolean(GameRule.DO_WEATHER_CYCLE)) { this.rainTime--; if (this.rainTime <= 0) { if (!this.setRaining(!this.raining)) { if (this.raining) { - setRainTime(ThreadLocalRandom.current().nextInt(12000) + 12000); + setRainTime(Utils.random.nextInt(12000) + 12000); } else { - setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); + setRainTime(Utils.random.nextInt(168000) + 12000); } } } @@ -777,9 +858,9 @@ public void doTick(int currentTick) { if (this.thunderTime <= 0) { if (!this.setThundering(!this.thundering)) { if (this.thundering) { - setThunderTime(ThreadLocalRandom.current().nextInt(12000) + 3600); + setThunderTime(Utils.random.nextInt(12000) + 3600); } else { - setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); + setThunderTime(Utils.random.nextInt(168000) + 12000); } } } @@ -794,7 +875,7 @@ public void doTick(int currentTick) { performThunder(entry.getLongKey(), entry.getValue()); } } else { - for (Map.Entry entry : getChunks().entrySet()) { + for (Map.Entry entry : chunks.entrySet()) { performThunder(entry.getKey(), entry.getValue()); } } @@ -806,20 +887,14 @@ public void doTick(int currentTick) { this.levelCurrentTick++; this.unloadChunks(); - this.timings.doTickPending.startTiming(); - - int polled = 0; - this.updateQueue.tick(this.getCurrentTick()); - this.timings.doTickPending.stopTiming(); + this.updateQueue.tick(this.levelCurrentTick); Block block; while ((block = this.normalUpdateQueue.poll()) != null) { block.onUpdate(BLOCK_UPDATE_NORMAL); } - TimingsHistory.entityTicks += this.updateEntities.size(); - this.timings.entityTick.startTiming(); if (!this.updateEntities.isEmpty()) { for (long id : new ArrayList<>(this.updateEntities.keySet())) { @@ -833,46 +908,41 @@ public void doTick(int currentTick) { } } } - this.timings.entityTick.stopTiming(); - TimingsHistory.tileEntityTicks += this.updateBlockEntities.size(); - this.timings.blockEntityTick.startTiming(); this.updateBlockEntities.removeIf(blockEntity -> !blockEntity.isValid() || !blockEntity.onUpdate()); - this.timings.blockEntityTick.stopTiming(); - this.timings.tickChunks.startTiming(); this.tickChunks(); - this.timings.tickChunks.stopTiming(); synchronized (changedBlocks) { if (!this.changedBlocks.isEmpty()) { if (!this.players.isEmpty()) { - ObjectIterator>>> iter = changedBlocks.long2ObjectEntrySet().fastIterator(); + ObjectIterator>>> iter = changedBlocks.long2ObjectEntrySet().fastIterator(); while (iter.hasNext()) { - Long2ObjectMap.Entry>> entry = iter.next(); + Long2ObjectMap.Entry>> entry = iter.next(); long index = entry.getLongKey(); - Map blocks = entry.getValue().get(); + Map blocks = entry.getValue().get(); int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); if (blocks == null || blocks.size() > MAX_BLOCK_CACHE) { - FullChunk chunk = this.getChunk(chunkX, chunkZ); + FullChunk chunk = this.getChunkIfLoaded(chunkX, chunkZ); + if (chunk == null) { + continue; + } for (Player p : this.getChunkPlayers(chunkX, chunkZ).values()) { p.onChunkChanged(chunk); } } else { - Collection toSend = this.getChunkPlayers(chunkX, chunkZ).values(); - Player[] playerArray = toSend.toArray(new Player[0]); + Player[] playerArray = this.getChunkPlayers(chunkX, chunkZ).values().toArray(new Player[0]); Vector3[] blocksArray = new Vector3[blocks.size()]; int i = 0; - for (char blockHash : blocks.keySet()) { - Vector3 hash = getBlockXYZ(index, blockHash); + for (int blockHash : blocks.keySet()) { + Vector3 hash = getBlockXYZ(index, blockHash, this.getDimensionData()); blocksArray[i++] = hash; } this.sendBlocks(playerArray, blocksArray, UpdateBlockPacket.FLAG_ALL); } } } - this.changedBlocks.clear(); } } @@ -887,8 +957,9 @@ public void doTick(int currentTick) { for (long index : this.chunkPackets.keySet()) { int chunkX = Level.getHashX(index); int chunkZ = Level.getHashZ(index); - Player[] chunkPlayers = this.getChunkPlayers(chunkX, chunkZ).values().toArray(new Player[0]); - if (chunkPlayers.length > 0) { + Map map = this.getChunkPlayers(chunkX, chunkZ); + if (!map.isEmpty()) { + Player[] chunkPlayers = map.values().toArray(new Player[0]); for (DataPacket pk : this.chunkPackets.get(index)) { Server.broadcastPacket(chunkPlayers, pk); } @@ -899,21 +970,22 @@ public void doTick(int currentTick) { if (gameRules.isStale()) { GameRulesChangedPacket packet = new GameRulesChangedPacket(); - packet.gameRules = gameRules; - Server.broadcastPacket(players.values().toArray(new Player[0]), packet); + packet.gameRulesMap = gameRules.getGameRules(); + Server.broadcastPacket(this.players.values(), packet); gameRules.refresh(); } - - this.timings.doTick.stopTiming(); } private void performThunder(long index, FullChunk chunk) { - if (areNeighboringChunksLoaded(index)) return; - if (ThreadLocalRandom.current().nextInt(10000) == 0) { + if (Utils.random.nextInt(100000) < 1) { + if (areNeighboringChunksLoaded(index)) { + return; + } + int LCG = this.getUpdateLCG() >> 2; - int chunkX = chunk.getX() * 16; - int chunkZ = chunk.getZ() * 16; + int chunkX = chunk.getX() << 4; + int chunkZ = chunk.getZ() << 4; Vector3 vector = this.adjustPosToNearbyEntity(new Vector3(chunkX + (LCG & 0xf), 0, chunkZ + (LCG >> 8 & 0xf))); Biome biome = Biome.getBiome(this.getBiomeId(vector.getFloorX(), vector.getFloorZ())); @@ -932,34 +1004,32 @@ private void performThunder(long index, FullChunk chunk) { .putList(new ListTag("Rotation").add(new FloatTag("", 0)) .add(new FloatTag("", 0))); - EntityLightning bolt = (EntityLightning) Entity.createEntity("Lightning", chunk, nbt); - if(bolt == null) return; + EntityLightning bolt = new EntityLightning(chunk, nbt); LightningStrikeEvent ev = new LightningStrikeEvent(this, bolt); - getServer().getPluginManager().callEvent(ev); + server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { bolt.spawnToAll(); + this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_THUNDER, -1, EntityLightning.NETWORK_ID); + this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_EXPLODE, -1, EntityLightning.NETWORK_ID); } else { bolt.setEffect(false); } - - this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_THUNDER, -1, EntityLightning.NETWORK_ID); - this.addLevelSoundEvent(vector, LevelSoundEventPacket.SOUND_EXPLODE, -1, EntityLightning.NETWORK_ID); } } public Vector3 adjustPosToNearbyEntity(Vector3 pos) { pos.y = this.getHighestBlockAt(pos.getFloorX(), pos.getFloorZ()); - AxisAlignedBB axisalignedbb = new SimpleAxisAlignedBB(pos.x, pos.y, pos.z, pos.getX(), 255, pos.getZ()).expand(3, 3, 3); + AxisAlignedBB axisalignedbb = new SimpleAxisAlignedBB(pos.x, pos.y, pos.z, pos.getX(), this.getMaxBlockY(), pos.getZ()).expand(3, 3, 3); List list = new ArrayList<>(); for (Entity entity : this.getCollidingEntities(axisalignedbb)) { - if (entity.isAlive() && canBlockSeeSky(entity)) { + if (entity.isAlive() && entity.canSeeSky()) { list.add(entity); } } if (!list.isEmpty()) { - return list.get(ThreadLocalRandom.current().nextInt(list.size())).getPosition(); + return list.get(Utils.random.nextInt(list.size())).getPosition(); } else { if (pos.getY() == -1) { pos = pos.up(2); @@ -974,21 +1044,29 @@ public void checkSleep() { return; } - int playerCount = 0; - int sleepingPlayerCount = 0; - for (Player p : this.getPlayers().values()) { - playerCount++; + int players = 0; + int sleeping = 0; + for (Player p : this.players.values()) { + if (p.isSpectator()) { + continue; + } + players++; if (p.isSleeping()) { - sleepingPlayerCount++; + sleeping++; } } - if (playerCount > 0 && sleepingPlayerCount / playerCount * 100 >= this.gameRules.getInteger(GameRule.PLAYERS_SLEEPING_PERCENTAGE)) { + if (players > 0 && sleeping / players * 100 >= gameRules.getInteger(GameRule.PLAYERS_SLEEPING_PERCENTAGE)) { int time = this.getTime() % Level.TIME_FULL; - if (time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE) { + if ((time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE) || this.isThundering()) { this.setTime(this.getTime() + Level.TIME_FULL - time); + if (this.isThundering()) { + this.setThundering(false); + this.setRaining(false); + } + for (Player p : this.getPlayers().values()) { p.stopSleep(); } @@ -1001,7 +1079,7 @@ public void sendBlockExtraData(int x, int y, int z, int id, int data) { } public void sendBlockExtraData(int x, int y, int z, int id, int data, Collection players) { - sendBlockExtraData(x, y, z, id, data, players.toArray(new Player[0])); + this.sendBlockExtraData(x, y, z, id, data, players.toArray(new Player[0])); } public void sendBlockExtraData(int x, int y, int z, int id, int data, Player[] players) { @@ -1020,22 +1098,25 @@ public void sendBlocks(Player[] target, Vector3[] blocks) { } public void sendBlocks(Player[] target, Vector3[] blocks, int flags) { - this.sendBlocks(target, blocks, flags, 0); + this.sendBlocks(target, blocks, flags, false, Block.LAYER_NORMAL); + this.sendBlocks(target, blocks, flags, false, Block.LAYER_WATERLOGGED); } public void sendBlocks(Player[] target, Vector3[] blocks, int flags, boolean optimizeRebuilds) { - this.sendBlocks(target, blocks, flags, 0, optimizeRebuilds); + this.sendBlocks(target, blocks, flags, optimizeRebuilds, Block.LAYER_NORMAL); + this.sendBlocks(target, blocks, flags, optimizeRebuilds, Block.LAYER_WATERLOGGED); } - public void sendBlocks(Player[] target, Vector3[] blocks, int flags, int dataLayer) { - this.sendBlocks(target, blocks, flags, dataLayer, false); + public void sendBlocks(Player[] target, Vector3[] blocks, int flags, BlockLayer blockLayer) { + this.sendBlocks(target, blocks, flags, false, blockLayer); } - public void sendBlocks(Player[] target, Vector3[] blocks, int flags, int dataLayer, boolean optimizeRebuilds) { + public void sendBlocks(Player[] target, Vector3[] blocks, int flags, boolean optimizeRebuilds, BlockLayer layer) { LongSet chunks = null; if (optimizeRebuilds) { chunks = new LongOpenHashSet(); } + for (Vector3 b : blocks) { if (b == null) { continue; @@ -1049,27 +1130,51 @@ public void sendBlocks(Player[] target, Vector3[] blocks, int flags, int dataLay first = true; } } - UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.x = (int) b.x; updateBlockPacket.y = (int) b.y; updateBlockPacket.z = (int) b.z; updateBlockPacket.flags = first ? flags : UpdateBlockPacket.FLAG_NONE; - updateBlockPacket.dataLayer = dataLayer; - int fullId; - if (b instanceof Block) { - fullId = ((Block) b).getFullId(); - } else { - fullId = getFullBlock((int) b.x, (int) b.y, (int) b.z); + updateBlockPacket.dataLayer = layer.ordinal(); + + Block block = (b instanceof Block && ((Block) b).getLayer() == layer) ? (Block) b : this.getBlockAsyncIfLoaded(null, (int) b.x, (int) b.y, (int) b.z, layer); + + UpdateBlockPacket packet = (UpdateBlockPacket) updateBlockPacket.clone(); + + try { + int fullBlock = block.getFullId(); + packet.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(fullBlock); + } catch (NoSuchElementException e) { + throw new IllegalStateException("Unable to create BlockUpdatePacket at (" + b.x + ", " + b.y + ", " + b.z + ") in " + getName()); + } + + for (Player player : target) { + player.dataPacket(packet); + } + } + } + + public void sendBlocks(Player target, Vector3[] blocks, int flags) { + for (Vector3 b : blocks) { + if (b == null) { + continue; } + + UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); + updateBlockPacket.x = (int) b.x; + updateBlockPacket.y = (int) b.y; + updateBlockPacket.z = (int) b.z; + updateBlockPacket.flags = flags; + + Block block = b instanceof Block ? (Block) b : this.getBlockAsyncIfLoaded(target.chunk, (int) b.x, (int) b.y, (int) b.z, BlockLayer.NORMAL); + try { - updateBlockPacket.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(fullId); + updateBlockPacket.blockRuntimeId = GlobalBlockPalette.getOrCreateRuntimeId(block.getFullId()); } catch (NoSuchElementException e) { - throw new IllegalStateException("Unable to create BlockUpdatePacket at (" + - b.x + ", " + b.y + ", " + b.z + ") in " + getName(), e); + throw new IllegalStateException("Unable to create BlockUpdatePacket at (" + b.x + ", " + b.y + ", " + b.z + ") in " + getName() + " for player " + target.getName()); } - Server.broadcastPacket(target, updateBlockPacket); + target.dataPacket(updateBlockPacket); } } @@ -1083,22 +1188,19 @@ private void tickChunks() { int randRange = 3 + chunksPerLoader / 30; randRange = Math.min(randRange, this.chunkTickRadius); - ThreadLocalRandom random = ThreadLocalRandom.current(); - if (!this.loaders.isEmpty()) { - for (ChunkLoader loader : this.loaders.values()) { - int chunkX = (int) loader.getX() >> 4; - int chunkZ = (int) loader.getZ() >> 4; - - long index = Level.chunkHash(chunkX, chunkZ); - int existingLoaders = Math.max(0, this.chunkTickList.getOrDefault(index, 0)); - this.chunkTickList.put(index, existingLoaders + 1); - for (int chunk = 0; chunk < chunksPerLoader; ++chunk) { - int dx = random.nextInt(2 * randRange) - randRange; - int dz = random.nextInt(2 * randRange) - randRange; - long hash = Level.chunkHash(dx + chunkX, dz + chunkZ); - if (!this.chunkTickList.containsKey(hash) && provider.isChunkLoaded(hash)) { - this.chunkTickList.put(hash, -1); - } + for (ChunkLoader loader : this.loaders.values()) { + int chunkX = NukkitMath.floorDouble(loader.getX()) >> 4; + int chunkZ = NukkitMath.floorDouble(loader.getZ()) >> 4; + + long index = Level.chunkHash(chunkX, chunkZ); + int existingLoaders = Math.max(0, this.chunkTickList.getOrDefault(index, 0)); + this.chunkTickList.put(index, existingLoaders + 1); + for (int chunk = 0; chunk < chunksPerLoader; ++chunk) { + int dx = Utils.random.nextInt(randRange << 1) - randRange; + int dz = Utils.random.nextInt(randRange << 1) - randRange; + long hash = Level.chunkHash(dx + chunkX, dz + chunkZ); + if (!this.chunkTickList.containsKey(hash) && provider.isChunkLoaded(hash)) { + this.chunkTickList.put(hash, -1); } } } @@ -1121,7 +1223,7 @@ private void tickChunks() { int chunkZ = getHashZ(index); FullChunk chunk; - if ((chunk = this.getChunk(chunkX, chunkZ, false)) == null) { + if ((chunk = this.getChunkIfLoaded(chunkX, chunkZ)) == null) { iter.remove(); continue; } else if (loaders <= 0) { @@ -1129,46 +1231,45 @@ private void tickChunks() { } for (Entity entity : chunk.getEntities().values()) { - entity.scheduleUpdate(); + if (entity.updateMode < 1 || (entity.updateMode % 3 == 2 && server.getTick() - entity.lastUpdate > 300)) { // Force an update every 15 seconds to check age for despawning + entity.scheduleUpdate(); + } } - int tickSpeed = gameRules.getInteger(GameRule.RANDOM_TICK_SPEED); - - if (tickSpeed > 0) { - if (this.useSections) { - for (ChunkSection section : ((Chunk) chunk).getSections()) { - if (!(section instanceof EmptyChunkSection)) { - int Y = section.getY(); - for (int i = 0; i < tickSpeed; ++i) { - int lcg = this.getUpdateLCG(); - int x = lcg & 0x0f; - int y = lcg >>> 8 & 0x0f; - int z = lcg >>> 16 & 0x0f; - - int fullId = section.getFullBlock(x, y, z); - int blockId = fullId >> 4; - if (randomTickBlocks[blockId]) { - Block block = Block.get(fullId, this, chunkX * 16 + x, (Y << 4) + y, chunkZ * 16 + z); - block.onUpdate(BLOCK_UPDATE_RANDOM); - } + + if (this.useSections) { + for (ChunkSection section : ((Chunk) chunk).getSections()) { + if (!(section instanceof EmptyChunkSection)) { + int Y = section.getY(); + for (int i = 0; i < gameRules.getInteger(GameRule.RANDOM_TICK_SPEED); ++i) { + int n = ThreadLocalRandom.current().nextInt(); + int x = n & 0xF; + int z = n >> 8 & 0xF; + int y = n >> 16 & 0xF; + + int fullId = section.getFullBlock(x, y, z); + int blockId = fullId >> Block.DATA_BITS; + if (blockId < randomTickBlocks.length && randomTickBlocks[blockId]) { + Block block = Block.get(fullId, this, (chunkX << 4) + x, (Y << 4) + y, (chunkZ << 4) + z); + block.onUpdate(BLOCK_UPDATE_RANDOM); } } } - } else { - for (int Y = 0; Y < 8 && (Y < 3 || blockTest != 0); ++Y) { - blockTest = 0; - for (int i = 0; i < tickSpeed; ++i) { - int lcg = this.getUpdateLCG(); - int x = lcg & 0x0f; - int y = lcg >>> 8 & 0x0f; - int z = lcg >>> 16 & 0x0f; - - int fullId = chunk.getFullBlock(x, y + (Y << 4), z); - int blockId = fullId >> 4; - blockTest |= fullId; - if (Level.randomTickBlocks[blockId]) { - Block block = Block.get(fullId, this, x, y + (Y << 4), z); - block.onUpdate(BLOCK_UPDATE_RANDOM); - } + } + } else { + for (int Y = 0; Y < 8 && (Y < 3 || blockTest != 0); ++Y) { + blockTest = 0; + for (int i = 0; i < gameRules.getInteger(GameRule.RANDOM_TICK_SPEED); ++i) { + int n = ThreadLocalRandom.current().nextInt(); + int x = n & 0xF; + int z = n >> 8 & 0xF; + int y = n >> 16 & 0xF; + + int fullId = chunk.getFullBlock(x, y + (Y << 4), z); + int blockId = fullId >> Block.DATA_BITS; + blockTest |= fullId; + if (blockId < randomTickBlocks.length && randomTickBlocks[blockId]) { + Block block = Block.get(fullId, this, x, y + (Y << 4), z); + block.onUpdate(BLOCK_UPDATE_RANDOM); } } } @@ -1186,38 +1287,44 @@ public boolean save() { } public boolean save(boolean force) { - if (!this.getAutoSave() && !force) { + if ((!this.autoSave || server.holdWorldSave) && !force) { return false; } this.server.getPluginManager().callEvent(new LevelSaveEvent(this)); - this.provider.setTime((int) this.time); - this.provider.setRaining(this.raining); - this.provider.setRainTime(this.rainTime); - this.provider.setThundering(this.thundering); - this.provider.setThunderTime(this.thunderTime); - this.provider.setCurrentTick(this.levelCurrentTick); - this.provider.setGameRules(this.gameRules); + this.saveLevelData(); this.saveChunks(); - if (this.provider instanceof BaseLevelProvider) { + return true; + } + + private void saveLevelData() { + try { + this.provider.setTime(this.time); + this.provider.setRaining(this.raining); + this.provider.setRainTime(this.rainTime); + this.provider.setThundering(this.thundering); + this.provider.setThunderTime(this.thunderTime); + this.provider.setCurrentTick(this.levelCurrentTick); + this.provider.setGameRules(this.gameRules); this.provider.saveLevelData(); + } catch (Exception ex) { + Server.getInstance().getLogger().error("Failed to save level data for " + this.getFolderName(), ex); } - - return true; } public void saveChunks() { provider.saveChunks(); } - public void updateAroundRedstone(Vector3 pos, BlockFace face) { + public void updateAroundRedstone(Vector3 pos, BlockFace ignoredFace) { for (BlockFace side : BlockFace.values()) { - if (face != null && side == face) { + if (ignoredFace != null && side == ignoredFace) { continue; } - this.getBlock(pos.getSideVec(side)).onUpdate(BLOCK_UPDATE_REDSTONE); + Vector3 sideVec = pos.getSideVec(side); + this.getBlock(sideVec).onUpdate(BLOCK_UPDATE_REDSTONE); } } @@ -1243,43 +1350,50 @@ public void updateComparatorOutputLevel(Vector3 v) { } public void updateAround(Vector3 pos) { - updateAround((int) pos.x, (int) pos.y, (int) pos.z); + this.updateAround((int) pos.x, (int) pos.y, (int) pos.z, Block.LAYER_NORMAL); + this.updateAround((int) pos.x, (int) pos.y, (int) pos.z, Block.LAYER_WATERLOGGED); } public void updateAround(int x, int y, int z) { + this.updateAround(x, y, z, Block.LAYER_NORMAL); + this.updateAround(x, y, z, Block.LAYER_WATERLOGGED); + } + + public void updateAround(int x, int y, int z, BlockLayer layer) { + Vector3 updatePos = new Vector3(x, y, z); BlockUpdateEvent ev; this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y - 1, z))); + ev = new BlockUpdateEvent(this.getBlock(null, x, y - 1, z, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y + 1, z))); + ev = new BlockUpdateEvent(this.getBlock(null, x, y + 1, z, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x - 1, y, z))); + ev = new BlockUpdateEvent(this.getBlock(null, x - 1, y, z, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x + 1, y, z))); + ev = new BlockUpdateEvent(this.getBlock(null, x + 1, y, z, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y, z - 1))); + ev = new BlockUpdateEvent(this.getBlock(null, x, y, z - 1, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } this.server.getPluginManager().callEvent( - ev = new BlockUpdateEvent(this.getBlock(x, y, z + 1))); + ev = new BlockUpdateEvent(this.getBlock(null, x, y, z + 1, layer, true).setUpdatePos(updatePos))); if (!ev.isCancelled()) { normalUpdateQueue.add(ev.getBlock()); } @@ -1298,11 +1412,11 @@ public void scheduleUpdate(Block block, Vector3 pos, int delay, int priority) { } public void scheduleUpdate(Block block, Vector3 pos, int delay, int priority, boolean checkArea) { - if (block.getId() == 0 || (checkArea && !this.isChunkLoaded(block.getFloorX() >> 4, block.getFloorZ() >> 4))) { + if (block.getId() == 0 || (checkArea && !this.isChunkLoaded(block.getChunkX(), block.getChunkZ()))) { return; } - BlockUpdateEntry entry = new BlockUpdateEntry(pos.floor(), block, ((long) delay) + getCurrentTick(), priority); + BlockUpdateEntry entry = new BlockUpdateEntry(pos.floor(), block, ((long) delay) + levelCurrentTick, priority); if (!this.updateQueue.contains(entry)) { this.updateQueue.add(entry); @@ -1323,11 +1437,11 @@ public boolean isBlockTickPending(Vector3 pos, Block block) { public Set getPendingBlockUpdates(FullChunk chunk) { int minX = (chunk.getX() << 4) - 2; - int maxX = minX + 16 + 2; + int maxX = minX + 18; int minZ = (chunk.getZ() << 4) - 2; - int maxZ = minZ + 16 + 2; + int maxZ = minZ + 18; - return this.getPendingBlockUpdates(new SimpleAxisAlignedBB(minX, 0, minZ, maxX, 256, maxZ)); + return this.getPendingBlockUpdates(new SimpleAxisAlignedBB(minX, this.getMinBlockY(), minZ, maxX, this.getMaxBlockY(), maxZ)); } public Set getPendingBlockUpdates(AxisAlignedBB boundingBox) { @@ -1339,6 +1453,14 @@ public Block[] getCollisionBlocks(AxisAlignedBB bb) { } public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { + return getCollisionBlocks(null, bb, targetFirst); + } + + public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targetFirst) { + return getCollisionBlocks(entity, bb, targetFirst, block -> block.getId() != 0); + } + + public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targetFirst, Predicate condition) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); @@ -1352,8 +1474,8 @@ public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); - if (block != null && block.getId() != 0 && block.collidesWithBB(bb)) { + Block block = this.getBlock(entity == null ? null : entity.chunk, x, y, z, false); + if (block != null && condition.test(block) && block.collidesWithBB(bb)) { return new Block[]{block}; } } @@ -1363,8 +1485,8 @@ public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); - if (block != null && block.getId() != 0 && block.collidesWithBB(bb)) { + Block block = this.getBlock(entity == null ? null : entity.chunk, x, y, z, false); + if (block != null && condition.test(block) && block.collidesWithBB(bb)) { collides.add(block); } } @@ -1375,6 +1497,28 @@ public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { return collides.toArray(new Block[0]); } + public boolean hasCollisionBlocks(Entity entity, AxisAlignedBB bb) { + int minX = NukkitMath.floorDouble(bb.getMinX()); + int minY = NukkitMath.floorDouble(bb.getMinY()); + int minZ = NukkitMath.floorDouble(bb.getMinZ()); + int maxX = NukkitMath.ceilDouble(bb.getMaxX()); + int maxY = NukkitMath.ceilDouble(bb.getMaxY()); + int maxZ = NukkitMath.ceilDouble(bb.getMaxZ()); + + for (int z = minZ; z <= maxZ; ++z) { + for (int x = minX; x <= maxX; ++x) { + for (int y = minY; y <= maxY; ++y) { + Block block = this.getBlock(entity.chunk, x, y, z, false); + if (block != null && block.getId() != 0 && block.collidesWithBB(bb)) { + return true; + } + } + } + } + + return false; + } + public boolean isFullBlock(Vector3 pos) { AxisAlignedBB bb; if (pos instanceof Block) { @@ -1398,6 +1542,8 @@ public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb, boolea } public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb, boolean entities, boolean solidEntities) { + if (entity.noClip) return new AxisAlignedBB[0]; + int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); @@ -1410,7 +1556,7 @@ public AxisAlignedBB[] getCollisionCubes(Entity entity, AxisAlignedBB bb, boolea for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.getBlock(this.temporalVector.setComponents(x, y, z), false); + Block block = this.getBlock(entity.chunk, x, y, z, false); if (!block.canPassThrough() && block.collidesWithBB(bb)) { collides.add(block.getBoundingBox()); } @@ -1440,7 +1586,7 @@ public boolean hasCollision(Entity entity, AxisAlignedBB bb, boolean entities) { for (int z = minZ; z <= maxZ; ++z) { for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { - Block block = this.getBlock(this.temporalVector.setComponents(x, y, z)); + Block block = this.getBlock(entity.chunk, x, y, z, false); if (!block.canPassThrough() && block.collidesWithBB(bb)) { return true; } @@ -1455,15 +1601,18 @@ public boolean hasCollision(Entity entity, AxisAlignedBB bb, boolean entities) { } public int getFullLight(Vector3 pos) { - FullChunk chunk = this.getChunk((int) pos.x >> 4, (int) pos.z >> 4, false); + if (pos.y < this.getMinBlockY() || pos.y > this.getMaxBlockY()) { + return 0; + } + + FullChunk chunk = this.getChunk(pos.getChunkX(), pos.getChunkZ(), false); int level = 0; if (chunk != null) { - level = chunk.getBlockSkyLight((int) pos.x & 0x0f, (int) pos.y & 0xff, (int) pos.z & 0x0f); + level = chunk.getBlockSkyLight((int) pos.x & 0x0f, (int) pos.y, (int) pos.z & 0x0f); level -= this.skyLightSubtracted; if (level < 15) { - level = Math.max(chunk.getBlockLight((int) pos.x & 0x0f, (int) pos.y & 0xff, (int) pos.z & 0x0f), - level); + level = Math.max(chunk.getBlockLight((int) pos.x & 0x0f, (int) pos.y, (int) pos.z & 0x0f), level); } } @@ -1471,12 +1620,11 @@ public int getFullLight(Vector3 pos) { } public int calculateSkylightSubtracted(float tickDiff) { - float angle = this.calculateCelestialAngle(getTime(), tickDiff); - float light = 1 - (MathHelper.cos(angle * ((float) Math.PI * 2F)) * 2 + 0.5f); + float light = 1 - (MathHelper.cos(this.calculateCelestialAngle(getTime(), tickDiff) * (6.2831855f)) * 2 + 0.5f); light = light < 0 ? 0 : light > 1 ? 1 : light; light = 1 - light; - light = (float) ((double) light * ((isRaining() ? 1 : 0) - (double) 5f / 16d)); - light = (float) ((double) light * ((isThundering() ? 1 : 0) - (double) 5f / 16d)); + light = (float) ((double) light * ((raining ? 1 : 0) - 0.3125)); + light = (float) ((double) light * ((isThundering() ? 1 : 0) - 0.3125)); light = 1 - light; return (int) (light * 11f); } @@ -1502,58 +1650,128 @@ public int getMoonPhase(long worldTime) { } public int getFullBlock(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, false).getFullBlock(x & 0x0f, y & 0xff, z & 0x0f); + return this.getFullBlock(null, x, y, z); } - public synchronized Block getBlock(Vector3 pos) { - return this.getBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()); + public int getFullBlock(FullChunk chunk, int x, int y, int z) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) return 0; + int cx = x >> 4; + int cz = z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + chunk = this.getChunk(cx, cz, false); + } + if (chunk == null) { + return 0; + } + return chunk.getFullBlock(x & 0x0f, y, z & 0x0f, Block.LAYER_NORMAL); + } + + public synchronized Block getBlock(Vector3 pos) { // Original API + return this.getBlock(null, pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), Block.LAYER_NORMAL, true); + } + + public synchronized Block getBlock(Vector3 pos, boolean load) { // Original API + return this.getBlock(null, pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), Block.LAYER_NORMAL, load); + } + + public synchronized Block getBlock(Vector3 pos, BlockLayer layer, boolean load) { + return this.getBlock(null, pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer, load); + } + + public synchronized Block getBlock(int x, int y, int z) { // Original API + return getBlock(null, x, y, z, Block.LAYER_NORMAL, true); } - public synchronized Block getBlock(Vector3 pos, boolean load) { - return this.getBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), load); + public synchronized Block getBlock(int x, int y, int z, boolean load) { // Original API + return this.getBlock(null, x, y, z, Block.LAYER_NORMAL, load); } - public synchronized Block getBlock(int x, int y, int z) { - return getBlock(x, y, z, true); + public synchronized Block getBlock(FullChunk chunk, int x, int y, int z, boolean load) { + return this.getBlock(chunk, x, y, z, Block.LAYER_NORMAL, load); } - public synchronized Block getBlock(int x, int y, int z, boolean load) { + // The chunk doesn't have to be correct, give the most likely one + public synchronized Block getBlock(FullChunk chunk, int x, int y, int z, BlockLayer layer, boolean load) { int fullState; - if (y >= 0 && y < 256) { + if (y >= this.getMinBlockY() && y <= this.getMaxBlockY()) { int cx = x >> 4; int cz = z >> 4; - BaseFullChunk chunk; - if (load) { - chunk = getChunk(cx, cz); + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + if (load) { + chunk = getChunk(cx, cz); + } else { + chunk = getChunkIfLoaded(cx, cz); + } + } + if (chunk == null) { + fullState = 0; } else { + fullState = chunk.getFullBlock(x & 0xF, y, z & 0xF, layer); + } + } else { + fullState = 0; + } + + Block block; + int blockId = fullState >> Block.DATA_BITS; + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + block = CustomBlockManager.get().getBlock(fullState); + } else { + block = Block.fullList[fullState].clone(); + } + + block.x = x; + block.y = y; + block.z = z; + block.level = this; + block.setLayer(layer); + return block; + } + + // Use only if getting correct block is not critical + private Block getBlockAsyncIfLoaded(FullChunk chunk, int x, int y, int z, BlockLayer layer) { + int fullState; + if (y >= this.getMinBlockY() && y <= this.getMaxBlockY()) { + int cx = x >> 4; + int cz = z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { chunk = getChunkIfLoaded(cx, cz); } - if (chunk != null) { - fullState = chunk.getFullBlock(x & 0xF, y, z & 0xF); - } else { + if (chunk == null) { fullState = 0; + } else { + fullState = chunk.getFullBlock(x & 0xF, y, z & 0xF, layer); } } else { fullState = 0; } - Block block = Block.fullList[fullState & 0xFFF].clone(); + + Block block; + int blockId = fullState >> Block.DATA_BITS; + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + block = CustomBlockManager.get().getBlock(fullState); + } else { + block = Block.fullList[fullState].clone(); + } + block.x = x; block.y = y; block.z = z; block.level = this; + block.setLayer(layer); return block; } + public void updateAllLight(Vector3 pos) { this.updateBlockSkyLight((int) pos.x, (int) pos.y, (int) pos.z); this.addLightUpdate((int) pos.x, (int) pos.y, (int) pos.z); } public void updateBlockSkyLight(int x, int y, int z) { - // todo } - public void updateBlockLight(Map> map) { + public void updateBlockLight(Map> map) { int size = map.size(); if (size == 0) { return; @@ -1563,36 +1781,32 @@ public void updateBlockLight(Map> map) { Long2ObjectOpenHashMap visited = new Long2ObjectOpenHashMap<>(); Long2ObjectOpenHashMap removalVisited = new Long2ObjectOpenHashMap<>(); - Iterator>> iter = map.entrySet().iterator(); + Iterator>> iter = map.entrySet().iterator(); while (iter.hasNext() && size-- > 0) { - Map.Entry> entry = iter.next(); + Map.Entry> entry = iter.next(); iter.remove(); long index = entry.getKey(); - Map blocks = entry.getValue(); - int chunkX = Level.getHashX(index); - int chunkZ = Level.getHashZ(index); - int bx = chunkX << 4; - int bz = chunkZ << 4; - for (char blockHash : blocks.keySet()) { - int hi = (byte) (blockHash >>> 8); - int lo = (byte) blockHash; - int y = lo & 0xFF; - int x = (hi & 0xF) + bx; - int z = ((hi >> 4) & 0xF) + bz; - BaseFullChunk chunk = getChunk(x >> 4, z >> 4, false); + Map blocks = entry.getValue(); + + for (int blockHash : blocks.keySet()) { + Vector3 pos = getBlockXYZ(index, blockHash, this.getDimensionData()); + BaseFullChunk chunk = getChunk(((int) pos.x) >> 4, ((int) pos.z) >> 4, false); + if (chunk != null) { - int lcx = x & 0xF; - int lcz = z & 0xF; - int oldLevel = chunk.getBlockLight(lcx, y, lcz); - int newLevel = Block.light[chunk.getBlockId(lcx, y, lcz)]; + int lcx = ((int) pos.x) & 0xF; + int lcz = ((int) pos.z) & 0xF; + int oldLevel = chunk.getBlockLight(lcx, ((int) pos.y), lcz); + int newLevel = Block.getBlockLight(chunk.getBlockId(lcx, ((int) pos.y), lcz)); if (oldLevel != newLevel) { - this.setBlockLightAt(x, y, z, newLevel); + this.setBlockLightAt(((int) pos.x), ((int) pos.y), ((int) pos.z), newLevel); + + long hash = Hash.hashBlock(((int) pos.x), ((int) pos.y), ((int) pos.z)); if (newLevel < oldLevel) { - removalVisited.put(Hash.hashBlock(x, y, z), changeBlocksPresent); - lightRemovalQueue.add(new Object[]{Hash.hashBlock(x, y, z), oldLevel}); + removalVisited.put(hash, changeBlocksPresent); + lightRemovalQueue.add(new Object[]{hash, oldLevel}); } else { - visited.put(Hash.hashBlock(x, y, z), changeBlocksPresent); - lightPropagationQueue.add(Hash.hashBlock(x, y, z)); + visited.put(hash, changeBlocksPresent); + lightPropagationQueue.add(hash); } } } @@ -1608,18 +1822,12 @@ public void updateBlockLight(Map> map) { int lightLevel = (int) val[1]; - this.computeRemoveBlockLight(x - 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); - this.computeRemoveBlockLight(x + 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); - this.computeRemoveBlockLight(x, y - 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); - this.computeRemoveBlockLight(x, y + 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); - this.computeRemoveBlockLight(x, y, z - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); - this.computeRemoveBlockLight(x, y, z + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, - removalVisited, visited); + this.computeRemoveBlockLight(x - 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(x + 1, y, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(x, y - 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(x, y + 1, z, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(x, y, z - 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); + this.computeRemoveBlockLight(x, y, z + 1, lightLevel, lightRemovalQueue, lightPropagationQueue, removalVisited, visited); } while (!lightPropagationQueue.isEmpty()) { @@ -1630,7 +1838,7 @@ public void updateBlockLight(Map> map) { int z = Hash.hashBlockZ(node); int lightLevel = this.getBlockLightAt(x, y, z) - - Block.lightFilter[this.getBlockIdAt(x, y, z)]; + - Block.getBlockLightFilter(this.getBlockIdAt(x, y, z)); if (lightLevel >= 1) { this.computeSpreadBlockLight(x - 1, y, z, lightLevel, lightPropagationQueue, visited); @@ -1646,19 +1854,20 @@ public void updateBlockLight(Map> map) { private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queue queue, Queue spreadQueue, Map visited, Map spreadVisited) { int current = this.getBlockLightAt(x, y, z); - long index = Hash.hashBlock(x, y, z); if (current != 0 && current < currentLight) { this.setBlockLightAt(x, y, z, 0); if (current > 1) { + long index = Hash.hashBlock(x, y, z); if (!visited.containsKey(index)) { visited.put(index, changeBlocksPresent); - queue.add(new Object[]{Hash.hashBlock(x, y, z), current}); + queue.add(new Object[]{index, current}); } } } else if (current >= currentLight) { + long index = Hash.hashBlock(x, y, z); if (!spreadVisited.containsKey(index)) { spreadVisited.put(index, changeBlocksPresent); - spreadQueue.add(Hash.hashBlock(x, y, z)); + spreadQueue.add(index); } } } @@ -1666,78 +1875,111 @@ private void computeRemoveBlockLight(int x, int y, int z, int currentLight, Queu private void computeSpreadBlockLight(int x, int y, int z, int currentLight, Queue queue, Map visited) { int current = this.getBlockLightAt(x, y, z); - long index = Hash.hashBlock(x, y, z); - if (current < currentLight - 1) { this.setBlockLightAt(x, y, z, currentLight); + long index = Hash.hashBlock(x, y, z); if (!visited.containsKey(index)) { visited.put(index, changeBlocksPresent); if (currentLight > 1) { - queue.add(Hash.hashBlock(x, y, z)); + queue.add(index); } } } } - private Map> lightQueue = new ConcurrentHashMap<>(8, 0.9f, 1); + private final Map> lightQueue = new ConcurrentHashMap<>(8, 0.9f, 1); public void addLightUpdate(int x, int y, int z) { long index = chunkHash(x >> 4, z >> 4); - Map currentMap = lightQueue.get(index); + Map currentMap = lightQueue.get(index); if (currentMap == null) { currentMap = new ConcurrentHashMap<>(8, 0.9f, 1); this.lightQueue.put(index, currentMap); } - currentMap.put(Level.localBlockHash(x, y, z), changeBlocksPresent); + currentMap.put(Level.localBlockHash(x, y, z, this.getDimensionData()), changeBlocksPresent); } @Override public synchronized void setBlockFullIdAt(int x, int y, int z, int fullId) { - setBlock(x, y, z, Block.fullList[fullId], false, false); + this.setBlockFullIdAt(x, y, z, Block.LAYER_NORMAL, fullId); + } + + @Override + public synchronized void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, int fullId) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + + Block block; + int blockId = fullId >> Block.DATA_BITS; + if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { + block = CustomBlockManager.get().getBlock(fullId); + } else { + block = Block.fullList[fullId]; + } + + this.setBlock(x, y, z, layer, block, false, false); + } + + public synchronized boolean setBlock(Vector3 pos, Block block) { // Original API + return this.setBlock(pos, Block.LAYER_NORMAL, block, false, true); + } + + public synchronized boolean setBlock(Vector3 pos, Block block, boolean direct) { // Original API + return this.setBlock(pos, Block.LAYER_NORMAL, block, direct, true); + } + + public synchronized boolean setBlock(Vector3 pos, Block block, boolean direct, boolean update) { // Original API + return this.setBlock(pos, Block.LAYER_NORMAL, block, direct, update); } - public synchronized boolean setBlock(Vector3 pos, Block block) { - return this.setBlock(pos, block, false); + public synchronized boolean setBlock(Vector3 pos, BlockLayer layer, Block block, boolean direct, boolean update) { + return this.setBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), layer, block, direct, update); } - public synchronized boolean setBlock(Vector3 pos, Block block, boolean direct) { - return this.setBlock(pos, block, direct, true); + public synchronized boolean setBlock(int x, int y, int z, Block block, boolean direct, boolean update) { // Original API + return this.setBlock(x, y, z, Block.LAYER_NORMAL, block, direct, update); } - public synchronized boolean setBlock(Vector3 pos, Block block, boolean direct, boolean update) { - return setBlock(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), block, direct, update); + public synchronized boolean setBlock(int x, int y, int z, BlockLayer layer, Block block, boolean direct, boolean update) { + return this.setBlock(x, y, z, layer, block, direct, update, true); } - public synchronized boolean setBlock(int x, int y, int z, Block block, boolean direct, boolean update) { - if (y < 0 || y >= 256) { + public synchronized boolean setBlock(int x, int y, int z, BlockLayer layer, Block block, boolean direct, boolean update, boolean send) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { return false; } - BaseFullChunk chunk = this.getChunk(x >> 4, z >> 4, true); + + int cx = x >> 4; + int cz = z >> 4; + BaseFullChunk chunk = this.getChunk(cx, cz, true); Block blockPrevious; -// synchronized (chunk) { - blockPrevious = chunk.getAndSetBlock(x & 0xF, y, z & 0xF, block); + blockPrevious = chunk.getAndSetBlock(x & 0xF, y, z & 0xF, layer, block); if (blockPrevious.getFullId() == block.getFullId()) { return false; } -// } + block.x = x; block.y = y; block.z = z; block.level = this; - int cx = x >> 4; - int cz = z >> 4; - long index = Level.chunkHash(cx, cz); - if (direct) { - this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(new Player[0]), new Block[]{block}, UpdateBlockPacket.FLAG_ALL_PRIORITY); - this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(new Player[0]), new Block[]{Block.get(Block.AIR, 0, block)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); - } else { - addBlockChange(index, x, y, z); + block.setLayer(layer); + + if (send) { + if (direct) { + this.sendBlocks(this.getChunkPlayers(cx, cz).values().toArray(new Player[0]), new Block[]{block}, UpdateBlockPacket.FLAG_ALL_PRIORITY, layer); + } else { + addBlockChange(Level.chunkHash(cx, cz), x, y, z); + } } - for (ChunkLoader loader : this.getChunkLoaders(cx, cz)) { - loader.onBlockChanged(block); + if (this.useChunkLoaderApi) { + for (ChunkLoader loader : this.getChunkLoaders(cx, cz)) { + loader.onBlockChanged(block); + } } + if (update) { if (blockPrevious.isTransparent() != block.isTransparent() || blockPrevious.getLightLevel() != block.getLightLevel()) { addLightUpdate(x, y, z); @@ -1745,11 +1987,14 @@ public synchronized boolean setBlock(int x, int y, int z, Block block, boolean d BlockUpdateEvent ev = new BlockUpdateEvent(block); this.server.getPluginManager().callEvent(ev); if (!ev.isCancelled()) { - for (Entity entity : this.getNearbyEntities(new SimpleAxisAlignedBB(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1))) { + for (Entity entity : this.getNearbyEntities(new SimpleAxisAlignedBB(x - 1, y - 1, z - 1, x + 1.1, y + 1.1, z + 1.1))) { + if (entity.updateMode % 2 == 1) { + entity.updateMode = 1; + } entity.scheduleUpdate(); } block = ev.getBlock(); - block.onUpdate(BLOCK_UPDATE_NORMAL); + if (!(block instanceof BlockObserver)) block.onUpdate(BLOCK_UPDATE_NORMAL); this.updateAround(x, y, z); } } @@ -1763,54 +2008,35 @@ private void addBlockChange(int x, int y, int z) { private void addBlockChange(long index, int x, int y, int z) { synchronized (changedBlocks) { - SoftReference> current = changedBlocks.computeIfAbsent(index, k -> new SoftReference<>(new HashMap<>())); - Map currentMap = current.get(); + SoftReference> current = changedBlocks.computeIfAbsent(index, k -> new SoftReference<>(new HashMap<>())); + Map currentMap = current.get(); if (currentMap != changeBlocksFullMap && currentMap != null) { if (currentMap.size() > MAX_BLOCK_CACHE) { this.changedBlocks.put(index, new SoftReference<>(changeBlocksFullMap)); } else { - currentMap.put(Level.localBlockHash(x, y, z), changeBlocksPresent); + currentMap.put(Level.localBlockHash(x, y, z, this.getDimensionData()), changeBlocksPresent); } } } } public void dropItem(Vector3 source, Item item) { - this.dropItem(source, item, null); + this.dropAndGetItem(source, item); } public void dropItem(Vector3 source, Item item, Vector3 motion) { - this.dropItem(source, item, motion, 10); + this.dropAndGetItem(source, item, motion); } public void dropItem(Vector3 source, Item item, Vector3 motion, int delay) { - this.dropItem(source, item, motion, false, delay); + this.dropAndGetItem(source, item, motion, delay); } public void dropItem(Vector3 source, Item item, Vector3 motion, boolean dropAround, int delay) { - if (motion == null) { - if (dropAround) { - float f = ThreadLocalRandom.current().nextFloat() * 0.5f; - float f1 = ThreadLocalRandom.current().nextFloat() * ((float) Math.PI * 2); - - motion = new Vector3(-MathHelper.sin(f1) * f, 0.20000000298023224, MathHelper.cos(f1) * f); - } else { - motion = new Vector3(new java.util.Random().nextDouble() * 0.2 - 0.1, 0.2, - new java.util.Random().nextDouble() * 0.2 - 0.1); - } - } - - if (item.getId() > 0 && item.getCount() > 0) { - EntityItem itemEntity = (EntityItem) Entity.createEntity("Item", - this.getChunk((int) source.getX() >> 4, (int) source.getZ() >> 4, true), - Entity.getDefaultNBT(source, motion, new Random().nextFloat() * 360, 0).putShort("Health", 5).putCompound("Item", NBTIO.putItemHelper(item)).putShort("PickupDelay", delay)); - - if (itemEntity != null) { - itemEntity.spawnToAll(); - } - } + this.dropAndGetItem(source, item, motion, dropAround, delay); } + @SuppressWarnings("UnusedReturnValue") public EntityItem dropAndGetItem(Vector3 source, Item item) { return this.dropAndGetItem(source, item, null); } @@ -1824,44 +2050,40 @@ public EntityItem dropAndGetItem(Vector3 source, Item item, Vector3 motion, int } public EntityItem dropAndGetItem(Vector3 source, Item item, Vector3 motion, boolean dropAround, int delay) { - EntityItem itemEntity = null; - - if (motion == null) { - if (dropAround) { - float f = ThreadLocalRandom.current().nextFloat() * 0.5f; - float f1 = ThreadLocalRandom.current().nextFloat() * ((float) Math.PI * 2); + if (item.getId() != 0 && item.getCount() > 0) { + if (motion == null) { + if (dropAround) { + float f = ThreadLocalRandom.current().nextFloat() * 0.5f; + float f1 = ThreadLocalRandom.current().nextFloat() * 6.2831855f; - motion = new Vector3(-MathHelper.sin(f1) * f, 0.20000000298023224, MathHelper.cos(f1) * f); - } else { - motion = new Vector3(new java.util.Random().nextDouble() * 0.2 - 0.1, 0.2, - new java.util.Random().nextDouble() * 0.2 - 0.1); + motion = new Vector3(-MathHelper.sin(f1) * f, 0.20000000298023224, MathHelper.cos(f1) * f); + } else { + motion = new Vector3(Utils.random.nextDouble() * 0.2 - 0.1, 0.2, Utils.random.nextDouble() * 0.2 - 0.1); + } } - } - CompoundTag itemTag = NBTIO.putItemHelper(item); - itemTag.setName("Item"); + CompoundTag itemTag = NBTIO.putItemHelper(item); + itemTag.setName("Item"); - if (item.getId() > 0 && item.getCount() > 0) { - itemEntity = (EntityItem) Entity.createEntity("Item", - this.getChunk((int) source.getX() >> 4, (int) source.getZ() >> 4, true), + EntityItem itemEntity = new EntityItem( + this.getChunk(source.getChunkX(), source.getChunkZ(), true), new CompoundTag().putList(new ListTag("Pos").add(new DoubleTag("", source.getX())) - .add(new DoubleTag("", source.getY())).add(new DoubleTag("", source.getZ()))) + .add(new DoubleTag("", source.getY())).add(new DoubleTag("", source.getZ()))) .putList(new ListTag("Motion").add(new DoubleTag("", motion.x)) .add(new DoubleTag("", motion.y)).add(new DoubleTag("", motion.z))) .putList(new ListTag("Rotation") - .add(new FloatTag("", new Random().nextFloat() * 360)) + .add(new FloatTag("", ThreadLocalRandom.current().nextFloat() * 360)) .add(new FloatTag("", 0))) .putShort("Health", 5).putCompound("Item", itemTag).putShort("PickupDelay", delay)); - if (itemEntity != null) { - itemEntity.spawnToAll(); - } + itemEntity.spawnToAll(); + return itemEntity; } - return itemEntity; + return null; } public Item useBreakOn(Vector3 vector) { @@ -1881,7 +2103,7 @@ public Item useBreakOn(Vector3 vector, Item item, Player player, boolean createP } public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, boolean createParticles) { - if (player != null && player.getGamemode() > 2) { + if (player != null && player.getGamemode() > Player.ADVENTURE) { return null; } Block target = this.getBlock(vector); @@ -1892,17 +2114,18 @@ public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } - boolean isSilkTouch = item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null; + Vector3 dropPosition = vector; + boolean isSilkTouch = item.isTool() && item.hasEnchantment(Enchantment.ID_SILK_TOUCH); if (player != null) { - if (player.getGamemode() == 2) { + if (player.getGamemode() == Player.ADVENTURE) { Tag tag = item.getNamedTagEntry("CanDestroy"); boolean canBreak = false; if (tag instanceof ListTag) { for (Tag v : ((ListTag) tag).getAll()) { if (v instanceof StringTag) { Item entry = Item.fromString(((StringTag) v).data); - if (entry.getId() > 0 && entry.getBlockUnsafe() != null && entry.getBlockUnsafe().getId() == target.getId()) { + if (entry.getId() != 0 && entry.getBlockUnsafe() != null && entry.getBlockUnsafe().getId() == target.getId()) { canBreak = true; break; } @@ -1914,11 +2137,17 @@ public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, } } - double breakTime = target.getBreakTime(item, player); - // this in - // block - // class - + Item[] eventDrops; + if (!player.isSurvival()) { + eventDrops = new Item[0]; + } else if (isSilkTouch && target.canSilkTouch()) { + eventDrops = new Item[]{target.toItem()}; + } else { + eventDrops = target.getDrops(item); + } + + double breakTime = target.getBreakTime(item, player); + if (player.isCreative() && breakTime > 0.15) { breakTime = 0.15; } @@ -1939,86 +2168,111 @@ public Item useBreakOn(Vector3 vector, BlockFace face, Item item, Player player, breakTime -= 0.15; - Item[] eventDrops; - if (!player.isSurvival()) { - eventDrops = new Item[0]; - } else if (isSilkTouch && target.canSilkTouch()) { - eventDrops = new Item[]{target.toItem()}; - } else { - eventDrops = target.getDrops(item); - } + long now = System.currentTimeMillis(); BlockBreakEvent ev = new BlockBreakEvent(player, target, face, item, eventDrops, player.isCreative(), - (player.lastBreak + breakTime * 1000) > System.currentTimeMillis()); - - if (player.isSurvival() && !target.isBreakable(item)) { - ev.setCancelled(); - } else if(!player.isOp() && isInSpawnRadius(target)) { - ev.setCancelled(); + (player.lastBreak + breakTime * 1000) > now, vector); + + if (!player.isCreative() && (!player.isBreakingBlock() || !target.equals(player.breakingBlock))) { + ev.setCancelled(true); + } else if ((player.isSurvival() || player.isAdventure()) && !target.isBreakable(item)) { + ev.setCancelled(true); + } else if (!player.isOp() && isInSpawnRadius(target)) { + ev.setCancelled(true); } else if (!ev.getInstaBreak() && ev.isFastBreak()) { - ev.setCancelled(); + ev.setCancelled(true); } + player.lastBreak = now; + this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return null; } - player.lastBreak = System.currentTimeMillis(); + if (target instanceof BlockBed) { + // Drops only from the top part, prevent a dupe + if ((target.getDamage() & 0x08) == 0x08) { + drops = ev.getDrops(); + } else { + drops = new Item[0]; + } + // Make sure no item is dropped if drops are cleared in BlockBreakEvent + ((BlockBed) target).canDropItem = ev.getDrops().length != 0; + } else { + drops = ev.getDrops(); + } - drops = ev.getDrops(); dropExp = ev.getDropExp(); + + if (ev.getDropPosition() != null) { + dropPosition = ev.getDropPosition(); + } } else if (!target.isBreakable(item)) { return null; - } else if (item.getEnchantment(Enchantment.ID_SILK_TOUCH) != null) { + } else if (item.hasEnchantment(Enchantment.ID_SILK_TOUCH)) { drops = new Item[]{target.toItem()}; } else { drops = target.getDrops(item); } - Block above = this.getBlock(new Vector3(target.x, target.y + 1, target.z)); - if (above != null) { - if (above.getId() == Item.FIRE) { - this.setBlock(above, Block.get(BlockID.AIR), true); - } + Vector3 above = new Vector3(target.x, target.y + 1, target.z); + int bid = this.getBlockIdAt((int) above.x, (int) above.y, (int) above.z); + if (bid == Item.FIRE || bid == Item.SOUL_FIRE) { + this.setBlock(above, Block.get(BlockID.AIR), true); } if (createParticles) { - Map players = this.getChunkPlayers((int) target.x >> 4, (int) target.z >> 4); - + Map players = this.getChunkPlayers(target.getChunkX(), target.getChunkZ()); this.addParticle(new DestroyBlockParticle(target.add(0.5), target), players.values()); - - if (player != null) { - players.remove(player.getLoaderId()); - } } - // Close BlockEntity before we check onBreak BlockEntity blockEntity = this.getBlockEntity(target); if (blockEntity != null) { + // Let client close the inventory for viewers, fixes refusing to open inventories afterward + if (blockEntity instanceof InventoryHolder) { + Inventory inventory = ((InventoryHolder) blockEntity).getInventory(); + if (inventory != null && !inventory.getViewers().isEmpty()) { + inventory.getViewers().clear(); + } + } + blockEntity.onBreak(); blockEntity.close(); this.updateComparatorOutputLevel(target); } - target.onBreak(item); + Block waterlogged = target.getWaterloggingType() != Block.WaterloggingType.NO_WATERLOGGING ? target.getLevelBlock(Block.LAYER_WATERLOGGED) : null; + + target.onBreak(item, player); + + if (waterlogged instanceof BlockLiquid) { + this.setBlock(target, Block.LAYER_WATERLOGGED, Block.get(Block.AIR), false, true); + this.setBlock(target, Block.LAYER_NORMAL, waterlogged, false, true); + this.scheduleUpdate(waterlogged, 1); + } item.useOn(target); if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { + this.addSound(target, cn.nukkit.level.Sound.RANDOM_BREAK); + this.addParticle(new ItemBreakParticle(target, item)); item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } if (this.gameRules.getBoolean(GameRule.DO_TILE_DROPS)) { - - if (!isSilkTouch && player != null && player.isSurvival() && dropExp > 0 && drops.length != 0) { - this.dropExpOrb(vector.add(0.5, 0.5, 0.5), dropExp); + // For example no xp from redstone if it's mined with stone pickaxe + // Spawners drop xp only when mined with a pickaxe and don't drop anything (to prevent xp dupe with plugins) + if (!isSilkTouch && player != null && ((drops.length != 0 && target.getId() != Block.MONSTER_SPAWNER) || (drops.length == 0 && target.getId() == Block.MONSTER_SPAWNER && item.isPickaxe()))) { + if (player.isSurvival() || player.isAdventure()) { + this.dropExpOrb(dropPosition.add(0.5, 0.5, 0.5), dropExp); + } } - if (player == null || player.isSurvival()) { + if (player == null || player.isSurvival() || player.isAdventure()) { for (Item drop : drops) { if (drop.getCount() > 0) { - this.dropItem(vector.add(0.5, 0.5, 0.5), drop); + this.dropItem(dropPosition.add(0.5, 0.5, 0.5), drop); } } } @@ -2036,20 +2290,17 @@ public void dropExpOrb(Vector3 source, int exp, Vector3 motion) { } public void dropExpOrb(Vector3 source, int exp, Vector3 motion, int delay) { - Random rand = ThreadLocalRandom.current(); - for (int split : EntityXPOrb.splitIntoOrbSizes(exp)) { - CompoundTag nbt = Entity.getDefaultNBT(source, motion == null ? new Vector3( - (rand.nextDouble() * 0.2 - 0.1) * 2, - rand.nextDouble() * 0.4, - (rand.nextDouble() * 0.2 - 0.1) * 2) : motion, - rand.nextFloat() * 360f, 0); - - nbt.putShort("Value", split); - nbt.putShort("PickupDelay", delay); - - Entity entity = Entity.createEntity("XpOrb", this.getChunk(source.getChunkX(), source.getChunkZ()), nbt); - if (entity != null) { - entity.spawnToAll(); + if (exp > 0) { + Random rand = ThreadLocalRandom.current(); + for (int split : EntityXPOrb.splitIntoOrbSizes(exp)) { + CompoundTag nbt = Entity.getDefaultNBT(source, motion == null ? new Vector3( + (rand.nextDouble() * 0.2 - 0.1) * 2, + rand.nextDouble() * 0.4, + (rand.nextDouble() * 0.2 - 0.1) * 2) : motion, + rand.nextFloat() * 360f, 0); + nbt.putShort("Value", split); + nbt.putShort("PickupDelay", delay); + Entity.createEntity("XpOrb", this.getChunk(source.getChunkX(), source.getChunkZ()), nbt).spawnToAll(); } } } @@ -2062,42 +2313,48 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float return this.useItemOn(vector, item, face, fx, fy, fz, player, true); } - + @SuppressWarnings("unchecked") public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float fy, float fz, Player player, boolean playSound) { Block target = this.getBlock(vector); Block block = target.getSide(face); - if (block.y > 255 || block.y < 0) { + if (block.y > this.getMaxBlockY() || block.y < this.getMinBlockY()) { return null; } - if (block.y > 127 && this.getDimension() == DIMENSION_NETHER) { - return null; - } - if (target.getId() == Item.AIR) { return null; } + if (face == BlockFace.UP && item.getBlockId() == BlockID.SCAFFOLDING && block.getId() == BlockID.SCAFFOLDING) { + while (block instanceof BlockScaffolding) { + block = block.up(); + } + } + if (player != null) { - PlayerInteractEvent ev = new PlayerInteractEvent(player, item, target, face, - target.getId() == 0 ? Action.RIGHT_CLICK_AIR : Action.RIGHT_CLICK_BLOCK); + PlayerInteractEvent ev = new PlayerInteractEvent(player, item, target, face, Action.RIGHT_CLICK_BLOCK); - if (player.getGamemode() > 2) { - ev.setCancelled(); + if (player.getGamemode() > Player.ADVENTURE) { + ev.setCancelled(true); } - if(!player.isOp() && isInSpawnRadius(target)) { - ev.setCancelled(); + if (!player.isOp() && isInSpawnRadius(target)) { + ev.setCancelled(true); } this.server.getPluginManager().callEvent(ev); + if (!ev.isCancelled()) { target.onUpdate(BLOCK_UPDATE_TOUCH); - if ((!player.isSneaking() || player.getInventory().getItemInHand().isNull()) && target.canBeActivated() && target.onActivate(item, player)) { + + if ((!player.isSneaking() || item.isNull()) && target.canBeActivated() && target.onActivate(item, player)) { if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { + this.addSound(target, cn.nukkit.level.Sound.RANDOM_BREAK); + this.addParticle(new ItemBreakParticle(target, item)); item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } + return item; } @@ -2108,22 +2365,29 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } } } else { - if(item.getId() == ItemID.BUCKET && ItemBucket.getDamageByTarget(item.getDamage()) == BlockID.WATER) { - player.getLevel().sendBlocks(new Player[]{player}, new Block[]{Block.get(Block.AIR, 0, target)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); + if (item.getId() == ItemID.BUCKET && ItemBucket.getDamageByTarget(item.getDamage()) == BlockID.WATER) { + player.getLevel().sendBlocks(new Player[]{player}, new Block[]{Block.get(Block.AIR, 0, target.getLevelBlock(BlockLayer.WATERLOGGED))}, UpdateBlockPacket.FLAG_ALL_PRIORITY, Block.LAYER_WATERLOGGED); } return null; } - if(item.getId() == ItemID.BUCKET && ItemBucket.getDamageByTarget(item.getDamage()) == BlockID.WATER) { - player.getLevel().sendBlocks(new Player[] {player}, new Block[] {Block.get(Block.AIR, 0, target)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, 1); + if (item.getId() == ItemID.BUCKET && ItemBucket.getDamageByTarget(item.getDamage()) == BlockID.WATER) { + player.getLevel().sendBlocks(new Player[] {player}, new Block[] {target.getLevelBlock(BlockLayer.WATERLOGGED)}, UpdateBlockPacket.FLAG_ALL_PRIORITY, Block.LAYER_WATERLOGGED); } - } else if (target.canBeActivated() && target.onActivate(item, player)) { + } else if (target.canBeActivated() && target.onActivate(item, null)) { if (item.isTool() && item.getDamage() >= item.getMaxDurability()) { item = new ItemBlock(Block.get(BlockID.AIR), 0, 0); } return item; } + + int blockID = item.getBlockId(); + if (blockID > 255 && provider instanceof Anvil) { + return null; + } + Block hand; + if (item.canBePlaced()) { hand = item.getBlock(); hand.position(block); @@ -2140,41 +2404,35 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float hand.position(block); } - if (!hand.canPassThrough() && hand.getBoundingBox() != null) { + if (!hand.canPassThrough() && hand.getBoundingBox() != null && !(hand instanceof BlockBed)) { Entity[] entities = this.getCollidingEntities(hand.getBoundingBox()); - int realCount = 0; for (Entity e : entities) { - if (e instanceof EntityArrow || e instanceof EntityItem || (e instanceof Player && ((Player) e).isSpectator())) { + if (player == e || e instanceof EntityProjectile || e instanceof EntityItem || (e instanceof Player && ((Player) e).isSpectator() || !e.canCollide())) { continue; } - ++realCount; + this.sendBlocks(player, new Block[]{block, target}, UpdateBlockPacket.FLAG_NONE); // Prevent ghost blocks + return null; // Entity in block } if (player != null) { - Vector3 diff = player.getNextPosition().subtract(player.getPosition()); - if (diff.lengthSquared() > 0.00001) { - AxisAlignedBB bb = player.getBoundingBox().getOffsetBoundingBox(diff.x, diff.y, diff.z); - if (hand.getBoundingBox().intersectsWith(bb)) { - ++realCount; - } + Vector3 diff = player.getPositionOffset(); + if (hand.getBoundingBox().intersectsWith(player.getBoundingBox().getOffsetBoundingBox(diff.x, diff.y, diff.z))) { + this.sendBlocks(player, new Block[]{block, target}, UpdateBlockPacket.FLAG_NONE); // Prevent ghost blocks + return null; // Player in block } } - - if (realCount > 0) { - return null; // Entity in block - } } if (player != null) { BlockPlaceEvent event = new BlockPlaceEvent(player, hand, block, target, item); - if (player.getGamemode() == 2) { + if (player.getGamemode() == Player.ADVENTURE) { Tag tag = item.getNamedTagEntry("CanPlaceOn"); boolean canPlace = false; if (tag instanceof ListTag) { for (Tag v : ((ListTag) tag).getAll()) { if (v instanceof StringTag) { Item entry = Item.fromString(((StringTag) v).data); - if (entry.getId() > 0 && entry.getBlockUnsafe() != null && entry.getBlockUnsafe().getId() == target.getId()) { + if (entry.getId() != 0 && entry.getBlockUnsafe() != null && entry.getBlockUnsafe().getId() == target.getId()) { canPlace = true; break; } @@ -2182,11 +2440,12 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } } if (!canPlace) { - event.setCancelled(); + event.setCancelled(true); } } - if(!player.isOp() && isInSpawnRadius(target)) { - event.setCancelled(); + + if (!player.isOp() && isInSpawnRadius(target)) { + event.setCancelled(true); } this.server.getPluginManager().callEvent(event); @@ -2195,10 +2454,31 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } } + boolean liquidMoved = false; + if (hand.getWaterloggingType() != Block.WaterloggingType.NO_WATERLOGGING || !hand.canBeFlowedInto() || !(block instanceof BlockLiquid || block.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockLiquid)) { + if ((block instanceof BlockLiquid) && ((BlockLiquid) block).usesWaterLogging() && hand.getWaterloggingType() != Block.WaterloggingType.NO_WATERLOGGING) { + liquidMoved = true; + this.setBlock(block, Block.LAYER_NORMAL, Block.get(BlockID.AIR), false, false); + this.setBlock(block, Block.LAYER_WATERLOGGED, block, false, false); + } + } + if (!hand.place(item, block, target, face, fx, fy, fz, player)) { + if (liquidMoved) { + this.setBlock(block, Block.LAYER_NORMAL, block, false, false); + this.setBlock(block, Block.LAYER_WATERLOGGED, Block.get(BlockID.AIR), false, false); + } return null; } + if (item.hasPersistentDataContainer() && item.getPersistentDataContainer().convertsToBlock()) { + block.getPersistentDataContainer().setStorage(item.getPersistentDataContainer().getStorage().clone()); + } + + if (liquidMoved) { + this.scheduleUpdate(block, 1); + } + if (player != null) { if (!player.isCreative()) { item.setCount(item.getCount() - 1); @@ -2206,7 +2486,17 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } if (playSound) { - this.addLevelSoundEvent(hand, LevelSoundEventPacket.SOUND_PLACE, GlobalBlockPalette.getOrCreateRuntimeId(hand.getId(), hand.getDamage())); + int soundData = GlobalBlockPalette.getOrCreateRuntimeId(hand.getId(), hand.getDamage()); + + LevelSoundEventPacket pk = new LevelSoundEventPacket(); + pk.sound = LevelSoundEventPacket.SOUND_PLACE; + pk.extraData = soundData; + pk.entityIdentifier = ""; + pk.x = (float) hand.x; + pk.y = (float) hand.y; + pk.z = (float) hand.z; + + Server.broadcastPacket(this.getChunkPlayers(hand.getChunkX(), hand.getChunkZ()).values(), pk); } if (item.getCount() <= 0) { @@ -2216,17 +2506,12 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } public boolean isInSpawnRadius(Vector3 vector3) { - int distance = this.server.getSpawnRadius(); - if (distance > -1) { - Vector2 t = new Vector2(vector3.x, vector3.z); - Vector2 s = new Vector2(this.getSpawnLocation().x, this.getSpawnLocation().z); - return t.distance(s) <= distance; - } - return false; + Position spawn; + return server.getSpawnRadius() > -1 && new Vector2(vector3.x, vector3.z).distance(new Vector2((spawn = this.getSpawnLocation()).x, spawn.z)) <= server.getSpawnRadius(); } public Entity getEntity(long entityId) { - return this.entities.containsKey(entityId) ? this.entities.get(entityId) : null; + return this.entities.get(entityId); } public Entity[] getEntities() { @@ -2249,8 +2534,7 @@ public Entity[] getCollidingEntities(AxisAlignedBB bb, Entity entity) { for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (Entity ent : this.getChunkEntities(x, z, false).values()) { - if ((entity == null || (ent != entity && entity.canCollideWith(ent))) - && ent.boundingBox.intersectsWith(bb)) { + if ((entity == null || (ent != entity && entity.canCollideWith(ent))) && ent.boundingBox.intersectsWith(bb)) { nearby.add(ent); } } @@ -2262,55 +2546,32 @@ public Entity[] getCollidingEntities(AxisAlignedBB bb, Entity entity) { } public Entity[] getNearbyEntities(AxisAlignedBB bb) { - return this.getNearbyEntities(bb, null); + return this.getNearbyEntities(bb, null, false); } - private static Entity[] EMPTY_ENTITY_ARR = new Entity[0]; - private static Entity[] ENTITY_BUFFER = new Entity[512]; - public Entity[] getNearbyEntities(AxisAlignedBB bb, Entity entity) { return getNearbyEntities(bb, entity, false); } public Entity[] getNearbyEntities(AxisAlignedBB bb, Entity entity, boolean loadChunks) { - int index = 0; + List nearby = new ArrayList<>(); int minX = NukkitMath.floorDouble((bb.getMinX() - 2) * 0.0625); int maxX = NukkitMath.ceilDouble((bb.getMaxX() + 2) * 0.0625); int minZ = NukkitMath.floorDouble((bb.getMinZ() - 2) * 0.0625); int maxZ = NukkitMath.ceilDouble((bb.getMaxZ() + 2) * 0.0625); - ArrayList overflow = null; - for (int x = minX; x <= maxX; ++x) { for (int z = minZ; z <= maxZ; ++z) { for (Entity ent : this.getChunkEntities(x, z, loadChunks).values()) { if (ent != entity && ent.boundingBox.intersectsWith(bb)) { - if (index < ENTITY_BUFFER.length) { - ENTITY_BUFFER[index] = ent; - } else { - if (overflow == null) overflow = new ArrayList<>(1024); - overflow.add(ent); - } - index++; + nearby.add(ent); } } } } - if (index == 0) return EMPTY_ENTITY_ARR; - Entity[] copy; - if (overflow == null) { - copy = Arrays.copyOfRange(ENTITY_BUFFER, 0, index); - Arrays.fill(ENTITY_BUFFER, 0, index, null); - } else { - copy = new Entity[ENTITY_BUFFER.length + overflow.size()]; - System.arraycopy(ENTITY_BUFFER, 0, copy, 0, ENTITY_BUFFER.length); - for (int i = 0; i < overflow.size(); i++) { - copy[ENTITY_BUFFER.length + i] = overflow.get(i); - } - } - return copy; + return nearby.toArray(new Entity[0]); } public Map getBlockEntities() { @@ -2318,7 +2579,7 @@ public Map getBlockEntities() { } public BlockEntity getBlockEntityById(long blockEntityId) { - return this.blockEntities.containsKey(blockEntityId) ? this.blockEntities.get(blockEntityId) : null; + return this.blockEntities.get(blockEntityId); } public Map getPlayers() { @@ -2330,20 +2591,42 @@ public Map getLoaders() { } public BlockEntity getBlockEntity(Vector3 pos) { - FullChunk chunk = this.getChunk((int) pos.x >> 4, (int) pos.z >> 4, false); + return getBlockEntity(null, pos); + } + + public BlockEntity getBlockEntity(FullChunk chunk, Vector3 pos) { + int by = pos.getFloorY(); + if (by < this.getMinBlockY() || by > this.getMaxBlockY()) return null; + + int cx = (int) pos.x >> 4; + int cz = (int) pos.z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + chunk = this.getChunk(cx, cz, false); + } if (chunk != null) { - return chunk.getTile((int) pos.x & 0x0f, (int) pos.y & 0xff, (int) pos.z & 0x0f); + return chunk.getTile((int) pos.x & 0x0f, by, (int) pos.z & 0x0f); } return null; } public BlockEntity getBlockEntityIfLoaded(Vector3 pos) { - FullChunk chunk = this.getChunkIfLoaded((int) pos.x >> 4, (int) pos.z >> 4); + return getBlockEntityIfLoaded(null, pos); + } + + public BlockEntity getBlockEntityIfLoaded(FullChunk chunk, Vector3 pos) { + int by = pos.getFloorY(); + if (by < this.getMinBlockY() || by > this.getMaxBlockY()) return null; + + int cx = (int) pos.x >> 4; + int cz = (int) pos.z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + chunk = this.getChunkIfLoaded(cx, cz); + } if (chunk != null) { - return chunk.getTile((int) pos.x & 0x0f, (int) pos.y & 0xff, (int) pos.z & 0x0f); + return chunk.getTile((int) pos.x & 0x0f, by, (int) pos.z & 0x0f); } return null; @@ -2364,78 +2647,213 @@ public Map getChunkBlockEntities(int X, int Z) { } @Override - public synchronized int getBlockIdAt(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBlockId(x & 0x0f, y & 0xff, z & 0x0f); + public int getBlockIdAt(int x, int y, int z) { + return getBlockIdAt(x, y, z, Block.LAYER_NORMAL); + } + + @Override + public synchronized int getBlockIdAt(int x, int y, int z, BlockLayer layer) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + + int cx = x >> 4; + int cz = z >> 4; + FullChunk chunk = this.getChunk(cx, cz, true); + return chunk.getBlockId(x & 0x0f, y, z & 0x0f, layer); + } + + public synchronized int getBlockIdAt(FullChunk chunk, int x, int y, int z) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + + int cx = x >> 4; + int cz = z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + chunk = this.getChunk(cx, cz, true); + } + return chunk.getBlockId(x & 0x0f, y, z & 0x0f, Block.LAYER_NORMAL); } @Override public synchronized void setBlockIdAt(int x, int y, int z, int id) { - this.getChunk(x >> 4, z >> 4, true).setBlockId(x & 0x0f, y & 0xff, z & 0x0f, id & 0xff); + this.setBlockIdAt(x, y, z, Block.LAYER_NORMAL, id); + } + + @Override + public synchronized void setBlockIdAt(int x, int y, int z, BlockLayer layer, int id) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + + FullChunk chunk = this.getChunk(x >> 4, z >> 4, true); + chunk.setBlockId(x & 0x0f, y, z & 0x0f, layer, id & 0xfff); addBlockChange(x, y, z); - temporalVector.setComponents(x, y, z); - for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { - loader.onBlockChanged(temporalVector); + + if (this.useChunkLoaderApi) { + temporalVector.setComponents(x, y, z); + for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { + loader.onBlockChanged(temporalVector); + } + } + + if (id == 0) { + updateEntitiesOnBlockChange(chunk); } } + @Override public synchronized void setBlockAt(int x, int y, int z, int id, int data) { + this.setBlockAtLayer(x, y, z, Block.LAYER_NORMAL, id, data); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id) { + return this.setBlockAtLayer(x, y, z, layer, id, 0); + } + + @Override + public synchronized boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id, int data) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return false; + } + BaseFullChunk chunk = this.getChunk(x >> 4, z >> 4, true); - chunk.setBlockId(x & 0x0f, y & 0xff, z & 0x0f, id & 0xff); - chunk.setBlockData(x & 0x0f, y & 0xff, z & 0x0f, data & 0xf); + boolean changed = chunk.setBlockAtLayer(x & 0x0f, y, z & 0x0f, layer, id & 0xfff, data & Block.DATA_MASK); + if (!changed) { + return false; + } + addBlockChange(x, y, z); - temporalVector.setComponents(x, y, z); - for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { - loader.onBlockChanged(temporalVector); + + if (this.useChunkLoaderApi) { + temporalVector.setComponents(x, y, z); + for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { + loader.onBlockChanged(temporalVector); + } + } + + if (id == 0) { + updateEntitiesOnBlockChange(chunk); + } + return true; + } + + private void updateEntitiesOnBlockChange(FullChunk chunk) { + if (chunk == null) { + return; + } + for (Entity entity : chunk.getEntities().values()) { + if (entity.updateMode % 2 == 1) { + entity.updateMode = 1; + } } } public synchronized int getBlockExtraDataAt(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBlockExtraData(x & 0x0f, y & 0xff, z & 0x0f); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + return this.getChunk(x >> 4, z >> 4, true).getBlockExtraData(x & 0x0f, y, z & 0x0f); } public synchronized void setBlockExtraDataAt(int x, int y, int z, int id, int data) { - this.getChunk(x >> 4, z >> 4, true).setBlockExtraData(x & 0x0f, y & 0xff, z & 0x0f, (data << 8) | id); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + this.getChunk(x >> 4, z >> 4, true).setBlockExtraData(x & 0x0f, y, z & 0x0f, (data << 8) | id); this.sendBlockExtraData(x, y, z, id, data); } @Override public synchronized int getBlockDataAt(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBlockData(x & 0x0f, y & 0xff, z & 0x0f); + return this.getBlockDataAt(null, x, y, z, Block.LAYER_NORMAL); + } + + @Override + public synchronized int getBlockDataAt(int x, int y, int z, BlockLayer layer) { + return this.getBlockDataAt(null, x, y, z, layer); + } + + public synchronized int getBlockDataAt(FullChunk chunk, int x, int y, int z, BlockLayer layer) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + int cx = x >> 4; + int cz = z >> 4; + if (chunk == null || cx != chunk.getX() || cz != chunk.getZ()) { + chunk = this.getChunk(cx, cz, true); + } + return chunk.getBlockData(x & 0x0f, y, z & 0x0f, layer); } @Override public synchronized void setBlockDataAt(int x, int y, int z, int data) { - this.getChunk(x >> 4, z >> 4, true).setBlockData(x & 0x0f, y & 0xff, z & 0x0f, data & 0x0f); + this.setBlockDataAt(x, y, z, Block.LAYER_NORMAL, data); + } + + @Override + public synchronized void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + + this.getChunk(x >> 4, z >> 4, true).setBlockData(x & 0x0f, y, z & 0x0f, layer, data & Block.DATA_MASK); addBlockChange(x, y, z); - temporalVector.setComponents(x, y, z); - for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { - loader.onBlockChanged(temporalVector); + + if (this.useChunkLoaderApi) { + temporalVector.setComponents(x, y, z); + for (ChunkLoader loader : this.getChunkLoaders(x >> 4, z >> 4)) { + loader.onBlockChanged(temporalVector); + } } } public synchronized int getBlockSkyLightAt(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBlockSkyLight(x & 0x0f, y & 0xff, z & 0x0f); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + return this.getChunk(x >> 4, z >> 4, true).getBlockSkyLight(x & 0x0f, y, z & 0x0f); } public synchronized void setBlockSkyLightAt(int x, int y, int z, int level) { - this.getChunk(x >> 4, z >> 4, true).setBlockSkyLight(x & 0x0f, y & 0xff, z & 0x0f, level & 0x0f); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + this.getChunk(x >> 4, z >> 4, true).setBlockSkyLight(x & 0x0f, y, z & 0x0f, level & 0x0f); } public synchronized int getBlockLightAt(int x, int y, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBlockLight(x & 0x0f, y & 0xff, z & 0x0f); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } + BaseFullChunk chunk = this.getChunkIfLoaded(x >> 4, z >> 4); + return chunk == null ? 0 : chunk.getBlockLight(x & 0x0f, y, z & 0x0f); } public synchronized void setBlockLightAt(int x, int y, int z, int level) { - this.getChunk(x >> 4, z >> 4, true).setBlockLight(x & 0x0f, y & 0xff, z & 0x0f, level & 0x0f); + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + + BaseFullChunk c = this.getChunkIfLoaded(x >> 4, z >> 4); + if (null != c) { + c.setBlockLight(x & 0x0f, y, z & 0x0f, level & 0x0f); + } } public int getBiomeId(int x, int z) { return this.getChunk(x >> 4, z >> 4, true).getBiomeId(x & 0x0f, z & 0x0f); } + public void setBiomeId(int x, int z, int biomeId) { + this.getChunk(x >> 4, z >> 4, true).setBiomeId(x & 0x0f, z & 0x0f, biomeId & 0x0f); + } + public void setBiomeId(int x, int z, byte biomeId) { - this.getChunk(x >> 4, z >> 4, true).setBiomeId(x & 0x0f, z & 0x0f, biomeId); + this.getChunk(x >> 4, z >> 4, true).setBiomeId(x & 0x0f, z & 0x0f, biomeId & 0x0f); } public int getHeightMap(int x, int z) { @@ -2446,6 +2864,14 @@ public void setHeightMap(int x, int z, int value) { this.getChunk(x >> 4, z >> 4, true).setHeightMap(x & 0x0f, z & 0x0f, value & 0x0f); } + public int getBiomeColor(int x, int z) { + return this.getChunk(x >> 4, z >> 4, true).getBiomeColor(x & 0x0f, z & 0x0f); + } + + public void setBiomeColor(int x, int z, int R, int G, int B) { + this.getChunk(x >> 4, z >> 4, true).setBiomeColor(x & 0x0f, z & 0x0f, R, G, B); + } + public Map getChunks() { return provider.getLoadedChunks(); } @@ -2465,8 +2891,7 @@ public BaseFullChunk getChunk(int chunkX, int chunkZ, boolean create) { } public BaseFullChunk getChunkIfLoaded(int chunkX, int chunkZ) { - long index = Level.chunkHash(chunkX, chunkZ); - return this.provider.getLoadedChunk(index); + return this.provider.getLoadedChunk(Level.chunkHash(chunkX, chunkZ)); } public void generateChunkCallback(int x, int z, BaseFullChunk chunk) { @@ -2474,9 +2899,8 @@ public void generateChunkCallback(int x, int z, BaseFullChunk chunk) { } public void generateChunkCallback(int x, int z, BaseFullChunk chunk, boolean isPopulated) { - Timings.generationCallbackTimer.startTiming(); long index = Level.chunkHash(x, z); - if (this.chunkPopulationQueue.containsKey(index)) { + if (this.chunkPopulationQueue.contains(index)) { FullChunk oldChunk = this.getChunk(x, z, false); for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { @@ -2491,11 +2915,13 @@ public void generateChunkCallback(int x, int z, BaseFullChunk chunk, boolean isP && chunk.getProvider() != null) { this.server.getPluginManager().callEvent(new ChunkPopulateEvent(chunk)); - for (ChunkLoader loader : this.getChunkLoaders(x, z)) { - loader.onChunkPopulated(chunk); + if (this.useChunkLoaderApi) { + for (ChunkLoader loader : this.getChunkLoaders(x, z)) { + loader.onChunkPopulated(chunk); + } } } - } else if (this.chunkGenerationQueue.containsKey(index) || this.chunkPopulationLock.containsKey(index)) { + } else if (this.chunkGenerationQueue.contains(index) || this.chunkPopulationLock.contains(index)) { this.chunkGenerationQueue.remove(index); this.chunkPopulationLock.remove(index); chunk.setProvider(this.provider); @@ -2504,7 +2930,6 @@ public void generateChunkCallback(int x, int z, BaseFullChunk chunk, boolean isP chunk.setProvider(this.provider); this.setChunk(x, z, chunk, false); } - Timings.generationCallbackTimer.stopTiming(); } @Override @@ -2528,8 +2953,6 @@ public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk, boolean unload if (oldChunk != chunk) { if (unload && oldChunk != null) { this.unloadChunk(chunkX, chunkZ, false, false); - - this.provider.setChunk(chunkX, chunkZ, chunk); } else { Map oldEntities = oldChunk != null ? oldChunk.getEntities() : Collections.emptyMap(); @@ -2563,8 +2986,8 @@ public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk, boolean unload } } - this.provider.setChunk(chunkX, chunkZ, chunk); } + this.provider.setChunk(chunkX, chunkZ, chunk); } chunk.setChanged(); @@ -2579,32 +3002,134 @@ public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk, boolean unload } public int getHighestBlockAt(int x, int z) { - return this.getChunk(x >> 4, z >> 4, true).getHighestBlockAt(x & 0x0f, z & 0x0f); + return this.getHighestBlockAt(x, z, true); + } + + public int getHighestBlockAt(int x, int z, boolean cache) { + return this.getChunk(x >> 4, z >> 4, true).getHighestBlockAt(x & 0x0f, z & 0x0f, cache); } public BlockColor getMapColorAt(int x, int z) { - int y = getHighestBlockAt(x, z); - while (y > 1) { - Block block = getBlock(new Vector3(x, y, z)); - BlockColor blockColor = block.getColor(); - if (blockColor.getAlpha() == 0x00) { - y--; + return getMapColorAt(x, getHighestBlockAt(x, z, false), z); + } + + public BlockColor getMapColorAt(int x, int y, int z) { + int minY = this.getMinBlockY(); + if (y < minY || y > this.getMaxBlockY()) return BlockColor.VOID_BLOCK_COLOR; + + int checkY = y; + FullChunk chunk = this.getChunk(x >> 4, z >> 4); + while (checkY > minY) { + Block block = this.getBlock(chunk, x, checkY, z, BlockLayer.NORMAL, false); + if (block instanceof BlockGrass) { + return getGrassColorAt(chunk, x, z); + //} else if (block instanceof BlockWater) { + // return getWaterColorAt(chunk, x, z); } else { - return blockColor; + BlockColor blockColor = block.getColor(); + if (blockColor.getAlpha() == 0x00) { + checkY--; + } else { + return blockColor; + } } } + return BlockColor.VOID_BLOCK_COLOR; } + private BlockColor getGrassColorAt(FullChunk chunk, int x, int z) { + int biome = chunk.getBiomeId(x & 0x0f, z & 0x0f); + + switch (biome) { + case 0: //ocean + case 7: //river + case 9: //end + case 24: //deep ocean + return new BlockColor("#8eb971"); + case 1: //plains + case 16: //beach + case 129: //sunflower plains + return new BlockColor("#91bd59"); + case 2: //desert + case 8: //hell + case 17: //desert hills + case 35: //savanna + case 36: //savanna plateau + case 130: //desert m + case 163: //savanna m + case 164: //savanna plateau m + return new BlockColor("#bfb755"); + case 3: //extreme hills + case 20: //extreme hills edge + case 25: //stone beach + case 34: //extreme hills + case 131: //extreme hills m + case 162: //extreme hills plus m + return new BlockColor("#8ab689"); + case 4: //forest + case 132: //flower forest + return new BlockColor("#79c05a"); + case 5: //taiga + case 19: //taiga hills + case 32: //mega taiga + case 33: //mega taiga hills + case 133: //taiga m + case 160: //mega spruce taiga + return new BlockColor("#86b783"); + case 6: //swamp + case 134: //swampland m + return new BlockColor("#6A7039"); + case 10: //frozen ocean + case 11: //frozen river + case 12: //ice plains + case 30: //cold taiga + case 31: //cold taiga hills + case 140: //ice plains spikes + case 158: //cold taiga m + return new BlockColor("#80b497"); + case 14: //mushroom island + case 15: //mushroom island shore + return new BlockColor("#55c93f"); + case 18: //forest hills + case 27: //birch forest + case 28: //birch forest hills + case 155: //birch forest m + case 156: //birch forest hills m + return new BlockColor("#88bb67"); + case 21: //jungle + case 22: //jungle hills + case 149: //jungle m + return new BlockColor("#59c93c"); + case 23: //jungle edge + case 151: //jungle edge m + return new BlockColor("#64c73f"); + case 26: //cold beach + return new BlockColor("#83b593"); + case 29: //roofed forest + case 157: //roofed forest m + return new BlockColor("#507a32"); + case 37: //mesa + case 38: //mesa plateau f + case 39: //mesa plateau + case 165: //mesa bryce + case 166: //mesa plateau f m + case 167: //mesa plateau m + return new BlockColor("#90814d"); + default: + return BlockColor.GRASS_BLOCK_COLOR; + } + } + public boolean isChunkLoaded(int x, int z) { return this.provider.isChunkLoaded(x, z); } private boolean areNeighboringChunksLoaded(long hash) { return this.provider.isChunkLoaded(hash + 1) && - this.provider.isChunkLoaded(hash - 1) && - this.provider.isChunkLoaded(hash + (1L << 32)) && - this.provider.isChunkLoaded(hash - (1L << 32)); + this.provider.isChunkLoaded(hash - 1) && + this.provider.isChunkLoaded(hash + (4294967296L)) && + this.provider.isChunkLoaded(hash - (4294967296L)); } public boolean isChunkGenerated(int x, int z) { @@ -2624,21 +3149,25 @@ public Position getSpawnLocation() { public void setSpawnLocation(Vector3 pos) { Position previousSpawn = this.getSpawnLocation(); this.provider.setSpawn(pos); + this.cachedSpawnPos = this.getSpawnLocation(); this.server.getPluginManager().callEvent(new SpawnChangeEvent(this, previousSpawn)); SetSpawnPositionPacket pk = new SetSpawnPositionPacket(); pk.spawnType = SetSpawnPositionPacket.TYPE_WORLD_SPAWN; pk.x = pos.getFloorX(); pk.y = pos.getFloorY(); pk.z = pos.getFloorZ(); + pk.dimension = this.getDimension(); for (Player p : getPlayers().values()) p.dataPacket(pk); } public void requestChunk(int x, int z, Player player) { - Preconditions.checkState(player.getLoaderId() > 0, player.getName() + " has no chunk loader"); + if (player.getLoaderId() <= 0) { + throw new IllegalStateException(player.getName() + " has no chunk loader"); + } + long index = Level.chunkHash(x, z); this.chunkSendQueue.putIfAbsent(index, new Int2ObjectOpenHashMap<>()); - this.chunkSendQueue.get(index).put(player.getLoaderId(), player); } @@ -2656,7 +3185,6 @@ private void sendChunk(int x, int z, long index, DataPacket packet) { } private void processChunkRequest() { - this.timings.syncChunkSendTimer.startTiming(); for (long index : this.chunkSendQueue.keySet()) { if (this.chunkSendTasks.contains(index)) { continue; @@ -2672,42 +3200,31 @@ private void processChunkRequest() { continue; } } - this.timings.syncChunkSendPrepareTimer.startTiming(); - AsyncTask task = this.provider.requestChunkTask(x, z); - if (task != null) { - this.server.getScheduler().scheduleAsyncTask(task); - } - this.timings.syncChunkSendPrepareTimer.stopTiming(); + this.provider.requestChunkTask(x, z); } - this.timings.syncChunkSendTimer.stopTiming(); } - public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount, byte[] payload) { - this.timings.syncChunkSendTimer.startTiming(); - long index = Level.chunkHash(x, z); - - if (this.cacheChunks) { - BatchPacket data = Player.getChunkCacheFromData(x, z, subChunkCount, payload); + public void chunkRequestCallback(long timestamp, int x, int z, int subChunkCount, byte[] payload, long index) { + if (server.cacheChunks) { + BatchPacket data = Player.getChunkCacheFromData(x, z, subChunkCount, payload, this.getDimension()); BaseFullChunk chunk = getChunk(x, z, false); if (chunk != null && chunk.getChanges() <= timestamp) { chunk.setChunkPacket(data); } this.sendChunk(x, z, index, data); - this.timings.syncChunkSendTimer.stopTiming(); return; } if (this.chunkSendTasks.contains(index)) { for (Player player : this.chunkSendQueue.get(index).values()) { if (player.isConnected() && player.usedChunks.containsKey(index)) { - player.sendChunk(x, z, subChunkCount, payload); + player.sendChunk(x, z, subChunkCount, payload, this.getDimension()); } } this.chunkSendQueue.remove(index); this.chunkSendTasks.remove(index); } - this.timings.syncChunkSendTimer.stopTiming(); } public void removeEntity(Entity entity) { @@ -2739,22 +3256,24 @@ public void addEntity(Entity entity) { public void addBlockEntity(BlockEntity blockEntity) { if (blockEntity.getLevel() != this) { - throw new LevelException("Invalid Block Entity level"); + throw new LevelException("BlockEntity is not in this level"); } blockEntities.put(blockEntity.getId(), blockEntity); } public void scheduleBlockEntityUpdate(BlockEntity entity) { - Preconditions.checkNotNull(entity, "entity"); - Preconditions.checkArgument(entity.getLevel() == this, "BlockEntity is not in this level"); + if (entity.getLevel() != this) { + throw new LevelException("BlockEntity is not in this level"); + } if (!updateBlockEntities.contains(entity)) { updateBlockEntities.add(entity); } } public void removeBlockEntity(BlockEntity entity) { - Preconditions.checkNotNull(entity, "entity"); - Preconditions.checkArgument(entity.getLevel() == this, "BlockEntity is not in this level"); + if (entity.getLevel() != this) { + throw new LevelException("BlockEntity is not in this level"); + } blockEntities.remove(entity.getId()); updateBlockEntities.remove(entity); } @@ -2764,7 +3283,8 @@ public boolean isChunkInUse(int x, int z) { } public boolean isChunkInUse(long hash) { - return this.chunkLoaders.containsKey(hash) && !this.chunkLoaders.get(hash).isEmpty(); + Map map = this.chunkLoaders.get(hash); + return map != null && !map.isEmpty(); } public boolean loadChunk(int x, int z) { @@ -2780,40 +3300,39 @@ public boolean loadChunk(int x, int z, boolean generate) { } private synchronized BaseFullChunk forceLoadChunk(long index, int x, int z, boolean generate) { - this.timings.syncChunkLoadTimer.startTiming(); BaseFullChunk chunk = this.provider.getChunk(x, z, generate); + if (chunk == null) { if (generate) { - throw new IllegalStateException("Could not create new Chunk"); + throw new IllegalStateException("Could not create new chunk"); } - this.timings.syncChunkLoadTimer.stopTiming(); - return chunk; + return null; } if (chunk.getProvider() != null) { this.server.getPluginManager().callEvent(new ChunkLoadEvent(chunk, !chunk.isGenerated())); } else { this.unloadChunk(x, z, false); - this.timings.syncChunkLoadTimer.stopTiming(); return chunk; } chunk.initChunk(); - if (!chunk.isLightPopulated() && chunk.isPopulated() - && this.getServer().getConfig("chunk-ticking.light-updates", false)) { - this.getServer().getScheduler().scheduleAsyncTask(new LightPopulationTask(this, chunk)); - } + /*if (!chunk.isLightPopulated() && chunk.isPopulated()) { + this.server.getScheduler().scheduleAsyncTask(new LightPopulationTask(this, chunk)); + }*/ if (this.isChunkInUse(index)) { this.unloadQueue.remove(index); - for (ChunkLoader loader : this.getChunkLoaders(x, z)) { - loader.onChunkLoaded(chunk); + + if (this.useChunkLoaderApi) { + for (ChunkLoader loader : this.getChunkLoaders(x, z)) { + loader.onChunkLoaded(chunk); + } } } else { this.unloadQueue.put(index, System.currentTimeMillis()); } - this.timings.syncChunkLoadTimer.stopTiming(); return chunk; } @@ -2861,54 +3380,66 @@ public synchronized boolean unloadChunk(int x, int z, boolean safe, boolean tryS return true; } - this.timings.doChunkUnload.startTiming(); - BaseFullChunk chunk = this.getChunk(x, z); if (chunk != null && chunk.getProvider() != null) { ChunkUnloadEvent ev = new ChunkUnloadEvent(chunk); this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { - this.timings.doChunkUnload.stopTiming(); return false; } } try { if (chunk != null) { - if (trySave && this.getAutoSave()) { - int entities = 0; - for (Entity e : chunk.getEntities().values()) { - if (e instanceof Player) { - continue; + if (trySave && this.saveOnUnloadEnabled) { + boolean needSave = chunk.hasChanged(); + + if (!needSave) { + for (Entity e : chunk.getEntities().values()) { + if (e.canSaveToStorage()) { + needSave = true; + break; + } } - ++entities; } - if (chunk.hasChanged() || !chunk.getBlockEntities().isEmpty() || entities > 0) { + if (!needSave) { + for (BlockEntity e : chunk.getBlockEntities().values()) { + if (e instanceof BlockEntityContainer) { + needSave = true; + break; + } + } + } + + if (needSave) { this.provider.setChunk(x, z, chunk); - this.provider.saveChunk(x, z); + this.provider.saveChunk(x, z, chunk); } } - for (ChunkLoader loader : this.getChunkLoaders(x, z)) { - loader.onChunkUnloaded(chunk); + + if (this.useChunkLoaderApi) { + for (ChunkLoader loader : this.getChunkLoaders(x, z)) { + loader.onChunkUnloaded(chunk); + } } } this.provider.unloadChunk(x, z, safe); } catch (Exception e) { MainLogger logger = this.server.getLogger(); - logger.error(this.server.getLanguage().translateString("nukkit.level.chunkUnloadError", e.toString())); - logger.logException(e); + logger.error(this.server.getLanguage().translateString("nukkit.level.chunkUnloadError", e.toString()), e); } - this.timings.doChunkUnload.stopTiming(); - return true; } public boolean isSpawnChunk(int X, int Z) { - Vector3 spawn = this.provider.getSpawn(); - return Math.abs(X - (spawn.getFloorX() >> 4)) <= 1 && Math.abs(Z - (spawn.getFloorZ() >> 4)) <= 1; + if (this.cachedSpawnPos == null) { + this.cachedSpawnPos = this.getSpawnLocation(); + } + + return Math.abs(X - (cachedSpawnPos.getChunkX())) <= 1 && Math.abs(Z - (cachedSpawnPos.getChunkZ())) <= 1; } public Position getSafeSpawn() { @@ -2916,56 +3447,52 @@ public Position getSafeSpawn() { } public Position getSafeSpawn(Vector3 spawn) { - if (spawn == null || spawn.y < 1) { + if (spawn == null /*|| spawn.y < 1*/) { spawn = this.getSpawnLocation(); } - if (spawn != null) { - Vector3 v = spawn.floor(); - FullChunk chunk = this.getChunk((int) v.x >> 4, (int) v.z >> 4, false); - int x = (int) v.x & 0x0f; - int z = (int) v.z & 0x0f; - if (chunk != null && chunk.isGenerated()) { - int y = (int) NukkitMath.clamp(v.y, 1, 254); - boolean wasAir = chunk.getBlockId(x, y - 1, z) == 0; - for (; y > 0; --y) { - int b = chunk.getFullBlock(x, y, z); - Block block = Block.get(b >> 4, b & 0x0f); - if (this.isFullBlock(block)) { - if (wasAir) { - y++; - break; - } - } else { - wasAir = true; + Vector3 pos = spawn.floor(); + FullChunk chunk = this.getChunk((int) pos.x >> 4, (int) pos.z >> 4, false); + int x = (int) pos.x & 0x0f; + int z = (int) pos.z & 0x0f; + if (chunk != null && chunk.isGenerated()) { + int y = NukkitMath.clamp((int) pos.y, this.getMinBlockY() + 1, this.getMaxBlockY() - 1); + boolean wasAir = chunk.getBlockId(x, y - 1, z) == 0; + for (; y > this.getMinBlockY(); --y) { + int fullId = chunk.getFullBlock(x, y, z); + Block block = Block.get(fullId >> Block.DATA_BITS, fullId & Block.DATA_MASK); + if (this.isFullBlock(block)) { + if (wasAir) { + y++; } + break; + } else { + wasAir = true; } + } - for (; y >= 0 && y < 255; y++) { - int b = chunk.getFullBlock(x, y + 1, z); - Block block = Block.get(b >> 4, b & 0x0f); + for (; y >= this.getMinBlockY() && y < this.getMaxBlockY(); y++) { + int fullId = chunk.getFullBlock(x, y + 1, z); + Block block = Block.get(fullId >> Block.DATA_BITS, fullId & Block.DATA_MASK); + if (!this.isFullBlock(block)) { + fullId = chunk.getFullBlock(x, y, z); + block = Block.get(fullId >> Block.DATA_BITS, fullId & Block.DATA_MASK); 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; + return new Position(pos.x + 0.5, y + 0.51, pos.z + 0.5, this); // Hack: + 0.51 for slabs } + } else { + ++y; } - - v.y = y; } - return new Position(spawn.x, v.y, spawn.z, this); + pos.y = y; } - return null; + return new Position(pos.x + 0.5, pos.y + 0.1, pos.z + 0.5, this); } public int getTime() { - return (int) time; + return time; } public boolean isDaytime() { @@ -2977,7 +3504,8 @@ public long getCurrentTick() { } public String getName() { - return this.provider.getName(); + return this.folderName; + //return this.provider.getName(); } public String getFolderName() { @@ -3014,19 +3542,17 @@ public boolean populateChunk(int x, int z) { public boolean populateChunk(int x, int z, boolean force) { long index = Level.chunkHash(x, z); - if (this.chunkPopulationQueue.containsKey(index) || this.chunkPopulationQueue.size() >= this.chunkPopulationQueueSize && !force) { + if (this.chunkPopulationQueue.contains(index) || this.chunkPopulationQueue.size() >= this.chunkPopulationQueueSize && !force) { return false; } BaseFullChunk chunk = this.getChunk(x, z, true); boolean populate; if (!chunk.isPopulated()) { - Timings.populationTimer.startTiming(); populate = true; for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { - if (this.chunkPopulationLock.containsKey(Level.chunkHash(x + xx, z + zz))) { - + if (this.chunkPopulationLock.contains(Level.chunkHash(x + xx, z + zz))) { populate = false; break; } @@ -3034,25 +3560,29 @@ public boolean populateChunk(int x, int z, boolean force) { } if (populate) { - if (!this.chunkPopulationQueue.containsKey(index)) { - this.chunkPopulationQueue.put(index, Boolean.TRUE); + if (!this.chunkPopulationQueue.contains(index)) { + this.chunkPopulationQueue.add(index); for (int xx = -1; xx <= 1; ++xx) { for (int zz = -1; zz <= 1; ++zz) { - this.chunkPopulationLock.put(Level.chunkHash(x + xx, z + zz), Boolean.TRUE); + this.chunkPopulationLock.add(Level.chunkHash(x + xx, z + zz)); } } - PopulationTask task = new PopulationTask(this, chunk); + AsyncTask task = this.generatorTaskFactory.populateChunkTask(chunk, this); this.server.getScheduler().scheduleAsyncTask(task); } } - Timings.populationTimer.stopTiming(); return false; } return true; } + @Override + public AsyncTask populateChunkTask(BaseFullChunk chunk, Level level) { + return new PopulationTask(this, chunk); + } + public void generateChunk(int x, int z) { this.generateChunk(x, z, false); } @@ -3063,29 +3593,27 @@ public void generateChunk(int x, int z, boolean force) { } long index = Level.chunkHash(x, z); - if (!this.chunkGenerationQueue.containsKey(index)) { - Timings.generationTimer.startTiming(); - this.chunkGenerationQueue.put(index, Boolean.TRUE); - GenerationTask task = new GenerationTask(this, this.getChunk(x, z, true)); + if (!this.chunkGenerationQueue.contains(index)) { + this.chunkGenerationQueue.add(index); + AsyncTask task = this.generatorTaskFactory.generateChunkTask(this.getChunk(x, z, true), this); this.server.getScheduler().scheduleAsyncTask(task); - Timings.generationTimer.stopTiming(); } } + @Override + public AsyncTask generateChunkTask(BaseFullChunk chunk, Level level) { + return new GenerationTask(level, chunk); + } + public void regenerateChunk(int x, int z) { this.unloadChunk(x, z, false, false); - this.cancelUnloadChunkRequest(x, z); - - BaseFullChunk chunk = provider.getEmptyChunk(x, z); - provider.setChunk(x, z, chunk); - + provider.setChunk(x, z, provider.getEmptyChunk(x, z)); this.generateChunk(x, z, true); } public void doChunkGarbageCollection() { - this.timings.doChunkGC.startTiming(); - // remove all invaild block entities. + // Remove all invalid block entities if (!blockEntities.isEmpty()) { ObjectIterator iter = blockEntities.values().iterator(); while (iter.hasNext()) { @@ -3114,13 +3642,13 @@ public void doChunkGarbageCollection() { } this.provider.doGarbageCollection(); - this.timings.doChunkGC.stopTiming(); } + public void doGarbageCollection(long allocatedTime) { long start = System.currentTimeMillis(); if (unloadChunks(start, allocatedTime, false)) { - allocatedTime = allocatedTime - (System.currentTimeMillis() - start); + allocatedTime -= (System.currentTimeMillis() - start); provider.doGarbageCollection(allocatedTime); } } @@ -3130,13 +3658,18 @@ public void unloadChunks() { } public void unloadChunks(boolean force) { - unloadChunks(96, force); + this.unloadChunks(50, force); } public void unloadChunks(int maxUnload, boolean force) { + if (server.holdWorldSave && !force && this.saveOnUnloadEnabled) { + return; + } + if (!this.unloadQueue.isEmpty()) { long now = System.currentTimeMillis(); + int unloaded = 0; LongList toRemove = null; for (Long2LongMap.Entry entry : unloadQueue.long2LongEntrySet()) { long index = entry.getLongKey(); @@ -3147,11 +3680,12 @@ public void unloadChunks(int maxUnload, boolean force) { if (!force) { long time = entry.getLongValue(); - if (maxUnload <= 0) { + if (unloaded > maxUnload) { break; } else if (time > (now - 30000)) { continue; } + unloaded++; } if (toRemove == null) toRemove = new LongArrayList(); @@ -3167,7 +3701,6 @@ public void unloadChunks(int maxUnload, boolean force) { if (this.unloadChunk(X, Z, true)) { this.unloadQueue.remove(index); - --maxUnload; } } } @@ -3177,12 +3710,16 @@ public void unloadChunks(int maxUnload, boolean force) { private int lastUnloadIndex; /** - * @param now - * @param allocatedTime - * @param force + * @param now current time + * @param allocatedTime allocated time + * @param force force * @return true if there is allocated time remaining */ private boolean unloadChunks(long now, long allocatedTime, boolean force) { + if (server.holdWorldSave && !force && this.saveOnUnloadEnabled) { + return false; + } + if (!this.unloadQueue.isEmpty()) { boolean result = true; int maxIterations = this.unloadQueue.size(); @@ -3207,7 +3744,7 @@ private boolean unloadChunks(long now, long allocatedTime, boolean force) { if (!force) { long time = entry.getLongValue(); - if (time > (now - 30000)) { + if (time > (now - 20000)) { continue; } } @@ -3217,8 +3754,8 @@ private boolean unloadChunks(long now, long allocatedTime, boolean force) { } if (toUnload != null) { - long[] arr = toUnload.toLongArray(); - for (long index : arr) { + //long[] arr = toUnload.toLongArray(); + for (long index : toUnload) { int X = getHashX(index); int Z = getHashZ(index); if (this.unloadChunk(X, Z, true)) { @@ -3265,6 +3802,8 @@ public void addPlayerMovement(Entity entity, double x, double y, double z, doubl pk.yaw = (float) yaw; pk.headYaw = (float) headYaw; pk.pitch = (float) pitch; + pk.onGround = entity.onGround; + if (entity.riding != null) { pk.ridingEid = entity.riding.getId(); pk.mode = MovePlayerPacket.MODE_PITCH; @@ -3284,7 +3823,9 @@ public void addEntityMovement(Entity entity, double x, double y, double z, doubl pk.pitch = pitch; pk.onGround = entity.onGround; - Server.broadcastPacket(entity.getViewers().values(), pk); + for (Player p : entity.getViewers().values()) { + p.dataPacket(pk); + } } public boolean isRaining() { @@ -3293,7 +3834,7 @@ public boolean isRaining() { public boolean setRaining(boolean raining) { WeatherChangeEvent ev = new WeatherChangeEvent(this, raining); - this.getServer().getPluginManager().callEvent(ev); + this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; @@ -3306,12 +3847,12 @@ public boolean setRaining(boolean raining) { if (raining) { pk.evid = LevelEventPacket.EVENT_START_RAIN; - int time = ThreadLocalRandom.current().nextInt(12000) + 12000; + int time = Utils.random.nextInt(12000) + 12000; pk.data = time; setRainTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_RAIN; - setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); + setRainTime(Utils.random.nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); @@ -3328,18 +3869,18 @@ public void setRainTime(int rainTime) { } public boolean isThundering() { - return isRaining() && this.thundering; + return raining && this.thundering; } public boolean setThundering(boolean thundering) { ThunderChangeEvent ev = new ThunderChangeEvent(this, thundering); - this.getServer().getPluginManager().callEvent(ev); + this.server.getPluginManager().callEvent(ev); if (ev.isCancelled()) { return false; } - if (thundering && !isRaining()) { + if (thundering && !raining) { setRaining(true); } @@ -3349,12 +3890,12 @@ public boolean setThundering(boolean thundering) { // These numbers are from Minecraft if (thundering) { pk.evid = LevelEventPacket.EVENT_START_THUNDER; - int time = ThreadLocalRandom.current().nextInt(12000) + 3600; + int time = Utils.random.nextInt(12000) + 3600; pk.data = time; setThunderTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_THUNDER; - setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); + setThunderTime(Utils.random.nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); @@ -3376,8 +3917,7 @@ public void sendWeather(Player[] players) { } LevelEventPacket pk = new LevelEventPacket(); - - if (this.isRaining()) { + if (this.raining) { pk.evid = LevelEventPacket.EVENT_START_RAIN; pk.data = this.rainTime; } else { @@ -3386,6 +3926,7 @@ public void sendWeather(Player[] players) { Server.broadcastPacket(players, pk); + pk = new LevelEventPacket(); if (this.isThundering()) { pk.evid = LevelEventPacket.EVENT_START_THUNDER; pk.data = this.thunderTime; @@ -3409,27 +3950,43 @@ public void sendWeather(Collection players) { this.sendWeather(players.toArray(new Player[0])); } - public DimensionData getDimensionData() { - return this.dimensionData; - } - public int getDimension() { return this.dimensionData.getDimensionId(); } + public int getMinBlockY() { + return this.dimensionData.getMinHeight(); + } + + public int getMaxBlockY() { + return this.dimensionData.getMaxHeight(); + } + public boolean canBlockSeeSky(Vector3 pos) { return this.getHighestBlockAt(pos.getFloorX(), pos.getFloorZ()) < pos.getY(); } + public boolean canBlockSeeSky(Block block) { + return this.getHighestBlockAt((int) block.getX(), (int) block.getZ()) < block.getY(); + } + public int getStrongPower(Vector3 pos, BlockFace direction) { - return this.getBlock(pos).getStrongPower(direction); + return getStrongPower(null, pos, direction); + } + + private int getStrongPower(FullChunk cachedChunk, Vector3 pos, BlockFace direction) { + return this.getBlock(cachedChunk, pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), true).getStrongPower(direction); } public int getStrongPower(Vector3 pos) { + return getStrongPower(null, pos); + } + + private int getStrongPower(FullChunk cachedChunk, Vector3 pos) { int i = 0; for (BlockFace face : BlockFace.values()) { - i = Math.max(i, this.getStrongPower(pos.getSideVec(face), face)); + i = Math.max(i, this.getStrongPower(cachedChunk, pos.getSideVec(face), face)); if (i >= 15) { return i; @@ -3437,38 +3994,6 @@ public int getStrongPower(Vector3 pos) { } return i; -// i = Math.max(i, this.getStrongPower(pos.down(), BlockFace.DOWN)); -// -// if (i >= 15) { -// return i; -// } else { -// i = Math.max(i, this.getStrongPower(pos.up(), BlockFace.UP)); -// -// if (i >= 15) { -// return i; -// } else { -// i = Math.max(i, this.getStrongPower(pos.north(), BlockFace.NORTH)); -// -// if (i >= 15) { -// return i; -// } else { -// i = Math.max(i, this.getStrongPower(pos.south(), BlockFace.SOUTH)); -// -// if (i >= 15) { -// return i; -// } else { -// i = Math.max(i, this.getStrongPower(pos.west(), BlockFace.WEST)); -// -// if (i >= 15) { -// return i; -// } else { -// i = Math.max(i, this.getStrongPower(pos.east(), BlockFace.EAST)); -// return i >= 15 ? i : i; -// } -// } -// } -// } -// } } public boolean isSidePowered(Vector3 pos, BlockFace face) { @@ -3476,17 +4001,24 @@ public boolean isSidePowered(Vector3 pos, BlockFace face) { } public int getRedstonePower(Vector3 pos, BlockFace face) { - Block block = this.getBlock(pos); - return block.isNormalBlock() ? this.getStrongPower(pos) : block.getWeakPower(face); + return getRedstonePower(null, pos, face); + } + + private int getRedstonePower(FullChunk cachedChunk, Vector3 pos, BlockFace face) { + Block block = this.getBlock(cachedChunk, pos.getFloorX(), pos.getFloorY(), pos.getFloorZ(), true); + return block.isNormalBlock() ? this.getStrongPower(cachedChunk, pos) : block.getWeakPower(face); } public boolean isBlockPowered(Vector3 pos) { + return isBlockPowered(null, pos); + } + + public boolean isBlockPowered(FullChunk cachedChunk, Vector3 pos) { for (BlockFace face : BlockFace.values()) { - if (this.getRedstonePower(pos.getSideVec(face), face) > 0) { + if (this.getRedstonePower(cachedChunk, pos.getSideVec(face), face) > 0) { return true; } } - return false; } @@ -3509,9 +4041,6 @@ public int isBlockIndirectlyGettingPowered(Vector3 pos) { } public boolean isAreaLoaded(AxisAlignedBB bb) { - if (bb.getMaxY() < 0 || bb.getMinY() >= 256) { - return false; - } int minX = NukkitMath.floorDouble(bb.getMinX()) >> 4; int minZ = NukkitMath.floorDouble(bb.getMinZ()) >> 4; int maxX = NukkitMath.floorDouble(bb.getMaxX()) >> 4; @@ -3528,13 +4057,18 @@ public boolean isAreaLoaded(AxisAlignedBB bb) { return true; } - public int getUpdateLCG() { + private int getUpdateLCG() { return (this.updateLCG = (this.updateLCG * 3) ^ LCG_CONSTANT); } - public boolean createPortal(Block target) { + public boolean isAnimalSpawningAllowedByTime() { + int time = this.getTime() % TIME_FULL; + return time < 13184 || time > 22800; + } + + public boolean createPortal(Block target, boolean fireCharge) { if (this.getDimension() == DIMENSION_THE_END) return false; - int maxPortalSize = 23; + final int maxPortalSize = 23; final int targX = target.getFloorX(); final int targY = target.getFloorY(); final int targZ = target.getFloorZ(); @@ -3584,25 +4118,24 @@ public boolean createPortal(Block target) { //find pillar or end of portal to start scan int scanX = targX; int scanY = targY + 1; - int scanZ = targZ; for (int i = 0; i < sizePosX + 1; i++) { //this must be air - if (this.getBlockIdAt(scanX + i, scanY, scanZ) != BlockID.AIR) { + if (this.getBlockIdAt(scanX + i, scanY, targZ) != BlockID.AIR) { return false; } - if (this.getBlockIdAt(scanX + i + 1, scanY, scanZ) == BlockID.OBSIDIAN) { + if (this.getBlockIdAt(scanX + i + 1, scanY, targZ) == BlockID.OBSIDIAN) { scanX += i; break; } } //make sure that the above loop finished - if (this.getBlockIdAt(scanX + 1, scanY, scanZ) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(scanX + 1, scanY, targZ) != BlockID.OBSIDIAN) { return false; } int innerWidth = 0; - LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { - int id = this.getBlockIdAt(scanX - i, scanY, scanZ); + LOOP: for (int i = 0; i < 21; i++) { + int id = this.getBlockIdAt(scanX - i, scanY, targZ); switch (id) { case BlockID.AIR: innerWidth++; @@ -3614,8 +4147,8 @@ public boolean createPortal(Block target) { } } int innerHeight = 0; - LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { - int id = this.getBlockIdAt(scanX, scanY + i, scanZ); + LOOP: for (int i = 0; i < 21; i++) { + int id = this.getBlockIdAt(scanX, scanY + i, targZ); switch (id) { case BlockID.AIR: innerHeight++; @@ -3626,9 +4159,9 @@ public boolean createPortal(Block target) { return false; } } - if (!(innerWidth <= maxPortalSize - 2 + if (!(innerWidth <= 21 && innerWidth >= 2 - && innerHeight <= maxPortalSize - 2 + && innerHeight <= 21 && innerHeight >= 3)) { return false; } @@ -3636,18 +4169,18 @@ public boolean createPortal(Block target) { for (int height = 0; height < innerHeight + 1; height++) { if (height == innerHeight) { for (int width = 0; width < innerWidth; width++) { - if (this.getBlockIdAt(scanX - width, scanY + height, scanZ) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(scanX - width, scanY + height, targZ) != BlockID.OBSIDIAN) { return false; } } } else { - if (this.getBlockIdAt(scanX + 1, scanY + height, scanZ) != BlockID.OBSIDIAN - || this.getBlockIdAt(scanX - innerWidth, scanY + height, scanZ) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(scanX + 1, scanY + height, targZ) != BlockID.OBSIDIAN + || this.getBlockIdAt(scanX - innerWidth, scanY + height, targZ) != BlockID.OBSIDIAN) { return false; } for (int width = 0; width < innerWidth; width++) { - if (this.getBlockIdAt(scanX - width, scanY + height, scanZ) != BlockID.AIR) { + if (this.getBlockIdAt(scanX - width, scanY + height, targZ) != BlockID.AIR) { return false; } } @@ -3656,36 +4189,39 @@ public boolean createPortal(Block target) { for (int height = 0; height < innerHeight; height++) { for (int width = 0; width < innerWidth; width++) { - this.setBlock(new Vector3(scanX - width, scanY + height, scanZ), Block.get(BlockID.NETHER_PORTAL)); + this.setBlock(new Vector3(scanX - width, scanY + height, targZ), Block.get(BlockID.NETHER_PORTAL)); } } - this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); + if (fireCharge) { + this.addSound(target, cn.nukkit.level.Sound.MOB_GHAST_FIREBALL); + } else { + this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); + } return true; } else if (sizeZ >= 2 && sizeZ <= maxPortalSize) { //start scan from 1 block above base //find pillar or end of portal to start scan - int scanX = targX; int scanY = targY + 1; int scanZ = targZ; for (int i = 0; i < sizePosZ + 1; i++) { //this must be air - if (this.getBlockIdAt(scanX, scanY, scanZ + i) != BlockID.AIR) { + if (this.getBlockIdAt(targX, scanY, scanZ + i) != BlockID.AIR) { return false; } - if (this.getBlockIdAt(scanX, scanY, scanZ + i + 1) == BlockID.OBSIDIAN) { + if (this.getBlockIdAt(targX, scanY, scanZ + i + 1) == BlockID.OBSIDIAN) { scanZ += i; break; } } //make sure that the above loop finished - if (this.getBlockIdAt(scanX, scanY, scanZ + 1) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(targX, scanY, scanZ + 1) != BlockID.OBSIDIAN) { return false; } int innerWidth = 0; - LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { - int id = this.getBlockIdAt(scanX, scanY, scanZ - i); + LOOP: for (int i = 0; i < 21; i++) { + int id = this.getBlockIdAt(targX, scanY, scanZ - i); switch (id) { case BlockID.AIR: innerWidth++; @@ -3697,8 +4233,8 @@ public boolean createPortal(Block target) { } } int innerHeight = 0; - LOOP: for (int i = 0; i < maxPortalSize - 2; i++) { - int id = this.getBlockIdAt(scanX, scanY + i, scanZ); + LOOP: for (int i = 0; i < 21; i++) { + int id = this.getBlockIdAt(targX, scanY + i, scanZ); switch (id) { case BlockID.AIR: innerHeight++; @@ -3709,9 +4245,9 @@ public boolean createPortal(Block target) { return false; } } - if (!(innerWidth <= maxPortalSize - 2 + if (!(innerWidth <= 21 && innerWidth >= 2 - && innerHeight <= maxPortalSize - 2 + && innerHeight <= 21 && innerHeight >= 3)) { return false; } @@ -3719,18 +4255,18 @@ public boolean createPortal(Block target) { for (int height = 0; height < innerHeight + 1; height++) { if (height == innerHeight) { for (int width = 0; width < innerWidth; width++) { - if (this.getBlockIdAt(scanX, scanY + height, scanZ - width) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(targX, scanY + height, scanZ - width) != BlockID.OBSIDIAN) { return false; } } } else { - if (this.getBlockIdAt(scanX, scanY + height, scanZ + 1) != BlockID.OBSIDIAN - || this.getBlockIdAt(scanX, scanY + height, scanZ - innerWidth) != BlockID.OBSIDIAN) { + if (this.getBlockIdAt(targX, scanY + height, scanZ + 1) != BlockID.OBSIDIAN + || this.getBlockIdAt(targX, scanY + height, scanZ - innerWidth) != BlockID.OBSIDIAN) { return false; } for (int width = 0; width < innerWidth; width++) { - if (this.getBlockIdAt(scanX, scanY + height, scanZ - width) != BlockID.AIR) { + if (this.getBlockIdAt(targX, scanY + height, scanZ - width) != BlockID.AIR) { return false; } } @@ -3739,64 +4275,100 @@ public boolean createPortal(Block target) { for (int height = 0; height < innerHeight; height++) { for (int width = 0; width < innerWidth; width++) { - this.setBlock(new Vector3(scanX, scanY + height, scanZ - width), Block.get(BlockID.NETHER_PORTAL)); + this.setBlock(new Vector3(targX, scanY + height, scanZ - width), Block.get(BlockID.NETHER_PORTAL)); } } - this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); + if (fireCharge) { + this.addSound(target, cn.nukkit.level.Sound.MOB_GHAST_FIREBALL); + } else { + this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); + } return true; } return false; } -// private static void orderGetRidings(Entity entity, LongSet set) { -// if (entity.riding != null) { -// if(!set.add(entity.riding.getId())) { -// throw new RuntimeException("Circular entity link detected (id = "+entity.riding.getId()+")"); -// } -// orderGetRidings(entity.riding, set); -// } -// } -// -// public List orderChunkEntitiesForSpawn(int chunkX, int chunkZ) { -// return orderChunkEntitiesForSpawn(getChunk(chunkX, chunkZ, false)); -// } -// -// public List orderChunkEntitiesForSpawn(BaseFullChunk chunk) { -// Comparator comparator = (o1, o2) -> { -// if (o1.riding == null) { -// if(o2 == null) { -// return 0; -// } -// -// return -1; -// } -// -// if (o2.riding == null) { -// return 1; -// } -// -// LongSet ridings = new LongOpenHashSet(); -// orderGetRidings(o1, ridings); -// -// if(ridings.contains(o2.getId())) { -// return 1; -// } -// -// ridings.clear(); -// orderGetRidings(o2, ridings); -// -// if(ridings.contains(o1.getId())) { -// return -1; -// } -// -// return 0; -// }; -// -// List sorted = new ArrayList<>(chunk.getEntities().values()); -// sorted.sort(comparator); -// -// return sorted; -// } + public Position calculatePortalMirror(Vector3 portal) { + double x; + double y; + double z; + + if (this.getDimension() == DIMENSION_NETHER) { + x = Math.floor(portal.getFloorX() << 3); + y = NukkitMath.clamp(EnumLevel.mRound32(portal.getFloorY()), 70, 256 - 10); + z = Math.floor(portal.getFloorZ() << 3); + + return new Position(x, y, z, Server.getInstance().getDefaultLevel()); + } else { + Level nether = Server.getInstance().getLevelByName("nether"); + if (nether == null) { + return null; + } + + x = Math.floor(portal.getFloorX() >> 3); + y = NukkitMath.clamp(EnumLevel.mRound32(portal.getFloorY()), 70, 128 - 10); + z = Math.floor(portal.getFloorZ() >> 3); + + return new Position(x, y, z, nether); + } + } + + public void setGeneratorTaskFactory(GeneratorTaskFactory factory) { + if (factory == null) { + throw new NullPointerException("GeneratorTaskFactory can not be null!"); + } + this.generatorTaskFactory = factory; + } + + public boolean isBlockWaterloggedAt(FullChunk chunk, int x, int y, int z) { + if (chunk == null || y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return false; + } + int block = chunk.getBlockId(x & 0x0f, y, z & 0x0f, Block.LAYER_WATERLOGGED); + return Block.hasWater(block); + } + + @Override + public String toString() { + return "Level@" + Integer.toHexString(hashCode()) + "[" + this.folderName + ']'; + } + + public void asyncChunk(BaseChunk chunk, long timestamp, int x, int z) { + this.asyncChunkThread.queue(chunk, timestamp, x, z, this.dimensionData); + } + + public PersistentDataContainer getPersistentDataContainer(Vector3 position) { + return this.getPersistentDataContainer(position, false); + } + + public PersistentDataContainer getPersistentDataContainer(Vector3 position, boolean create) { + BlockEntity blockEntity = this.getBlockEntity(position); + if (blockEntity != null) { + return blockEntity.getPersistentDataContainer(); + } + + if (create) { + CompoundTag compound = BlockEntity.getDefaultCompound(position, BlockEntity.PERSISTENT_CONTAINER); + blockEntity = BlockEntity.createBlockEntity(BlockEntity.PERSISTENT_CONTAINER, this.getChunk(position.getChunkX(), position.getChunkZ()), compound); + + if (blockEntity == null) { + throw new IllegalStateException("Failed to create persistent container block entity at " + position); + } + return blockEntity.getPersistentDataContainer(); + } + + return new DelegatePersistentDataContainer() { + @Override + protected PersistentDataContainer createDelegate() { + return getPersistentDataContainer(position, true); + } + }; + } + + public boolean hasPersistentDataContainer(Vector3 position) { + BlockEntity blockEntity = this.getBlockEntity(position); + return blockEntity != null && blockEntity.hasPersistentDataContainer(); + } } diff --git a/src/main/java/cn/nukkit/level/LevelProviderConverter.java b/src/main/java/cn/nukkit/level/LevelProviderConverter.java deleted file mode 100644 index 51e78cefe7a..00000000000 --- a/src/main/java/cn/nukkit/level/LevelProviderConverter.java +++ /dev/null @@ -1,141 +0,0 @@ -package cn.nukkit.level; - -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.anvil.Anvil; -import cn.nukkit.level.format.anvil.Chunk; -import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.format.generic.ChunkConverter; -import cn.nukkit.level.format.leveldb.LevelDB; -import cn.nukkit.level.format.mcregion.McRegion; -import cn.nukkit.level.format.mcregion.RegionLoader; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.utils.LevelException; -import cn.nukkit.utils.Utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class LevelProviderConverter { - - private LevelProvider provider; - private Class toClass; - private Level level; - private String path; - - LevelProviderConverter(Level level, String path) { - this.level = level; - this.path = path; - } - - LevelProviderConverter from(LevelProvider provider) { - if (!(provider instanceof McRegion) && !(provider instanceof LevelDB)) { - throw new IllegalArgumentException("From type can be only McRegion or LevelDB"); - } - this.provider = provider; - return this; - } - - LevelProviderConverter to(Class toClass) { - if (toClass != Anvil.class) { - throw new IllegalArgumentException("To type can be only Anvil"); - } - this.toClass = toClass; - return this; - } - - LevelProvider perform() throws IOException { - new File(path).mkdir(); - File dat = new File(provider.getPath(), "level.dat.old"); - new File(provider.getPath(), "level.dat").renameTo(dat); - Utils.copyFile(dat, new File(path, "level.dat")); - LevelProvider result; - try { - if (provider instanceof LevelDB) { - try (FileInputStream stream = new FileInputStream(path + "level.dat")) { - stream.skip(8); - CompoundTag levelData = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN); - if (levelData != null) { - NBTIO.writeGZIPCompressed(new CompoundTag().putCompound("Data", levelData), new FileOutputStream(path + "level.dat")); - } else { - throw new IOException("LevelData can not be null"); - } - } catch (IOException e) { - throw new LevelException("Invalid level.dat"); - } - } - result = toClass.getConstructor(Level.class, String.class).newInstance(level, path); - } catch (Exception e) { - throw new RuntimeException(e); - } - if (toClass == Anvil.class) { - if (provider instanceof McRegion) { - new File(path, "region").mkdir(); - for (File file : new File(provider.getPath() + "region/").listFiles()) { - Matcher m = Pattern.compile("-?\\d+").matcher(file.getName()); - int regionX, regionZ; - try { - if (m.find()) { - regionX = Integer.parseInt(m.group()); - } else continue; - if (m.find()) { - regionZ = Integer.parseInt(m.group()); - } else continue; - } catch (NumberFormatException e) { - continue; - } - RegionLoader region = new RegionLoader(provider, regionX, regionZ); - for (Integer index : region.getLocationIndexes()) { - int chunkX = index & 0x1f; - int chunkZ = index >> 5; - BaseFullChunk old = region.readChunk(chunkX, chunkZ); - if (old == null) continue; - int x = (regionX << 5) | chunkX; - int z = (regionZ << 5) | chunkZ; - FullChunk chunk = new ChunkConverter(result) - .from(old) - .to(Chunk.class) - .perform(); - result.saveChunk(x, z, chunk); - } - region.close(); - } - } - if (provider instanceof LevelDB) { - new File(path, "region").mkdir(); - for (byte[] key : ((LevelDB) provider).getTerrainKeys()) { - int x = getChunkX(key); - int z = getChunkZ(key); - BaseFullChunk old = ((LevelDB) provider).readChunk(x, z); - FullChunk chunk = new ChunkConverter(result) - .from(old) - .to(Chunk.class) - .perform(); - result.saveChunk(x, z, chunk); - } - } - result.doGarbageCollection(); - } - return result; - } - - private static int getChunkX(byte[] key) { - return (key[3] << 24) | - (key[2] << 16) | - (key[1] << 8) | - key[0]; - } - - private static int getChunkZ(byte[] key) { - return (key[7] << 24) | - (key[6] << 16) | - (key[5] << 8) | - key[4]; - } -} diff --git a/src/main/java/cn/nukkit/level/ListChunkManager.java b/src/main/java/cn/nukkit/level/ListChunkManager.java index 1df86a9a595..8c2c1310443 100644 --- a/src/main/java/cn/nukkit/level/ListChunkManager.java +++ b/src/main/java/cn/nukkit/level/ListChunkManager.java @@ -1,6 +1,7 @@ package cn.nukkit.level; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.format.generic.BaseFullChunk; import java.util.ArrayList; @@ -9,8 +10,8 @@ public class ListChunkManager implements ChunkManager { - private ChunkManager parent; - private List blocks; + private final ChunkManager parent; + private final List blocks; public ListChunkManager(ChunkManager parent) { this.parent = parent; @@ -18,23 +19,17 @@ public ListChunkManager(ChunkManager parent) { } @Override - public int getBlockIdAt(int x, int y, int z) { - Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); - return optionalBlock.map(Block::getId).orElseGet(() -> this.parent.getBlockIdAt(x, y, z)); + public int getBlockIdAt(int x, int y, int z, BlockLayer layer) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer).findAny(); + return optionalBlock.map(Block::getId).orElseGet(() -> this.parent.getBlockIdAt(x, y, z, layer)); } @Override - public void setBlockFullIdAt(int x, int y, int z, int fullId) { - this.blocks.removeIf(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z); - this.blocks.add(Block.get(fullId, null, x, y, z)); - } - - @Override - public void setBlockIdAt(int x, int y, int z, int id) { - Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); - Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + public void setBlockIdAt(int x, int y, int z, BlockLayer layer, int id) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer).findAny(); + Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z, layer), this.getBlockDataAt(x, y, z, layer), new Position(x, y, z), layer)); this.blocks.remove(block); - this.blocks.add(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + this.blocks.add(Block.get(this.getBlockIdAt(x, y, z, layer), this.getBlockDataAt(x, y, z, layer), new Position(x, y, z), layer)); } @Override @@ -44,20 +39,38 @@ public void setBlockAt(int x, int y, int z, int id, int data) { } @Override - public int getBlockDataAt(int x, int y, int z) { - Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); - return optionalBlock.map(Block::getDamage).orElseGet(() -> this.parent.getBlockDataAt(x, y, z)); + public void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, int fullId) { + this.blocks.removeIf(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer); + this.blocks.add(Block.get(fullId, null, x, y, z, layer)); + } + + @Override + public int getBlockDataAt(int x, int y, int z, BlockLayer layer) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer).findAny(); + return optionalBlock.map(Block::getDamage).orElseGet(() -> this.parent.getBlockDataAt(x, y, z, layer)); } @Override - public void setBlockDataAt(int x, int y, int z, int data) { - Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z).findAny(); - Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z), this.getBlockDataAt(x, y, z), new Position(x, y, z))); + public void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data) { + Optional optionalBlock = this.blocks.stream().filter(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer).findAny(); + Block block = optionalBlock.orElse(Block.get(this.getBlockIdAt(x, y, z, layer), this.getBlockDataAt(x, y, z, layer), new Position(x, y, z), layer)); this.blocks.remove(block); block.setDamage(data); this.blocks.add(block); } + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id) { + return this.setBlockAtLayer(x, y, z, layer, id, 0); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id, int data) { + this.blocks.removeIf(block -> block.getFloorX() == x && block.getFloorY() == y && block.getFloorZ() == z && block.getLayer() == layer); + this.blocks.add(Block.get(id, data, new Position(x, y, z), layer)); + return true; + } + @Override public BaseFullChunk getChunk(int chunkX, int chunkZ) { return this.parent.getChunk(chunkX, chunkZ); @@ -81,5 +94,4 @@ public long getSeed() { public List getBlocks() { return this.blocks; } - } diff --git a/src/main/java/cn/nukkit/level/Location.java b/src/main/java/cn/nukkit/level/Location.java index baa119cc08d..d473c0fba79 100644 --- a/src/main/java/cn/nukkit/level/Location.java +++ b/src/main/java/cn/nukkit/level/Location.java @@ -4,7 +4,7 @@ import cn.nukkit.utils.LevelException; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Location extends Position { @@ -26,19 +26,19 @@ public Location(double x, double y) { } public Location(double x, double y, double z) { - this(x, y, z, 0); + this(x, y, z, 0, 0, 0, null); } public Location(double x, double y, double z, Level level) { - this(x, y, z, 0, 0, level); + this(x, y, z, 0, 0, 0, level); } public Location(double x, double y, double z, double yaw) { - this(x, y, z, yaw, 0); + this(x, y, z, yaw, 0, 0, null); } public Location(double x, double y, double z, double yaw, double pitch) { - this(x, y, z, yaw, pitch, null); + this(x, y, z, yaw, pitch, 0, null); } public Location(double x, double y, double z, double yaw, double pitch, Level level) { @@ -60,19 +60,19 @@ public Location(double x, double y, double z, double yaw, double pitch, double h } public static Location fromObject(Vector3 pos) { - return fromObject(pos, null, 0.0f, 0.0f); + return fromObject(pos, null, 0.0f, 0.0f, 0.0f); } public static Location fromObject(Vector3 pos, Level level) { - return fromObject(pos, level, 0.0f, 0.0f); + return fromObject(pos, level, 0.0f, 0.0f, 0.0f); } public static Location fromObject(Vector3 pos, Level level, double yaw) { - return fromObject(pos, level, yaw, 0.0f); + return fromObject(pos, level, yaw, 0.0f, 0.0f); } public static Location fromObject(Vector3 pos, Level level, double yaw, double pitch) { - return new Location(pos.x, pos.y, pos.z, yaw, pitch, (level == null) ? ((pos instanceof Position) ? ((Position) pos).level : null) : level); + return fromObject(pos, level, yaw, pitch, 0.0f); } public static Location fromObject(Vector3 pos, Level level, double yaw, double pitch, double headYaw) { @@ -91,9 +91,30 @@ public double getHeadYaw() { return this.headYaw; } + public Location setYaw(double yaw) { + this.yaw = yaw; + return this; + } + + public Location setBothYaw(double yaw) { + this.yaw = yaw; + this.headYaw = yaw; + return this; + } + + public Location setPitch(double pitch) { + this.pitch = pitch; + return this; + } + + public Location setHeadYaw(double headYaw) { + this.headYaw = headYaw; + return this; + } + @Override public String 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 + ", headYaw=" + this.headYaw + ")"; + return "Location(level=" + (this.isValid() ? this.getLevel().getName() : "null") + ", x=" + this.x + ", y=" + this.y + ", z=" + this.z + ", yaw=" + this.yaw + ", pitch=" + this.pitch + ", headYaw=" + this.headYaw + ')'; } @Override @@ -178,8 +199,8 @@ public Location abs() { } public Vector3 getDirectionVector() { - double pitch = ((getPitch() + 90) * Math.PI) / 180; - double yaw = ((getYaw() + 90) * Math.PI) / 180; + double pitch = ((this.pitch + 90) * Math.PI) / 180; + double yaw = ((this.yaw + 90) * Math.PI) / 180; double x = Math.sin(pitch) * Math.cos(yaw); double z = Math.sin(pitch) * Math.sin(yaw); double y = Math.cos(pitch); diff --git a/src/main/java/cn/nukkit/level/MovingObjectPosition.java b/src/main/java/cn/nukkit/level/MovingObjectPosition.java index b65ad226e6d..8f1cc5a47ab 100644 --- a/src/main/java/cn/nukkit/level/MovingObjectPosition.java +++ b/src/main/java/cn/nukkit/level/MovingObjectPosition.java @@ -4,7 +4,7 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class MovingObjectPosition { diff --git a/src/main/java/cn/nukkit/level/ParticleEffect.java b/src/main/java/cn/nukkit/level/ParticleEffect.java index dde4da6509a..5f08c259d53 100644 --- a/src/main/java/cn/nukkit/level/ParticleEffect.java +++ b/src/main/java/cn/nukkit/level/ParticleEffect.java @@ -1,6 +1,7 @@ package cn.nukkit.level; public enum ParticleEffect { + ARROWSPELL("minecraft:arrow_spell_emitter"), BALLOON_GAS("minecraft:balloon_gas_particle"), BASIC_BUBBLE("minecraft:basic_bubble_particle"), diff --git a/src/main/java/cn/nukkit/level/Position.java b/src/main/java/cn/nukkit/level/Position.java index f128d93155f..5ee0e5a17a5 100644 --- a/src/main/java/cn/nukkit/level/Position.java +++ b/src/main/java/cn/nukkit/level/Position.java @@ -1,16 +1,18 @@ package cn.nukkit.level; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.utils.LevelException; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Position extends Vector3 { + public Level level; public Position() { @@ -78,7 +80,7 @@ public Position getSide(BlockFace face, int step) { @Override public String toString() { - return "Position(level=" + (this.isValid() ? this.getLevel().getName() : "null") + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ")"; + return "Position(level=" + (this.isValid() ? this.level.getName() : "null") + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ')'; } @Override @@ -90,7 +92,12 @@ public Position setComponents(double x, double y, double z) { } public Block getLevelBlock() { - if (this.isValid()) return this.level.getBlock(this); + if (this.isValid()) return this.level.getBlock(this, BlockLayer.NORMAL, true); + else throw new LevelException("Undefined Level reference"); + } + + public Block getLevelBlock(BlockLayer layer) { + if (this.isValid()) return this.level.getBlock(this, layer, true); else throw new LevelException("Undefined Level reference"); } diff --git a/src/main/java/cn/nukkit/level/Sound.java b/src/main/java/cn/nukkit/level/Sound.java index 0ed349b41f0..2328552a3cf 100644 --- a/src/main/java/cn/nukkit/level/Sound.java +++ b/src/main/java/cn/nukkit/level/Sound.java @@ -4,6 +4,7 @@ * @author CreeperFace */ public enum Sound { + AMBIENT_BASALT_DELTAS_ADDITIONS("ambient.basalt_deltas.additions"), AMBIENT_BASALT_DELTAS_LOOP("ambient.basalt_deltas.loop"), AMBIENT_BASALT_DELTAS_MOOD("ambient.basalt_deltas.mood"), diff --git a/src/main/java/cn/nukkit/level/biome/Biome.java b/src/main/java/cn/nukkit/level/biome/Biome.java index c6b4dce2b16..6a0e69ca75f 100644 --- a/src/main/java/cn/nukkit/level/biome/Biome.java +++ b/src/main/java/cn/nukkit/level/biome/Biome.java @@ -5,26 +5,22 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Biome implements BlockID { - public static final int MAX_BIOMES = 256; - public static final Biome[] biomes = new Biome[MAX_BIOMES]; + public static final Biome[] biomes = new Biome[256]; public static final List unorderedBiomes = new ObjectArrayList<>(); private static final Int2ObjectMap runtimeId2Identifier = new Int2ObjectOpenHashMap<>(); @@ -34,14 +30,10 @@ public abstract class Biome implements BlockID { private float heightVariation = 0.3f; static { - try (InputStream stream = Biome.class.getClassLoader().getResourceAsStream("biome_id_map.json")) { - JsonObject json = JsonParser.parseReader(new InputStreamReader(stream)).getAsJsonObject(); - for (String identifier : json.keySet()) { - int biomeId = json.get(identifier).getAsInt(); - runtimeId2Identifier.put(biomeId, identifier); - } - } catch (NullPointerException | IOException e) { - throw new AssertionError("Unable to load biome mapping from biome_id_map.json", e); + JsonObject json = Utils.loadJsonResource("biome_id_map.json").getAsJsonObject(); + for (String identifier : json.keySet()) { + int biomeId = json.get(identifier).getAsInt(); + runtimeId2Identifier.put(biomeId, identifier); } } @@ -129,7 +121,7 @@ public float getHeightVariation() { @Override public int hashCode() { - return getId(); + return id; } @Override @@ -137,7 +129,11 @@ public boolean equals(Object obj) { return hashCode() == obj.hashCode(); } - //whether or not water should freeze into ice on generation + /** + * Whether or not water should freeze into ice on generation + * + * @return overhang + */ public boolean isFreezing() { return false; } diff --git a/src/main/java/cn/nukkit/level/biome/BiomeSelector.java b/src/main/java/cn/nukkit/level/biome/BiomeSelector.java index d4fbf007e7e..fc17b25cba7 100644 --- a/src/main/java/cn/nukkit/level/biome/BiomeSelector.java +++ b/src/main/java/cn/nukkit/level/biome/BiomeSelector.java @@ -4,12 +4,11 @@ import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ -//WIP -//do not touch lol public class BiomeSelector { + private final SimplexF temperature; private final SimplexF rainfall; private final SimplexF river; @@ -25,90 +24,27 @@ public BiomeSelector(NukkitRandom random) { } public Biome pickBiome(int x, int z) { - /*double noiseOcean = ocean.noise2D(x, z, true); - double noiseTemp = temperature.noise2D(x, z, true); - double noiseRain = rainfall.noise2D(x, z, true); - if (noiseOcean < -0.15) { - if (noiseOcean < -0.9) { - return EnumBiome.MUSHROOM_ISLAND.biome; - } else { - return EnumBiome.OCEAN.biome; - } - } - double noiseRiver = Math.abs(river.noise2D(x, z, true)); - if (noiseRiver < 0.04) { - return EnumBiome.RIVER.biome; - } - return EnumBiome.OCEAN.biome;*/ - - // > using actual biome selectors in 2018 - //x >>= 6; - //z >>= 6; - - //here's a test for just every biome, for making sure there's no crashes: - //return Biome.unorderedBiomes.get(Math.abs(((int) x >> 5) ^ 6457109 * ((int) z >> 5) ^ 9800471) % Biome.unorderedBiomes.size()); - - //a couple random high primes: 6457109 9800471 7003231 - - //here's a test for mesas - /*boolean doPlateau = ocean.noise2D(x, z, true) < 0f; - boolean doF = rainfall.noise2D(x, z, true) < -0.5f; - if (doPlateau) { - boolean doM = temperature.noise2D(x, z, true) < 0f; - if (doM && doF) { - return EnumBiome.MESA_PLATEAU_F_M.biome; - } else if (doM) { - return EnumBiome.MESA_PLATEAU_M.biome; - } else if (doF) { - return EnumBiome.MESA_PLATEAU_F.biome; - } else { - return EnumBiome.MESA_PLATEAU.biome; - } - } else { - return doF ? EnumBiome.MESA_BRYCE.biome : EnumBiome.MESA.biome; - }*/ - - //here's a test for extreme hills + oceans - /*double noiseOcean = ocean.noise2D(x, z, true); - if (noiseOcean < -0.15f) { - return EnumBiome.OCEAN.biome; - } else if (noiseOcean < -0.19f) { - return EnumBiome.STONE_BEACH.biome; - } else { - boolean plus = temperature.noise2D(x, z, true) < 0f; - boolean m = rainfall.noise2D(x, z, true) < 0f; - if (plus && m) { - return EnumBiome.EXTREME_HILLS_PLUS_M.biome; - } else if (m) { - return EnumBiome.EXTREME_HILLS_M.biome; - } else if (plus) { - return EnumBiome.EXTREME_HILLS_PLUS.biome; - } else { - return EnumBiome.EXTREME_HILLS.biome; - } - }*/ - float noiseOcean = ocean.noise2D(x, z, true); float noiseRiver = river.noise2D(x, z, true); float temperature = this.temperature.noise2D(x, z, true); float rainfall = this.rainfall.noise2D(x, z, true); EnumBiome biome; if (noiseOcean < -0.15f) { - if (noiseOcean < -0.91f) { - if (noiseOcean < -0.92f) { - biome = EnumBiome.MUSHROOM_ISLAND; - } else { - biome = EnumBiome.MUSHROOM_ISLAND_SHORE; - } + if (noiseOcean < -0.65f) { + biome = EnumBiome.MUSHROOM_ISLAND_SHORE; } else { - if (rainfall < 0f) { - biome = EnumBiome.OCEAN; + if (rainfall < 0f) { + if (temperature < -0.4f) { + biome = EnumBiome.FROZEN_OCEAN; + } else { + biome = EnumBiome.OCEAN; + } } else { biome = EnumBiome.DEEP_OCEAN; } } } else if (Math.abs(noiseRiver) < 0.04f) { - if (temperature < -0.3f) { + if (temperature < -0.4f) { biome = EnumBiome.FROZEN_RIVER; } else { biome = EnumBiome.RIVER; @@ -116,7 +52,7 @@ public Biome pickBiome(int x, int z) { } else { float hills = this.hills.noise2D(x, z, true); if (temperature < -0.379f) { - //freezing + // freezing if (noiseOcean < -0.12f) { biome = EnumBiome.COLD_BEACH; } else if (rainfall < 0f) { @@ -137,7 +73,7 @@ public Biome pickBiome(int x, int z) { } else if (noiseOcean < -0.12f) { biome = EnumBiome.BEACH; } else if (temperature < 0f) { - //cold + // cold if (hills < 0.2f) { if (rainfall < -0.5f) { biome = EnumBiome.EXTREME_HILLS_M; @@ -160,10 +96,10 @@ public Biome pickBiome(int x, int z) { } } } else if (temperature < 0.5f) { - //normal + // normal if (temperature < 0.25f) { if (rainfall < 0f) { - if (noiseOcean < 0f){ + if (noiseOcean < 0f) { biome = EnumBiome.SUNFLOWER_PLAINS; } else { biome = EnumBiome.PLAINS; @@ -203,7 +139,7 @@ public Biome pickBiome(int x, int z) { } } } else { - //hot + // hot if (rainfall < 0f) { if (noiseOcean < 0f) { biome = EnumBiome.DESERT_M; diff --git a/src/main/java/cn/nukkit/level/biome/EnumBiome.java b/src/main/java/cn/nukkit/level/biome/EnumBiome.java index 751aa13a98e..2fbab084ea2 100644 --- a/src/main/java/cn/nukkit/level/biome/EnumBiome.java +++ b/src/main/java/cn/nukkit/level/biome/EnumBiome.java @@ -1,5 +1,6 @@ package cn.nukkit.level.biome; +import cn.nukkit.level.biome.impl.EndBiome; import cn.nukkit.level.biome.impl.HellBiome; import cn.nukkit.level.biome.impl.beach.BeachBiome; import cn.nukkit.level.biome.impl.beach.ColdBeachBiome; @@ -16,6 +17,10 @@ import cn.nukkit.level.biome.impl.mesa.*; import cn.nukkit.level.biome.impl.mushroom.MushroomIslandBiome; import cn.nukkit.level.biome.impl.mushroom.MushroomIslandShoreBiome; +import cn.nukkit.level.biome.impl.nether.BasaltDeltasBiome; +import cn.nukkit.level.biome.impl.nether.CrimsonForestBiome; +import cn.nukkit.level.biome.impl.nether.SoulSandValleyBiome; +import cn.nukkit.level.biome.impl.nether.WarpedForestBiome; import cn.nukkit.level.biome.impl.ocean.DeepOceanBiome; import cn.nukkit.level.biome.impl.ocean.FrozenOceanBiome; import cn.nukkit.level.biome.impl.ocean.OceanBiome; @@ -41,25 +46,27 @@ *

*/ public enum EnumBiome { - OCEAN(0, new OceanBiome()),// + + OCEAN(0, new OceanBiome()), PLAINS(1, new PlainsBiome()), DESERT(2, new DesertBiome()), EXTREME_HILLS(3, new ExtremeHillsBiome()), FOREST(4, new ForestBiome()), TAIGA(5, new TaigaBiome()), SWAMP(6, new SwampBiome()), - RIVER(7, new RiverBiome()),// + RIVER(7, new RiverBiome()), HELL(8, new HellBiome()), - FROZEN_OCEAN(10, new FrozenOceanBiome()), //DOES NOT GENERATE NATUALLY + END(9, new EndBiome()), + FROZEN_OCEAN(10, new FrozenOceanBiome()), FROZEN_RIVER(11, new FrozenRiverBiome()), ICE_PLAINS(12, new IcePlainsBiome()), - MUSHROOM_ISLAND(14, new MushroomIslandBiome()),// + MUSHROOM_ISLAND(14, new MushroomIslandBiome()), MUSHROOM_ISLAND_SHORE(15, new MushroomIslandShoreBiome()), BEACH(16, new BeachBiome()), DESERT_HILLS(17, new DesertHillsBiome()), FOREST_HILLS(18, new ForestHillsBiome()), TAIGA_HILLS(19, new TaigaHillsBiome()), - EXTREME_HILLS_EDGE(20, new ExtremeHillsEdgeBiome()), //DOES NOT GENERATE NATUALLY + EXTREME_HILLS_EDGE(20, new ExtremeHillsEdgeBiome()), JUNGLE(21, new JungleBiome()), JUNGLE_HILLS(22, new JungleHillsBiome()), JUNGLE_EDGE(23, new JungleEdgeBiome()), @@ -79,14 +86,14 @@ public enum EnumBiome { MESA(37, new MesaBiome()), MESA_PLATEAU_F(38, new MesaPlateauFBiome()), MESA_PLATEAU(39, new MesaPlateauBiome()), - // All biomes below this comment are mutated variants of existing biomes + // All biomes below this comment are mutated variants of existing biomes SUNFLOWER_PLAINS(129, new SunflowerPlainsBiome()), DESERT_M(130, new DesertMBiome()), EXTREME_HILLS_M(131, new ExtremeHillsMBiome()), FLOWER_FOREST(132, new FlowerForestBiome()), TAIGA_M(133, new TaigaMBiome()), SWAMPLAND_M(134, new SwamplandMBiome()), - //no, the following jumps in IDs are NOT mistakes + // No, the following jumps in IDs are NOT mistakes ICE_PLAINS_SPIKES(140, new IcePlainsSpikesBiome()), JUNGLE_M(149, new JungleMBiome()), JUNGLE_EDGE_M(151, new JungleEdgeMBiome()), @@ -100,7 +107,12 @@ public enum EnumBiome { SAVANNA_PLATEAU_M(164, new SavannaPlateauMBiome()), MESA_BRYCE(165, new MesaBryceBiome()), MESA_PLATEAU_F_M(166, new MesaPlateauFMBiome()), - MESA_PLATEAU_M(167, new MesaPlateauMBiome()); + MESA_PLATEAU_M(167, new MesaPlateauMBiome()), + + SOULSAND_VALLEY(178, new SoulSandValleyBiome()), + CRIMSON_FOREST(179, new CrimsonForestBiome()), + WARPED_FOREST(180, new WarpedForestBiome()), + BASALT_DELTAS(181, new BasaltDeltasBiome()); public final int id; public final Biome biome; diff --git a/src/main/java/cn/nukkit/level/biome/impl/EndBiome.java b/src/main/java/cn/nukkit/level/biome/impl/EndBiome.java new file mode 100644 index 00000000000..4eb1c6e6bf7 --- /dev/null +++ b/src/main/java/cn/nukkit/level/biome/impl/EndBiome.java @@ -0,0 +1,20 @@ +package cn.nukkit.level.biome.impl; + +import cn.nukkit.level.biome.Biome; + +public class EndBiome extends Biome { + + public EndBiome() { + + } + + @Override + public String getName() { + return "The End"; + } + + @Override + public boolean canRain() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/biome/impl/HellBiome.java b/src/main/java/cn/nukkit/level/biome/impl/HellBiome.java index 02eab20af32..9ba7ad4cc01 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/HellBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/HellBiome.java @@ -1,13 +1,29 @@ package cn.nukkit.level.biome.impl; -import cn.nukkit.level.biome.Biome; +import cn.nukkit.block.Block; +import cn.nukkit.level.biome.type.CoveredBiome; + +public class HellBiome extends CoveredBiome { + + public HellBiome() { + + } -public class HellBiome extends Biome { @Override public String getName() { return "Hell"; } + @Override + public int getSurfaceId(int x, int y, int z) { + return Block.NETHERRACK << Block.DATA_BITS; + } + + @Override + public int getGroundId(int x, int y, int z) { + return Block.NETHERRACK << Block.DATA_BITS; + } + @Override public boolean canRain() { return false; diff --git a/src/main/java/cn/nukkit/level/biome/impl/beach/BeachBiome.java b/src/main/java/cn/nukkit/level/biome/impl/beach/BeachBiome.java index c12b0d9273f..096177c8ff5 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/beach/BeachBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/beach/BeachBiome.java @@ -2,18 +2,22 @@ import cn.nukkit.level.biome.type.SandyBiome; import cn.nukkit.level.generator.populator.impl.PopulatorSugarcane; +import cn.nukkit.level.generator.populator.impl.PopulatorTallSugarcane; /** - * Author: PeratX + * @author PeratX * Nukkit Project */ public class BeachBiome extends SandyBiome { public BeachBiome() { PopulatorSugarcane sugarcane = new PopulatorSugarcane(); - sugarcane.setBaseAmount(0); sugarcane.setRandomAmount(3); this.addPopulator(sugarcane); + PopulatorTallSugarcane tallSugarcane = new PopulatorTallSugarcane(); + tallSugarcane.setRandomAmount(1); + this.addPopulator(tallSugarcane); + this.setBaseHeight(0f); this.setHeightVariation(0.025f); } diff --git a/src/main/java/cn/nukkit/level/biome/impl/beach/ColdBeachBiome.java b/src/main/java/cn/nukkit/level/biome/impl/beach/ColdBeachBiome.java index bd0aa74e941..ed295ac0ed0 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/beach/ColdBeachBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/beach/ColdBeachBiome.java @@ -1,12 +1,10 @@ package cn.nukkit.level.biome.impl.beach; +import cn.nukkit.block.Block; import cn.nukkit.level.biome.type.SandyBiome; -import cn.nukkit.level.generator.populator.impl.WaterIcePopulator; public class ColdBeachBiome extends SandyBiome { public ColdBeachBiome() { - WaterIcePopulator ice = new WaterIcePopulator(); - this.addPopulator(ice); this.setBaseHeight(0f); this.setHeightVariation(0.025f); @@ -14,7 +12,7 @@ public ColdBeachBiome() { @Override public int getCoverId(int x, int z) { - return SNOW_LAYER << 4; + return Block.SNOW_LAYER << Block.DATA_BITS; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/desert/DesertBiome.java b/src/main/java/cn/nukkit/level/biome/impl/desert/DesertBiome.java index a031e538186..85e66256f27 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/desert/DesertBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/desert/DesertBiome.java @@ -5,7 +5,7 @@ import cn.nukkit.level.generator.populator.impl.PopulatorDeadBush; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class DesertBiome extends SandyBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/desert/DesertMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/desert/DesertMBiome.java index 0eba5e38ac4..869082be994 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/desert/DesertMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/desert/DesertMBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.desert; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class DesertMBiome extends DesertBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsBiome.java index d54292f9306..4d224e4959f 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsBiome.java @@ -9,7 +9,7 @@ import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* make sure this is touching another extreme hills type or it'll look dumb diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsEdgeBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsEdgeBiome.java index 063136fbf1e..aeffaa73288 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsEdgeBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsEdgeBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.extremehills; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class ExtremeHillsEdgeBiome extends ExtremeHillsBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsMBiome.java index df49bc00fc1..6f510a6bb02 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsMBiome.java @@ -1,10 +1,11 @@ package cn.nukkit.level.biome.impl.extremehills; +import cn.nukkit.block.Block; import cn.nukkit.level.generator.noise.nukkit.f.SimplexF; import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* make sure this is touching another extreme hills type or it'll look dumb @@ -12,8 +13,8 @@ * very smooth hills with flat areas between */ public class ExtremeHillsMBiome extends ExtremeHillsPlusBiome { - private static final SimplexF gravelNoise = new SimplexF(new NukkitRandom(0), 1f, 1 / 4f, 1 / 64f); - private boolean isGravel = false; + + private static final SimplexF gravelNoise = new SimplexF(new NukkitRandom(), 1f, 1 / 4f, 1 / 64f); public ExtremeHillsMBiome() { this(true); @@ -33,7 +34,7 @@ public String getName() { @Override public int getSurfaceId(int x, int y, int z) { - return gravelNoise.noise2D(x, z, true) < -0.75f ? GRAVEL << 4 : super.getSurfaceId(x, y, z); + return gravelNoise.noise2D(x, z, true) < -0.75f ? GRAVEL << Block.DATA_BITS : super.getSurfaceId(x, y, z); } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusBiome.java index 3243a9dd5c7..b93b2e89d2c 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.extremehills; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* make sure this is touching another extreme hills type or it'll look dumb diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusMBiome.java index c439866165f..dfb7e190368 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/ExtremeHillsPlusMBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.extremehills; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* make sure this is touching another extreme hills type or it'll look dumb @@ -21,9 +21,4 @@ public ExtremeHillsPlusMBiome() { public String getName() { return "Extreme Hills+ M"; } - - @Override - public boolean doesOverhang() { - return false; - } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/extremehills/StoneBeachBiome.java b/src/main/java/cn/nukkit/level/biome/impl/extremehills/StoneBeachBiome.java index f5b192bf217..de7615193e7 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/extremehills/StoneBeachBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/extremehills/StoneBeachBiome.java @@ -10,6 +10,7 @@ * Nearly ertical cliffs, but no overhangs. Height difference is 2-7 near ocean, and pretty much flat everywhere else */ public class StoneBeachBiome extends CoveredBiome { + public StoneBeachBiome() { this.setBaseHeight(0.1f); this.setHeightVariation(0.8f); diff --git a/src/main/java/cn/nukkit/level/biome/impl/forest/FlowerForestBiome.java b/src/main/java/cn/nukkit/level/biome/impl/forest/FlowerForestBiome.java index 714ab73fd31..3fc819f38eb 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/forest/FlowerForestBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/forest/FlowerForestBiome.java @@ -5,7 +5,7 @@ import cn.nukkit.level.generator.populator.impl.PopulatorFlower; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class FlowerForestBiome extends ForestBiome { @@ -19,6 +19,7 @@ public FlowerForestBiome(int type) { //see https://minecraft.gamepedia.com/Flower#Flower_biomes PopulatorFlower flower = new PopulatorFlower(); flower.setBaseAmount(10); + flower.setRandomAmount(10); flower.addType(DANDELION, 0); flower.addType(RED_FLOWER, BlockFlower.TYPE_POPPY); flower.addType(RED_FLOWER, BlockFlower.TYPE_ALLIUM); @@ -40,6 +41,6 @@ public FlowerForestBiome(int type) { @Override public String getName() { - return this.type == TYPE_BIRCH ? "Birch Forest" : "Forest"; + return this.type == TYPE_BIRCH ? "Birch Flower Forest" : "Flower Forest"; } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/forest/ForestBiome.java b/src/main/java/cn/nukkit/level/biome/impl/forest/ForestBiome.java index 8ea91c8c37c..6336c315532 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/forest/ForestBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/forest/ForestBiome.java @@ -2,10 +2,11 @@ import cn.nukkit.block.BlockSapling; import cn.nukkit.level.biome.type.GrassyBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorFallenTree; import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ForestBiome extends GrassyBiome { @@ -25,15 +26,21 @@ public ForestBiome(int type) { this.type = type; PopulatorTree trees = new PopulatorTree(type == TYPE_BIRCH_TALL ? BlockSapling.BIRCH_TALL : BlockSapling.BIRCH); - trees.setBaseAmount(type == TYPE_NORMAL ? 3 : 6); + trees.setBaseAmount(type == TYPE_NORMAL ? 3 : 10); + trees.setRandomAmount(3); this.addPopulator(trees); if (type == TYPE_NORMAL) { - //normal forest biomes have both oak and birch trees + // Normal forest biomes have both oak and birch trees trees = new PopulatorTree(BlockSapling.OAK); - trees.setBaseAmount(3); + trees.setBaseAmount(4); + trees.setRandomAmount(3); this.addPopulator(trees); } + + PopulatorFallenTree fallenTree = new PopulatorFallenTree(); + fallenTree.setType(type); + this.addPopulator(fallenTree); } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/forest/ForestHillsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/forest/ForestHillsBiome.java index 90fd136a2ff..658cea5d315 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/forest/ForestHillsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/forest/ForestHillsBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.forest; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class ForestHillsBiome extends ForestBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsBiome.java index dbf0754d78a..b608cee9857 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsBiome.java @@ -5,7 +5,7 @@ import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class IcePlainsBiome extends SnowyBiome { @@ -14,12 +14,9 @@ public IcePlainsBiome() { super(); PopulatorTree trees = new PopulatorTree(BlockSapling.SPRUCE); - trees.setBaseAmount(0); trees.setRandomAmount(1); this.addPopulator(trees); - - this.setBaseHeight(0.125f); this.setHeightVariation(0.05f); } diff --git a/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsSpikesBiome.java b/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsSpikesBiome.java index a28ced2405d..57cedf2e0af 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsSpikesBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/iceplains/IcePlainsSpikesBiome.java @@ -1,12 +1,13 @@ package cn.nukkit.level.biome.impl.iceplains; +import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class IcePlainsSpikesBiome extends IcePlainsBiome { @@ -20,17 +21,13 @@ public IcePlainsSpikesBiome() { @Override public int getSurfaceId(int x, int y, int z) { - return SNOW_BLOCK << 4; + return Block.SNOW_BLOCK << Block.DATA_BITS; } public String getName() { return "Ice Plains Spikes"; } - @Override - public boolean isFreezing() { - return true; - } /** * @author DaPorkchop_ diff --git a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleBiome.java b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleBiome.java index 67f08a40f4a..40ebee75dfd 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleBiome.java @@ -1,6 +1,7 @@ package cn.nukkit.level.biome.impl.jungle; import cn.nukkit.level.biome.type.GrassyBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorBamboo; import cn.nukkit.level.generator.populator.impl.PopulatorMelon; import cn.nukkit.level.generator.populator.impl.tree.JungleBigTreePopulator; import cn.nukkit.level.generator.populator.impl.tree.JungleTreePopulator; @@ -14,16 +15,21 @@ public JungleBiome() { JungleTreePopulator trees = new JungleTreePopulator(); trees.setBaseAmount(10); + trees.setRandomAmount(10); this.addPopulator(trees); JungleBigTreePopulator bigTrees = new JungleBigTreePopulator(); - bigTrees.setBaseAmount(6); + bigTrees.setBaseAmount(8); + bigTrees.setRandomAmount(8); this.addPopulator(bigTrees); PopulatorMelon melon = new PopulatorMelon(); - melon.setBaseAmount(-65); - melon.setRandomAmount(70); + melon.setRandomAmount(2); this.addPopulator(melon); + + PopulatorBamboo bamboo = new PopulatorBamboo(); + bamboo.setRandomAmount(2); + this.addPopulator(bamboo); } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeBiome.java b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeBiome.java index 88faf8753ca..620936c8d50 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeBiome.java @@ -4,9 +4,6 @@ * @author DaPorkchpo_ */ public class JungleEdgeBiome extends JungleBiome { - public JungleEdgeBiome() { - super(); - } @Override public String getName() { diff --git a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeMBiome.java index e0397a0bfb0..2a53b8cd788 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/jungle/JungleEdgeMBiome.java @@ -3,11 +3,7 @@ /** * @author DaPorkchpo_ */ -//porktodo: this biome has steep cliffs and flat plains public class JungleEdgeMBiome extends JungleEdgeBiome { - public JungleEdgeMBiome() { - super(); - } @Override public String getName() { diff --git a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBiome.java index db5ce288e48..a5db08432a3 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBiome.java @@ -1,5 +1,6 @@ package cn.nukkit.level.biome.impl.mesa; +import cn.nukkit.block.Block; import cn.nukkit.block.BlockSand; import cn.nukkit.level.biome.type.CoveredBiome; import cn.nukkit.level.generator.noise.nukkit.f.SimplexF; @@ -8,7 +9,7 @@ import cn.nukkit.math.NukkitRandom; import java.util.Arrays; -import java.util.Random; +import java.util.SplittableRandom; /** * @author DaPorkchop_ @@ -16,12 +17,13 @@ * Handles the placement of stained clay for all mesa variants */ public class MesaBiome extends CoveredBiome { - static final int[] colorLayer = new int[64]; + + static final int[] colorLayer = new int[64]; static final SimplexF redSandNoise = new SimplexF(new NukkitRandom(937478913), 2f, 1 / 4f, 1 / 4f); - static final SimplexF colorNoise = new SimplexF(new NukkitRandom(193759875), 2f, 1 / 4f, 1 / 32f); + static final SimplexF colorNoise = new SimplexF(new NukkitRandom(193759875), 2f, 1 / 4f, 1 / 32f); static { - Random random = new Random(29864); + SplittableRandom random = new SplittableRandom(29864); Arrays.fill(colorLayer, -1); // hard clay, other values are stained clay setRandomLayerColor(random, 14, 1); // orange @@ -41,20 +43,8 @@ public class MesaBiome extends CoveredBiome { } } - private static void setRandomLayerColor(Random random, int sliceCount, int color) { - for (int i = 0; i < random.nextInt(4) + sliceCount; i++) { - int j = random.nextInt(colorLayer.length); - int k = 0; - while (k < random.nextInt(2) + 1 && j < colorLayer.length) { - colorLayer[j++] = color; - k++; - } - } - } - - private SimplexF moundNoise = new SimplexF(new NukkitRandom(347228794), 2f, 1 / 4f, getMoundFrequency()); protected int moundHeight; - + private final SimplexF moundNoise = new SimplexF(new NukkitRandom(347228794), 2f, 1 / 4f, getMoundFrequency()); public MesaBiome() { PopulatorCactus cactus = new PopulatorCactus(); cactus.setBaseAmount(1); @@ -69,6 +59,17 @@ public MesaBiome() { this.setMoundHeight(17); } + private static void setRandomLayerColor(SplittableRandom random, int sliceCount, int color) { + for (int i = 0; i < random.nextInt(4) + sliceCount; i++) { + int j = random.nextInt(colorLayer.length); + int k = 0; + while (k < random.nextInt(2) + 1 && j < colorLayer.length) { + colorLayer[j++] = color; + k++; + } + } + } + public void setMoundHeight(int height) { this.moundHeight = height; } @@ -81,10 +82,10 @@ public int getSurfaceDepth(int x, int y, int z) { @Override public int getSurfaceId(int x, int y, int z) { if (y < (71 + Math.round((redSandNoise.noise2D(x, z, true) + 1) * 1.5f))) { - return (SAND << 4) | BlockSand.RED; + return (SAND << Block.DATA_BITS) | BlockSand.RED; } else { int meta = colorLayer[(y + Math.round((colorNoise.noise2D(x, z, true) + 1) * 1.5f)) & 0x3F]; - return (meta == -1 ? TERRACOTTA << 4 : STAINED_TERRACOTTA << 4) | Math.max(0, meta); + return (meta == -1 ? TERRACOTTA << Block.DATA_BITS : STAINED_TERRACOTTA << Block.DATA_BITS) | Math.max(0, meta); } } @@ -95,7 +96,7 @@ public int getGroundDepth(int x, int y, int z) { @Override public int getGroundId(int x, int y, int z) { - return RED_SANDSTONE << 4; + return Block.RED_SANDSTONE << Block.DATA_BITS; } @Override @@ -104,7 +105,7 @@ public String getName() { } protected float getMoundFrequency() { - return 1 / 128f; + return 0.0078125f; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBryceBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBryceBiome.java index c3f15c0d46f..2251a2f6fb0 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBryceBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaBryceBiome.java @@ -4,9 +4,6 @@ * @author DaPorkchop_ */ public class MesaBryceBiome extends MesaBiome { - public MesaBryceBiome() { - super(); - } @Override public String getName() { @@ -15,7 +12,7 @@ public String getName() { @Override protected float getMoundFrequency() { - return 1 / 16f; + return 0.0625f; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFBiome.java index 3a5433c70af..716dcefb533 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFBiome.java @@ -1,5 +1,6 @@ package cn.nukkit.level.biome.impl.mesa; +import cn.nukkit.block.Block; import cn.nukkit.block.BlockSapling; import cn.nukkit.level.generator.populator.impl.PopulatorTree; @@ -18,7 +19,7 @@ public MesaPlateauFBiome() { @Override public int getCoverId(int x, int z) { - return GRASS << 4; + return GRASS << Block.DATA_BITS; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFMBiome.java index 26a8afadab5..e9c46c7454f 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauFMBiome.java @@ -3,11 +3,7 @@ /** * @author DaPorkchop_ */ -//porktodo: this biome has much smaller and more frequent plateaus than the normal mesa plateau (which is all one giant one) public class MesaPlateauFMBiome extends MesaPlateauFBiome { - public MesaPlateauFMBiome() { - super(); - } @Override public String getName() { diff --git a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauMBiome.java index dea211ea421..d77a1c92b9d 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mesa/MesaPlateauMBiome.java @@ -3,7 +3,6 @@ /** * @author DaPorkchop_ */ -//porktodo: the plateaus here are smaller and less frequent than in the normal counterpart (which is one giant plateau) public class MesaPlateauMBiome extends MesaBiome { public MesaPlateauMBiome() { super(); @@ -16,11 +15,9 @@ public String getName() { return "Mesa Plateau M"; } - - @Override protected float getMoundFrequency() { - return 1 / 50f; + return 0.02f; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java b/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java index a859e7414eb..284c22463aa 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/mushroom/MushroomIslandBiome.java @@ -1,5 +1,6 @@ package cn.nukkit.level.biome.impl.mushroom; +import cn.nukkit.block.Block; import cn.nukkit.level.biome.type.GrassyBiome; import cn.nukkit.level.generator.populator.impl.MushroomPopulator; @@ -20,6 +21,6 @@ public String getName() { @Override public int getSurfaceId(int x, int y, int z) { - return MYCELIUM << 4; + return Block.MYCELIUM << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/nether/BasaltDeltasBiome.java b/src/main/java/cn/nukkit/level/biome/impl/nether/BasaltDeltasBiome.java new file mode 100644 index 00000000000..19a6fb73b1c --- /dev/null +++ b/src/main/java/cn/nukkit/level/biome/impl/nether/BasaltDeltasBiome.java @@ -0,0 +1,43 @@ +package cn.nukkit.level.biome.impl.nether; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.biome.type.CoveredBiome; +import cn.nukkit.level.generator.object.ore.OreType; +import cn.nukkit.level.generator.populator.impl.PopulatorBasaltDeltaLava; +import cn.nukkit.level.generator.populator.impl.PopulatorBasaltDeltaMagma; +import cn.nukkit.level.generator.populator.impl.PopulatorBasaltDeltaPillar; +import cn.nukkit.level.generator.populator.impl.PopulatorOre; + +public class BasaltDeltasBiome extends CoveredBiome { + + public BasaltDeltasBiome() { + this.addPopulator(new PopulatorOre(BlockID.BASALT, new OreType[]{ + new OreType(Block.get(BlockID.BLACKSTONE), 4, 128, 0, 128, BASALT) + })); + + this.addPopulator(new PopulatorBasaltDeltaLava()); + this.addPopulator(new PopulatorBasaltDeltaMagma()); + this.addPopulator(new PopulatorBasaltDeltaPillar()); + } + + @Override + public String getName() { + return "Basalt Deltas"; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return Block.BASALT << Block.DATA_BITS; + } + + @Override + public int getGroundId(int x, int y, int z) { + return Block.BASALT << Block.DATA_BITS; + } + + @Override + public boolean canRain() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/biome/impl/nether/CrimsonForestBiome.java b/src/main/java/cn/nukkit/level/biome/impl/nether/CrimsonForestBiome.java new file mode 100644 index 00000000000..70fb4b97ea0 --- /dev/null +++ b/src/main/java/cn/nukkit/level/biome/impl/nether/CrimsonForestBiome.java @@ -0,0 +1,36 @@ +package cn.nukkit.level.biome.impl.nether; + +import cn.nukkit.block.Block; +import cn.nukkit.level.biome.type.CoveredBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorCrimsonFungus; +import cn.nukkit.level.generator.populator.impl.PopulatorCrimsonForestGround; +import cn.nukkit.level.generator.populator.impl.PopulatorWeepingVines; + +public class CrimsonForestBiome extends CoveredBiome { + + public CrimsonForestBiome() { + this.addPopulator(new PopulatorCrimsonFungus()); + this.addPopulator(new PopulatorCrimsonForestGround()); + this.addPopulator(new PopulatorWeepingVines()); + } + + @Override + public String getName() { + return "Crimson Forest"; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return Block.CRIMSON_NYLIUM << Block.DATA_BITS; + } + + @Override + public int getGroundId(int x, int y, int z) { + return Block.NETHERRACK << Block.DATA_BITS; + } + + @Override + public boolean canRain() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/biome/impl/nether/SoulSandValleyBiome.java b/src/main/java/cn/nukkit/level/biome/impl/nether/SoulSandValleyBiome.java new file mode 100644 index 00000000000..7604223a781 --- /dev/null +++ b/src/main/java/cn/nukkit/level/biome/impl/nether/SoulSandValleyBiome.java @@ -0,0 +1,39 @@ +package cn.nukkit.level.biome.impl.nether; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.biome.type.CoveredBiome; +import cn.nukkit.level.generator.object.ore.OreType; +import cn.nukkit.level.generator.populator.impl.*; + +public class SoulSandValleyBiome extends CoveredBiome { + + public SoulSandValleyBiome() { + this.addPopulator(new PopulatorOre(BlockID.SOUL_SAND, new OreType[]{ + new OreType(Block.get(SOUL_SOIL), 3, 128, 0, 128, SOUL_SAND) + })); + + this.addPopulator(new PopulatorNetherFire(SOUL_FIRE, SOUL_SOIL)); + this.addPopulator(new PopulatorSoulSandValleyGround()); + } + + @Override + public String getName() { + return "Soul Sand Valley"; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return Block.SOUL_SAND << Block.DATA_BITS; + } + + @Override + public int getGroundId(int x, int y, int z) { + return Block.SOUL_SAND << Block.DATA_BITS; + } + + @Override + public boolean canRain() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/biome/impl/nether/WarpedForestBiome.java b/src/main/java/cn/nukkit/level/biome/impl/nether/WarpedForestBiome.java new file mode 100644 index 00000000000..456be94005f --- /dev/null +++ b/src/main/java/cn/nukkit/level/biome/impl/nether/WarpedForestBiome.java @@ -0,0 +1,36 @@ +package cn.nukkit.level.biome.impl.nether; + +import cn.nukkit.block.Block; +import cn.nukkit.level.biome.type.CoveredBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorTwistingVines; +import cn.nukkit.level.generator.populator.impl.PopulatorWarpedFungus; +import cn.nukkit.level.generator.populator.impl.PopulatorWarpedForestGround; + +public class WarpedForestBiome extends CoveredBiome { + + public WarpedForestBiome() { + this.addPopulator(new PopulatorWarpedFungus()); + this.addPopulator(new PopulatorWarpedForestGround()); + this.addPopulator(new PopulatorTwistingVines()); + } + + @Override + public String getName() { + return "Warped Forest"; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return Block.WARPED_NYLIUM << Block.DATA_BITS; + } + + @Override + public int getGroundId(int x, int y, int z) { + return Block.NETHERRACK << Block.DATA_BITS; + } + + @Override + public boolean canRain() { + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/biome/impl/ocean/DeepOceanBiome.java b/src/main/java/cn/nukkit/level/biome/impl/ocean/DeepOceanBiome.java index d86bbab4146..13c3866d75f 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/ocean/DeepOceanBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/ocean/DeepOceanBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.ocean; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class DeepOceanBiome extends OceanBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/ocean/FrozenOceanBiome.java b/src/main/java/cn/nukkit/level/biome/impl/ocean/FrozenOceanBiome.java index 242dd35c2b4..ee82f379da4 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/ocean/FrozenOceanBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/ocean/FrozenOceanBiome.java @@ -1,9 +1,9 @@ package cn.nukkit.level.biome.impl.ocean; -import cn.nukkit.level.generator.populator.impl.WaterIcePopulator; +import cn.nukkit.block.Block; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* This biome does not generate naturally @@ -11,9 +11,6 @@ public class FrozenOceanBiome extends OceanBiome { public FrozenOceanBiome() { super(); - - WaterIcePopulator ice = new WaterIcePopulator(); - this.addPopulator(ice); } @Override @@ -30,4 +27,9 @@ public boolean isFreezing() { public boolean canRain() { return false; } + + @Override + public int getCoverId(int x, int z) { + return SNOW_LAYER << Block.DATA_BITS; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/ocean/OceanBiome.java b/src/main/java/cn/nukkit/level/biome/impl/ocean/OceanBiome.java index cda7e9df1ea..e5e1a81221f 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/ocean/OceanBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/ocean/OceanBiome.java @@ -1,15 +1,45 @@ package cn.nukkit.level.biome.impl.ocean; +import cn.nukkit.block.Block; import cn.nukkit.level.biome.type.WateryBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorKelp; +import cn.nukkit.level.generator.populator.impl.PopulatorSeagrass; +import cn.nukkit.level.generator.populator.impl.PopulatorUnderwaterFloor; + +import java.util.Arrays; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class OceanBiome extends WateryBiome { public OceanBiome() { - this.setBaseHeight(-1f); + PopulatorUnderwaterFloor underwaterFloor = new PopulatorUnderwaterFloor(1.0, SAND, 2, 4, 2, Arrays.asList(GRASS, DIRT)); + underwaterFloor.setBaseAmount(3); + this.addPopulator(underwaterFloor); + + PopulatorUnderwaterFloor underwaterFloorClay = new PopulatorUnderwaterFloor(1.0, CLAY_BLOCK, 1, 2, 1, Arrays.asList(DIRT, CLAY_BLOCK)); + underwaterFloorClay.setBaseAmount(1); + this.addPopulator(underwaterFloorClay); + + PopulatorUnderwaterFloor underwaterFloorGravel = new PopulatorUnderwaterFloor(1.0, GRAVEL, 2, 3, 2, Arrays.asList(GRASS, DIRT)); + underwaterFloorGravel.setBaseAmount(1); + this.addPopulator(underwaterFloorGravel); + + if (!(this instanceof FrozenOceanBiome)) { + PopulatorKelp populatorKelp = new PopulatorKelp(); + populatorKelp.setBaseAmount(-135); + populatorKelp.setRandomAmount(180); + this.addPopulator(populatorKelp); + } + + PopulatorSeagrass populatorSeagrass = new PopulatorSeagrass(); + populatorSeagrass.setBaseAmount(24); + populatorSeagrass.setBaseAmount(24); + this.addPopulator(populatorSeagrass); + + this.setBaseHeight(-1.0f); this.setHeightVariation(0.1f); } @@ -18,7 +48,18 @@ public String getName() { return "Ocean"; } - public int getGroundId(int y) { - return GRAVEL << 4; + @Override + public int getGroundId(int x, int y, int z) { + return Block.GRAVEL << Block.DATA_BITS; + } + + @Override + public int getSurfaceDepth(int x, int y, int z) { + return 1; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return SAND << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/plains/PlainsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/plains/PlainsBiome.java index 9b727eaea6f..ecfca50ae9b 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/plains/PlainsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/plains/PlainsBiome.java @@ -1,9 +1,12 @@ package cn.nukkit.level.biome.impl.plains; +import cn.nukkit.block.BlockSapling; import cn.nukkit.level.biome.type.GrassyBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorPumpkin; +import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class PlainsBiome extends GrassyBiome { @@ -11,6 +14,12 @@ public class PlainsBiome extends GrassyBiome { public PlainsBiome() { super(); + PopulatorTree trees = new PopulatorTree(BlockSapling.OAK); + trees.setRandomAmount(1); + this.addPopulator(trees); + + this.addPopulator(new PopulatorPumpkin()); + this.setBaseHeight(0.125f); this.setHeightVariation(0.05f); } diff --git a/src/main/java/cn/nukkit/level/biome/impl/plains/SunflowerPlainsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/plains/SunflowerPlainsBiome.java index 3896b80b460..73e0c3d26f8 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/plains/SunflowerPlainsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/plains/SunflowerPlainsBiome.java @@ -1,21 +1,31 @@ package cn.nukkit.level.biome.impl.plains; import cn.nukkit.block.BlockDoublePlant; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.level.biome.type.GrassyBiome; import cn.nukkit.level.generator.populator.impl.PopulatorDoublePlant; +import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ -public class SunflowerPlainsBiome extends PlainsBiome { +public class SunflowerPlainsBiome extends GrassyBiome { public SunflowerPlainsBiome() { super(); + PopulatorTree trees = new PopulatorTree(BlockSapling.OAK); + trees.setRandomAmount(1); + this.addPopulator(trees); + PopulatorDoublePlant sunflower = new PopulatorDoublePlant(BlockDoublePlant.SUNFLOWER); sunflower.setBaseAmount(8); sunflower.setRandomAmount(5); this.addPopulator(sunflower); + + this.setBaseHeight(0.125f); + this.setHeightVariation(0.05f); } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/river/FrozenRiverBiome.java b/src/main/java/cn/nukkit/level/biome/impl/river/FrozenRiverBiome.java index 0ad727df48e..79b9647fd47 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/river/FrozenRiverBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/river/FrozenRiverBiome.java @@ -1,17 +1,14 @@ package cn.nukkit.level.biome.impl.river; -import cn.nukkit.level.generator.populator.impl.WaterIcePopulator; +import cn.nukkit.block.Block; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class FrozenRiverBiome extends RiverBiome { public FrozenRiverBiome() { super(); - - WaterIcePopulator ice = new WaterIcePopulator(); - this.addPopulator(ice); } @Override @@ -28,4 +25,9 @@ public boolean isFreezing() { public boolean canRain() { return false; } + + @Override + public int getCoverId(int x, int z) { + return SNOW_LAYER << Block.DATA_BITS; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/river/RiverBiome.java b/src/main/java/cn/nukkit/level/biome/impl/river/RiverBiome.java index 1b5db752e81..cb92d801b1c 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/river/RiverBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/river/RiverBiome.java @@ -1,14 +1,46 @@ package cn.nukkit.level.biome.impl.river; +import cn.nukkit.block.Block; import cn.nukkit.level.biome.type.WateryBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorSeagrass; +import cn.nukkit.level.generator.populator.impl.PopulatorSugarcane; +import cn.nukkit.level.generator.populator.impl.PopulatorTallSugarcane; +import cn.nukkit.level.generator.populator.impl.PopulatorUnderwaterFloor; + +import java.util.Arrays; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class RiverBiome extends WateryBiome { public RiverBiome() { + PopulatorUnderwaterFloor underwaterFloorSand = new PopulatorUnderwaterFloor(1.0, SAND, 2, 4, 2, Arrays.asList(GRASS, DIRT)); + underwaterFloorSand.setBaseAmount(3); + addPopulator(underwaterFloorSand); + + PopulatorUnderwaterFloor underwaterFloorClay = new PopulatorUnderwaterFloor(1.0, CLAY_BLOCK, 1, 2, 1, Arrays.asList(DIRT, CLAY_BLOCK)); + underwaterFloorClay.setBaseAmount(1); + addPopulator(underwaterFloorClay); + + PopulatorUnderwaterFloor underwaterFloorGravel = new PopulatorUnderwaterFloor(1.0, GRAVEL, 2, 3, 2, Arrays.asList(GRASS, DIRT)); + underwaterFloorGravel.setBaseAmount(1); + addPopulator(underwaterFloorGravel); + + PopulatorSeagrass populatorSeagrass = new PopulatorSeagrass(); + populatorSeagrass.setBaseAmount(24); + populatorSeagrass.setBaseAmount(24); + addPopulator(populatorSeagrass); + + PopulatorSugarcane sugarcane = new PopulatorSugarcane(); + sugarcane.setRandomAmount(3); + this.addPopulator(sugarcane); + + PopulatorTallSugarcane tallSugarcane = new PopulatorTallSugarcane(); + tallSugarcane.setRandomAmount(1); + this.addPopulator(tallSugarcane); + this.setBaseHeight(-0.5f); this.setHeightVariation(0f); } @@ -17,4 +49,14 @@ public RiverBiome() { public String getName() { return "River"; } + + @Override + public int getSurfaceDepth(int x, int y, int z) { + return 1; + } + + @Override + public int getSurfaceId(int x, int y, int z) { + return GRASS << Block.DATA_BITS; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/roofedforest/RoofedForestBiome.java b/src/main/java/cn/nukkit/level/biome/impl/roofedforest/RoofedForestBiome.java index 2bcd2061d7f..17986f5dd8c 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/roofedforest/RoofedForestBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/roofedforest/RoofedForestBiome.java @@ -1,5 +1,7 @@ package cn.nukkit.level.biome.impl.roofedforest; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockFlower; import cn.nukkit.level.biome.type.GrassyBiome; import cn.nukkit.level.generator.populator.impl.MushroomPopulator; import cn.nukkit.level.generator.populator.impl.PopulatorFlower; @@ -11,17 +13,18 @@ public RoofedForestBiome() { super(); DarkOakTreePopulator tree = new DarkOakTreePopulator(); - tree.setBaseAmount(20); - tree.setRandomAmount(10); + tree.setBaseAmount(24); + tree.setRandomAmount(24); this.addPopulator(tree); PopulatorFlower flower = new PopulatorFlower(); - flower.setBaseAmount(2); - this.addPopulator(flower); + flower.setBaseAmount(3); + flower.addType(Block.DANDELION, 0); + flower.addType(Block.RED_FLOWER, BlockFlower.TYPE_POPPY); MushroomPopulator mushroom = new MushroomPopulator(); - mushroom.setBaseAmount(0); - mushroom.setRandomAmount(1); + mushroom.setBaseAmount(1); + mushroom.setRandomAmount(2); this.addPopulator(mushroom); } @@ -29,5 +32,4 @@ public RoofedForestBiome() { public String getName() { return "Roofed Forest"; } - } diff --git a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaBiome.java index 980a0838728..8edf451c168 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaBiome.java @@ -2,6 +2,7 @@ import cn.nukkit.block.BlockSapling; import cn.nukkit.level.biome.type.GrassyBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorFlower; import cn.nukkit.level.generator.populator.impl.tree.SavannaTreePopulator; /** @@ -16,6 +17,10 @@ public SavannaBiome() { tree.setBaseAmount(1); this.addPopulator(tree); + PopulatorFlower flower = new PopulatorFlower(); + flower.setBaseAmount(2); + this.addPopulator(flower); + this.setBaseHeight(0.125f); this.setHeightVariation(0.05f); } diff --git a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaMBiome.java index e84f090344f..ae81f3ed647 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaMBiome.java @@ -35,4 +35,9 @@ public String getName() { public boolean doesOverhang() { return true; } + + @Override + public boolean canRain() { + return true; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauBiome.java b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauBiome.java index 2a59464411e..98a4df34ccb 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauBiome.java @@ -16,4 +16,9 @@ public SavannaPlateauBiome() { public String getName() { return "Savanna Plateau"; } + + @Override + public boolean canRain() { + return true; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauMBiome.java index 26f221e3f66..c5e89e58fd0 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/savanna/SavannaPlateauMBiome.java @@ -3,8 +3,6 @@ /** * @author DaPorkchop_ */ -//porktodo: this is just like savanna plateau with individual spikes -//see https://minecraft.gamepedia.com/Biome#Plateau_M public class SavannaPlateauMBiome extends SavannaPlateauBiome { public SavannaPlateauMBiome() { diff --git a/src/main/java/cn/nukkit/level/biome/impl/swamp/SwampBiome.java b/src/main/java/cn/nukkit/level/biome/impl/swamp/SwampBiome.java index fef542a9725..54d2285a6d8 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/swamp/SwampBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/swamp/SwampBiome.java @@ -3,14 +3,13 @@ import cn.nukkit.block.Block; import cn.nukkit.block.BlockFlower; import cn.nukkit.level.biome.type.GrassyBiome; -import cn.nukkit.level.generator.populator.impl.MushroomPopulator; -import cn.nukkit.level.generator.populator.impl.PopulatorFlower; -import cn.nukkit.level.generator.populator.impl.PopulatorLilyPad; -import cn.nukkit.level.generator.populator.impl.PopulatorSmallMushroom; +import cn.nukkit.level.generator.populator.impl.*; import cn.nukkit.level.generator.populator.impl.tree.SwampTreePopulator; +import java.util.Arrays; + /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class SwampBiome extends GrassyBiome { @@ -18,6 +17,15 @@ public class SwampBiome extends GrassyBiome { public SwampBiome() { super(); + PopulatorUnderwaterFloor underwaterFloorClay = new PopulatorUnderwaterFloor(1.0, CLAY_BLOCK, 1, 2, 1, Arrays.asList(DIRT, CLAY_BLOCK)); + underwaterFloorClay.setBaseAmount(1); + addPopulator(underwaterFloorClay); + + PopulatorSeagrass populatorSeagrass = new PopulatorSeagrass(); + populatorSeagrass.setBaseAmount(24); + populatorSeagrass.setBaseAmount(24); + addPopulator(populatorSeagrass); + PopulatorLilyPad lilypad = new PopulatorLilyPad(); lilypad.setBaseAmount(4); lilypad.setRandomAmount(2); @@ -38,7 +46,6 @@ public SwampBiome() { this.addPopulator(mushroom); PopulatorSmallMushroom smallMushroom = new PopulatorSmallMushroom(); - smallMushroom.setBaseAmount(0); smallMushroom.setRandomAmount(2); this.addPopulator(smallMushroom); diff --git a/src/main/java/cn/nukkit/level/biome/impl/swamp/SwamplandMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/swamp/SwamplandMBiome.java index 0579ceeaa45..5cf743ee270 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/swamp/SwamplandMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/swamp/SwamplandMBiome.java @@ -1,16 +1,11 @@ package cn.nukkit.level.biome.impl.swamp; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ -//porktodo: this should be flat in most places, and only rise up in a few public class SwamplandMBiome extends SwampBiome { - public SwamplandMBiome() { - super(); - } - @Override public String getName() { return "Swampland M"; diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaBiome.java index d46818f7c4b..000f55bc948 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaBiome.java @@ -1,18 +1,15 @@ package cn.nukkit.level.biome.impl.taiga; -import cn.nukkit.level.generator.populator.impl.WaterIcePopulator; +import cn.nukkit.block.Block; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class ColdTaigaBiome extends TaigaBiome { public ColdTaigaBiome() { super(); - WaterIcePopulator ice = new WaterIcePopulator(); - this.addPopulator(ice); - this.setBaseHeight(0.2f); this.setHeightVariation(0.2f); } @@ -24,7 +21,7 @@ public String getName() { @Override public int getCoverId(int x, int z) { - return SNOW_LAYER << 4; + return Block.SNOW_LAYER << Block.DATA_BITS; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaHillsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaHillsBiome.java index 56413acba4e..0c693e81233 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaHillsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaHillsBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.taiga; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class ColdTaigaHillsBiome extends ColdTaigaBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaMBiome.java index 56398838905..99e5a24ee79 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/ColdTaigaMBiome.java @@ -1,14 +1,10 @@ package cn.nukkit.level.biome.impl.taiga; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ -//porktodo: this biome has very steep cliffs public class ColdTaigaMBiome extends ColdTaigaBiome { - public ColdTaigaMBiome() { - super(); - } @Override public String getName() { diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaSpruceTaigaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaSpruceTaigaBiome.java index be128a614d9..b368a1cb11e 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaSpruceTaigaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaSpruceTaigaBiome.java @@ -1,9 +1,12 @@ package cn.nukkit.level.biome.impl.taiga; +import cn.nukkit.block.Block; +import cn.nukkit.level.generator.populator.impl.PopulatorForestRock; +import cn.nukkit.level.generator.populator.impl.PopulatorSmallMushroom; import cn.nukkit.level.generator.populator.impl.tree.SpruceBigTreePopulator; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class MegaSpruceTaigaBiome extends TaigaBiome { @@ -13,10 +16,27 @@ public MegaSpruceTaigaBiome() { SpruceBigTreePopulator bigTrees = new SpruceBigTreePopulator(); bigTrees.setBaseAmount(6); this.addPopulator(bigTrees); + + PopulatorForestRock rock = new PopulatorForestRock(); + rock.setRandomAmount(2); + this.addPopulator(rock); + + /*PopulatorFlower flower = new PopulatorFlower(); + flower.setRandomAmount(3); + flower.addType(Block.DANDELION, 0);*/ + + PopulatorSmallMushroom smallMushroom = new PopulatorSmallMushroom(); + smallMushroom.setRandomAmount(3); + this.addPopulator(smallMushroom); } @Override public String getName() { return "Mega Spruce Taiga"; } + + @Override + public int getSurfaceId(int x, int y, int z) { + return PODZOL << Block.DATA_BITS; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaBiome.java index 52491af9839..5d53973e109 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaBiome.java @@ -1,19 +1,34 @@ package cn.nukkit.level.biome.impl.taiga; -import cn.nukkit.level.generator.populator.impl.tree.SpruceBigTreePopulator; +import cn.nukkit.block.Block; +import cn.nukkit.level.generator.populator.impl.PopulatorForestRock; +import cn.nukkit.level.generator.populator.impl.PopulatorSmallMushroom; +import cn.nukkit.level.generator.populator.impl.tree.SpruceMegaTreePopulator; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class MegaTaigaBiome extends TaigaBiome { public MegaTaigaBiome() { super(); - SpruceBigTreePopulator bigTrees = new SpruceBigTreePopulator(); + SpruceMegaTreePopulator bigTrees = new SpruceMegaTreePopulator(); bigTrees.setBaseAmount(6); this.addPopulator(bigTrees); + PopulatorForestRock rock = new PopulatorForestRock(); + rock.setRandomAmount(2); + this.addPopulator(rock); + + /*PopulatorFlower flower = new PopulatorFlower(); + flower.setRandomAmount(3); + flower.addType(Block.DANDELION, 0);*/ + + PopulatorSmallMushroom smallMushroom = new PopulatorSmallMushroom(); + smallMushroom.setRandomAmount(3); + this.addPopulator(smallMushroom); + this.setBaseHeight(0.2f); this.setHeightVariation(0.2f); } @@ -22,4 +37,9 @@ public MegaTaigaBiome() { public String getName() { return "Mega Taiga"; } + + @Override + public int getSurfaceId(int x, int y, int z) { + return PODZOL << Block.DATA_BITS; + } } diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaHillsBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaHillsBiome.java index d4e31de7f1c..73f0d3fcceb 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaHillsBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/MegaTaigaHillsBiome.java @@ -1,7 +1,7 @@ package cn.nukkit.level.biome.impl.taiga; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class MegaTaigaHillsBiome extends MegaTaigaBiome { diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaBiome.java index 79712255d49..b4863fed6d1 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaBiome.java @@ -1,11 +1,14 @@ package cn.nukkit.level.biome.impl.taiga; +import cn.nukkit.block.BlockDoublePlant; import cn.nukkit.block.BlockSapling; import cn.nukkit.level.biome.type.GrassyBiome; +import cn.nukkit.level.generator.populator.impl.PopulatorDoublePlant; +import cn.nukkit.level.generator.populator.impl.PopulatorSweetBerryBush; import cn.nukkit.level.generator.populator.impl.PopulatorTree; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class TaigaBiome extends GrassyBiome { @@ -16,6 +19,16 @@ public TaigaBiome() { trees.setBaseAmount(10); this.addPopulator(trees); + if (!(this instanceof ColdTaigaBiome)) { + PopulatorDoublePlant tallGrass = new PopulatorDoublePlant(BlockDoublePlant.LARGE_FERN); + tallGrass.setBaseAmount(2); + this.addPopulator(tallGrass); + + PopulatorSweetBerryBush bush = new PopulatorSweetBerryBush(); + bush.setRandomAmount(3); + this.addPopulator(bush); + } + this.setBaseHeight(0.2f); this.setHeightVariation(0.2f); } diff --git a/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaMBiome.java b/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaMBiome.java index c14f067b784..4f09ee75086 100644 --- a/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaMBiome.java +++ b/src/main/java/cn/nukkit/level/biome/impl/taiga/TaigaMBiome.java @@ -1,10 +1,9 @@ package cn.nukkit.level.biome.impl.taiga; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ -//porktodo: this should be flat-ish in most places, and upheavals should be steep public class TaigaMBiome extends TaigaBiome { public TaigaMBiome() { super(); diff --git a/src/main/java/cn/nukkit/level/biome/type/CoveredBiome.java b/src/main/java/cn/nukkit/level/biome/type/CoveredBiome.java index b934cd3b1d1..b4d4a6e7f91 100644 --- a/src/main/java/cn/nukkit/level/biome/type/CoveredBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/CoveredBiome.java @@ -1,19 +1,21 @@ package cn.nukkit.level.biome.type; +import cn.nukkit.block.Block; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.Normal; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project *

* A biome with ground covering *

*/ public abstract class CoveredBiome extends Biome { + public int getCoverId(int x, int z) { - return AIR << 4; + return 0; } public int getSurfaceDepth(int x, int y, int z) { @@ -38,7 +40,7 @@ public void doCover(int x, int z, FullChunk chunk) { int realY; //start one below build limit in case of cover blocks for (int y = 254; y > 32; y--) { - if (chunk.getFullBlock(x, y, z) == STONE << 4) { + if (chunk.getFullBlock(x, y, z) == (STONE << Block.DATA_BITS)) { COVER: if (!hasCovered) { if (y >= Normal.seaHeight) { @@ -46,7 +48,7 @@ public void doCover(int x, int z, FullChunk chunk) { int surfaceDepth = this.getSurfaceDepth(fullX, y, fullZ); for (int i = 0; i < surfaceDepth; i++) { realY = y - i; - if (chunk.getFullBlock(x, realY, z) == STONE << 4) { + if (chunk.getFullBlock(x, realY, z) == (STONE << Block.DATA_BITS)) { chunk.setFullBlockId(x, realY, z, this.getSurfaceId(fullX, realY, fullZ)); } else break COVER; } @@ -55,7 +57,7 @@ public void doCover(int x, int z, FullChunk chunk) { int groundDepth = this.getGroundDepth(fullX, y, fullZ); for (int i = 0; i < groundDepth; i++) { realY = y - i; - if (chunk.getFullBlock(x, realY, z) == STONE << 4) { + if (chunk.getFullBlock(x, realY, z) == (STONE << Block.DATA_BITS)) { chunk.setFullBlockId(x, realY, z, this.getGroundId(fullX, realY, fullZ)); } else break COVER; } diff --git a/src/main/java/cn/nukkit/level/biome/type/GrassyBiome.java b/src/main/java/cn/nukkit/level/biome/type/GrassyBiome.java index 91048cab4b7..40228f092c0 100644 --- a/src/main/java/cn/nukkit/level/biome/type/GrassyBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/GrassyBiome.java @@ -1,14 +1,16 @@ package cn.nukkit.level.biome.type; +import cn.nukkit.block.Block; import cn.nukkit.block.BlockDoublePlant; import cn.nukkit.level.generator.populator.impl.PopulatorDoublePlant; import cn.nukkit.level.generator.populator.impl.PopulatorGrass; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class GrassyBiome extends CoveredBiome { + public GrassyBiome() { PopulatorGrass grass = new PopulatorGrass(); grass.setBaseAmount(30); @@ -21,11 +23,11 @@ public GrassyBiome() { @Override public int getSurfaceId(int x, int y, int z) { - return GRASS << 4; + return Block.GRASS << Block.DATA_BITS; } @Override public int getGroundId(int x, int y, int z) { - return DIRT << 4; + return Block.DIRT << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java b/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java index 751e5e6b99f..dca312b5194 100644 --- a/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/SandyBiome.java @@ -1,7 +1,9 @@ package cn.nukkit.level.biome.type; +import cn.nukkit.block.Block; + /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class SandyBiome extends CoveredBiome { @@ -12,7 +14,7 @@ public int getSurfaceDepth(int x, int y, int z) { @Override public int getSurfaceId(int x, int y, int z) { - return SAND << 4; + return Block.SAND << Block.DATA_BITS; } @Override @@ -22,6 +24,6 @@ public int getGroundDepth(int x, int y, int z) { @Override public int getGroundId(int x, int y, int z) { - return SANDSTONE << 4; + return Block.SANDSTONE << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/biome/type/SnowyBiome.java b/src/main/java/cn/nukkit/level/biome/type/SnowyBiome.java index a8fb734fb5f..33e7c0d893d 100644 --- a/src/main/java/cn/nukkit/level/biome/type/SnowyBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/SnowyBiome.java @@ -1,22 +1,24 @@ package cn.nukkit.level.biome.type; -import cn.nukkit.level.generator.populator.impl.WaterIcePopulator; +import cn.nukkit.block.Block; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public abstract class SnowyBiome extends GrassyBiome { public SnowyBiome() { super(); - - WaterIcePopulator waterIce = new WaterIcePopulator(); - this.addPopulator(waterIce); } @Override public int getCoverId(int x, int z) { - return SNOW_LAYER << 4; + return Block.SNOW_LAYER << Block.DATA_BITS; + } + + @Override + public boolean isFreezing() { + return true; } @Override diff --git a/src/main/java/cn/nukkit/level/biome/type/WateryBiome.java b/src/main/java/cn/nukkit/level/biome/type/WateryBiome.java index 17ba88e2aa4..9e9ff127b6a 100644 --- a/src/main/java/cn/nukkit/level/biome/type/WateryBiome.java +++ b/src/main/java/cn/nukkit/level/biome/type/WateryBiome.java @@ -1,10 +1,13 @@ package cn.nukkit.level.biome.type; +import cn.nukkit.block.Block; + /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public abstract class WateryBiome extends CoveredBiome { + @Override public int getSurfaceDepth(int x, int y, int z) { return 0; @@ -23,6 +26,6 @@ public int getGroundDepth(int x, int y, int z) { @Override public int getGroundId(int x, int y, int z) { - return DIRT << 4; + return Block.DIRT << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/format/Chunk.java b/src/main/java/cn/nukkit/level/format/Chunk.java index 0862796331f..cc5b4617e4f 100644 --- a/src/main/java/cn/nukkit/level/format/Chunk.java +++ b/src/main/java/cn/nukkit/level/format/Chunk.java @@ -1,10 +1,11 @@ package cn.nukkit.level.format; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface Chunk extends FullChunk { + byte SECTION_COUNT = 16; boolean isSectionEmpty(float fY); @@ -15,6 +16,10 @@ public interface Chunk extends FullChunk { ChunkSection[] getSections(); + default int getSectionOffset() { + return 0; + } + class Entry { public final int chunkX; public final int chunkZ; diff --git a/src/main/java/cn/nukkit/level/format/ChunkSection.java b/src/main/java/cn/nukkit/level/format/ChunkSection.java index 9b08de1e62c..35171c110a2 100644 --- a/src/main/java/cn/nukkit/level/format/ChunkSection.java +++ b/src/main/java/cn/nukkit/level/format/ChunkSection.java @@ -1,33 +1,67 @@ package cn.nukkit.level.format; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.utils.BinaryStream; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface ChunkSection { + int getY(); - int getBlockId(int x, int y, int z); + default int getBlockId(int x, int y, int z) { + return this.getBlockId(x, y, z, Block.LAYER_NORMAL); + } + + int getBlockId(int x, int y, int z, BlockLayer layer); + + default void setBlockId(int x, int y, int z, int id) { + this.setBlockId(x, y, z, Block.LAYER_NORMAL, id); + } + + void setBlockId(int x, int y, int z, BlockLayer layer, int id); + + default int getBlockData(int x, int y, int z) { + return this.getBlockData(x, y, z, Block.LAYER_NORMAL); + } - void setBlockId(int x, int y, int z, int id); + int getBlockData(int x, int y, int z, BlockLayer layer); - int getBlockData(int x, int y, int z); + default void setBlockData(int x, int y, int z, int data) { + this.setBlockData(x, y, z, Block.LAYER_NORMAL, data); + } - void setBlockData(int x, int y, int z, int data); + void setBlockData(int x, int y, int z, BlockLayer layer, int data); - int getFullBlock(int x, int y, int z); + default int getFullBlock(int x, int y, int z) { + return this.getFullBlock(x, y, z, Block.LAYER_NORMAL); + } - Block getAndSetBlock(int x, int y, int z, Block block); + int getFullBlock(int x, int y, int z, BlockLayer layer); - boolean setFullBlockId(int x, int y, int z, int fullId); + default Block getAndSetBlock(int x, int y, int z, Block block) { + return this.getAndSetBlock(x, y, z, Block.LAYER_NORMAL, block); + } + + Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block); + + default boolean setFullBlockId(int x, int y, int z, int fullId) { + return this.setFullBlockId(x, y, z, Block.LAYER_NORMAL, fullId); + } + + boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId); boolean setBlock(int x, int y, int z, int blockId); boolean setBlock(int x, int y, int z, int blockId, int meta); + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId); + + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta); + int getBlockSkyLight(int x, int y, int z); void setBlockSkyLight(int x, int y, int z, int level); @@ -49,4 +83,4 @@ public interface ChunkSection { void writeTo(BinaryStream stream); ChunkSection copy(); -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/FullChunk.java b/src/main/java/cn/nukkit/level/format/FullChunk.java index 895f3c0b6ed..805918657ca 100644 --- a/src/main/java/cn/nukkit/level/format/FullChunk.java +++ b/src/main/java/cn/nukkit/level/format/FullChunk.java @@ -1,14 +1,16 @@ package cn.nukkit.level.format; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.entity.Entity; -import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.util.PalettedBlockStorage; + import java.io.IOException; import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface FullChunk extends Cloneable { @@ -32,25 +34,57 @@ default void setPosition(int x, int z) { void setProvider(LevelProvider provider); - int getFullBlock(int x, int y, int z); + default int getFullBlock(int x, int y, int z) { + return this.getFullBlock(x, y, z, Block.LAYER_NORMAL); + } + + int getFullBlock(int x, int y, int z, BlockLayer layer); + + default Block getAndSetBlock(int x, int y, int z, Block block) { + return this.getAndSetBlock(x, y, z, Block.LAYER_NORMAL, block); + } - Block getAndSetBlock(int x, int y, int z, Block block); + Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block); default boolean setFullBlockId(int x, int y, int z, int fullId) { - return setBlock(x, y, z, fullId >> 4, fullId & 0xF); + return setFullBlockId(x, y, z, Block.LAYER_NORMAL, fullId); } - boolean setBlock(int x, int y, int z, int blockId); + default boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId) { + return setBlockAtLayer(x, y, z, layer, fullId >> Block.DATA_BITS, fullId & Block.DATA_MASK); + } + + boolean setBlock(int x, int y, int z, int blockId); + + boolean setBlock(int x, int y, int z, int blockId, int meta); - boolean setBlock(int x, int y, int z, int blockId, int meta); + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id); - int getBlockId(int x, int y, int z); + boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id, int data); - void setBlockId(int x, int y, int z, int id); + default int getBlockId(int x, int y, int z) { + return this.getBlockId(x, y, z, Block.LAYER_NORMAL); + } - int getBlockData(int x, int y, int z); + int getBlockId(int x, int y, int z, BlockLayer layer); - void setBlockData(int x, int y, int z, int data); + default void setBlockId(int x, int y, int z, int id) { + this.setBlockId(x, y, z, Block.LAYER_NORMAL, id); + } + + void setBlockId(int x, int y, int z, BlockLayer layer, int id); + + default int getBlockData(int x, int y, int z) { + return this.getBlockData(x, y, z, Block.LAYER_NORMAL); + } + + int getBlockData(int x, int y, int z, BlockLayer layer); + + default void setBlockData(int x, int y, int z, int data) { + this.setBlockData(x, y, z, Block.LAYER_NORMAL, data); + } + + void setBlockData(int x, int y, int z, BlockLayer layer, int data); int getBlockExtraData(int x, int y, int z); @@ -76,18 +110,44 @@ default boolean setFullBlockId(int x, int y, int z, int fullId) { void populateSkyLight(); + default boolean has3dBiomes() { + return false; + } + + default PalettedBlockStorage getBiomeStorage(int y) { + return null; + } + int getBiomeId(int x, int z); - void setBiomeId(int x, int z, byte biomeId); + default int getBiomeId(int x, int y, int z) { + return this.getBiomeId(x, z); + } + + void setBiomeIdAndColor(int x, int z, int idAndColor); + + default void setBiomeId(int x, int y, int z, int biomeId) { + this.setBiomeId(x, y, z, (byte) biomeId); + } default void setBiomeId(int x, int z, int biomeId) { setBiomeId(x, z, (byte) biomeId); } - default void setBiome(int x, int z, Biome biome) { - setBiomeId(x, z, (byte) biome.getId()); + default void setBiomeId(int x, int y, int z, byte biomeId) { + this.setBiomeId(x, z, biomeId); } + void setBiomeId(int x, int z, byte biomeId); + + default void setBiome(int x, int z, cn.nukkit.level.biome.Biome biome) { + setBiomeId(x, z, biome.getId()); + } + + int getBiomeColor(int x, int z); + + void setBiomeColor(int x, int z, int r, int g, int b); + boolean isLightPopulated(); void setLightPopulated(); @@ -136,6 +196,10 @@ default void setBiome(int x, int z, Biome biome) { byte[] getBiomeIdArray(); + void setBiomeIdArray(byte[] biomeIdArray); + + int[] getBiomeColorArray(); + byte[] getHeightMapArray(); byte[] getBlockIdArray(); @@ -157,4 +221,4 @@ default void setBiome(int x, int z, Biome biome) { void setChanged(); void setChanged(boolean changed); -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/LevelProvider.java b/src/main/java/cn/nukkit/level/format/LevelProvider.java index 9f7de6c674f..92f78b5b678 100644 --- a/src/main/java/cn/nukkit/level/format/LevelProvider.java +++ b/src/main/java/cn/nukkit/level/format/LevelProvider.java @@ -4,19 +4,19 @@ import cn.nukkit.level.Level; import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.math.Vector3; -import cn.nukkit.scheduler.AsyncTask; import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface LevelProvider { + byte ORDER_YZX = 0; byte ORDER_ZXY = 1; - AsyncTask requestChunkTask(int X, int Z); + void requestChunkTask(int X, int Z); String getPath(); diff --git a/src/main/java/cn/nukkit/level/format/LevelProviderManager.java b/src/main/java/cn/nukkit/level/format/LevelProviderManager.java index e8c8aba9339..89a4753b2f4 100644 --- a/src/main/java/cn/nukkit/level/format/LevelProviderManager.java +++ b/src/main/java/cn/nukkit/level/format/LevelProviderManager.java @@ -6,10 +6,11 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class LevelProviderManager { + protected static final Map> providers = new HashMap<>(); public static void addProvider(Server server, Class clazz) { @@ -34,8 +35,6 @@ public static Class getProvider(String path) { } public static Class getProviderByName(String name) { - name = name.trim().toLowerCase(); - return providers.getOrDefault(name, null); + return providers.getOrDefault(name.trim().toLowerCase(), null); } - } diff --git a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java index cffd7a2e89a..ebe5113043c 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/Anvil.java +++ b/src/main/java/cn/nukkit/level/format/anvil/Anvil.java @@ -1,49 +1,42 @@ package cn.nukkit.level.format.anvil; +import cn.nukkit.Server; import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.level.format.generic.BaseLevelProvider; import cn.nukkit.level.format.generic.BaseRegionLoader; -import cn.nukkit.level.format.generic.serializer.NetworkChunkSerializer; import cn.nukkit.level.generator.Generator; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.scheduler.AsyncTask; -import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.ChunkException; import it.unimi.dsi.fastutil.objects.ObjectIterator; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -import java.util.function.BiConsumer; import java.util.regex.Pattern; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Anvil extends BaseLevelProvider { - public static final int VERSION = 19133; - private static final byte[] PAD_256 = new byte[256]; - public static final int EXTENDED_NEGATIVE_SUB_CHUNKS = 4; public Anvil(Level level, String path) throws IOException { super(level, path); } + @SuppressWarnings("unused") public static String getProviderName() { return "anvil"; } - public static byte getProviderOrder() { - return ORDER_YZX; - } - + @SuppressWarnings("unused") public static boolean usesChunkSection() { return true; } @@ -72,7 +65,6 @@ public static void generate(String path, String name, long seed, Class callback = (stream, subchunks) -> - this.getLevel().chunkRequestCallback(timestamp, x, z, subchunks, stream.getBuffer()); - NetworkChunkSerializer.serialize(chunk, callback, this.level.getDimensionData()); - return null; + + level.asyncChunk(chunk.cloneForChunkSending(), timestamp, x, z); } private int lastPosition = 0; @@ -135,7 +125,6 @@ public void doGarbageCollection(long time) { BaseFullChunk chunk = iter.next(); if (chunk == null) continue; if (chunk.isGenerated() && chunk.isPopulated() && chunk instanceof Chunk) { - Chunk anvilChunk = (Chunk) chunk; chunk.compress(); if (System.currentTimeMillis() - start >= time) break; } @@ -149,13 +138,13 @@ public synchronized BaseFullChunk loadChunk(long index, int chunkX, int chunkZ, int regionX = getRegionIndexX(chunkX); int regionZ = getRegionIndexZ(chunkZ); BaseRegionLoader region = this.loadRegion(regionX, regionZ); - this.level.timings.syncChunkLoadDataTimer.startTiming(); - BaseFullChunk chunk; - try { - chunk = region.readChunk(chunkX - regionX * 32, chunkZ - regionZ * 32); - } catch (IOException e) { - throw new RuntimeException(e); + BaseFullChunk chunk = null; + try { // Note: We don't want to ignore corrupted regions here as those would eventually cause problems when chunks are saved + chunk = region.readChunk(chunkX - (regionX << 5), chunkZ - (regionZ << 5)); + } catch (IOException ex) { + Server.getInstance().getLogger().error("Failed to read chunk " + chunkX + ", " + chunkZ, ex); } + if (chunk == null) { if (create) { chunk = this.getEmptyChunk(chunkX, chunkZ); @@ -164,7 +153,6 @@ public synchronized BaseFullChunk loadChunk(long index, int chunkX, int chunkZ, } else { putChunk(index, chunk); } - this.level.timings.syncChunkLoadDataTimer.stopTiming(); return chunk; } @@ -175,16 +163,15 @@ public synchronized void saveChunk(int X, int Z) { try { this.loadRegion(X >> 5, Z >> 5).writeChunk(chunk); } catch (Exception e) { - throw new ChunkException("Error saving chunk (" + X + ", " + Z + ")", e); + throw new ChunkException("Error saving chunk (" + X + ", " + Z + ')', e); } } } - @Override public synchronized void saveChunk(int x, int z, FullChunk chunk) { if (!(chunk instanceof Chunk)) { - throw new ChunkException("Invalid Chunk class"); + throw new ChunkException("Invalid chunk class (" + x + ", " + z + ')'); } int regionX = x >> 5; int regionZ = z >> 5; @@ -198,6 +185,7 @@ public synchronized void saveChunk(int x, int z, FullChunk chunk) { } } + @SuppressWarnings("unused") public static ChunkSection createChunkSection(int y) { ChunkSection cs = new ChunkSection(y); cs.hasSkyLight = true; diff --git a/src/main/java/cn/nukkit/level/format/anvil/Chunk.java b/src/main/java/cn/nukkit/level/format/anvil/Chunk.java index 4f0d6dec7a2..5339122700e 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/Chunk.java +++ b/src/main/java/cn/nukkit/level/format/anvil/Chunk.java @@ -1,6 +1,5 @@ package cn.nukkit.level.format.anvil; -import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.blockentity.BlockEntity; @@ -11,10 +10,7 @@ import cn.nukkit.level.format.generic.EmptyChunkSection; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.*; -import cn.nukkit.utils.BinaryStream; -import cn.nukkit.utils.BlockUpdateEntry; -import cn.nukkit.utils.ChunkException; -import cn.nukkit.utils.Zlib; +import cn.nukkit.utils.*; import java.io.ByteArrayInputStream; import java.io.DataInputStream; @@ -24,7 +20,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Chunk extends BaseChunk { @@ -38,6 +34,11 @@ public Chunk clone() { return (Chunk) super.clone(); } + @Override + public Chunk cloneForChunkSending() { + return (Chunk) super.cloneForChunkSending(); + } + public Chunk(LevelProvider level) { this(level, null); } @@ -59,9 +60,9 @@ public Chunk(LevelProvider level, CompoundTag nbt) { } if (nbt == null) { - this.biomes = new byte[16 * 16]; + this.biomes = new byte[256]; this.sections = new cn.nukkit.level.format.ChunkSection[16]; - if (16 >= 0) System.arraycopy(EmptyChunkSection.EMPTY, 0, this.sections, 0, 16); + System.arraycopy(EmptyChunkSection.EMPTY, 0, this.sections, 0, 16); return; } @@ -94,11 +95,11 @@ public Chunk(LevelProvider level, CompoundTag nbt) { this.setPosition(nbt.getInt("xPos"), nbt.getInt("zPos")); if (sections.length > SECTION_COUNT) { - throw new ChunkException("Invalid amount of chunks"); + throw new ChunkException("Invalid amount of chunks: " + sections.length); } if (nbt.contains("BiomeColors")) { - this.biomes = new byte[16 * 16]; + this.biomes = new byte[256]; int[] biomeColors = nbt.getIntArray("BiomeColors"); if (biomeColors != null && biomeColors.length == 256) { BiomePalette palette = new BiomePalette(biomeColors); @@ -131,7 +132,7 @@ public Chunk(LevelProvider level, CompoundTag nbt) { ListTag updateEntries = nbt.getList("TileTicks", CompoundTag.class); - if (updateEntries != null && updateEntries.size() > 0) { + if (updateEntries != null && updateEntries.size() > 0 && updateEntries.size() < 10000) { for (CompoundTag entryNBT : updateEntries.getAll()) { Block block = null; @@ -213,7 +214,7 @@ public CompoundTag getNBT() { tag.put("V", new ByteTag("V", (byte) 1)); tag.put("TerrainGenerated", new ByteTag("TerrainGenerated", (byte) (isGenerated() ? 1 : 0))); - tag.put("TerrainPopulated", new ByteTag("TerrainPopulated", (byte) (isPopulated() ? 1 : 0))); + tag.put("TerrainPopulated", new ByteTag("TerrainPopulated", (byte) (terrainPopulated ? 1 : 0))); return tag; } @@ -226,11 +227,12 @@ public static Chunk fromBinary(byte[] data, LevelProvider provider) { try { CompoundTag chunk = NBTIO.read(new ByteArrayInputStream(Zlib.inflate(data)), ByteOrder.BIG_ENDIAN); - if (!chunk.contains("Level") || !(chunk.get("Level") instanceof CompoundTag)) { + Tag levelTag = chunk.get("Level"); + if (!(levelTag instanceof CompoundTag)) { return null; } - return new Chunk(provider, chunk.getCompound("Level")); + return new Chunk(provider, (CompoundTag) levelTag); } catch (Exception e) { Server.getInstance().getLogger().logException(e); return null; @@ -245,11 +247,13 @@ public static Chunk fromFastBinary(byte[] data) { public static Chunk fromFastBinary(byte[] data, LevelProvider provider) { try { CompoundTag chunk = NBTIO.read(new DataInputStream(new ByteArrayInputStream(data)), ByteOrder.BIG_ENDIAN); - if (!chunk.contains("Level") || !(chunk.get("Level") instanceof CompoundTag)) { + + Tag levelTag = chunk.get("Level"); + if (!(levelTag instanceof CompoundTag)) { return null; } - return new Chunk(provider, chunk.getCompound("Level")); + return new Chunk(provider, (CompoundTag) levelTag); } catch (Exception e) { return null; } @@ -258,95 +262,27 @@ public static Chunk fromFastBinary(byte[] data, LevelProvider provider) { @Override public byte[] toFastBinary() { - CompoundTag nbt = this.getNBT().copy(); - nbt.remove("BiomeColors"); - - nbt.putInt("xPos", this.getX()); - nbt.putInt("zPos", this.getZ()); - - nbt.putByteArray("Biomes", this.getBiomeIdArray()); - int[] heightInts = new int[256]; - byte[] heightBytes = this.getHeightMapArray(); - for (int i = 0; i < heightInts.length; i++) { - heightInts[i] = heightBytes[i] & 0xFF; - } - - for (cn.nukkit.level.format.ChunkSection section : this.getSections()) { - if (section instanceof EmptyChunkSection) { - continue; - } - CompoundTag s = new CompoundTag(null); - s.putByte("Y", section.getY()); - s.putByteArray("Blocks", section.getIdArray()); - s.putByteArray("Data", section.getDataArray()); - s.putByteArray("BlockLight", section.getLightArray()); - s.putByteArray("SkyLight", section.getSkyLightArray()); - nbt.getList("Sections", CompoundTag.class).add(s); - } - - ArrayList entities = new ArrayList<>(); - for (Entity entity : this.getEntities().values()) { - if (!(entity instanceof Player) && !entity.closed) { - entity.saveNBT(); - entities.add(entity.namedTag); - } - } - ListTag entityListTag = new ListTag<>("Entities"); - entityListTag.setAll(entities); - nbt.putList(entityListTag); - - ArrayList tiles = new ArrayList<>(); - for (BlockEntity blockEntity : this.getBlockEntities().values()) { - blockEntity.saveNBT(); - tiles.add(blockEntity.namedTag); - } - ListTag tileListTag = new ListTag<>("TileEntities"); - tileListTag.setAll(tiles); - nbt.putList(tileListTag); - - Set entries = this.provider.getLevel().getPendingBlockUpdates(this); - - if (entries != null) { - ListTag tileTickTag = new ListTag<>("TileTicks"); - long totalTime = this.provider.getLevel().getCurrentTick(); - - for (BlockUpdateEntry entry : entries) { - CompoundTag entryNBT = new CompoundTag() - .putString("i", entry.block.getSaveId()) - .putInt("x", entry.pos.getFloorX()) - .putInt("y", entry.pos.getFloorY()) - .putInt("z", entry.pos.getFloorZ()) - .putInt("t", (int) (entry.delay - totalTime)) - .putInt("p", entry.priority); - tileTickTag.add(entryNBT); - } - - nbt.putList(tileTickTag); - } - - BinaryStream extraData = new BinaryStream(); - Map extraDataArray = this.getBlockExtraDataArray(); - extraData.putInt(extraDataArray.size()); - for (Integer key : extraDataArray.keySet()) { - extraData.putInt(key); - extraData.putShort(extraDataArray.get(key)); - } - - nbt.putByteArray("ExtraData", extraData.getBuffer()); - - CompoundTag chunk = new CompoundTag(""); - chunk.putCompound("Level", nbt); + CompoundTag chunk = chunkNBT(); try { return NBTIO.write(chunk, ByteOrder.BIG_ENDIAN); } catch (IOException e) { throw new RuntimeException(e); } - } @Override public byte[] toBinary() { + CompoundTag chunk = chunkNBT(); + + try { + return Zlib.deflate(NBTIO.write(chunk, ByteOrder.BIG_ENDIAN), 7); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private CompoundTag chunkNBT() { CompoundTag nbt = this.getNBT().copy(); nbt.remove("BiomeColors"); @@ -358,7 +294,7 @@ public byte[] toBinary() { if (section instanceof EmptyChunkSection) { continue; } - CompoundTag s = new CompoundTag(null); + CompoundTag s = new CompoundTag(); s.putByte("Y", (section.getY())); s.putByteArray("Blocks", section.getIdArray()); s.putByteArray("Data", section.getDataArray()); @@ -369,6 +305,7 @@ public byte[] toBinary() { nbt.putList(sectionList); nbt.putByteArray("Biomes", this.getBiomeIdArray()); + int[] heightInts = new int[256]; byte[] heightBytes = this.getHeightMapArray(); for (int i = 0; i < heightInts.length; i++) { @@ -378,26 +315,28 @@ public byte[] toBinary() { ArrayList entities = new ArrayList<>(); for (Entity entity : this.getEntities().values()) { - if (!(entity instanceof Player) && !entity.closed) { + if (entity.canSaveToStorage() && !entity.closed) { entity.saveNBT(); entities.add(entity.namedTag); } } + ListTag entityListTag = new ListTag<>("Entities"); entityListTag.setAll(entities); nbt.putList(entityListTag); ArrayList tiles = new ArrayList<>(); for (BlockEntity blockEntity : this.getBlockEntities().values()) { - blockEntity.saveNBT(); - tiles.add(blockEntity.namedTag); + if (blockEntity.canSaveToStorage()) { + blockEntity.saveNBT(); + tiles.add(blockEntity.namedTag); + } } ListTag tileListTag = new ListTag<>("TileEntities"); tileListTag.setAll(tiles); nbt.putList(tileListTag); Set entries = this.provider.getLevel().getPendingBlockUpdates(this); - if (entries != null) { ListTag tileTickTag = new ListTag<>("TileTicks"); long totalTime = this.provider.getLevel().getCurrentTick(); @@ -419,21 +358,15 @@ public byte[] toBinary() { BinaryStream extraData = new BinaryStream(); Map extraDataArray = this.getBlockExtraDataArray(); extraData.putInt(extraDataArray.size()); - for (Integer key : extraDataArray.keySet()) { - extraData.putInt(key); - extraData.putShort(extraDataArray.get(key)); + for (Map.Entry entry : extraDataArray.entrySet()) { + extraData.putInt(entry.getKey()); + extraData.putShort(entry.getValue()); } - nbt.putByteArray("ExtraData", extraData.getBuffer()); CompoundTag chunk = new CompoundTag(""); chunk.putCompound("Level", nbt); - - try { - return Zlib.deflate(NBTIO.write(chunk, ByteOrder.BIG_ENDIAN), RegionLoader.COMPRESSION_LEVEL); - } catch (Exception e) { - throw new RuntimeException(e); - } + return chunk; } @Override @@ -450,7 +383,7 @@ public int getBlockSkyLight(int x, int y, int z) { if (height < y) { return 15; } else if (height == y) { - return Block.transparent[getBlockId(x, y, z)] ? 15 : 0; + return Block.isBlockTransparentById(getBlockId(x, y, z)) ? 15 : 0; } else { return section.getBlockSkyLight(x, y & 0x0f, z); } @@ -496,7 +429,6 @@ public static Chunk getEmptyChunk(int chunkX, int chunkZ, LevelProvider provider chunk.inhabitedTime = 0; chunk.terrainGenerated = false; chunk.terrainPopulated = false; -// chunk.lightPopulated = false; return chunk; } catch (Exception e) { return null; @@ -517,4 +449,9 @@ public boolean compress() { } return result; } + + @Override + public String toString() { + return "(Anvil) Chunk[" + getX() + "," + getZ() + "] (inhabitedTime=" + inhabitedTime + " terrainPopulated=" + terrainPopulated + " terrainGenerated=" + terrainGenerated + ')'; + } } diff --git a/src/main/java/cn/nukkit/level/format/anvil/ChunkSection.java b/src/main/java/cn/nukkit/level/format/anvil/ChunkSection.java index ffc3ae7ac7e..517c5192b38 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/ChunkSection.java +++ b/src/main/java/cn/nukkit/level/format/anvil/ChunkSection.java @@ -1,24 +1,26 @@ package cn.nukkit.level.format.anvil; +import cn.nukkit.Server; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.format.anvil.util.BlockStorage; import cn.nukkit.level.format.anvil.util.NibbleArray; import cn.nukkit.level.format.generic.EmptyChunkSection; -import cn.nukkit.level.util.PalettedBlockStorage; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.utils.*; +import cn.nukkit.utils.Binary; +import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.Utils; +import cn.nukkit.utils.Zlib; import java.io.IOException; import java.util.Arrays; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ChunkSection implements cn.nukkit.level.format.ChunkSection { - private static final PalettedBlockStorage EMPTY_STORAGE = PalettedBlockStorage.createFromBlockPalette(); - private final int y; private final BlockStorage storage; @@ -29,10 +31,10 @@ public class ChunkSection implements cn.nukkit.level.format.ChunkSection { protected boolean hasBlockLight; protected boolean hasSkyLight; - private ChunkSection(int y, BlockStorage storage, byte[] blockLight, byte[] skyLight, byte[] compressedLight, - boolean hasBlockLight, boolean hasSkyLight) { + public ChunkSection(int y, BlockStorage storage, byte[] blockLight, byte[] skyLight, byte[] compressedLight, boolean hasBlockLight, boolean hasSkyLight) { this.y = y; this.storage = storage; + this.blockLight = blockLight; this.skyLight = skyLight; this.compressedLight = compressedLight; this.hasBlockLight = hasBlockLight; @@ -61,8 +63,10 @@ public ChunkSection(CompoundTag nbt) { for (int z = 0; z < 16; z++) { for (int y = 0; y < 16; y++) { int index = getAnvilIndex(x, y, z); - storage.setBlockId(x, y, z, blocks[index]); + // Set block data first so we can overwrite it when removing data values from air in setBlockId storage.setBlockData(x, y, z, data.get(index)); + int b = blocks[index] & 0xff; + storage.setBlockId(x, y, z, b); } } } @@ -81,21 +85,32 @@ public int getY() { } @Override - public int getBlockId(int x, int y, int z) { + public int getBlockId(int x, int y, int z, BlockLayer layer) { + if (layer != BlockLayer.NORMAL) { + return 0; + } synchronized (storage) { return storage.getBlockId(x, y, z); } } @Override - public void setBlockId(int x, int y, int z, int id) { + public void setBlockId(int x, int y, int z, BlockLayer layer, int id) { + if (layer != BlockLayer.NORMAL) { + return; + } + synchronized (storage) { storage.setBlockId(x, y, z, id); } } @Override - public boolean setFullBlockId(int x, int y, int z, int fullId) { + public boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId) { + if (layer != BlockLayer.NORMAL) { + return true; + } + synchronized (storage) { storage.setFullBlock(x, y, z, (char) fullId); } @@ -103,21 +118,33 @@ public boolean setFullBlockId(int x, int y, int z, int fullId) { } @Override - public int getBlockData(int x, int y, int z) { + public int getBlockData(int x, int y, int z, BlockLayer layer) { + if (layer != BlockLayer.NORMAL) { + return 0; + } + synchronized (storage) { return storage.getBlockData(x, y, z); } } @Override - public void setBlockData(int x, int y, int z, int data) { + public void setBlockData(int x, int y, int z, BlockLayer layer, int data) { + if (layer != BlockLayer.NORMAL) { + return; + } + synchronized (storage) { storage.setBlockData(x, y, z, data); } } @Override - public int getFullBlock(int x, int y, int z) { + public int getFullBlock(int x, int y, int z, BlockLayer layer) { + if (layer != BlockLayer.NORMAL) { + return 0; + } + synchronized (storage) { return storage.getFullBlock(x, y, z); } @@ -130,19 +157,36 @@ public boolean setBlock(int x, int y, int z, int blockId) { } } - public Block getAndSetBlock(int x, int y, int z, Block block) { + public Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block) { + if (layer != BlockLayer.NORMAL) { + return Block.get(Block.AIR); + } + synchronized (storage) { int fullId = storage.getAndSetFullBlock(x, y, z, block.getFullId()); - return Block.fullList[fullId].clone(); + return Block.getUnsafe(fullId).clone(); } } @Override public boolean setBlock(int x, int y, int z, int blockId, int meta) { - int newFullId = (blockId << 4) + meta; + return this.setBlockAtLayer(x, y, z, Block.LAYER_NORMAL, blockId, meta); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId) { + return this.setBlockAtLayer(x, y, z, layer, blockId, 0); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta) { + if (layer != BlockLayer.NORMAL) { + return true; + } + synchronized (storage) { - int previousFullId = storage.getAndSetFullBlock(x, y, z, newFullId); - return (newFullId != previousFullId); + int fullId = (blockId << Block.DATA_BITS) | meta; + return storage.getAndSetFullBlock(x, y, z, fullId) != fullId; } } @@ -251,16 +295,14 @@ public byte[] getDataArray() { @Override public byte[] getSkyLightArray() { - if (this.skyLight != null) return skyLight; - if (hasSkyLight) { - if (compressedLight != null) { - inflate(); - return this.skyLight; + if (this.skyLight != null) return this.skyLight; + if (this.hasSkyLight) { + if (this.compressedLight != null) { + this.inflate(); + if (this.skyLight != null) return this.skyLight; } - return EmptyChunkSection.EMPTY_SKY_LIGHT_ARR; - } else { - return EmptyChunkSection.EMPTY_LIGHT_ARR; } + return EmptyChunkSection.EMPTY_SKY_LIGHT_ARR; } private void inflate() { @@ -285,19 +327,18 @@ private void inflate() { } } } catch (IOException e) { - e.printStackTrace(); + Server.getInstance().getLogger().logException(e); } } @Override public byte[] getLightArray() { - if (this.blockLight != null) return blockLight; - if (hasBlockLight) { - inflate(); - return this.blockLight; - } else { - return EmptyChunkSection.EMPTY_LIGHT_ARR; + if (this.blockLight != null) return this.blockLight; + if (this.hasBlockLight) { + this.inflate(); + if (this.blockLight != null) return this.blockLight; } + return EmptyChunkSection.EMPTY_LIGHT_ARR; } @Override @@ -305,24 +346,10 @@ public boolean isEmpty() { return false; } - private byte[] toXZY(char[] raw) { - byte[] buffer = ThreadCache.byteCache6144.get(); - for (int i = 0; i < 4096; i++) { - buffer[i] = (byte) (raw[i] >> 4); - } - for (int i = 0, j = 4096; i < 4096; i += 2, j++) { - buffer[j] = (byte) (((raw[i + 1] & 0xF) << 4) | (raw[i] & 0xF)); - } - return buffer; - } - @Override public void writeTo(BinaryStream stream) { synchronized (storage) { - stream.putByte((byte) 8); // Paletted chunk because Mojang messed up the old one - stream.putByte((byte) 2); this.storage.writeTo(stream); - EMPTY_STORAGE.writeTo(stream); } } @@ -352,7 +379,7 @@ public boolean compress() { try { compressedLight = Zlib.deflate(toDeflate, 1); } catch (Exception e) { - e.printStackTrace(); + Server.getInstance().getLogger().logException(e); } } return true; @@ -371,4 +398,4 @@ public ChunkSection copy() { this.hasSkyLight ); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/anvil/RegionLoader.java b/src/main/java/cn/nukkit/level/format/anvil/RegionLoader.java index ffdbd8c4ba9..5f7aa8a6e37 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/RegionLoader.java +++ b/src/main/java/cn/nukkit/level/format/anvil/RegionLoader.java @@ -14,10 +14,11 @@ import java.util.TreeMap; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class RegionLoader extends BaseRegionLoader { + public RegionLoader(LevelProvider level, int regionX, int regionZ) throws IOException { super(level, regionX, regionZ, "mca"); } @@ -42,42 +43,42 @@ public Chunk readChunk(int x, int z) throws IOException { } try { - Integer[] table = this.locationTable.get(index); - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(table[0] << 12); - int length = raf.readInt(); - byte compression = raf.readByte(); - if (length <= 0 || length >= MAX_SECTOR_LENGTH) { - if (length >= MAX_SECTOR_LENGTH) { - table[0] = ++this.lastSector; - table[1] = 1; - this.locationTable.put(index, table); - MainLogger.getLogger().error("Corrupted chunk header detected"); + Integer[] table = this.locationTable.get(index); + RandomAccessFile raf = this.getRandomAccessFile(); + raf.seek(table[0] << 12); + int length = raf.readInt(); + byte compression = raf.readByte(); + if (length <= 0 || length >= MAX_SECTOR_LENGTH) { + if (length >= MAX_SECTOR_LENGTH) { + table[0] = ++this.lastSector; + table[1] = 1; + this.locationTable.put(index, table); + MainLogger.getLogger().error("Corrupted chunk header detected (" + x + ", " + z + ") (" + this.levelProvider.getName() + "/r." + this.x + "." + this.z + ".mca)"); + } + return null; } - return null; - } - if (length > (table[1] << 12)) { - MainLogger.getLogger().error("Corrupted bigger chunk detected"); - table[1] = length >> 12; - this.locationTable.put(index, table); - this.writeLocationIndex(index); - } else if (compression != COMPRESSION_ZLIB && compression != COMPRESSION_GZIP) { - MainLogger.getLogger().error("Invalid compression type"); - return null; - } + if (length > (table[1] << 12)) { + MainLogger.getLogger().error("Corrupted bigger chunk detected (" + x + ", " + z + ") (" + this.levelProvider.getName() + "/r." + this.x + "." + this.z + ".mca)"); + table[1] = length >> 12; + this.locationTable.put(index, table); + this.writeLocationIndex(index); + } else if (compression != COMPRESSION_ZLIB && compression != COMPRESSION_GZIP) { + MainLogger.getLogger().error("Invalid compression type (" + x + ", " + z + ") (" + this.levelProvider.getName() + "/r." + this.x + "." + this.z + ".mca)"); + return null; + } - byte[] data = new byte[length - 1]; - raf.readFully(data); - Chunk chunk = this.unserializeChunk(data); - if (chunk != null) { - return chunk; - } else { - MainLogger.getLogger().error("Corrupted chunk detected at (" + x + ", " + z + ") in " + levelProvider.getName()); - return null; - } - } catch (EOFException e) { - MainLogger.getLogger().error("Your world is corrupt, because some code is bad and corrupted it. oops. "); + byte[] data = new byte[length - 1]; + raf.readFully(data); + Chunk chunk = this.unserializeChunk(data); + if (chunk != null) { + return chunk; + } else { + MainLogger.getLogger().error("Corrupted chunk detected (" + x + ", " + z + ") (" + this.levelProvider.getName() + "/r." + this.x + "." + this.z + ".mca)"); + return null; + } + } catch (EOFException e) { + MainLogger.getLogger().error("World corruption occurred (" + x + ", " + z + ") (" + this.levelProvider.getName() + "/r." + this.x + "." + this.z + ".mca)"); return null; } } @@ -96,7 +97,7 @@ public boolean chunkExists(int x, int z) { protected void saveChunk(int x, int z, byte[] chunkData) throws IOException { int length = chunkData.length + 1; if (length + 4 > MAX_SECTOR_LENGTH) { - throw new ChunkException("Chunk is too big! " + (length + 4) + " > " + MAX_SECTOR_LENGTH); + throw new ChunkException("Chunk [" + x + ", " + z + "] is too big! " + (length + 4) + " > " + MAX_SECTOR_LENGTH); } int sectors = (int) Math.ceil((length + 4) / 4096d); int index = getChunkOffset(x, z); @@ -119,7 +120,7 @@ protected void saveChunk(int x, int z, byte[] chunkData) throws IOException { RandomAccessFile raf = this.getRandomAccessFile(); raf.seek(table[0] << 12); - BinaryStream stream = new BinaryStream(); + BinaryStream stream = new BinaryStream(new byte[5 + chunkData.length]).reset(); stream.put(Binary.writeInt(length)); stream.putByte(COMPRESSION_ZLIB); stream.put(chunkData); @@ -214,8 +215,8 @@ protected void loadLocationTable() throws IOException { RandomAccessFile raf = this.getRandomAccessFile(); raf.seek(0); this.lastSector = 1; - int[] data = new int[1024 * 2]; //1024 records * 2 times - for (int i = 0; i < 1024 * 2; i++) { + int[] data = new int[2048]; //1024 records * 2 times + for (int i = 0; i < 2048; i++) { data[i] = raf.readInt(); } for (int i = 0; i < 1024; ++i) { @@ -245,7 +246,7 @@ private int cleanGarbage() throws IOException { RandomAccessFile raf = this.getRandomAccessFile(); Map sectors = new TreeMap<>(); for (Map.Entry entry : this.locationTable.entrySet()) { - Integer index = (Integer) entry.getKey(); + int index = (int) entry.getKey(); Integer[] data = (Integer[]) entry.getValue(); if (data[0] == 0 || data[1] == 0) { this.locationTable.put(index, new Integer[]{0, 0, 0}); diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java index 0aa025eb075..3d58f2d3a82 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java +++ b/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java @@ -4,8 +4,8 @@ import cn.nukkit.utils.ThreadCache; import java.util.Arrays; -@Deprecated public final class BiomePalette { + private int biome; private BitArray256 encodedData; private IntPalette palette; @@ -77,15 +77,12 @@ public synchronized void set(int index, int value) { raw[i] = palette.getKey(raw[i]); } - int oldRaw = raw[4]; - raw[index] = value; palette.add(value); - int oldBits = MathHelper.log2(palette.length() - 2); int newBits = MathHelper.log2(palette.length() - 1); - if (oldBits != newBits) { + if (MathHelper.log2(palette.length() - 2) != newBits) { encodedData = new BitArray256(newBits); } @@ -96,7 +93,6 @@ public synchronized void set(int index, int value) { encodedData.fromRaw(raw); } } - } public synchronized int[] toRaw() { diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray256.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray256.java index 4a60bbfadb1..3591101d694 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray256.java +++ b/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray256.java @@ -6,12 +6,13 @@ * @author https://github.com/boy0001/ */ public final class BitArray256 { + private final int bitsPerEntry; protected final long[] data; public BitArray256(int bitsPerEntry) { this.bitsPerEntry = bitsPerEntry; - int longLen = (this.bitsPerEntry * 256) >> 6; + int longLen = (this.bitsPerEntry << 8) >> 6; this.data = new long[longLen]; } @@ -26,7 +27,7 @@ public final void setAt(int index, int value) { int localBitIndexStart = bitIndexStart & 63; this.data[longIndexStart] = this.data[longIndexStart] & ~((long) ((1 << bitsPerEntry) - 1) << localBitIndexStart) | ((long) value) << localBitIndexStart; - if(localBitIndexStart > 64 - bitsPerEntry) { + if (localBitIndexStart > 64 - bitsPerEntry) { int longIndexEnd = longIndexStart + 1; int localShiftStart = 64 - localBitIndexStart; int localShiftEnd = bitsPerEntry - localShiftStart; @@ -40,11 +41,10 @@ public final int getAt(int index) { int longIndexStart = bitIndexStart >> 6; int localBitIndexStart = bitIndexStart & 63; - if(localBitIndexStart <= 64 - bitsPerEntry) { + if (localBitIndexStart <= 64 - bitsPerEntry) { return (int)(this.data[longIndexStart] >>> localBitIndexStart & ((1 << bitsPerEntry) - 1)); } else { - int localShift = 64 - localBitIndexStart; - return (int) ((this.data[longIndexStart] >>> localBitIndexStart | this.data[longIndexStart + 1] << localShift) & ((1 << bitsPerEntry) - 1)); + return (int) ((this.data[longIndexStart] >>> localBitIndexStart | this.data[longIndexStart + 1] << (64 - localBitIndexStart)) & ((1 << bitsPerEntry) - 1)); } } @@ -55,8 +55,7 @@ public final void fromRaw(int[] arr) { } public BitArray256 grow(int newBitsPerEntry) { - int amtGrow = newBitsPerEntry - this.bitsPerEntry; - if (amtGrow <= 0) return this; + if (newBitsPerEntry - this.bitsPerEntry <= 0) return this; BitArray256 newBitArray = new BitArray256(newBitsPerEntry); int[] buffer = ThreadCache.intCache256.get(); diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray4096.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray4096.java deleted file mode 100644 index e4891e8ffc7..00000000000 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BitArray4096.java +++ /dev/null @@ -1,163 +0,0 @@ -package cn.nukkit.level.format.anvil.palette; - -import cn.nukkit.utils.ThreadCache; - -/** - * @author https://github.com/boy0001/ - */ -public final class BitArray4096 { - - private final int bitsPerEntry; - private final int maxSeqLocIndex; - private final int maxEntryValue; - private final long[] data; - - public BitArray4096(int bitsPerEntry) { - this.bitsPerEntry = bitsPerEntry; - this.maxSeqLocIndex = 64 - bitsPerEntry; - maxEntryValue = (1 << bitsPerEntry) - 1; - int longLen = (this.bitsPerEntry * 4096) >> 6; - this.data = new long[longLen]; - } - - public final void setAt(int index, int value) { - if (data.length == 0) return; - int bitIndexStart = index * bitsPerEntry; - int longIndexStart = bitIndexStart >> 6; - int localBitIndexStart = bitIndexStart & 63; - this.data[longIndexStart] = this.data[longIndexStart] & ~((long) maxEntryValue << localBitIndexStart) | ((long) value) << localBitIndexStart; - - if(localBitIndexStart > maxSeqLocIndex) { - int longIndexEnd = longIndexStart + 1; - int localShiftStart = 64 - localBitIndexStart; - int localShiftEnd = bitsPerEntry - localShiftStart; - this.data[longIndexEnd] = this.data[longIndexEnd] >>> localShiftEnd << localShiftEnd | (((long) value) >> localShiftStart); - } - } - - public final int getAt(int index) { - if (data.length == 0) return 0; - int bitIndexStart = index * bitsPerEntry; - - int longIndexStart = bitIndexStart >> 6; - - int localBitIndexStart = bitIndexStart & 63; - if(localBitIndexStart <= maxSeqLocIndex) { - return (int)(this.data[longIndexStart] >>> localBitIndexStart & maxEntryValue); - } else { - int localShift = 64 - localBitIndexStart; - return (int) ((this.data[longIndexStart] >>> localBitIndexStart | this.data[longIndexStart + 1] << localShift) & maxEntryValue); - } - } - - public final void fromRawSlow(char[] arr) { - for (int i = 0; i < arr.length; i++) { - setAt(i, arr[i]); - } - } - - public final void fromRaw(char[] arr) { - final long[] data = this.data; - final int dataLength = data.length; - final int bitsPerEntry = this.bitsPerEntry; - final int maxEntryValue = this.maxEntryValue; - final int maxSeqLocIndex = this.maxSeqLocIndex; - - int localStart = 0; - char lastVal; - int arrI = 0; - long l = 0; - long nextVal; - for (int i = 0; i < dataLength; i++) { - for (; localStart <= maxSeqLocIndex; localStart += bitsPerEntry) { - lastVal = arr[arrI++]; - l |= ((long) lastVal << localStart); - } - if (localStart < 64) { - if (i != dataLength - 1) { - lastVal = arr[arrI++]; - int shift = 64 - localStart; - - nextVal = lastVal >> shift; - - l |= ((lastVal - (nextVal << shift)) << localStart); - - data[i] = l; - data[i + 1] = l = nextVal; - - localStart -= maxSeqLocIndex; - } - } else { - localStart = 0; - data[i] = l; - l = 0; - } - } - } - - public BitArray4096 grow(int newBitsPerEntry) { - int amtGrow = newBitsPerEntry - this.bitsPerEntry; - if (amtGrow <= 0) return this; - BitArray4096 newBitArray = new BitArray4096(newBitsPerEntry); - - char[] buffer = ThreadCache.charCache4096.get(); - toRaw(buffer); - newBitArray.fromRaw(buffer); - - return newBitArray; - } - - public BitArray4096 growSlow(int bitsPerEntry) { - BitArray4096 newBitArray = new BitArray4096(bitsPerEntry); - for (int i = 0; i < 4096; i++) { - newBitArray.setAt(i, getAt(i)); - } - return newBitArray; - } - - public final char[] toRawSlow() { - char[] arr = new char[4096]; - for (int i = 0; i < arr.length; i++) { - arr[i] = (char) getAt(i); - } - return arr; - } - - public final char[] toRaw() { - return toRaw(new char[4096]); - } - - protected final char[] toRaw(char[] buffer) { - final long[] data = this.data; - final int dataLength = data.length; - final int bitsPerEntry = this.bitsPerEntry; - final int maxEntryValue = this.maxEntryValue; - final int maxSeqLocIndex = this.maxSeqLocIndex; - - int localStart = 0; - char lastVal; - int arrI = 0; - long l; - for (int i = 0; i < dataLength; i++) { - l = data[i]; - for (; localStart <= maxSeqLocIndex; localStart += bitsPerEntry) { - lastVal = (char) (l >>> localStart & maxEntryValue); - buffer[arrI++] = lastVal; - } - if (localStart < 64) { - if (i != dataLength - 1) { - lastVal = (char) (l >>> localStart); - localStart -= maxSeqLocIndex; - l = data[i + 1]; - int localShift = bitsPerEntry - localStart; - lastVal |= l << localShift; - lastVal &= maxEntryValue; - buffer[arrI++] = lastVal; - } - } else { - localStart = 0; - } - } - return buffer; - } -} diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BlockDataPalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BlockDataPalette.java deleted file mode 100644 index 1ca87183028..00000000000 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BlockDataPalette.java +++ /dev/null @@ -1,221 +0,0 @@ -package cn.nukkit.level.format.anvil.palette; - -import cn.nukkit.Server; -import cn.nukkit.math.MathHelper; -import cn.nukkit.utils.ThreadCache; -import com.google.common.base.Preconditions; - -import java.util.Arrays; -import java.util.BitSet; - -/** - * @author https://github.com/boy0001/ - */ -public final class BlockDataPalette implements Cloneable { - private static final int BLOCK_SIZE = 4096; - private volatile char[] rawData; - - private volatile BitArray4096 encodedData; - private volatile CharPalette palette; - - // TODO compress unused sections - // private byte[] compressedData; - - public BlockDataPalette() { - this(new char[BLOCK_SIZE]); - } - - public BlockDataPalette(char[] rawData) { - Preconditions.checkArgument(rawData.length == BLOCK_SIZE, "Data is not 4096"); - this.rawData = rawData; - } - - private char[] getCachedRaw() { - char[] raw = rawData; - if (raw != null) { - return raw; - } else if (!Server.getInstance().isPrimaryThread()) { - return getRaw(); - } - return rawData; - } - - public synchronized char[] getRaw() { - CharPalette palette = this.palette; - BitArray4096 encodedData = this.encodedData; - this.encodedData = null; - this.palette = null; - - char[] raw = rawData; - if (raw == null && palette != null) { - if (encodedData != null) { - raw = encodedData.toRaw(); - } else { - raw = new char[BLOCK_SIZE]; - } - for (int i = 0; i < BLOCK_SIZE; i++) { - raw[i] = palette.getKey(raw[i]); - } - } else { - raw = new char[BLOCK_SIZE]; - } - rawData = raw; - return rawData; - } - - private int getIndex(int x, int y, int z) { - return (x << 8) + (z << 4) + y; // XZY = Bedrock format - } - - public int getBlockData(int x, int y, int z) { - return getFullBlock(x, y, z) & 0xF; - } - - public int getBlockId(int x, int y, int z) { - return getFullBlock(x, y, z) >> 4; - } - - public void setBlockId(int x, int y, int z, int id) { - setFullBlock(x, y, z, (char) (id << 4)); - } - - public synchronized void setBlockData(int x, int y, int z, int data) { - int index = getIndex(x, y, z); - char[] raw = getCachedRaw(); - - if (raw != null) { - int fullId = raw[index]; - raw[index] = (char) ((fullId & 0xFFF0) | data); - } if (palette != null && encodedData != null) { - char fullId = palette.getKey(encodedData.getAt(index)); - if ((fullId & 0xF) != data) { - setPaletteFullBlock(index, (char) ((fullId & 0xFFF0) | data)); - } - } else { - throw new IllegalStateException("Raw data and pallete was null"); - } - } - - public int getFullBlock(int x, int y, int z) { - return getFullBlock(getIndex(x, y, z)); - } - - public void setFullBlock(int x, int y, int z, int value) { - this.setFullBlock(getIndex(x, y, z), (char) value); - } - - public int getAndSetFullBlock(int x, int y, int z, int value) { - return getAndSetFullBlock(getIndex(x, y, z), (char) value); - } - - private int getAndSetFullBlock(int index, char value) { - char[] raw = getCachedRaw(); - if (raw != null) { - char result = raw[index]; - raw[index] = value; - return result; - } else if (palette != null && encodedData != null) { - char result = palette.getKey(encodedData.getAt(index)); - if (result != value) { - setPaletteFullBlock(index, value); - } - return result; - } else { - throw new IllegalStateException("Raw data and pallete was null"); - } - } - - private int getFullBlock(int index) { - char[] raw = getCachedRaw(); - if (raw != null) { - return raw[index]; - } else if (palette != null && encodedData != null) { - return palette.getKey(encodedData.getAt(index)); - } else { - throw new IllegalStateException("Raw data and pallete was null"); - } - } - - private void setFullBlock(int index, char value) { - char[] raw = getCachedRaw(); - if (raw != null) { - raw[index] = value; - } else if (!setPaletteFullBlock(index, value)) { - throw new IllegalStateException("Raw data and pallete was null"); - } - } - - private synchronized boolean setPaletteFullBlock(int index, char value) { - CharPalette palette = this.palette; - BitArray4096 encodedData = this.encodedData; - if (palette != null && encodedData != null) { - char encodedValue = palette.getValue(value); - if (encodedValue != Character.MAX_VALUE) { - encodedData.setAt(index, encodedValue); - } else { - char[] raw = encodedData.toRaw(); - for (int i = 0; i < BLOCK_SIZE; i++) { - raw[i] = palette.getKey(raw[i]); - } - raw[index] = value; - this.rawData = raw; - this.encodedData = null; - this.palette = null; - } - return true; - } - return false; - } - - public synchronized boolean compress() { - char[] raw = rawData; - if (raw != null) { - char unique = 0; - - BitSet countTable = ThreadCache.boolCache4096.get(); - char[] mapFullTable = ThreadCache.charCache4096.get(); - char[] mapBitTable = ThreadCache.charCache4096v2.get(); - countTable.clear(); - for (char c : raw) { - if (!countTable.get(c)) { - mapBitTable[unique] = c; - countTable.set(c); - unique++; - } - } - - char[] keys = Arrays.copyOfRange(mapBitTable, 0, unique); - if (keys.length > 1) { - Arrays.sort(keys); - for (char c = 0; c < keys.length; c++) { - mapFullTable[keys[c]] = c; - } - } else { - mapFullTable[keys[0]] = 0; - } - - CharPalette palette = new CharPalette(); - palette.set(keys); - - int bits = MathHelper.log2(unique - 1); - BitArray4096 encodedData = new BitArray4096(bits); - - for (int i = 0; i < raw.length; i++) { - mapBitTable[i] = mapFullTable[raw[i]]; - } - - encodedData.fromRaw(mapBitTable); - - this.palette = palette; - this.encodedData = encodedData; - rawData = null; - return true; - } - return false; - } - - public synchronized BlockDataPalette clone() { - char[] raw = getRaw(); - return new BlockDataPalette(raw.clone()); - } -} diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BytePalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BytePalette.java deleted file mode 100644 index 0d75eae5d5f..00000000000 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BytePalette.java +++ /dev/null @@ -1,93 +0,0 @@ -package cn.nukkit.level.format.anvil.palette; - -import java.util.Arrays; - -/** - * @author https://github.com/boy0001/ - */ -public class BytePalette { - private static byte[] BYTE0 = new byte[0]; - private byte[] keys = BYTE0; - private byte lastIndex = Byte.MIN_VALUE; - - public void add(byte key) { - keys = insert(key); - lastIndex = Byte.MIN_VALUE; - } - - protected void set(byte[] keys) { - this.keys = keys; - lastIndex = Byte.MIN_VALUE; - } - - private byte[] insert(byte val) { - lastIndex = Byte.MIN_VALUE; - if (keys.length == 0) { - return new byte[] { val }; - } - else if (val < keys[0]) { - byte[] s = new byte[keys.length + 1]; - System.arraycopy(keys, 0, s, 1, keys.length); - s[0] = val; - return s; - } else if (val > keys[keys.length - 1]) { - byte[] s = Arrays.copyOf(keys, keys.length + 1); - s[keys.length] = val; - return s; - } - byte[] s = Arrays.copyOf(keys, keys.length + 1); - for (int i = 0; i < s.length; i++) { - if (keys[i] < val) { - continue; - } - System.arraycopy(keys, i, s, i + 1, s.length - i - 1); - s[i] = val; - break; - } - return s; - } - - public byte getKey(int index) { - return keys[index]; - } - - public byte getValue(byte key) { - byte lastTmp = lastIndex; - boolean hasLast = lastTmp != Byte.MIN_VALUE; - int index; - if (hasLast) { - byte lastKey = keys[lastTmp]; - if (lastKey == key) return lastTmp; - if (lastKey > key) { - index = binarySearch0(0, lastTmp, key); - } else { - index = binarySearch0(lastTmp + 1, keys.length, key); - } - } else { - index = binarySearch0(0, keys.length, key); - } - if (index >= keys.length || index < 0) { - return lastIndex = Byte.MIN_VALUE; - } else { - return lastIndex = (byte) index; - } - } - - private int binarySearch0(int fromIndex, int toIndex, byte key) { - int low = fromIndex; - int high = toIndex - 1; - - while (low <= high) { - int mid = (low + high) >>> 1; - byte midVal = keys[mid]; - - if (midVal < key) - low = mid + 1; - else if (midVal > key) - high = mid - 1; - else - return mid; // key found - } - return -(low + 1); // key not found. - } -} diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/CharPalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/CharPalette.java deleted file mode 100644 index 4e18cde54b5..00000000000 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/CharPalette.java +++ /dev/null @@ -1,93 +0,0 @@ -package cn.nukkit.level.format.anvil.palette; - -import java.util.Arrays; - -/** - * @author https://github.com/boy0001/ - */ -public class CharPalette { - private static char[] CHAR0 = new char[0]; - private char[] keys = CHAR0; - private char lastIndex = Character.MAX_VALUE; - - public void add(char key) { - keys = insert(key); - lastIndex = Character.MAX_VALUE; - } - - protected void set(char[] keys) { - this.keys = keys; - lastIndex = Character.MAX_VALUE; - } - - private char[] insert(char val) { - lastIndex = Character.MAX_VALUE; - if (keys.length == 0) { - return new char[] { val }; - } - else if (val < keys[0]) { - char[] s = new char[keys.length + 1]; - System.arraycopy(keys, 0, s, 1, keys.length); - s[0] = val; - return s; - } else if (val > keys[keys.length - 1]) { - char[] s = Arrays.copyOf(keys, keys.length + 1); - s[keys.length] = val; - return s; - } - char[] s = Arrays.copyOf(keys, keys.length + 1); - for (int i = 0; i < s.length; i++) { - if (keys[i] < val) { - continue; - } - System.arraycopy(keys, i, s, i + 1, s.length - i - 1); - s[i] = val; - break; - } - return s; - } - - public char getKey(int index) { - return keys[index]; - } - - public char getValue(char key) { - char lastTmp = lastIndex; - boolean hasLast = lastTmp != Character.MAX_VALUE; - int index; - if (hasLast) { - char lastKey = keys[lastTmp]; - if (lastKey == key) return lastTmp; - if (lastKey > key) { - index = binarySearch0(0, lastTmp, key); - } else { - index = binarySearch0(lastTmp + 1, keys.length, key); - } - } else { - index = binarySearch0(0, keys.length, key); - } - if (index >= keys.length || index < 0) { - return lastIndex = Character.MAX_VALUE; - } else { - return lastIndex = (char) index; - } - } - - private int binarySearch0(int fromIndex, int toIndex, char key) { - int low = fromIndex; - int high = toIndex - 1; - - while (low <= high) { - int mid = (low + high) >>> 1; - char midVal = keys[mid]; - - if (midVal < key) - low = mid + 1; - else if (midVal > key) - high = mid - 1; - else - return mid; // key found - } - return -(low + 1); // key not found. - } -} diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/IntPalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/IntPalette.java index 86a65209361..a460c546379 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/IntPalette.java +++ b/src/main/java/cn/nukkit/level/format/anvil/palette/IntPalette.java @@ -6,7 +6,8 @@ * @author https://github.com/boy0001/ */ public class IntPalette { - private static int[] INT0 = new int[0]; + + private static final int[] INT0 = new int[0]; private int[] keys = INT0; private int lastIndex = Integer.MIN_VALUE; @@ -15,11 +16,6 @@ public void add(int key) { lastIndex = Integer.MIN_VALUE; } - protected void set(int[] keys) { - this.keys = keys; - lastIndex = Integer.MIN_VALUE; - } - private int[] insert(int val) { lastIndex = Integer.MIN_VALUE; if (keys.length == 0) { diff --git a/src/main/java/cn/nukkit/level/format/anvil/util/BlockStorage.java b/src/main/java/cn/nukkit/level/format/anvil/util/BlockStorage.java index fbeb28121da..c1ad91559a3 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/util/BlockStorage.java +++ b/src/main/java/cn/nukkit/level/format/anvil/util/BlockStorage.java @@ -1,13 +1,12 @@ package cn.nukkit.level.format.anvil.util; +import cn.nukkit.block.Block; import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.util.PalettedBlockStorage; import cn.nukkit.utils.BinaryStream; -import com.google.common.base.Preconditions; - -import java.util.Arrays; public class BlockStorage { + private static final int SECTION_SIZE = 4096; private final byte[] blockIds; private final NibbleArray blockData; @@ -24,7 +23,7 @@ private BlockStorage(byte[] blockIds, NibbleArray blockData) { private static int getIndex(int x, int y, int z) { int index = (x << 8) + (z << 4) + y; // XZY = Bedrock format - Preconditions.checkArgument(index >= 0 && index < SECTION_SIZE, "Invalid index"); + if (index < 0 || index >= SECTION_SIZE) throw new IllegalArgumentException("Invalid index"); return index; } @@ -49,60 +48,66 @@ public int getFullBlock(int x, int y, int z) { } public void setFullBlock(int x, int y, int z, int value) { - this.setFullBlock(getIndex(x, y, z), (short) value); + this.setFullBlock(getIndex(x, y, z), value); } public int getAndSetFullBlock(int x, int y, int z, int value) { - return getAndSetFullBlock(getIndex(x, y, z), (short) value); + return getAndSetFullBlock(getIndex(x, y, z), value); } - private int getAndSetFullBlock(int index, short value) { - Preconditions.checkArgument(value < 0xfff, "Invalid full block"); - byte oldBlock = blockIds[index]; + private int getAndSetFullBlock(int index, int value) { + // Convert to old data bits + int block = value >> Block.DATA_BITS; + int meta = value & Block.DATA_MASK; + value = (block << 4) + Math.min(meta, 15); + + if (value >= 0x1fff) throw new IllegalArgumentException("Invalid full block " + value); + int oldBlock = blockIds[index] & 0xff; byte oldData = blockData.get(index); - byte newBlock = (byte) ((value & 0xff0) >> 4); byte newData = (byte) (value & 0xf); - if (oldBlock != newBlock) { - blockIds[index] = newBlock; + if (oldBlock != block) { + blockIds[index] = (byte) (block & 0xff); } if (oldData != newData) { blockData.set(index, newData); } - return ((oldBlock & 0xff) << 4) | oldData; - } - private int getFullBlock(int index) { - byte block = blockIds[index]; - byte data = blockData.get(index); - return ((block & 0xff) << 4) | data; + // Convert to new data bits + return (oldBlock << Block.DATA_BITS) | oldData; } - private void setFullBlock(int index, short value) { - Preconditions.checkArgument(value < 0xfff, "Invalid full block"); - byte block = (byte) ((value & 0xff0) >> 4); - byte data = (byte) (value & 0xf); + private int getFullBlock(int index) { + int b = blockIds[index] & 0xff; - blockIds[index] = block; - blockData.set(index, data); + // Convert to new data bits + return (b << Block.DATA_BITS) | blockData.get(index); } - public byte[] getBlockIds() { - return Arrays.copyOf(blockIds, blockIds.length); - } + private void setFullBlock(int index, int value) { + // Convert to old data bits + int block = value >> Block.DATA_BITS; + int meta = value & Block.DATA_MASK; + value = (block << 4) + Math.min(meta, 15); - public byte[] getBlockData() { - return blockData.getData(); + if (value >= 0x1fff) throw new IllegalArgumentException("Invalid full block " + value); + blockIds[index] = (byte) (block & 0xff); + blockData.set(index, (byte) (value & 0xf)); } public void writeTo(BinaryStream stream) { - PalettedBlockStorage storage = PalettedBlockStorage.createFromBlockPalette(); + stream.putByte((byte) 8); // paletted + + PalettedBlockStorage layer = PalettedBlockStorage.createFromBlockPalette(); for (int i = 0; i < SECTION_SIZE; i++) { - storage.setBlock(i, GlobalBlockPalette.getOrCreateRuntimeId(blockIds[i] & 0xff, blockData.get(i))); + layer.setBlock(i, GlobalBlockPalette.getOrCreateRuntimeId(blockIds[i] & 0xff, blockData.get(i))); } - storage.writeTo(stream); + + stream.putByte((byte) 1); // layers + + layer.writeTo(stream); } public BlockStorage copy() { return new BlockStorage(blockIds.clone(), blockData.copy()); } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/level/format/anvil/util/NibbleArray.java b/src/main/java/cn/nukkit/level/format/anvil/util/NibbleArray.java index 45ebe670cc8..a8bf00db46e 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/util/NibbleArray.java +++ b/src/main/java/cn/nukkit/level/format/anvil/util/NibbleArray.java @@ -3,11 +3,14 @@ import com.google.common.base.Preconditions; +import java.util.Arrays; + public class NibbleArray implements Cloneable { + private final byte[] data; public NibbleArray(int length) { - data = new byte[length / 2]; + data = new byte[(length >> 1)]; } public NibbleArray(byte[] array) { @@ -15,8 +18,8 @@ public NibbleArray(byte[] array) { } public byte get(int index) { - Preconditions.checkElementIndex(index, data.length * 2); - byte val = data[index / 2]; + if (index >= data.length << 1) throw new IndexOutOfBoundsException("index=" + index + ", data.length=" + data.length); + byte val = data[index >> 1]; if ((index & 1) == 0) { return (byte) (val & 0x0f); } else { @@ -25,10 +28,13 @@ public byte get(int index) { } public void set(int index, byte value) { - Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); - Preconditions.checkElementIndex(index, data.length * 2); + if (value != (value & 15)) { + throw new IllegalArgumentException("Nibbles must have a value between 0 and 15."); + } else if (index >= data.length << 1 || index < 0) { + throw new IndexOutOfBoundsException("index=" + index + ", data.length=" + data.length); + } value &= 0xf; - int half = index / 2; + int half = index >> 1; byte previous = data[half]; if ((index & 1) == 0) { data[half] = (byte) (previous & 0xf0 | value); @@ -37,12 +43,23 @@ public void set(int index, byte value) { } } + public void remove(int index) { + if (index >= data.length << 1 || index < 0) { + throw new IndexOutOfBoundsException("index=" + index + ", data.length=" + data.length); + } + int half = index >> 1; + byte previous = data[half]; + if ((index & 1) == 0) { + data[half] = (byte) (previous & 0xf0); + } else { + data[half] = (byte) (previous & 0x0f); + } + } + public void fill(byte value) { Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15."); value &= 0xf; - for (int i = 0; i < data.length; i++) { - data[i] = (byte) ((value << 4) | value); - } + Arrays.fill(data, (byte) ((value << 4) | value)); } public void copyFrom(byte[] bytes) { diff --git a/src/main/java/cn/nukkit/level/format/generic/Anvil2LevelDBConverter.java b/src/main/java/cn/nukkit/level/format/generic/Anvil2LevelDBConverter.java new file mode 100644 index 00000000000..49ada2a64ff --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/generic/Anvil2LevelDBConverter.java @@ -0,0 +1,237 @@ +package cn.nukkit.level.format.generic; + +import cn.nukkit.Server; +import cn.nukkit.level.DimensionData; +import cn.nukkit.level.DimensionEnum; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.anvil.Anvil; +import cn.nukkit.level.format.anvil.RegionLoader; +import cn.nukkit.level.format.leveldb.LevelDBProvider; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.level.generator.Generator; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.scheduler.TaskHandler; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Anvil2LevelDBConverter { + + private final Level sourceLevel; + private final Level targetLevel; + private final Executor executor; + + public Anvil2LevelDBConverter(Level sourceLevel) { + this.sourceLevel = sourceLevel; + + Server server = sourceLevel.getServer(); + String levelName = sourceLevel.getFolderName() + "-convert"; + + if (!server.generateLevel(levelName, 0, Generator.getGenerator("void"))) { + throw new IllegalStateException("Failed to generate target level, make sure it doesn't exist: " + levelName); + } + + this.targetLevel = server.getLevelByName(levelName); + if (this.targetLevel == null) { + throw new IllegalStateException("Failed to load target level: " + levelName); + } + + this.sourceLevel.setAutoSave(false); + this.sourceLevel.isBeingConverted = true; + + this.targetLevel.setAutoSave(false); + this.targetLevel.isBeingConverted = true; + + // DO NOT INCREASE THREAD COUNT + this.executor = Executors.newFixedThreadPool(1, new ThreadFactoryBuilder() + .setNameFormat("Converted Thread " + sourceLevel.getFolderName() + " - %s") + .build()); + } + + public CompletableFuture convert() { + try { + return this.convertUnsafe(); + } catch (Exception e) { + throw new RuntimeException("Failed to convert level " + this.sourceLevel.getFolderName(), e); + } + } + + private CompletableFuture convertUnsafe() throws IOException { + Anvil anvil = (Anvil) this.sourceLevel.getProvider(); + LevelDBProvider levelDBProvider = (LevelDBProvider) this.targetLevel.getProvider(); + + Server server = this.sourceLevel.getServer(); + server.getLogger().info("Converting level " + this.targetLevel.getFolderName()); + + // Clone level data + CompoundTag levelData = anvil.getLevelData().clone(); + levelData.putString("generatorName", this.sourceLevel.getGenerator().getName()); + levelData.remove("GameRules"); + levelDBProvider.setLevelData(levelData, this.sourceLevel.getGameRules()); + levelDBProvider.saveLevelData(); + + boolean nether = this.sourceLevel.getDimension() == Level.DIMENSION_NETHER; + DimensionData dimensionData = DimensionEnum.getDataFromId(this.sourceLevel.getDimension()); + if (dimensionData == null) { + server.getLogger().warning("Invalid DimensionData, using OVERWORLD"); + dimensionData = DimensionEnum.OVERWORLD.getDimensionData(); + } + this.targetLevel.setDimensionData(dimensionData); + + List regions = new ObjectArrayList<>(); + Path regionFolder = Paths.get("worlds/" + this.sourceLevel.getFolderName() + "/region"); + try (DirectoryStream stream = Files.newDirectoryStream(regionFolder, "**.mca")) { + for (Path path : stream) { + regions.add(path); + } + } + + AtomicInteger regionCounter = new AtomicInteger(); + AtomicInteger chunksConverted = new AtomicInteger(); + AtomicInteger chunksConvertedPerSecond = new AtomicInteger(); + + TaskHandler tickFuture = server.getScheduler().scheduleRepeatingTask(() -> + chunksConvertedPerSecond.set(0), 20); + + IntConsumer callback = chunksCount -> { + chunksConverted.addAndGet(chunksCount); + int regionNumber = regionCounter.incrementAndGet(); + + int chps = chunksConvertedPerSecond.addAndGet(chunksCount); + String message = "[Convert-%s] [%s/%s] [%s chps] Converted %s chunks"; + server.getLogger().info(String.format(message, this.sourceLevel.getFolderName(), regionNumber, regions.size(), chps, chunksCount)); + }; + + List> futures = new ObjectArrayList<>(); + for (Path regionPath : regions) { + CompletableFuture future = new CompletableFuture<>(); + this.executor.execute(() -> { + convertRegion(regionPath, anvil, levelDBProvider, callback, nether ? 128 : 256); + future.complete(null); + }); + futures.add(future); + } + + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).whenCompleteAsync((v, error) -> { + server.getScheduler().cancelTask(tickFuture.getTaskId()); + if (error != null) { + server.getLogger().error("Failed to convert level " + this.sourceLevel.getFolderName(), error); + } else { + this.convertFinished(); + } + }, server.getScheduler()::scheduleTask); + } + + private void convertFinished() { + Server server = this.sourceLevel.getServer(); + + server.unloadLevel(this.targetLevel); + + if (sourceLevel.equals(server.getDefaultLevel())) { + server.getLogger().warning("Couldn't unload original level due to it being loaded as the default level. Please restart the server."); + } else { + server.unloadLevel(this.sourceLevel); + } + + server.getLogger().info("[Convert-" + this.sourceLevel.getFolderName() + "] All done! Converted level is saved under worlds/" + this.targetLevel.getFolderName() + "/"); + } + + private static void convertRegion(Path regionFile, Anvil anvil, LevelDBProvider levelDBProvider, IntConsumer callback, int maxY) { + RegionPosition regionPos = RegionPosition.fromPath(regionFile); + assert regionPos != null; + + try { + BaseRegionLoader regionLoader = new RegionLoader(anvil, regionPos.x, regionPos.z); + int chunks = 0; + for (int x = 0; x < 32; x++) { + for (int z = 0; z < 32; z++) { + int chunkX = regionPos.x << 5 | x; + int chunkZ = regionPos.z << 5 | z; + + BaseFullChunk oldChunk = regionLoader.readChunk(chunkX - (getRegionIndexX(chunkX) << 5), chunkZ - (getRegionIndexZ(chunkZ) << 5)); // anvil.getChunk(chunkX, chunkZ, false); + if (oldChunk == null || (!oldChunk.isPopulated() && !oldChunk.isGenerated())) { + continue; + } + + chunks++; + BaseFullChunk newChunk = levelDBProvider.getChunk(chunkX, chunkZ, true); + convertChunk(oldChunk, (LevelDBChunk) newChunk, levelDBProvider, anvil, maxY); + } + } + + callback.accept(chunks); + } catch (IOException e) { + throw new IllegalStateException("Failed to convert region " + regionPos, e); + } + } + + private static void convertChunk(BaseFullChunk oldChunk, LevelDBChunk newChunk, LevelDBProvider levelDBProvider, Anvil anvil, int maxY) { + oldChunk.initChunk(); + newChunk.setGenerated(true); + newChunk.setPopulated(oldChunk.isPopulated()); + + newChunk.setBiomeIdArray(oldChunk.getBiomeIdArray()); + + newChunk.heightMap = Arrays.copyOf(oldChunk.getHeightMapArray(), oldChunk.getHeightMapArray().length); + newChunk.tiles = oldChunk.getBlockEntities(); + newChunk.entities = oldChunk.getEntities(); + newChunk.tileList = oldChunk.tileList; + + for (int blockX = 0; blockX < 16; blockX++) { + for (int blockY = 0; blockY < maxY; blockY++) { + for (int blockZ = 0; blockZ < 16; blockZ++) { + int fullId = oldChunk.getFullBlock(blockX, blockY, blockZ); + newChunk.setFullBlockId(blockX, blockY, blockZ, fullId); + newChunk.setBlockLight(blockX, blockY, blockZ, oldChunk.getBlockSkyLight(blockX, blockY, blockZ)); + } + } + } + + levelDBProvider.saveChunkSync(newChunk.getX(), newChunk.getZ(), newChunk); + levelDBProvider.unloadChunk(newChunk.getX(), newChunk.getZ(), false); + anvil.unloadChunk(oldChunk.getX(), oldChunk.getZ(), false); + } + + protected static int getRegionIndexX(int chunkX) { + return chunkX >> 5; + } + + protected static int getRegionIndexZ(int chunkZ) { + return chunkZ >> 5; + } + + @ToString + @RequiredArgsConstructor + private static class RegionPosition { + private static final Pattern PATTERN = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$"); + + private final int x; + private final int z; + + static RegionPosition fromPath(Path regionPath) { + Matcher matcher = PATTERN.matcher(regionPath.getFileName().toString()); + if (!matcher.matches()) { + return null; + } + int x = Integer.parseInt(matcher.group(1)); + int z = Integer.parseInt(matcher.group(2)); + return new RegionPosition(x, z); + } + } +} diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java index 4dd5d53ec7c..8c608fc4d0f 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java @@ -2,10 +2,10 @@ import cn.nukkit.Server; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.level.format.Chunk; import cn.nukkit.level.format.ChunkSection; -import cn.nukkit.level.format.LevelProvider; import cn.nukkit.utils.ChunkException; import java.io.IOException; @@ -14,19 +14,34 @@ import java.util.Arrays; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BaseChunk extends BaseFullChunk implements Chunk { - protected ChunkSection[] sections; + public ChunkSection[] sections; + + @SuppressWarnings("MismatchedReadAndWriteOfArray") + private static final byte[] emptyIdArray = new byte[4096]; + @SuppressWarnings("MismatchedReadAndWriteOfArray") + private static final byte[] emptyDataArray = new byte[2048]; @Override public BaseChunk clone() { BaseChunk chunk = (BaseChunk) super.clone(); - if (this.biomes != null) chunk.biomes = this.biomes.clone(); - chunk.heightMap = this.getHeightMapArray().clone(); + if (sections != null && sections[0] != null) { + chunk.sections = new ChunkSection[sections.length]; + for (int i = 0; i < sections.length; i++) { + chunk.sections[i] = sections[i].copy(); + } + } + return chunk; + } + + @Override + public BaseChunk cloneForChunkSending() { + BaseChunk chunk = (BaseChunk) super.cloneForChunkSending(); if (sections != null && sections[0] != null) { chunk.sections = new ChunkSection[sections.length]; for (int i = 0; i < sections.length; i++) { @@ -39,13 +54,20 @@ public BaseChunk clone() { private void removeInvalidTile(int x, int y, int z) { BlockEntity entity = getTile(x, y, z); if (entity != null && !entity.isBlockEntityValid()) { - removeBlockEntity(entity); + this.removeBlockEntity(entity); + if (!entity.closed) { + entity.closed = true; + if (entity.level != null) { + entity.level.removeBlockEntity(entity); + entity.level = null; + } + } } } @Override - public int getFullBlock(int x, int y, int z) { - return this.sections[y >> 4].getFullBlock(x, y & 0x0f, z); + public int getFullBlock(int x, int y, int z, BlockLayer layer) { + return this.getSection(y >> 4).getFullBlock(x, y & 0x0f, z, layer); } @Override @@ -54,36 +76,36 @@ public boolean setBlock(int x, int y, int z, int blockId) { } @Override - public Block getAndSetBlock(int x, int y, int z, Block block) { + public Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block) { int Y = y >> 4; try { setChanged(); - return this.sections[Y].getAndSetBlock(x, y & 0x0f, z, block); + return this.getSection(Y).getAndSetBlock(x, y & 0x0f, z, layer, block); } catch (ChunkException e) { try { this.setInternalSection(Y, (ChunkSection) this.providerClass.getMethod("createChunkSection", int.class).invoke(this.providerClass, Y)); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - return this.sections[Y].getAndSetBlock(x, y & 0x0f, z, block); + return this.getSection(Y).getAndSetBlock(x, y & 0x0f, z, layer, block); } finally { removeInvalidTile(x, y, z); } } @Override - public boolean setFullBlockId(int x, int y, int z, int fullId) { + public boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId) { int Y = y >> 4; try { setChanged(); - return this.sections[Y].setFullBlockId(x, y & 0x0f, z, fullId); + return this.getSection(Y).setFullBlockId(x, y & 0x0f, z, layer, fullId); } catch (ChunkException e) { try { this.setInternalSection(Y, (ChunkSection) this.providerClass.getMethod("createChunkSection", int.class).invoke(this.providerClass, Y)); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - return this.sections[Y].setFullBlockId(x, y & 0x0f, z, fullId); + return this.getSection(Y).setFullBlockId(x, y & 0x0f, z, layer, fullId); } finally { removeInvalidTile(x, y, z); } @@ -91,27 +113,37 @@ public boolean setFullBlockId(int x, int y, int z, int fullId) { @Override public boolean setBlock(int x, int y, int z, int blockId, int meta) { + return this.setBlockAtLayer(x, y, z, BlockLayer.NORMAL, blockId, meta); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id) { + return this.setBlockAtLayer(x, y, z, layer, id, 0); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta) { int Y = y >> 4; try { setChanged(); - return this.sections[Y].setBlock(x, y & 0x0f, z, blockId, meta); + return this.getSection(Y).setBlockAtLayer(x, y & 0x0f, z, layer, blockId, meta); } catch (ChunkException e) { try { this.setInternalSection(Y, (ChunkSection) this.providerClass.getMethod("createChunkSection", int.class).invoke(this.providerClass, Y)); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - return this.sections[Y].setBlock(x, y & 0x0f, z, blockId, meta); + return this.getSection(Y).setBlockAtLayer(x, y & 0x0f, z, layer, blockId, meta); } finally { removeInvalidTile(x, y, z); } } @Override - public void setBlockId(int x, int y, int z, int id) { + public void setBlockId(int x, int y, int z, BlockLayer layer, int id) { int Y = y >> 4; try { - this.sections[Y].setBlockId(x, y & 0x0f, z, id); + this.getSection(Y).setBlockId(x, y & 0x0f, z, layer, id); setChanged(); } catch (ChunkException e) { try { @@ -119,27 +151,26 @@ public void setBlockId(int x, int y, int z, int id) { } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - this.sections[Y].setBlockId(x, y & 0x0f, z, id); + this.getSection(Y).setBlockId(x, y & 0x0f, z, layer, id); } finally { removeInvalidTile(x, y, z); } } - @Override - public int getBlockId(int x, int y, int z) { - return this.sections[y >> 4].getBlockId(x, y & 0x0f, z); + public int getBlockId(int x, int y, int z, BlockLayer layer) { + return this.getSection(y >> 4).getBlockId(x, y & 0x0f, z, layer); } @Override - public int getBlockData(int x, int y, int z) { - return this.sections[y >> 4].getBlockData(x, y & 0x0f, z); + public int getBlockData(int x, int y, int z, BlockLayer layer) { + return this.getSection(y >> 4).getBlockData(x, y & 0x0f, z, layer); } @Override - public void setBlockData(int x, int y, int z, int data) { + public void setBlockData(int x, int y, int z, BlockLayer layer, int data) { int Y = y >> 4; try { - this.sections[Y].setBlockData(x, y & 0x0f, z, data); + this.getSection(Y).setBlockData(x, y & 0x0f, z, layer, data); setChanged(); } catch (ChunkException e) { try { @@ -147,7 +178,7 @@ public void setBlockData(int x, int y, int z, int data) { } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - this.sections[Y].setBlockData(x, y & 0x0f, z, data); + this.getSection(Y).setBlockData(x, y & 0x0f, z, layer, data); } finally { removeInvalidTile(x, y, z); } @@ -155,14 +186,14 @@ public void setBlockData(int x, int y, int z, int data) { @Override public int getBlockSkyLight(int x, int y, int z) { - return this.sections[y >> 4].getBlockSkyLight(x, y & 0x0f, z); + return this.getSection(y >> 4).getBlockSkyLight(x, y & 0x0f, z); } @Override public void setBlockSkyLight(int x, int y, int z, int level) { int Y = y >> 4; try { - this.sections[Y].setBlockSkyLight(x, y & 0x0f, z, level); + this.getSection(Y).setBlockSkyLight(x, y & 0x0f, z, level); setChanged(); } catch (ChunkException e) { try { @@ -170,20 +201,20 @@ public void setBlockSkyLight(int x, int y, int z, int level) { } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - this.sections[Y].setBlockSkyLight(x, y & 0x0f, z, level); + this.getSection(Y).setBlockSkyLight(x, y & 0x0f, z, level); } } @Override public int getBlockLight(int x, int y, int z) { - return this.sections[y >> 4].getBlockLight(x, y & 0x0f, z); + return this.getSection(y >> 4).getBlockLight(x, y & 0x0f, z); } @Override public void setBlockLight(int x, int y, int z, int level) { int Y = y >> 4; try { - this.sections[Y].setBlockLight(x, y & 0x0f, z, level); + this.getSection(Y).setBlockLight(x, y & 0x0f, z, level); setChanged(); } catch (ChunkException e) { try { @@ -191,35 +222,39 @@ public void setBlockLight(int x, int y, int z, int level) { } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e1) { Server.getInstance().getLogger().logException(e1); } - this.sections[Y].setBlockLight(x, y & 0x0f, z, level); + this.getSection(Y).setBlockLight(x, y & 0x0f, z, level); } } @Override public boolean isSectionEmpty(float fY) { - return this.sections[(int) fY] instanceof EmptyChunkSection; + return this.getSection(fY) instanceof EmptyChunkSection; } @Override public ChunkSection getSection(float fY) { - return this.sections[(int) fY]; + int index = (this.getSectionOffset() + (int) fY); + if (index >= this.sections.length) { + Throwable t = new Throwable("Tried to get chunk section " + index + ", but chunk has only " + this.sections.length + " sections!"); + this.getProvider().getLevel().getServer().getLogger().logException(t); + index = this.sections.length - 1; + } + return this.sections[index]; } @Override public boolean setSection(float fY, ChunkSection section) { - byte[] emptyIdArray = new byte[4096]; - byte[] emptyDataArray = new byte[2048]; if (Arrays.equals(emptyIdArray, section.getIdArray()) && Arrays.equals(emptyDataArray, section.getDataArray())) { - this.sections[(int) fY] = EmptyChunkSection.EMPTY[(int) fY]; + this.sections[this.getSectionOffset() + (int) fY] = EmptyChunkSection.EMPTY[(int) fY]; } else { - this.sections[(int) fY] = section; + this.sections[this.getSectionOffset() + (int) fY] = section; } setChanged(); return true; } private void setInternalSection(float fY, ChunkSection section) { - this.sections[(int) fY] = section; + this.sections[this.getSectionOffset() + (int) fY] = section; setChanged(); } @@ -230,41 +265,41 @@ public boolean load() throws IOException { @Override public boolean load(boolean generate) throws IOException { - return this.getProvider() != null && this.getProvider().getChunk(this.getX(), this.getZ(), true) != null; + return this.provider != null && this.provider.getChunk(this.getX(), this.getZ(), true) != null; } @Override public byte[] getBlockIdArray() { - ByteBuffer buffer = ByteBuffer.allocate(4096 * SECTION_COUNT); + ByteBuffer buffer = ByteBuffer.allocate(65536); for (int y = 0; y < SECTION_COUNT; y++) { - buffer.put(this.sections[y].getIdArray()); + buffer.put(this.getSection(y).getIdArray()); } return buffer.array(); } @Override public byte[] getBlockDataArray() { - ByteBuffer buffer = ByteBuffer.allocate(2048 * SECTION_COUNT); + ByteBuffer buffer = ByteBuffer.allocate(32768); for (int y = 0; y < SECTION_COUNT; y++) { - buffer.put(this.sections[y].getDataArray()); + buffer.put(this.getSection(y).getDataArray()); } return buffer.array(); } @Override public byte[] getBlockSkyLightArray() { - ByteBuffer buffer = ByteBuffer.allocate(2048 * SECTION_COUNT); + ByteBuffer buffer = ByteBuffer.allocate(32768); for (int y = 0; y < SECTION_COUNT; y++) { - buffer.put(this.sections[y].getSkyLightArray()); + buffer.put(this.getSection(y).getSkyLightArray()); } return buffer.array(); } @Override public byte[] getBlockLightArray() { - ByteBuffer buffer = ByteBuffer.allocate(2048 * SECTION_COUNT); + ByteBuffer buffer = ByteBuffer.allocate(32768); for (int y = 0; y < SECTION_COUNT; y++) { - buffer.put(this.sections[y].getLightArray()); + buffer.put(this.getSection(y).getLightArray()); } return buffer.array(); } @@ -273,14 +308,4 @@ public byte[] getBlockLightArray() { public ChunkSection[] getSections() { return sections; } - - @Override - public byte[] getHeightMapArray() { - return this.heightMap; - } - - @Override - public LevelProvider getProvider() { - return this.provider; - } } diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java index 0b9127a2ba7..e03e1bcd39a 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java @@ -1,13 +1,17 @@ package cn.nukkit.level.format.generic; import cn.nukkit.Player; -import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.blockentity.BlockEntityContainer; +import cn.nukkit.blockentity.PersistentDataContainerBlockEntity; import cn.nukkit.entity.Entity; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.format.LevelProvider; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; import cn.nukkit.nbt.tag.ListTag; import cn.nukkit.nbt.tag.NumberTag; @@ -22,10 +26,11 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BaseFullChunk implements FullChunk, ChunkManager { + protected Map entities; protected Map tiles; @@ -66,7 +71,7 @@ public abstract class BaseFullChunk implements FullChunk, ChunkManager { protected boolean isInit; - protected BatchPacket chunkPacket; + private BatchPacket chunkPacket; @Override public BaseFullChunk clone() { @@ -76,6 +81,7 @@ public BaseFullChunk clone() { } catch (CloneNotSupportedException e) { return null; } + if (this.biomes != null) { chunk.biomes = this.biomes.clone(); } @@ -97,38 +103,61 @@ public BaseFullChunk clone() { } if (this.heightMap != null) { - chunk.heightMap = this.getHeightMapArray().clone(); + chunk.heightMap = this.heightMap.clone(); + } + return chunk; + } + + protected BaseFullChunk cloneForChunkSending() { + BaseFullChunk chunk; + try { + chunk = (BaseFullChunk) super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + + if (this.tiles != null) { + chunk.tiles = new Long2ObjectOpenHashMap<>(this.tiles); } + + // No need to deep clone biome or block data as we don't modify them + + // Not needed for chunk sending + chunk.entities = null; + chunk.tileList = null; + chunk.NBTentities = null; + chunk.NBTtiles = null; + chunk.extraData = null; + return chunk; } public void setChunkPacket(BatchPacket packet) { if (packet != null) { packet.trim(); + this.chunkPacket = packet; } - this.chunkPacket = packet; } public BatchPacket getChunkPacket() { - BatchPacket pk = chunkPacket; + BatchPacket pk = this.chunkPacket; if (pk != null) { pk.trim(); } - return chunkPacket; + return pk; } public void initChunk() { if (this.getProvider() != null && !this.isInit) { boolean changed = false; if (this.NBTentities != null) { - this.getProvider().getLevel().timings.syncChunkLoadEntitiesTimer.startTiming(); for (CompoundTag nbt : NBTentities) { if (!nbt.contains("id")) { this.setChanged(); continue; } ListTag pos = nbt.getList("Pos"); - if ((((NumberTag) pos.get(0)).getData().intValue() >> 4) != this.getX() || ((((NumberTag) pos.get(2)).getData().intValue() >> 4) != this.getZ())) { + if ((((NumberTag) pos.get(0)).getData().intValue() >> 4) != this.x || ((((NumberTag) pos.get(2)).getData().intValue() >> 4) != this.z)) { changed = true; continue; } @@ -137,29 +166,26 @@ public void initChunk() { changed = true; } } - this.getProvider().getLevel().timings.syncChunkLoadEntitiesTimer.stopTiming(); this.NBTentities = null; } if (this.NBTtiles != null) { - this.getProvider().getLevel().timings.syncChunkLoadBlockEntitiesTimer.startTiming(); for (CompoundTag nbt : NBTtiles) { if (nbt != null) { if (!nbt.contains("id")) { changed = true; continue; } - if ((nbt.getInt("x") >> 4) != this.getX() || ((nbt.getInt("z") >> 4) != this.getZ())) { + if ((nbt.getInt("x") >> 4) != this.x || ((nbt.getInt("z") >> 4) != this.z)) { changed = true; continue; } - BlockEntity blockEntity = BlockEntity.createBlockEntity(nbt.getString("id"), this, nbt); + BlockEntity blockEntity = BlockEntity.createBlockEntity(nbt.getString("id").replaceFirst("BlockEntity", ""), this, nbt); if (blockEntity == null) { changed = true; } } } - this.getProvider().getLevel().timings.syncChunkLoadBlockEntitiesTimer.stopTiming(); this.NBTtiles = null; } @@ -193,12 +219,12 @@ public void setPosition(int x, int z) { public final void setX(int x) { this.x = x; - this.hash = Level.chunkHash(x, getZ()); + this.hash = Level.chunkHash(x, z); } public final void setZ(int z) { this.z = z; - this.hash = Level.chunkHash(getX(), z); + this.hash = Level.chunkHash(x, z); } @Override @@ -210,7 +236,7 @@ public LevelProvider getProvider() { public void setProvider(LevelProvider provider) { this.provider = provider; - if(provider != null) { + if (provider != null) { this.providerClass = provider.getClass(); } } @@ -222,10 +248,27 @@ public int getBiomeId(int x, int z) { @Override public void setBiomeId(int x, int z, byte biomeId) { - this.setChanged(); this.biomes[(x << 4) | z] = biomeId; } + @Override + public void setBiomeId(int x, int z, int biomeId) { + this.biomes[(x << 4) | z] = (byte) biomeId; + } + + @Override + public int getBiomeColor(int x, int z) { + return 0; + } + + @Override + public void setBiomeIdAndColor(int x, int z, int idAndColor) { + } + + @Override + public void setBiomeColor(int x, int z, int r, int g, int b) { + } + @Override public int getHeightMap(int x, int z) { return this.heightMap[(z << 4) | x] & 0xFF; @@ -266,12 +309,12 @@ public void setBlockExtraData(int x, int y, int z, int data) { this.extraData.put(Level.chunkBlockHash(x, y, z), data); } - this.setChanged(true); + this.setChanged(); } @Override public void populateSkyLight() { - for (int z = 0; z < 16; ++z) { + /*for (int z = 0; z < 16; ++z) { for (int x = 0; x < 16; ++x) { int top = this.getHeightMap(x, z); for (int y = 255; y > top; --y) { @@ -285,7 +328,7 @@ public void populateSkyLight() { } this.setHeightMap(x, z, this.getHighestBlockAt(x, z, false)); } - } + }*/ } @Override @@ -301,9 +344,11 @@ public int getHighestBlockAt(int x, int z, boolean cache) { return h; } } - for (int y = 255; y >= 0; --y) { + for (int y = this.provider.getLevel().getMaxBlockY(); y >= this.provider.getLevel().getMinBlockY(); --y) { if (getBlockId(x, y, z) != 0x00) { - this.setHeightMap(x, z, y); + if (cache) { + this.setHeightMap(x, z, y); + } return y; } } @@ -338,10 +383,13 @@ public void addBlockEntity(BlockEntity blockEntity) { this.tileList = new Int2ObjectOpenHashMap<>(); } this.tiles.put(blockEntity.getId(), blockEntity); - int index = ((blockEntity.getFloorZ() & 0x0f) << 12) | ((blockEntity.getFloorX() & 0x0f) << 8) | (blockEntity.getFloorY() & 0xff); + + int y = blockEntity.getFloorY() - this.getProvider().getLevel().getMinBlockY(); + int index = ((blockEntity.getFloorZ() & 0x0f) << 16) | ((blockEntity.getFloorX() & 0x0f) << 12) | y; if (this.tileList.containsKey(index) && !this.tileList.get(index).equals(blockEntity)) { BlockEntity entity = this.tileList.get(index); this.tiles.remove(entity.getId()); + entity.onReplacedWith(blockEntity); entity.close(); } this.tileList.put(index, blockEntity); @@ -354,14 +402,25 @@ public void addBlockEntity(BlockEntity blockEntity) { public void removeBlockEntity(BlockEntity blockEntity) { if (this.tiles != null) { this.tiles.remove(blockEntity.getId()); - int index = ((blockEntity.getFloorZ() & 0x0f) << 12) | ((blockEntity.getFloorX() & 0x0f) << 8) | (blockEntity.getFloorY() & 0xff); - this.tileList.remove(index); + int y = blockEntity.getFloorY() - this.getProvider().getLevel().getMinBlockY(); + this.tileList.remove(((blockEntity.getFloorZ() & 0x0f) << 16) | ((blockEntity.getFloorX() & 0x0f) << 12) | y); + + if (!(blockEntity instanceof PersistentDataContainerBlockEntity) && blockEntity.hasPersistentDataContainer()) { + this.createPersistentBlockContainer(blockEntity, blockEntity.getPersistentDataContainer().getStorage()); + } + if (this.isInit) { this.setChanged(); } } } + private BlockEntity createPersistentBlockContainer(Vector3 pos, CompoundTag storage) { + CompoundTag tag = BlockEntity.getDefaultCompound(pos, BlockEntity.PERSISTENT_CONTAINER); + tag.putCompound(PersistentDataContainer.STORAGE_TAG, storage); + return BlockEntity.createBlockEntity(BlockEntity.PERSISTENT_CONTAINER, this, tag); + } + @Override public Map getEntities() { return entities == null ? Collections.emptyMap() : entities; @@ -379,12 +438,13 @@ public Map getBlockExtraDataArray() { @Override public BlockEntity getTile(int x, int y, int z) { - return this.tileList != null ? this.tileList.get((z << 12) | (x << 8) | y) : null; + int capY = this.getProvider() == null ? y : y - this.getProvider().getLevel().getMinBlockY(); //TODO: better fix + return this.tileList != null ? this.tileList.get((z << 16) | (x << 12) | capY) : null; } @Override public boolean isLoaded() { - return this.getProvider() != null && this.getProvider().isChunkLoaded(this.getX(), this.getZ()); + return this.getProvider() != null && this.getProvider().isChunkLoaded(this.x, this.z); } @Override @@ -394,12 +454,12 @@ public boolean load() throws IOException { @Override public boolean load(boolean generate) throws IOException { - return this.getProvider() != null && this.getProvider().getChunk(this.getX(), this.getZ(), true) != null; + return this.getProvider() != null && this.getProvider().getChunk(this.x, this.z, true) != null; } @Override public boolean unload() throws Exception { - return this.unload(true, true); + return this.unload(provider.getLevel().isSaveOnUnloadEnabled(), true); } @Override @@ -413,8 +473,30 @@ public boolean unload(boolean save, boolean safe) { if (provider == null) { return true; } - if (save && this.changes != 0) { - provider.saveChunk(this.getX(), this.getZ()); + if (save) { + boolean needSave = this.hasChanged(); + + if (!needSave) { + for (Entity e : this.getEntities().values()) { + if (e.canSaveToStorage()) { + needSave = true; + break; + } + } + } + + if (!needSave) { + for (BlockEntity e : this.getBlockEntities().values()) { + if (e instanceof BlockEntityContainer) { + needSave = true; + break; + } + } + } + + if (needSave) { + provider.saveChunk(this.x, this.z, this); + } } if (safe) { for (Entity entity : this.getEntities().values()) { @@ -462,6 +544,16 @@ public byte[] getBiomeIdArray() { return this.biomes; } + @Override + public void setBiomeIdArray(byte[] biomeIdArray) { + this.biomes = biomeIdArray; + } + + @Override + public int[] getBiomeColorArray() { + return new int[0]; + } + @Override public byte[] getHeightMapArray() { return this.heightMap; @@ -479,7 +571,7 @@ public boolean hasChanged() { @Override public void setChanged() { this.changes++; - chunkPacket = null; + this.chunkPacket = null; } @Override @@ -511,54 +603,66 @@ public void setLightPopulated(boolean value) { } - @Override - public int getBlockIdAt(int x, int y, int z) { - if (x >> 4 == getX() && z >> 4 == getZ()) { - return getBlockId(x & 15, y, z & 15); + public int getBlockIdAt(int x, int y, int z, BlockLayer layer) { + if (x >> 4 == this.x && z >> 4 == this.z) { + return getBlockId(x & 15, y, z & 15, layer); } return 0; } @Override - public void setBlockFullIdAt(int x, int y, int z, int fullId) { - if (x >> 4 == getX() && z >> 4 == getZ()) { - setFullBlockId(x & 15, y, z & 15, fullId); + public void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, int fullId) { + if (x >> 4 == this.x && z >> 4 == this.z) { + setFullBlockId(x & 15, y, z & 15, layer, fullId); } } @Override - public void setBlockIdAt(int x, int y, int z, int id) { - if (x >> 4 == getX() && z >> 4 == getZ()) { - setBlockId(x & 15, y, z & 15, id); + public void setBlockIdAt(int x, int y, int z, BlockLayer layer, int id) { + if (x >> 4 == this.x && z >> 4 == this.z) { + setBlockId(x & 15, y, z & 15, layer, id); } } @Override public void setBlockAt(int x, int y, int z, int id, int data) { - if (x >> 4 == getX() && z >> 4 == getZ()) { + if (x >> 4 == this.x && z >> 4 == this.z) { setBlock(x & 15, y, z & 15, id, data); } } @Override - public int getBlockDataAt(int x, int y, int z) { - if (x >> 4 == getX() && z >> 4 == getZ()) { - return getBlockIdAt(x & 15, y, z & 15); + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta) { + int oldId = this.getBlockId(x, y, z, layer); + int oldData = this.getBlockData(x, y, z, layer); + if (oldId != blockId || oldData != meta) { + this.setBlockAtLayer(x, y, z, layer, blockId); + this.setBlockData(x, y, z, layer, meta); + return true; + } else { + return false; + } + } + + @Override + public int getBlockDataAt(int x, int y, int z, BlockLayer layer) { + if (x >> 4 == this.x && z >> 4 == this.z) { + return getBlockIdAt(x & 15, y, z & 15, layer); } return 0; } @Override - public void setBlockDataAt(int x, int y, int z, int data) { - if (x >> 4 == getX() && z >> 4 == getZ()) { - setBlockData(x & 15, y, z & 15, data); + public void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data) { + if (x >> 4 == this.x && z >> 4 == this.z) { + setBlockData(x & 15, y, z & 15, layer, data); } } @Override public BaseFullChunk getChunk(int chunkX, int chunkZ) { - if (chunkX == getX() && chunkZ == getZ()) return this; + if (chunkX == x && chunkZ == z) return this; return null; } @@ -578,9 +682,8 @@ public long getSeed() { } public boolean compress() { - BatchPacket pk = chunkPacket; - if (pk != null) { - pk.trim(); + if (this.chunkPacket != null) { + this.chunkPacket.trim(); return true; } return false; diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseLevelProvider.java b/src/main/java/cn/nukkit/level/format/generic/BaseLevelProvider.java index d95f47bd189..996c6975744 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseLevelProvider.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseLevelProvider.java @@ -17,19 +17,20 @@ import it.unimi.dsi.fastutil.objects.ObjectIterator; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class BaseLevelProvider implements LevelProvider { + protected Level level; protected final String path; @@ -49,12 +50,40 @@ public abstract class BaseLevelProvider implements LevelProvider { public BaseLevelProvider(Level level, String path) throws IOException { this.level = level; this.path = path; + File file_path = new File(this.path); if (!file_path.exists()) { file_path.mkdirs(); } - CompoundTag levelData = NBTIO.readCompressed(new FileInputStream(new File(this.getPath() + "level.dat")), ByteOrder.BIG_ENDIAN); - if (levelData.get("Data") instanceof CompoundTag) { + + CompoundTag levelData = null; + File levelDat = new File(this.path + "level.dat"); + + try { + levelData = NBTIO.readCompressed(Files.newInputStream(levelDat.toPath()), ByteOrder.BIG_ENDIAN); + } catch (Exception ex1) { + Server.getInstance().getLogger().error("Failed to load level.dat in " + file_path.getPath(), ex1); + + try { + File backup = new File(this.path + "level.dat_old"); + + if (backup.exists()) { + Server.getInstance().getLogger().warning("Attempting to load level.dat_old in " + file_path.getPath()); + + // Save a copy of the corrupted one + com.google.common.io.Files.copy(levelDat, new File(this.path + "level.dat_invalid")); + + // Replace the corrupted one with a backup + com.google.common.io.Files.copy(backup, levelDat); + + levelData = NBTIO.readCompressed(Files.newInputStream(levelDat.toPath()), ByteOrder.BIG_ENDIAN); + } + } catch (Exception ex2) { + Server.getInstance().getLogger().error("Failed to load level.dat_old in " + file_path.getPath(), ex2); + } + } + + if (levelData != null && levelData.get("Data") instanceof CompoundTag) { this.levelData = levelData.getCompound("Data"); } else { throw new LevelException("Invalid level.dat"); @@ -83,7 +112,7 @@ public int size() { public void unloadChunks() { ObjectIterator iter = chunks.values().iterator(); while (iter.hasNext()) { - iter.next().unload(true, false); + iter.next().unload(level.isSaveOnUnloadEnabled(), false); iter.remove(); } } @@ -280,7 +309,6 @@ public void doGarbageCollection() { lastRegion.set(null); iter.remove(); } - } } } @@ -289,9 +317,9 @@ public void doGarbageCollection() { public void saveChunks() { synchronized (chunks) { for (BaseFullChunk chunk : this.chunks.values()) { - if (chunk.getChanges() != 0) { + if (chunk.hasChanged()) { chunk.setChanged(false); - this.saveChunk(chunk.getX(), chunk.getZ()); + this.saveChunk(chunk.getX(), chunk.getZ(), chunk); } } } @@ -303,17 +331,24 @@ public CompoundTag getLevelData() { @Override public void saveLevelData() { + String file = this.path + "level.dat"; + File old = new File(file); + if (old.exists()) { + try { + com.google.common.io.Files.copy(old, new File(file + ".bak")); + } catch (IOException e) { + Server.getInstance().getLogger().logException(e); + } + } try { - NBTIO.writeGZIPCompressed(new CompoundTag().putCompound("Data", this.levelData), new FileOutputStream(this.getPath() + "level.dat")); + NBTIO.writeGZIPCompressed(new CompoundTag().putCompound("Data", this.levelData), Files.newOutputStream(Paths.get(file))); } catch (IOException e) { throw new RuntimeException(e); } } public void updateLevelName(String name) { - if (!this.getName().equals(name)) { - this.levelData.putString("LevelName", name); - } + this.levelData.putString("LevelName", name); } @Override @@ -391,25 +426,24 @@ public BaseFullChunk getChunk(int chunkX, int chunkZ, boolean create) { synchronized (chunks) { lastChunk.set(tmp = chunks.get(index)); } - if (tmp != null) { - return tmp; - } else { + if (tmp == null) { tmp = this.loadChunk(index, chunkX, chunkZ, create); lastChunk.set(tmp); - return tmp; } + return tmp; } @Override public void setChunk(int chunkX, int chunkZ, FullChunk chunk) { if (!(chunk instanceof BaseFullChunk)) { - throw new ChunkException("Invalid Chunk class"); + throw new ChunkException("Invalid chunk class (" + chunkX + ", " + chunkZ + ')'); } chunk.setProvider(this); chunk.setPosition(chunkX, chunkZ); long index = Level.chunkHash(chunkX, chunkZ); synchronized (chunks) { - if (this.chunks.containsKey(index) && !this.chunks.get(index).equals(chunk)) { + FullChunk oldChunk = this.chunks.get(index); + if (oldChunk != null && !oldChunk.equals(chunk)) { this.unloadChunk(chunkX, chunkZ, false); } this.chunks.put(index, (BaseFullChunk) chunk); @@ -444,6 +478,8 @@ public synchronized void close() { @Override public boolean isChunkGenerated(int chunkX, int chunkZ) { BaseRegionLoader region = this.getRegion(chunkX >> 5, chunkZ >> 5); - return region != null && region.chunkExists(chunkX - region.getX() * 32, chunkZ - region.getZ() * 32) && this.getChunk(chunkX - region.getX() * 32, chunkZ - region.getZ() * 32, true).isGenerated(); + BaseFullChunk chunk; + return region != null && region.chunkExists(chunkX - (region.getX() << 5), chunkZ - (region.getZ() << 5)) && + (chunk = this.getChunk(chunkX - (region.getX() << 5), chunkZ - (region.getZ() << 5))) != null && chunk.isGenerated(); } } diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseRegionLoader.java b/src/main/java/cn/nukkit/level/format/generic/BaseRegionLoader.java index c75848c3235..ba09a371818 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseRegionLoader.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseRegionLoader.java @@ -2,33 +2,32 @@ import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.format.LevelProvider; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; -import java.util.HashMap; -import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ abstract public class BaseRegionLoader { - public static final int VERSION = 1; + public static final byte COMPRESSION_GZIP = 1; public static final byte COMPRESSION_ZLIB = 2; public static final int MAX_SECTOR_LENGTH = 256 << 12; - public static final int COMPRESSION_LEVEL = 7; protected int x; protected int z; protected int lastSector; protected LevelProvider levelProvider; - private RandomAccessFile randomAccessFile; + private final RandomAccessFile randomAccessFile; // TODO: A simple array will perform better and use less memory - protected final Map locationTable = new HashMap<>(); + protected final Int2ObjectMap locationTable = new Int2ObjectOpenHashMap<>(); public long lastUsed; @@ -37,7 +36,7 @@ public BaseRegionLoader(LevelProvider level, int regionX, int regionZ, String ex this.x = regionX; this.z = regionZ; this.levelProvider = level; - String filePath = this.levelProvider.getPath() + "region/r." + regionX + "." + regionZ + "." + ext; + String filePath = this.levelProvider.getPath() + "region/r." + regionX + '.' + regionZ + '.' + ext; File file = new File(filePath); boolean exists = file.exists(); if (!exists) { @@ -54,14 +53,10 @@ public BaseRegionLoader(LevelProvider level, int regionX, int regionZ, String ex this.lastUsed = System.currentTimeMillis(); } catch (IOException e) { - throw new RuntimeException(e); + throw new RuntimeException("Unable to load r." + regionX + '.' + regionZ + '.' + ext, e); } } - public void compress() { - // TODO - } - public RandomAccessFile getRandomAccessFile() { return randomAccessFile; } @@ -99,5 +94,4 @@ public void close() throws IOException { public Integer[] getLocationIndexes() { return this.locationTable.keySet().toArray(new Integer[0]); } - } diff --git a/src/main/java/cn/nukkit/level/format/generic/ChunkConverter.java b/src/main/java/cn/nukkit/level/format/generic/ChunkConverter.java deleted file mode 100644 index 35d12f84c36..00000000000 --- a/src/main/java/cn/nukkit/level/format/generic/ChunkConverter.java +++ /dev/null @@ -1,91 +0,0 @@ -package cn.nukkit.level.format.generic; - -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.anvil.Chunk; -import cn.nukkit.level.format.anvil.ChunkSection; - -import java.util.ArrayList; - -public class ChunkConverter { - - private BaseFullChunk chunk; - private Class toClass; - private LevelProvider provider; - - public ChunkConverter(LevelProvider provider) { - this.provider = provider; - } - - public ChunkConverter from(BaseFullChunk chunk) { - if (!(chunk instanceof cn.nukkit.level.format.mcregion.Chunk) && !(chunk instanceof cn.nukkit.level.format.leveldb.Chunk)) { - throw new IllegalArgumentException("From type can be only McRegion or LevelDB"); - } - this.chunk = chunk; - return this; - } - - public ChunkConverter to(Class toClass) { - if (toClass != Chunk.class) { - throw new IllegalArgumentException("To type can be only Anvil"); - } - this.toClass = toClass; - return this; - } - - public FullChunk perform() { - BaseFullChunk result; - try { - result = (BaseFullChunk) toClass.getMethod("getEmptyChunk", int.class, int.class, LevelProvider.class).invoke(null, chunk.getX(), chunk.getZ(), provider); - } catch (Exception e) { - throw new RuntimeException(e); - } - if (toClass == Chunk.class) { - for (int Y = 0; Y < 8; Y++) { - boolean empty = true; - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - if (chunk.getBlockId(x, (Y << 4) | y, z) != 0) { - empty = false; - break; - } - } - if (!empty) break; - } - if (!empty) break; - } - if (!empty) { - ChunkSection section = new ChunkSection(Y); - for (int x = 0; x < 16; x++) { - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - section.setBlockId(x, y, z, chunk.getBlockId(x, (Y << 4) | y, z)); - section.setBlockData(x, y, z, chunk.getBlockData(x, (Y << 4) | y, z)); - section.setBlockLight(x, y, z, chunk.getBlockLight(x, (Y << 4) | y, z)); - section.setBlockSkyLight(x, y, z, chunk.getBlockSkyLight(x, (Y << 4) | y, z)); - } - } - } - ((BaseChunk) result).sections[Y] = section; - } - } - } - System.arraycopy(chunk.biomes, 0, result.biomes, 0, 256); - System.arraycopy(chunk.getHeightMapArray(), 0, result.heightMap, 0, 256); - if (chunk.NBTentities != null && !chunk.NBTentities.isEmpty()) { - result.NBTentities = new ArrayList<>(chunk.NBTentities.size()); - chunk.NBTentities.forEach((nbt) -> result.NBTentities.add(nbt.copy())); - } - - if (chunk.NBTtiles != null && !chunk.NBTtiles.isEmpty()) { - result.NBTtiles = new ArrayList<>(chunk.NBTtiles.size()); - chunk.NBTtiles.forEach((nbt) -> result.NBTtiles.add(nbt.copy())); - } - result.setGenerated(); - result.setPopulated(); - result.setLightPopulated(); - result.initChunk(); - return result; - } -} diff --git a/src/main/java/cn/nukkit/level/format/generic/EmptyChunkSection.java b/src/main/java/cn/nukkit/level/format/generic/EmptyChunkSection.java index adcb277931e..d38d68396b5 100644 --- a/src/main/java/cn/nukkit/level/format/generic/EmptyChunkSection.java +++ b/src/main/java/cn/nukkit/level/format/generic/EmptyChunkSection.java @@ -1,30 +1,30 @@ package cn.nukkit.level.format.generic; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.format.ChunkSection; -import cn.nukkit.level.util.BitArrayVersion; -import cn.nukkit.level.util.PalettedBlockStorage; import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.ChunkException; import java.util.Arrays; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EmptyChunkSection implements ChunkSection { + public static final EmptyChunkSection[] EMPTY = new EmptyChunkSection[16]; - private static final PalettedBlockStorage EMPTY_STORAGE = PalettedBlockStorage.createFromBlockPalette(BitArrayVersion.V1); + + public static final byte[] EMPTY_LIGHT_ARR = new byte[2048]; + public static final byte[] EMPTY_SKY_LIGHT_ARR = new byte[2048]; + private static final byte[] EMPTY_ID_ARR = new byte[4096]; static { for (int y = 0; y < EMPTY.length; y++) { EMPTY[y] = new EmptyChunkSection(y); } - } - public static byte[] EMPTY_LIGHT_ARR = new byte[2048]; - public static byte[] EMPTY_SKY_LIGHT_ARR = new byte[2048]; - static { + Arrays.fill(EMPTY_SKY_LIGHT_ARR, (byte) 255); } @@ -40,17 +40,17 @@ public int getY() { } @Override - final public int getBlockId(int x, int y, int z) { + public int getBlockId(int x, int y, int z, BlockLayer layer) { return 0; } @Override - public int getFullBlock(int x, int y, int z) throws ChunkException { + public int getFullBlock(int x, int y, int z, BlockLayer layer) { return 0; } @Override - public Block getAndSetBlock(int x, int y, int z, Block block) { + public Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block) { if (block.getId() != 0) throw new ChunkException("Tried to modify an empty Chunk"); return Block.get(0); } @@ -67,14 +67,26 @@ public boolean setBlock(int x, int y, int z, int blockId, int meta) throws Chunk return false; } + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId) { + if (blockId != 0) throw new ChunkException("Tried to modify an empty Chunk"); + return false; + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta) { + if (blockId != 0) throw new ChunkException("Tried to modify an empty Chunk"); + return false; + } + @Override public byte[] getIdArray() { - return new byte[4096]; + return EMPTY_ID_ARR; } @Override public byte[] getDataArray() { - return new byte[2048]; + return EMPTY_LIGHT_ARR; // empty 2048 } @Override @@ -88,22 +100,22 @@ public byte[] getLightArray() { } @Override - final public void setBlockId(int x, int y, int z, int id) throws ChunkException { + public void setBlockId(int x, int y, int z, BlockLayer layer, int id) { if (id != 0) throw new ChunkException("Tried to modify an empty Chunk"); } @Override - final public int getBlockData(int x, int y, int z) { + public int getBlockData(int x, int y, int z, BlockLayer layer) { return 0; } @Override - public void setBlockData(int x, int y, int z, int data) throws ChunkException { + public void setBlockData(int x, int y, int z, BlockLayer layer, int data) { if (data != 0) throw new ChunkException("Tried to modify an empty Chunk"); } @Override - public boolean setFullBlockId(int x, int y, int z, int fullId) { + public boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId) { if (fullId != 0) throw new ChunkException("Tried to modify an empty Chunk"); return false; } @@ -135,10 +147,8 @@ public boolean isEmpty() { @Override public void writeTo(BinaryStream stream) { - stream.putByte((byte) 8); - stream.putByte((byte) 2); - EMPTY_STORAGE.writeTo(stream); - EMPTY_STORAGE.writeTo(stream); + stream.putByte((byte) 8); // paletted + stream.putByte((byte) 0); // layers } @Override diff --git a/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkData.java b/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkData.java new file mode 100644 index 00000000000..5ad70eedb7f --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkData.java @@ -0,0 +1,13 @@ +package cn.nukkit.level.format.generic.serializer; + +import cn.nukkit.level.DimensionData; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class NetworkChunkData { + + private int chunkSections; + private final DimensionData dimensionData; +} diff --git a/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkSerializer.java b/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkSerializer.java index 37f948d19ad..41fd12f6f34 100644 --- a/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkSerializer.java +++ b/src/main/java/cn/nukkit/level/format/generic/serializer/NetworkChunkSerializer.java @@ -3,6 +3,7 @@ import cn.nukkit.blockentity.BlockEntity; import cn.nukkit.blockentity.BlockEntitySpawnable; import cn.nukkit.level.DimensionData; +import cn.nukkit.level.Level; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.format.ChunkSection; import cn.nukkit.level.format.generic.BaseChunk; @@ -26,8 +27,8 @@ public class NetworkChunkSerializer { private static final byte[] negativeSubChunks; static { - // Build up 4 SubChunks for the extended negative height BinaryStream stream = new BinaryStream(); + // Build up 4 SubChunks for the extended negative height for (int i = 0; i < EXTENDED_NEGATIVE_SUB_CHUNKS; i++) { stream.putByte((byte) 8); // SubChunk version stream.putByte((byte) 0); // 0 layers @@ -35,14 +36,7 @@ public class NetworkChunkSerializer { negativeSubChunks = stream.getBuffer(); } - public static void serialize(BaseChunk chunk, BiConsumer callback, DimensionData dimensionData) { - byte[] blockEntities; - if (chunk.getBlockEntities().isEmpty()) { - blockEntities = new byte[0]; - } else { - blockEntities = serializeEntities(chunk); - } - + public static void serialize(BaseChunk chunk, BiConsumer callback, DimensionData dimensionData) { int subChunkCount = 0; ChunkSection[] sections = chunk.getSections(); for (int i = sections.length - 1; i >= 0; i--) { @@ -52,17 +46,32 @@ public static void serialize(BaseChunk chunk, BiConsumer } } + BinaryStream stream = ThreadCache.binaryStream.get().reset(); + NetworkChunkData chunkData = new NetworkChunkData(subChunkCount, dimensionData); + serialize1_18_30(stream, chunk, sections, chunkData); + + byte[] blockEntities; + if (chunk.getBlockEntities().isEmpty()) { + blockEntities = new byte[0]; + } else { + blockEntities = serializeEntities(chunk); + } + stream.put(blockEntities); + + callback.accept(stream, chunkData); + } + + private static void serialize1_18_30(BinaryStream stream, BaseFullChunk chunk, ChunkSection[] sections, NetworkChunkData chunkData) { + DimensionData dimensionData = chunkData.getDimensionData(); int maxDimensionSections = dimensionData.getHeight() >> 4; - subChunkCount = Math.min(maxDimensionSections, subChunkCount); + int subChunkCount = Math.min(maxDimensionSections, chunkData.getChunkSections()); - // In 1.18 3D biome palettes were introduced. However, current world format - // used internally doesn't support them, so we need to convert from legacy 2D - byte[] biomePalettes = convert2DBiomesTo3D(chunk, maxDimensionSections); - BinaryStream stream = ThreadCache.binaryStream.get().reset(); + byte[] biomePalettes = serialize3DBiomes(chunk, maxDimensionSections); + stream.reset(); // Overworld has negative coordinates, but we currently do not support them int writtenSections = subChunkCount; - if (/*dimensionData.getDimensionId() == Level.DIMENSION_OVERWORLD && subChunkCount < maxDimensionSections*/true) { //TODO: change client dimension + if (dimensionData.getDimensionId() == Level.DIMENSION_OVERWORLD && subChunkCount < maxDimensionSections) { stream.put(negativeSubChunks); writtenSections += EXTENDED_NEGATIVE_SUB_CHUNKS; } @@ -73,8 +82,8 @@ public static void serialize(BaseChunk chunk, BiConsumer stream.put(biomePalettes); stream.putByte((byte) 0); // Border blocks - stream.put(blockEntities); - callback.accept(stream, writtenSections); + + chunkData.setChunkSections(writtenSections); } private static byte[] serializeEntities(BaseChunk chunk) { @@ -92,24 +101,34 @@ private static byte[] serializeEntities(BaseChunk chunk) { } } - private static byte[] convert2DBiomesTo3D(BaseFullChunk chunk, int sections) { - PalettedBlockStorage palette = PalettedBlockStorage.createWithDefaultState(Biome.getBiomeIdOrCorrect(chunk.getBiomeId(0, 0))); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - int biomeId = Biome.getBiomeIdOrCorrect(chunk.getBiomeId(x, z)); - for (int y = 0; y < 16; y++) { - palette.setBlock(x, y, z, biomeId); + private static byte[] serialize3DBiomes(BaseFullChunk chunk, int sections) { + if (!chunk.has3dBiomes()) { // Convert 2D biomes to 3D + PalettedBlockStorage palette = PalettedBlockStorage.createWithDefaultState(Biome.getBiomeIdOrCorrect(chunk.getBiomeId(0, 0))); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int biomeId = chunk.getBiomeId(x, z); + for (int y = 0; y < 16; y++) { + palette.setBlock(x, y, z, biomeId); + } } } + + BinaryStream stream = ThreadCache.binaryStream.get().reset(); + palette.writeTo(stream, Biome::getBiomeIdOrCorrect); + byte[] bytes = stream.getBuffer(); + stream.reset(); + + for (int i = 0; i < sections; i++) { + stream.put(bytes); + } + return stream.getBuffer(); } BinaryStream stream = ThreadCache.binaryStream.get().reset(); - palette.writeTo(stream); - byte[] bytes = stream.getBuffer(); - stream.reset(); - for (int i = 0; i < sections; i++) { - stream.put(bytes); + PalettedBlockStorage storage = chunk.getBiomeStorage(i); + storage.writeTo(stream, Biome::getBiomeIdOrCorrect); + } return stream.getBuffer(); } diff --git a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java new file mode 100644 index 00000000000..a9e868c1608 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java @@ -0,0 +1,284 @@ +package cn.nukkit.level.format.leveldb; + +import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterChunker; +import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterVanilla; +import cn.nukkit.level.format.leveldb.structure.BlockStateSnapshot; +import cn.nukkit.utils.MainLogger; +import net.jodah.expiringmap.ExpirationPolicy; +import net.jodah.expiringmap.ExpiringMap; +import org.cloudburstmc.blockstateupdater.*; +import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.network.util.Preconditions; +import it.unimi.dsi.fastutil.Hash; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; + +public class BlockStateMapping { + private static final Logger log = LogManager.getLogger("LevelDB-Logger"); + private static final Logger serverLog = LogManager.getLogger(MainLogger.class); + private static final int UPDATE_BLOCK = 284; + + private static final CompoundTagUpdaterContext CONTEXT; + private static final int LATEST_UPDATER_VERSION; + private static final BlockStateMapping INSTANCE = new BlockStateMapping(PALETTE_VERSION); + + private static final ExpiringMap BLOCK_UPDATE_CACHE = ExpiringMap.builder() + .maxSize(1024) + .expiration(60, TimeUnit.SECONDS) + .expirationPolicy(ExpirationPolicy.ACCESSED) + .build(); + + static { + INSTANCE.setLegacyMapper(new NukkitLegacyMapper()); + NukkitLegacyMapper.registerStates(INSTANCE); + + List updaters = new ArrayList<>(); + updaters.add(BlockStateUpdaterBase.INSTANCE); + updaters.add(BlockStateUpdater_1_10_0.INSTANCE); + updaters.add(BlockStateUpdater_1_12_0.INSTANCE); + updaters.add(BlockStateUpdater_1_13_0.INSTANCE); + updaters.add(BlockStateUpdater_1_14_0.INSTANCE); + updaters.add(BlockStateUpdater_1_15_0.INSTANCE); + updaters.add(BlockStateUpdater_1_16_0.INSTANCE); + updaters.add(BlockStateUpdater_1_16_210.INSTANCE); + updaters.add(BlockStateUpdater_1_17_30.INSTANCE); + updaters.add(BlockStateUpdater_1_17_40.INSTANCE); + updaters.add(BlockStateUpdater_1_18_10.INSTANCE); + updaters.add(BlockStateUpdater_1_18_30.INSTANCE); + updaters.add(BlockStateUpdater_1_19_0.INSTANCE); + updaters.add(BlockStateUpdater_1_19_20.INSTANCE); + updaters.add(BlockStateUpdater_1_19_70.INSTANCE); + updaters.add(BlockStateUpdater_1_19_80.INSTANCE); + updaters.add(BlockStateUpdater_1_20_0.INSTANCE); + updaters.add(BlockStateUpdater_1_20_10.INSTANCE); + updaters.add(BlockStateUpdaterVanilla.INSTANCE); + + boolean chunkerSupport = Boolean.parseBoolean(System.getProperty("leveldb-chunker")); + if (chunkerSupport) { + updaters.add(BlockStateUpdaterChunker.INSTANCE); + serverLog.warn("Enabled chunker.app LevelDB updater. This may impact chunk loading performance!"); + } + + CompoundTagUpdaterContext context = new CompoundTagUpdaterContext(); + updaters.forEach(updater -> updater.registerUpdaters(context)); + + CONTEXT = context; + LATEST_UPDATER_VERSION = context.getLatestVersion(); + log.info("Latest block state updater version {}", context.getLatestVersion()); + } + + public static BlockStateMapping get() { + return INSTANCE; + } + + private final Int2ObjectMap runtime2State = new Int2ObjectOpenHashMap<>(); + private final Object2ObjectMap paletteMap = new Object2ObjectOpenCustomHashMap<>(new Hash.Strategy() { + @Override + public int hashCode(NbtMap nbtMap) { + return nbtMap.hashCode(); + } + + @Override + public boolean equals(NbtMap nbtMap1, NbtMap nbtMap2) { + return Objects.equals(nbtMap1, nbtMap2); + } + }); + private final int version; + + private LegacyStateMapper legacyMapper; + + private int defaultRuntimeId = -1; + private BlockStateSnapshot defaultState; + + public BlockStateMapping(int version) { + this(version, null); + } + + public BlockStateMapping(int version, LegacyStateMapper legacyMapper) { + this.version = version; + this.legacyMapper = legacyMapper; + } + + public void registerState(int runtimeId, NbtMap state) { + Preconditions.checkArgument(!this.runtime2State.containsKey(runtimeId), + "Mapping for runtimeId " + runtimeId + " is already created!"); + Preconditions.checkArgument(!this.paletteMap.containsKey(state), + "Mapping for state is already created: " + state); + + BlockStateSnapshot blockState = BlockStateSnapshot.builder() + .version(this.version) + .vanillaState(state) + .runtimeId(runtimeId) + .build(); + this.runtime2State.put(runtimeId, blockState); + this.paletteMap.put(state, blockState); + } + + public void clearMapping() { + this.runtime2State.clear(); + this.paletteMap.clear(); + } + + public BlockStateSnapshot getState(int legacyId, int data) { + int runtimeId = this.legacyMapper.legacyToRuntime(legacyId, data); + if (runtimeId == -1) { + log.warn("Can not find state! No legacy2runtime mapping for " + legacyId + ":" + data); + return this.getDefaultState(); + } + return this.getState(runtimeId); + } + + public BlockStateSnapshot getState(int runtimeId) { + BlockStateSnapshot state = this.runtime2State.get(runtimeId); + if (state == null) { + log.warn("Can not find state! No runtime2State mapping for " + runtimeId); + return this.getDefaultState(); + } + return state; + } + + public BlockStateSnapshot getState(NbtMap vanillaState) { + BlockStateSnapshot state = this.paletteMap.get(vanillaState); + if (state == null) { + log.warn("Can not find block state! " + vanillaState); + return this.getDefaultState(); + } + return state; + } + + public BlockStateSnapshot getStateUnsafe(NbtMap vanillaState) { + return this.paletteMap.get(vanillaState); + } + + public int getRuntimeId(int legacyId, int data) { + int runtimeId = this.legacyMapper.legacyToRuntime(legacyId, data); + if (runtimeId == -1) { + log.warn("Can not find runtimeId! No legacy2runtime mapping for " + legacyId + ":" + data); + return this.getDefaultRuntimeId(); + } + return runtimeId; + } + + public int getFullId(int runtimeId) { + int fullId = this.legacyMapper.runtimeToFullId(runtimeId); + if (fullId == -1) { + log.warn("Can not find legacyId! No runtime2FullId mapping for " + runtimeId); + fullId = this.legacyMapper.runtimeToFullId(this.getDefaultRuntimeId()); + Preconditions.checkArgument(fullId != -1, "Can not find fullId for default runtimeId: " + this.getDefaultRuntimeId()); + } + return fullId; + } + + public int getLegacyId(int runtimeId) { + int legacyId = this.legacyMapper.runtimeToLegacyId(runtimeId); + if (legacyId == -1) { + log.warn("Can not find legacyId! No runtime2legacy mapping for " + runtimeId); + legacyId = this.legacyMapper.runtimeToLegacyId(this.getDefaultRuntimeId()); + Preconditions.checkArgument(legacyId != -1, "Can not find legacyId for default runtimeId: " + this.getDefaultRuntimeId()); + } + return legacyId; + } + + public int getLegacyData(int runtimeId) { + int data = this.legacyMapper.runtimeToLegacyData(runtimeId); + if (data == -1) { + log.warn("Can not find legacyId! No runtime2legacy mapping for " + runtimeId); + data = this.legacyMapper.runtimeToLegacyData(this.getDefaultRuntimeId()); + Preconditions.checkArgument(data != -1, "Can not find legacyData for default runtimeId: " + this.getDefaultRuntimeId()); } + return data; + } + + public void setDefaultBlock(int legacyId, int legacyData) { + int runtimeId = this.legacyMapper.legacyToRuntime(legacyId, legacyData); + Preconditions.checkArgument(runtimeId != -1, "Can not find runtimeId mapping for default block: " + legacyId + ":" + legacyData); + this.defaultRuntimeId = runtimeId; + + BlockStateSnapshot state = this.runtime2State.get(runtimeId); + Preconditions.checkNotNull(state, "Can not find state for default block: " + legacyId + ":" + legacyData); + this.defaultState = state; + } + + public int getDefaultRuntimeId() { + if (this.defaultRuntimeId == -1) { + this.setDefaultBlock(UPDATE_BLOCK, 0); + } + return this.defaultRuntimeId; + } + + public BlockStateSnapshot getDefaultState() { + if (this.defaultState == null) { + this.setDefaultBlock(UPDATE_BLOCK, 0); + } + return this.defaultState; + } + + public BlockStateSnapshot updateState(NbtMap state) { + BlockStateSnapshot blockState = this.paletteMap.get(state); + if (blockState == null) { + blockState = this.updateStateUnsafe(state); + } + return blockState; + } + + public BlockStateSnapshot updateStateUnsafe(NbtMap state) { + return this.getState(this.updateVanillaState(state)); + } + + public BlockStateSnapshot getUpdatedState(NbtMap state) { + if (this.paletteMap.get(state) == null) { + return this.getState(this.updateVanillaState(state)); + } + return null; + } + + public NbtMap updateVanillaState(NbtMap state) { + NbtMap cached = BLOCK_UPDATE_CACHE.get(state); + if (cached == null) { + int version = state.getInt("version"); // TODO: validate this when updating next time + cached = CONTEXT.update(state, LATEST_UPDATER_VERSION == version ? version - 1 : version); + BLOCK_UPDATE_CACHE.put(state, cached); + } + return cached; + } + + public BlockStateSnapshot getUpdatedOrCustom(NbtMap state) { + return this.getUpdatedOrCustom(state, this.updateVanillaState(state)); + } + + public BlockStateSnapshot getUpdatedOrCustom(NbtMap state, NbtMap updated) { + BlockStateSnapshot blockState = this.getStateUnsafe(updated); + if (blockState != null) { + return blockState; + } + + return BlockStateSnapshot.builder() + .vanillaState(state) + .runtimeId(this.getDefaultState().getRuntimeId()) + .version(this.version) + .custom(true) + .build(); + } + + public void setLegacyMapper(LegacyStateMapper legacyMapper) { + this.legacyMapper = legacyMapper; + } + + public LegacyStateMapper getLegacyMapper() { + return this.legacyMapper; + } + + public int getVersion() { + return this.version; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/Chunk.java b/src/main/java/cn/nukkit/level/format/leveldb/Chunk.java deleted file mode 100644 index 6ee2c7de042..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/Chunk.java +++ /dev/null @@ -1,537 +0,0 @@ -package cn.nukkit.level.format.leveldb; - -import cn.nukkit.Player; -import cn.nukkit.Server; -import cn.nukkit.block.Block; -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.entity.Entity; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.anvil.palette.BiomePalette; -import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.format.leveldb.key.EntitiesKey; -import cn.nukkit.level.format.leveldb.key.ExtraDataKey; -import cn.nukkit.level.format.leveldb.key.TilesKey; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.stream.NBTInputStream; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.Tag; -import cn.nukkit.utils.Binary; -import cn.nukkit.utils.BinaryStream; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.*; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class Chunk extends BaseFullChunk { - - public static final int DATA_LENGTH = 16384 * (2 + 1 + 1 + 1) + 256 + 1024; - - protected boolean isLightPopulated = false; - protected boolean isPopulated = false; - protected boolean isGenerated = false; - - public Chunk(LevelProvider level, int chunkX, int chunkZ, byte[] terrain) { - this(level, chunkX, chunkZ, terrain, null); - } - - public Chunk(Class providerClass, int chunkX, int chunkZ, byte[] terrain) { - this(null, chunkX, chunkZ, terrain, null); - this.providerClass = providerClass; - } - - public Chunk(LevelProvider level, int chunkX, int chunkZ, byte[] terrain, List entityData) { - this(level, chunkX, chunkZ, terrain, entityData, null); - } - - public Chunk(LevelProvider level, int chunkX, int chunkZ, byte[] terrain, List entityData, List tileData) { - this(level, chunkX, chunkZ, terrain, entityData, tileData, null); - } - - public Chunk(LevelProvider level, int chunkX, int chunkZ, byte[] terrain, List entityData, List tileData, Map extraData) { - ByteBuffer buffer = ByteBuffer.wrap(terrain).order(ByteOrder.BIG_ENDIAN); - - byte[] blocks = new byte[32768]; - buffer.get(blocks); - - byte[] data = new byte[16384]; - buffer.get(data); - - byte[] skyLight = new byte[16384]; - buffer.get(skyLight); - - byte[] blockLight = new byte[16384]; - buffer.get(blockLight); - - byte[] heightMap = new byte[256]; - for (int i = 0; i < 256; i++) { - heightMap[i] = buffer.get(); - } - - int[] biomeColors = new int[256]; - for (int i = 0; i < 256; i++) { - biomeColors[i] = buffer.getInt(); - } - - this.provider = level; - if (level != null) { - this.providerClass = level.getClass(); - } - - this.setPosition(chunkX, chunkZ); - - this.blocks = blocks; - this.data = data; - this.skyLight = skyLight; - this.blockLight = blockLight; - - this.biomes = new byte[16 * 16]; - if (biomeColors.length == 256) { - BiomePalette palette = new BiomePalette(biomeColors); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - biomes[(x << 4) | z] = (byte) (palette.get(x, z) >> 24); - } - } - } - - if (heightMap.length == 256) { - this.heightMap = heightMap; - } else { - byte[] bytes = new byte[256]; - Arrays.fill(bytes, (byte) 256); - this.heightMap = bytes; - } - - this.NBTentities = entityData; - this.NBTtiles = tileData; - this.extraData = extraData; - } - - @Override - public int getBlockId(int x, int y, int z) { - return this.blocks[(x << 11) | (z << 7) | y] & 0xff; - } - - @Override - public void setBlockId(int x, int y, int z, int id) { - this.blocks[(x << 11) | (z << 7) | y] = (byte) id; - setChanged(); - } - - @Override - public int getBlockData(int x, int y, int z) { - int b = this.data[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return b & 0x0f; - } else { - return b >> 4; - } - } - - @Override - public void setBlockData(int x, int y, int z, int data) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - this.data[i] = (byte) ((old & 0xf0) | (data & 0x0f)); - } else { - this.data[i] = (byte) (((data & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public int getFullBlock(int x, int y, int z) { - int i = (x << 11) | (z << 7) | y; - int block = this.blocks[i] & 0xff; - int data = this.data[i >> 1] & 0xff; - if ((y & 1) == 0) { - return (block << 4) | (data & 0x0f); - } else { - return (block << 4) | (data >> 4); - } - } - - @Override - public Block getAndSetBlock(int x, int y, int z, Block block) { - int i = (x << 11) | (z << 7) | y; - boolean changed = false; - byte id = (byte) block.getId(); - - byte previousId = this.blocks[i]; - - if (previousId != id) { - this.blocks[i] = id; - changed = true; - } - - int previousData; - i >>= 1; - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - previousData = old & 0x0f; - if (Block.hasMeta[block.getId()]) { - this.data[i] = (byte) ((old & 0xf0) | (block.getDamage() & 0x0f)); - if (block.getDamage() != previousData) { - changed = true; - } - } - } else { - previousData = old >> 4; - if (Block.hasMeta[block.getId()]) { - this.data[i] = (byte) (((block.getDamage() & 0x0f) << 4) | (old & 0x0f)); - if (block.getDamage() != previousData) { - changed = true; - } - } - } - - if (changed) { - setChanged(); - } - return Block.get(previousId, previousData); - } - - @Override - public boolean setBlock(int x, int y, int z, int blockId) { - return setBlock(x, y, z, blockId, 0); - } - - @Override - public boolean setBlock(int x, int y, int z, int blockId, int meta) { - int i = (x << 11) | (z << 7) | y; - boolean changed = false; - byte id = (byte) blockId; - if (this.blocks[i] != id) { - this.blocks[i] = id; - changed = true; - } - - if (Block.hasMeta[blockId]) { - i >>= 1; - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - this.data[i] = (byte) ((old & 0xf0) | (meta & 0x0f)); - if ((old & 0x0f) != meta) { - changed = true; - } - } else { - this.data[i] = (byte) (((meta & 0x0f) << 4) | (old & 0x0f)); - if (meta != (old >> 4)) { - changed = true; - } - } - } - - if (changed) { - setChanged(); - } - return changed; - } - - @Override - public int getBlockSkyLight(int x, int y, int z) { - int sl = this.skyLight[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return sl & 0x0f; - } else { - return sl >> 4; - } - } - - @Override - public void setBlockSkyLight(int x, int y, int z, int level) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.skyLight[i] & 0xff; - if ((y & 1) == 0) { - this.skyLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); - } else { - this.skyLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public int getBlockLight(int x, int y, int z) { - int b = this.blockLight[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return b & 0x0f; - } else { - return b >> 4; - } - } - - @Override - public void setBlockLight(int x, int y, int z, int level) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.blockLight[i] & 0xff; - if ((y & 1) == 0) { - this.blockLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); - } else { - this.blockLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public boolean isLightPopulated() { - return this.isLightPopulated; - } - - @Override - public void setLightPopulated() { - this.setLightPopulated(true); - } - - @Override - public void setLightPopulated(boolean value) { - this.isLightPopulated = value; - } - - @Override - public boolean isPopulated() { - return this.isPopulated; - } - - @Override - public void setPopulated() { - this.setPopulated(true); - } - - @Override - public void setPopulated(boolean value) { - this.isPopulated = true; - } - - @Override - public boolean isGenerated() { - return this.isGenerated; - } - - @Override - public void setGenerated() { - this.setGenerated(true); - } - - @Override - public void setGenerated(boolean value) { - this.isGenerated = true; - } - - public static Chunk fromBinary(byte[] data) { - return fromBinary(data, null); - } - - public static Chunk fromBinary(byte[] data, LevelProvider provider) { - try { - - int chunkX = Binary.readLInt(new byte[]{data[0], data[1], data[2], data[3]}); - int chunkZ = Binary.readLInt(new byte[]{data[4], data[5], data[6], data[7]}); - byte[] chunkData = Binary.subBytes(data, 8, data.length - 1); - - int flags = data[data.length - 1]; - - List entities = new ArrayList<>(); - List tiles = new ArrayList<>(); - - Map extraDataMap = new HashMap<>(); - - if (provider instanceof LevelDB) { - byte[] entityData = ((LevelDB) provider).getDatabase().get(EntitiesKey.create(chunkX, chunkZ).toArray()); - if (entityData != null && entityData.length > 0) { - try (NBTInputStream nbtInputStream = new NBTInputStream(new ByteArrayInputStream(entityData), ByteOrder.LITTLE_ENDIAN)) { - while (nbtInputStream.available() > 0) { - Tag tag = Tag.readNamedTag(nbtInputStream); - if (!(tag instanceof CompoundTag)) { - throw new IOException("Root tag must be a named compound tag"); - } - entities.add((CompoundTag) tag); - } - } - } - - byte[] tileData = ((LevelDB) provider).getDatabase().get(TilesKey.create(chunkX, chunkZ).toArray()); - if (tileData != null && tileData.length > 0) { - try (NBTInputStream nbtInputStream = new NBTInputStream(new ByteArrayInputStream(tileData), ByteOrder.LITTLE_ENDIAN)) { - while (nbtInputStream.available() > 0) { - Tag tag = Tag.readNamedTag(nbtInputStream); - if (!(tag instanceof CompoundTag)) { - throw new IOException("Root tag must be a named compound tag"); - } - tiles.add((CompoundTag) tag); - } - } - } - - byte[] extraData = ((LevelDB) provider).getDatabase().get(ExtraDataKey.create(chunkX, chunkZ).toArray()); - if (extraData != null && extraData.length > 0) { - BinaryStream stream = new BinaryStream(tileData); - int count = stream.getInt(); - for (int i = 0; i < count; ++i) { - int key = stream.getInt(); - int value = stream.getShort(); - extraDataMap.put(key, value); - } - } - - /*if (!entities.isEmpty() || !blockEntities.isEmpty()) { - CompoundTag ct = new CompoundTag(); - ListTag entityList = new ListTag<>("entities"); - ListTag tileList = new ListTag<>("blockEntities"); - - entityList.list = entities; - tileList.list = blockEntities; - ct.putList(entityList); - ct.putList(tileList); - NBTIO.write(ct, new File(Nukkit.DATA_PATH + chunkX + "_" + chunkZ + ".dat")); - }*/ - - - Chunk chunk = new Chunk(provider, chunkX, chunkZ, chunkData, entities, tiles, extraDataMap); - - if ((flags & 0x01) > 0) { - chunk.setGenerated(); - } - - if ((flags & 0x02) > 0) { - chunk.setPopulated(); - } - - if ((flags & 0x04) > 0) { - chunk.setLightPopulated(); - } - return chunk; - } - } catch (Exception e) { - Server.getInstance().getLogger().logException(e); - } - return null; - } - - public static Chunk fromFastBinary(byte[] data) { - return fromFastBinary(data, null); - } - - public static Chunk fromFastBinary(byte[] data, LevelProvider provider) { - return fromBinary(data, provider); - } - - @Override - public byte[] toFastBinary() { - return this.toBinary(false); - } - - @Override - public byte[] toBinary() { - return this.toBinary(false); - } - - public byte[] toBinary(boolean saveExtra) { - try { - LevelProvider provider = this.getProvider(); - - if (saveExtra && provider instanceof LevelDB) { - - List entities = new ArrayList<>(); - - for (Entity entity : this.getEntities().values()) { - if (!(entity instanceof Player) && !entity.closed) { - entity.saveNBT(); - entities.add(entity.namedTag); - } - } - - EntitiesKey entitiesKey = EntitiesKey.create(this.getX(), this.getZ()); - if (!entities.isEmpty()) { - ((LevelDB) provider).getDatabase().put(entitiesKey.toArray(), NBTIO.write(entities)); - } else { - ((LevelDB) provider).getDatabase().delete(entitiesKey.toArray()); - } - - List tiles = new ArrayList<>(); - - for (BlockEntity blockEntity : this.getBlockEntities().values()) { - if (!blockEntity.closed) { - blockEntity.saveNBT(); - tiles.add(blockEntity.namedTag); - } - } - - TilesKey tilesKey = TilesKey.create(this.getX(), this.getZ()); - if (!tiles.isEmpty()) { - ((LevelDB) provider).getDatabase().put(tilesKey.toArray(), NBTIO.write(tiles)); - } else { - ((LevelDB) provider).getDatabase().delete(tilesKey.toArray()); - } - - ExtraDataKey extraDataKey = ExtraDataKey.create(this.getX(), this.getZ()); - if (!this.getBlockExtraDataArray().isEmpty()) { - BinaryStream extraData = new BinaryStream(); - Map extraDataArray = this.getBlockExtraDataArray(); - extraData.putInt(extraDataArray.size()); - for (Integer key : extraDataArray.keySet()) { - extraData.putInt(key); - extraData.putShort(extraDataArray.get(key)); - } - ((LevelDB) provider).getDatabase().put(extraDataKey.toArray(), extraData.getBuffer()); - } else { - ((LevelDB) provider).getDatabase().delete(extraDataKey.toArray()); - } - - } - - byte[] heightMap = this.getHeightMapArray(); - - byte[] biomeColors = new byte[this.biomes.length * 4]; - for (int i = 0; i < this.biomes.length; i++) { - byte[] bytes = Binary.writeInt(this.biomes[i] << 24); - biomeColors[i * 4] = bytes[0]; - biomeColors[i * 4 + 1] = bytes[1]; - biomeColors[i * 4 + 2] = bytes[2]; - biomeColors[i * 4 + 3] = bytes[3]; - } - - return Binary.appendBytes( - Binary.writeLInt(this.getX()), - Binary.writeLInt(this.getZ()), - this.getBlockIdArray(), - this.getBlockDataArray(), - this.getBlockSkyLightArray(), - this.getBlockLightArray(), - heightMap, - biomeColors, - new byte[]{(byte) (((this.isLightPopulated ? 0x04 : 0) | (this.isPopulated() ? 0x02 : 0) | (this.isGenerated() ? 0x01 : 0)) & 0xff)} - ); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static Chunk getEmptyChunk(int chunkX, int chunkZ) { - return getEmptyChunk(chunkX, chunkZ, null); - } - - public static Chunk getEmptyChunk(int chunkX, int chunkZ, LevelProvider provider) { - try { - Chunk chunk; - if (provider != null) { - chunk = new Chunk(provider, chunkX, chunkZ, new byte[DATA_LENGTH]); - } else { - chunk = new Chunk(LevelDB.class, chunkX, chunkZ, new byte[DATA_LENGTH]); - } - - byte[] skyLight = new byte[16384]; - Arrays.fill(skyLight, (byte) 0xff); - chunk.skyLight = skyLight; - return chunk; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LegacyStateMapper.java b/src/main/java/cn/nukkit/level/format/leveldb/LegacyStateMapper.java new file mode 100644 index 00000000000..fc3bd55c298 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/LegacyStateMapper.java @@ -0,0 +1,11 @@ +package cn.nukkit.level.format.leveldb; + +public interface LegacyStateMapper { + int legacyToRuntime(int legacyId, int meta); + + int runtimeToLegacyId(int runtimeId); + + int runtimeToLegacyData(int runtimeId); + + int runtimeToFullId(int runtimeId); +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java deleted file mode 100644 index 5ad5bae06d6..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/LevelDB.java +++ /dev/null @@ -1,560 +0,0 @@ -package cn.nukkit.level.format.leveldb; - -import cn.nukkit.Server; -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.blockentity.BlockEntitySpawnable; -import cn.nukkit.level.GameRules; -import cn.nukkit.level.Level; -import cn.nukkit.level.format.ChunkSection; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.format.leveldb.key.BaseKey; -import cn.nukkit.level.format.leveldb.key.FlagsKey; -import cn.nukkit.level.format.leveldb.key.TerrainKey; -import cn.nukkit.level.format.leveldb.key.VersionKey; -import cn.nukkit.level.generator.Generator; -import cn.nukkit.math.Vector3; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.scheduler.AsyncTask; -import cn.nukkit.utils.*; -import org.iq80.leveldb.DB; -import org.iq80.leveldb.Options; -import org.iq80.leveldb.impl.Iq80DBFactory; - -import java.io.*; -import java.nio.ByteOrder; -import java.util.*; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class LevelDB implements LevelProvider { - - protected Map chunks = new HashMap<>(); - - protected DB db; - - protected Level level; - - protected final String path; - - protected CompoundTag levelData; - - public LevelDB(Level level, String path) { - this.level = level; - this.path = path; - File file_path = new File(this.path); - if (!file_path.exists()) { - file_path.mkdirs(); - } - - try (FileInputStream stream = new FileInputStream(this.getPath() + "level.dat")) { - stream.skip(8); - CompoundTag levelData = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN); - if (levelData != null) { - this.levelData = levelData; - } else { - throw new IOException("LevelData can not be null"); - } - } catch (IOException e) { - throw new LevelException("Invalid level.dat"); - } - - if (!this.levelData.contains("generatorName")) { - this.levelData.putString("generatorName", Generator.getGenerator("DEFAULT").getSimpleName().toLowerCase()); - } - - if (!this.levelData.contains("generatorOptions")) { - this.levelData.putString("generatorOptions", ""); - } - - try { - this.db = Iq80DBFactory.factory.open(new File(this.getPath() + "/db"), new Options().createIfMissing(true)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static String getProviderName() { - return "leveldb"; - } - - public static byte getProviderOrder() { - return ORDER_ZXY; - } - - public static boolean usesChunkSection() { - return false; - } - - public static boolean isValid(String path) { - return new File(path + "/level.dat").exists() && new File(path + "/db").isDirectory(); - } - - public static void generate(String path, String name, long seed, Class generator) throws IOException { - generate(path, name, seed, generator, new HashMap<>()); - } - - public static void generate(String path, String name, long seed, Class generator, Map options) throws IOException { - if (!new File(path + "/db").exists()) { - new File(path + "/db").mkdirs(); - } - - CompoundTag levelData = new CompoundTag("") - .putLong("currentTick", 0) - .putInt("DayCycleStopTime", -1) - .putInt("GameType", 0) - .putInt("Generator", Generator.getGeneratorType(generator)) - .putBoolean("hasBeenLoadedInCreative", false) - .putLong("LastPlayed", System.currentTimeMillis() / 1000) - .putString("LevelName", name) - .putFloat("lightningLevel", 0) - .putInt("lightningTime", new Random().nextInt()) - .putInt("limitedWorldOriginX", 128) - .putInt("limitedWorldOriginY", 70) - .putInt("limitedWorldOriginZ", 128) - .putInt("Platform", 0) - .putFloat("rainLevel", 0) - .putInt("rainTime", new Random().nextInt()) - .putLong("RandomSeed", seed) - .putByte("spawnMobs", 0) - .putInt("SpawnX", 128) - .putInt("SpawnY", 70) - .putInt("SpawnZ", 128) - .putInt("storageVersion", 4) - .putLong("Time", 0) - .putLong("worldStartCount", ((long) Integer.MAX_VALUE) & 0xffffffffL); - - byte[] data = NBTIO.write(levelData, ByteOrder.LITTLE_ENDIAN); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(Binary.writeLInt(3)); - outputStream.write(Binary.writeLInt(data.length)); - outputStream.write(data); - - Utils.writeFile(path + "level.dat", new ByteArrayInputStream(outputStream.toByteArray())); - - DB db = Iq80DBFactory.factory.open(new File(path + "/db"), new Options().createIfMissing(true)); - db.close(); - } - - @Override - public void saveLevelData() { - try { - byte[] data = NBTIO.write(levelData, ByteOrder.LITTLE_ENDIAN); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - outputStream.write(Binary.writeLInt(3)); - outputStream.write(Binary.writeLInt(data.length)); - outputStream.write(data); - - Utils.writeFile(path + "level.dat", new ByteArrayInputStream(outputStream.toByteArray())); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - - @Override - public Chunk getEmptyChunk(int chunkX, int chunkZ) { - return Chunk.getEmptyChunk(chunkX, chunkZ, this); - } - - @Override - public AsyncTask requestChunkTask(int x, int z) { - Chunk chunk = this.getChunk(x, z, false); - if (chunk == null) { - throw new ChunkException("Invalid Chunk sent"); - } - - long timestamp = chunk.getChanges(); - - BinaryStream stream = new BinaryStream(); - stream.putByte((byte) 0); // subchunk version - - stream.put(chunk.getBlockIdArray()); - stream.put(chunk.getBlockDataArray()); - stream.put(chunk.getBlockSkyLightArray()); - stream.put(chunk.getBlockLightArray()); - stream.put(chunk.getHeightMapArray()); - stream.put(chunk.getBiomeIdArray()); - - Map extra = chunk.getBlockExtraDataArray(); - stream.putLInt(extra.size()); - if (!extra.isEmpty()) { - for (Integer key : extra.values()) { - stream.putLInt(key); - stream.putLShort(extra.get(key)); - } - } - - if (!chunk.getBlockEntities().isEmpty()) { - List tagList = new ArrayList<>(); - - for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { - if (blockEntity instanceof BlockEntitySpawnable) { - tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound()); - } - } - - try { - stream.put(NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - this.getLevel().chunkRequestCallback(timestamp, x, z, 16, stream.getBuffer()); - - return null; - } - - @Override - public void unloadChunks() { - for (Chunk chunk : new ArrayList<>(this.chunks.values())) { - this.unloadChunk(chunk.getX(), chunk.getZ(), false); - } - this.chunks = new HashMap<>(); - } - - @Override - public String getGenerator() { - return this.levelData.getString("generatorName"); - } - - @Override - public Map getGeneratorOptions() { - return new HashMap() { - { - put("preset", levelData.getString("generatorOptions")); - } - }; - } - - @Override - public BaseFullChunk getLoadedChunk(int X, int Z) { - return this.getLoadedChunk(Level.chunkHash(X, Z)); - } - - @Override - public BaseFullChunk getLoadedChunk(long hash) { - return this.chunks.get(hash); - } - - @Override - public Map getLoadedChunks() { - return this.chunks; - } - - @Override - public boolean isChunkLoaded(int x, int z) { - return this.isChunkLoaded(Level.chunkHash(x, z)); - } - - @Override - public boolean isChunkLoaded(long hash) { - return this.chunks.containsKey(hash); - } - - @Override - public void saveChunks() { - for (Chunk chunk : this.chunks.values()) { - this.saveChunk(chunk.getX(), chunk.getZ()); - } - } - - @Override - public boolean loadChunk(int x, int z) { - return this.loadChunk(x, z, false); - } - - @Override - public boolean loadChunk(int x, int z, boolean create) { - long index = Level.chunkHash(x, z); - if (this.chunks.containsKey(index)) { - return true; - } - - this.level.timings.syncChunkLoadDataTimer.startTiming(); - Chunk chunk = this.readChunk(x, z); - if (chunk == null && create) { - chunk = Chunk.getEmptyChunk(x, z, this); - } - this.level.timings.syncChunkLoadDataTimer.stopTiming(); - if (chunk != null) { - this.chunks.put(index, chunk); - return true; - } - - return false; - } - - public Chunk readChunk(int chunkX, int chunkZ) { - byte[] data; - if (!this.chunkExists(chunkX, chunkZ) || (data = this.db.get(TerrainKey.create(chunkX, chunkZ).toArray())) == null) { - return null; - } - - byte[] flags = this.db.get(FlagsKey.create(chunkX, chunkZ).toArray()); - if (flags == null) { - flags = new byte[]{0x03}; - } - - return Chunk.fromBinary( - Binary.appendBytes( - Binary.writeLInt(chunkX), - Binary.writeLInt(chunkZ), - data, - flags) - , this); - } - - private void writeChunk(Chunk chunk) { - byte[] binary = chunk.toBinary(true); - this.db.put(TerrainKey.create(chunk.getX(), chunk.getZ()).toArray(), Binary.subBytes(binary, 8, binary.length - 1)); - this.db.put(FlagsKey.create(chunk.getX(), chunk.getZ()).toArray(), Binary.subBytes(binary, binary.length - 1)); - this.db.put(VersionKey.create(chunk.getX(), chunk.getZ()).toArray(), new byte[]{0x02}); - } - - @Override - public boolean unloadChunk(int x, int z) { - return this.unloadChunk(x, z, true); - } - - @Override - public boolean unloadChunk(int x, int z, boolean safe) { - long index = Level.chunkHash(x, z); - Chunk chunk = this.chunks.getOrDefault(index, null); - if (chunk != null && chunk.unload(false, safe)) { - this.chunks.remove(index); - return true; - } - - return false; - } - - @Override - public void saveChunk(int x, int z) { - if (this.isChunkLoaded(x, z)) { - this.writeChunk(this.getChunk(x, z)); - } - } - - @Override - public void saveChunk(int x, int z, FullChunk chunk) { - if (!(chunk instanceof Chunk)) { - throw new ChunkException("Invalid Chunk class"); - } - this.writeChunk((Chunk) chunk); - } - - @Override - public Chunk getChunk(int x, int z) { - return this.getChunk(x, z, false); - } - - @Override - public Chunk getChunk(int x, int z, boolean create) { - long index = Level.chunkHash(x, z); - if (this.chunks.containsKey(index)) { - return this.chunks.get(index); - } else { - this.loadChunk(x, z, create); - return this.chunks.getOrDefault(index, null); - } - } - - public DB getDatabase() { - return db; - } - - @Override - public void setChunk(int chunkX, int chunkZ, FullChunk chunk) { - if (!(chunk instanceof Chunk)) { - throw new ChunkException("Invalid Chunk class"); - } - chunk.setProvider(this); - - chunk.setPosition(chunkX, chunkZ); - long index = Level.chunkHash(chunkX, chunkZ); - - if (this.chunks.containsKey(index) && !this.chunks.get(index).equals(chunk)) { - this.unloadChunk(chunkX, chunkZ, false); - } - - this.chunks.put(index, (Chunk) chunk); - } - - public static ChunkSection createChunkSection(int y) { - return null; - } - - private boolean chunkExists(int chunkX, int chunkZ) { - return this.db.get(VersionKey.create(chunkX, chunkZ).toArray()) != null; - } - - @Override - public boolean isChunkGenerated(int x, int z) { - return this.chunkExists(x, z) && this.getChunk(x, z, false) != null; - - } - - @Override - public boolean isChunkPopulated(int x, int z) { - return this.getChunk(x, z) != null; - } - - @Override - public void close() { - this.unloadChunks(); - try { - this.db.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - this.level = null; - } - - @Override - public String getPath() { - return path; - } - - public Server getServer() { - return this.level.getServer(); - } - - @Override - public Level getLevel() { - return level; - } - - @Override - public String getName() { - return this.levelData.getString("LevelName"); - } - - @Override - public boolean isRaining() { - return this.levelData.getFloat("rainLevel") > 0; - } - - @Override - public void setRaining(boolean raining) { - this.levelData.putFloat("rainLevel", raining ? 1.0f : 0); - } - - @Override - public int getRainTime() { - return this.levelData.getInt("rainTime"); - } - - @Override - public void setRainTime(int rainTime) { - this.levelData.putInt("rainTime", rainTime); - } - - @Override - public boolean isThundering() { - return this.levelData.getFloat("lightningLevel") > 0; - } - - @Override - public void setThundering(boolean thundering) { - this.levelData.putFloat("lightningLevel", thundering ? 1.0f : 0); - } - - @Override - public int getThunderTime() { - return this.levelData.getInt("lightningTime"); - } - - @Override - public void setThunderTime(int thunderTime) { - this.levelData.putInt("lightningTime", thunderTime); - } - - @Override - public long getCurrentTick() { - return this.levelData.getLong("currentTick"); - } - - @Override - public void setCurrentTick(long currentTick) { - this.levelData.putLong("currentTick", currentTick); - } - - @Override - public long getTime() { - return this.levelData.getLong("Time"); - } - - @Override - public void setTime(long value) { - this.levelData.putLong("Time", value); - } - - @Override - public long getSeed() { - return this.levelData.getLong("RandomSeed"); - } - - @Override - public void setSeed(long value) { - this.levelData.putLong("RandomSeed", value); - } - - @Override - public Vector3 getSpawn() { - return new Vector3(this.levelData.getInt("SpawnX"), this.levelData.getInt("SpawnY"), this.levelData.getInt("SpawnZ")); - } - - @Override - public void setSpawn(Vector3 pos) { - this.levelData.putInt("SpawnX", (int) pos.x); - this.levelData.putInt("SpawnY", (int) pos.y); - this.levelData.putInt("SpawnZ", (int) pos.z); - } - - @Override - public GameRules getGamerules() { - GameRules rules = GameRules.getDefault(); - - if (this.levelData.contains("GameRules")) - rules.readNBT(this.levelData.getCompound("GameRules")); - - return rules; - } - - @Override - public void setGameRules(GameRules rules) { - this.levelData.putCompound("GameRules", rules.writeNBT()); - } - - @Override - public void doGarbageCollection() { - - } - - public CompoundTag getLevelData() { - return levelData; - } - - public void updateLevelName(String name) { - if (!this.getName().equals(name)) { - this.levelData.putString("LevelName", name); - } - } - - public byte[][] getTerrainKeys() { - List result = new ArrayList<>(); - this.db.forEach((entry) -> { - byte[] key = entry.getKey(); - if (key.length > 8 && key[8] == BaseKey.DATA_TERRAIN) { - result.add(key); - } - }); - return result.toArray(new byte[0][]); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java new file mode 100644 index 00000000000..103a1f190fa --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java @@ -0,0 +1,25 @@ +package cn.nukkit.level.format.leveldb; + +import static org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext.makeVersion; + +public class LevelDBConstants { + // This is protocol version if block palette used in storage + public static final int PALETTE_VERSION = 594; //v1_20_10; + // By combining this versions we can get block state version + // NOTE: This is not necessary bumped everytime with PALETTE_VERSION + // Last time this was bumped in 1.18.10 + public static final int STATE_MAYOR_VERSION = 1; + public static final int STATE_MINOR_VERSION = 20; + public static final int STATE_PATCH_VERSION = 10; + public static final int STATE_VERSION = makeVersion(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION) + 32; // 32 updaters were added in 1.20.10 + // Chunk version that will be currently used as default + public static final int LATEST_CHUNK_VERSION = 40; + // SubChunk version used for serializing chunks into storage + public static final int LATEST_SUBCHUNK_VERSION = 8; + // Lowest SubChunk index + public static final int MIN_SUBCHUNK_INDEX = -4; + // Highest SubChunk index + public static final int MAX_SUBCHUNK_INDEX = 19; + // SubChunks count + public static final int MAX_SUBCHUNK_COUNT = MAX_SUBCHUNK_INDEX - MIN_SUBCHUNK_INDEX + 1; +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBKey.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBKey.java new file mode 100644 index 00000000000..5b717fe44f0 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBKey.java @@ -0,0 +1,83 @@ +package cn.nukkit.level.format.leveldb; + +public enum LevelDBKey { + DATA_3D('+'), + DATA_2D('-'), + DATA_2D_LEGACY('.'), + SUBCHUNK_PREFIX('/'), + LEGACY_TERRAIN('0'), + BLOCK_ENTITIES('1'), + ENTITIES('2'), + PENDING_TICKS('3'), + BLOCK_EXTRA_DATA('4'), + BIOME_STATE('5'), + STATE_FINALIZATION('6'), + + BORDER_BLOCKS('8'), + HARDCODED_SPAWNERS('9'), + + FLAGS('f'), + + VERSION_OLD('v'), + VERSION(','); + + private final byte encoded; + + LevelDBKey(char encoded) { + this.encoded = (byte) encoded; + } + + public byte[] getKey(int chunkX, int chunkZ, int dimension) { + if (dimension == 0) { + return this.getKey(chunkX, chunkZ, false, 0); + } else { + return this.getKey(chunkX, chunkZ, dimension, false, 0); + } + } + + public byte[] getKey(int chunkX, int chunkZ, int y, int dimension) { + if (dimension == 0) { + return this.getKey(chunkX, chunkZ, true, y); + } else { + return this.getKey(chunkX, chunkZ, dimension, true, y); + } + } + + private byte[] getKey(int chunkX, int chunkZ, boolean extend, int y) { + byte[] bytes = new byte[extend ? 10 : 9]; + bytes[0] = (byte) (chunkX & 0xff); + bytes[1] = (byte) ((chunkX >>> 8) & 0xff); + bytes[2] = (byte) ((chunkX >>> 16) & 0xff); + bytes[3] = (byte) ((chunkX >>> 24) & 0xff); + bytes[4] = (byte) (chunkZ & 0xff); + bytes[5] = (byte) ((chunkZ >>> 8) & 0xff); + bytes[6] = (byte) ((chunkZ >>> 16) & 0xff); + bytes[7] = (byte) ((chunkZ >>> 24) & 0xff); + bytes[8] = this.encoded; + if (extend) { + bytes[9] = (byte) y; + } + return bytes; + } + + private byte[] getKey(int chunkX, int chunkZ, int dimension, boolean extend, int y) { + byte[] bytes = new byte[extend ? 14 : 13]; + bytes[0] = (byte) (chunkX & 0xff); + bytes[1] = (byte) ((chunkX >>> 8) & 0xff); + bytes[2] = (byte) ((chunkX >>> 16) & 0xff); + bytes[3] = (byte) ((chunkX >>> 24) & 0xff); + bytes[4] = (byte) (chunkZ & 0xff); + bytes[5] = (byte) ((chunkZ >>> 8) & 0xff); + bytes[6] = (byte) ((chunkZ >>> 16) & 0xff); + bytes[7] = (byte) ((chunkZ >>> 24) & 0xff); + bytes[8] = (byte) (dimension & 0xff); + bytes[9] = (byte) ((dimension >>> 8) & 0xff); + bytes[10] = (byte) ((dimension >>> 16) & 0xff); + bytes[11] = (byte) ((dimension >>> 24) & 0xff); + bytes[12] = this.encoded; + if (extend) { + bytes[13] = (byte) y; + } + return bytes; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java new file mode 100644 index 00000000000..6c035b1af3b --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java @@ -0,0 +1,741 @@ +package cn.nukkit.level.format.leveldb; + +import cn.nukkit.Server; +import cn.nukkit.level.GameRule; +import cn.nukkit.level.GameRules; +import cn.nukkit.level.Level; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.LevelProvider; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.level.format.leveldb.serializer.*; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunkSection; +import cn.nukkit.level.generator.Generator; +import cn.nukkit.math.Vector3; +import cn.nukkit.nbt.stream.NBTInputStream; +import cn.nukkit.nbt.stream.NBTOutputStream; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.Tag; +import cn.nukkit.utils.ChunkException; +import cn.nukkit.utils.LevelException; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.nukkitx.network.util.Preconditions; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import net.daporkchop.ldbjni.DBProvider; +import net.daporkchop.ldbjni.LevelDB; +import net.daporkchop.lib.natives.FeatureBuilder; +import org.iq80.leveldb.CompressionType; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.Options; +import org.iq80.leveldb.WriteBatch; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; + +public class LevelDBProvider implements LevelProvider { + + private Level level; + private final Path path; + private final DB db; + + private final Long2ObjectMap chunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + + private CompoundTag levelData; + private Vector3 spawn; + private int lastGcPosition = 0; + + private final ExecutorService executor; + + private volatile boolean closed; + + private static final DBProvider JAVA_LDB_PROVIDER = (DBProvider) FeatureBuilder.create(LevelDBProvider.class).addJava("net.daporkchop.ldbjni.java.JavaDBProvider").build(); + + public LevelDBProvider(Level level, String path) throws IOException { + this.level = level; + this.path = Paths.get(path); + + Path dbPath = this.path.resolve("db"); + Files.createDirectories(dbPath); + Preconditions.checkArgument(Files.isDirectory(dbPath), "db is not a directory"); + + Options options = new Options() + .createIfMissing(true) + .compressionType(CompressionType.ZLIB_RAW) + .cacheSize(1024L * 1024L * level.getServer().levelDbCache) + .blockSize(64 * 1024); + this.db = level.getServer().useNativeLevelDB ? LevelDB.PROVIDER.open(dbPath.toFile(), options) : JAVA_LDB_PROVIDER.open(dbPath.toFile(), options); + + this.levelData = loadLevelData(this.path); + + if (!this.levelData.contains("generatorName")) { + Class generator = null; + if (this.levelData.contains("Generator")) { + generator = Generator.getGenerator(this.levelData.getInt("Generator")); + } + + if (generator == null) { + generator = Generator.getGenerator("DEFAULT"); + } + this.levelData.putString("generatorName", generator.getSimpleName().toLowerCase()); + } + + if (!this.levelData.contains("generatorOptions")) { + this.levelData.putString("generatorOptions", ""); + } + + this.spawn = new Vector3(this.levelData.getInt("SpawnX"), this.levelData.getInt("SpawnY"), this.levelData.getInt("SpawnZ")); + + ThreadFactoryBuilder builder = new ThreadFactoryBuilder(); + builder.setNameFormat("LevelDB Executor-" + this.getName() + " #%s"); + builder.setUncaughtExceptionHandler((thread, ex) -> Server.getInstance().getLogger().error("Exception in " + thread.getName(), ex)); + this.executor = Executors.newFixedThreadPool(3, builder.build()); + } + + @SuppressWarnings("unused") + public static String getProviderName() { + return "leveldb"; + } + + @SuppressWarnings("unused") + public static boolean usesChunkSection() { + return true; + } + + public static boolean isValid(String path) { + Path worldPath = Paths.get(path); + return Files.exists(worldPath.resolve("level.dat")) && Files.exists(worldPath.resolve("db")); + } + + public static void generate(String path, String name, long seed, Class generator) throws IOException { + generate(path, name, seed, generator, new HashMap<>()); + } + + public static void generate(String path, String name, long seed, Class generator, Map options) throws IOException { + Path worldPath = Paths.get(path); + Path dbPath = worldPath.resolve("db"); + + if (!Files.isDirectory(dbPath)) { + Files.createDirectories(dbPath); + } + + CompoundTag levelData = new CompoundTag() + .putInt("PM1EGen", (generator == cn.nukkit.level.generator.Void.class ? 0 : 2)) // Don't set for converted worlds + .putLong("DayTime", 0) + .putInt("GameType", 0) + .putInt("Generator", Generator.getGeneratorType(generator)) + .putString("generatorName", Generator.getGeneratorName(generator)) + .putString("generatorOptions", options.getOrDefault("preset", "")) + .putString("LevelName", name) + .putBoolean("raining", false) + .putInt("rainTime", 0) + .putLong("RandomSeed", seed) + .putInt("SpawnX", 128) + .putInt("SpawnY", 70) + .putInt("SpawnZ", 128) + .putBoolean("thundering", false) + .putInt("thunderTime", 0) + .putLong("Time", 0); + + saveLevelData(levelData, worldPath); + } + + @Override + public BaseFullChunk getChunk(int chunkX, int chunkZ) { + return this.getChunk(chunkX, chunkZ, false); + } + + @Override + public BaseFullChunk getChunk(int chunkX, int chunkZ, boolean create) { + // BaseFullChunk tmp = lastChunk.get(); + // if (tmp != null && tmp.getX() == chunkX && tmp.getZ() == chunkZ) { + // return tmp; + // } + long index = Level.chunkHash(chunkX, chunkZ); + BaseFullChunk chunk = this.chunks.get(index); + if (chunk == null) { + chunk = this.readOrCreateChunk(chunkX, chunkZ, create); + } + return chunk; + } + + @Override + public void setChunk(int chunkX, int chunkZ, FullChunk chunk) { + if (!(chunk instanceof LevelDBChunk)) throw new IllegalArgumentException("Only LevelDB chunks are supported"); + chunk.setProvider(this); + chunk.setPosition(chunkX, chunkZ); + long index = Level.chunkHash(chunkX, chunkZ); + + FullChunk oldChunk = this.chunks.get(index); + if (oldChunk != null && !oldChunk.equals(chunk)) { + this.unloadChunk(chunkX, chunkZ, false); + } + this.chunks.put(index, (BaseFullChunk) chunk); + } + + @Override + public boolean loadChunk(int chunkX, int chunkZ) { + return this.loadChunk(chunkX, chunkZ, false); + } + + @Override + public boolean loadChunk(int chunkX, int chunkZ, boolean create) { + long index = Level.chunkHash(chunkX, chunkZ); + if (this.chunks.containsKey(index)) { + return true; + } + + return this.readOrCreateChunk(chunkX, chunkZ, create) != null; + } + + @Override + public boolean unloadChunk(int chunkX, int chunkZ) { + return this.unloadChunk(chunkX, chunkZ, true); + } + + @Override + public boolean unloadChunk(int chunkX, int chunkZ, boolean safe) { + long index = Level.chunkHash(chunkX, chunkZ); + BaseFullChunk chunk = this.chunks.get(index); + if (chunk == null || !chunk.unload(false, safe)) { + return false; + } + // TODO: this.lastChunk.set(null); + this.chunks.remove(index, chunk); // TODO: Do this after saveChunkFuture to prevent loading of old copy + return true; + } + + @Override + public BaseFullChunk getLoadedChunk(int chunkX, int chunkZ) { + // BaseFullChunk tmp = lastChunk.get(); + // if (tmp != null && tmp.getX() == chunkX && tmp.getZ() == chunkZ) { + // return tmp; + // } + long index = Level.chunkHash(chunkX, chunkZ); + return this.chunks.get(index); + } + + @Override + public BaseFullChunk getLoadedChunk(long hash) { + // BaseFullChunk tmp = lastChunk.get(); + // if (tmp != null && tmp.getX() == chunkX && tmp.getZ() == chunkZ) { + // return tmp; + // } + return this.chunks.get(hash); + } + + @Override + public boolean isChunkLoaded(int X, int Z) { + return this.isChunkLoaded(Level.chunkHash(X, Z)); + } + + @Override + public boolean isChunkLoaded(long hash) { + return this.chunks.containsKey(hash); + } + + private synchronized BaseFullChunk readOrCreateChunk(int chunkX, int chunkZ, boolean create) { + BaseFullChunk chunk = null; + try { + chunk = this.readChunk(chunkX, chunkZ); + } catch (Exception ex) { + Server.getInstance().getLogger().error("Failed to read chunk " + chunkX + ", " + chunkZ, ex); + } + + if (chunk == null && create) { + chunk = this.getEmptyChunk(chunkX, chunkZ); + } else if (chunk == null) { + return null; + } + + this.chunks.put(Level.chunkHash(chunkX, chunkZ), chunk); + return chunk; + } + + private BaseFullChunk readChunk(int chunkX, int chunkZ) { + byte[] versionValue = this.db.get(LevelDBKey.VERSION.getKey(chunkX, chunkZ, this.level.getDimension())); + if (versionValue == null || versionValue.length != 1) { + versionValue = this.db.get(LevelDBKey.VERSION_OLD.getKey(chunkX, chunkZ, this.level.getDimension())); + if (versionValue == null || versionValue.length != 1) { + return null; + } + } + + ChunkBuilder chunkBuilder = new ChunkBuilder(chunkX, chunkZ, this); + byte[] finalizationState = this.db.get(LevelDBKey.STATE_FINALIZATION.getKey(chunkX, chunkZ, this.level.getDimension())); + if (finalizationState == null) { + chunkBuilder.state(ChunkBuilder.STATE_FINISHED); + } else { + chunkBuilder.state(Unpooled.wrappedBuffer(finalizationState).readIntLE() + 1); + } + + byte chunkVersion = versionValue[0]; + if (chunkVersion < 7) { + chunkBuilder.dirty(); + } + + ChunkSerializers.deserializeChunk(this.db, chunkBuilder, chunkVersion); + + Data3dSerializer.deserialize(this.db, chunkBuilder); + if (!chunkBuilder.has3dBiomes()) { + Data2dSerializer.deserialize(this.db, chunkBuilder); + } + + BlockEntitySerializer.loadBlockEntities(this.db, chunkBuilder); + EntitySerializer.loadEntities(this.db, chunkBuilder); + return chunkBuilder.build(); + } + + @Override + public void saveChunk(int chunkX, int chunkZ) { + BaseFullChunk chunk = this.getChunk(chunkX, chunkZ); + if (chunk != null) { + this.saveChunk(chunkX, chunkZ, chunk); + } + } + + @Override + public void saveChunk(int chunkX, int chunkZ, FullChunk chunk0) { + this.saveChunkFuture(chunkX, chunkZ, chunk0); + } + + public CompletableFuture saveChunkFuture(int chunkX, int chunkZ, FullChunk chunk0) { + if (!(chunk0 instanceof LevelDBChunk)) throw new IllegalArgumentException("Only LevelDB chunks are supported"); + LevelDBChunk chunk = (LevelDBChunk) chunk0; + chunk.setX(chunkX); + chunk.setZ(chunkZ); + if (!chunk.isGenerated()) { + return CompletableFuture.completedFuture(null); + } + chunk.setChanged(false); + + WriteBatch batch = this.db.createWriteBatch(); + ChunkSerializers.serializeChunk(batch, chunk, LATEST_CHUNK_VERSION); + if (chunk.has3dBiomes()) { + Data3dSerializer.serialize(batch, chunk); + } else { + Data2dSerializer.serialize(batch, chunk); + } + + batch.put(LevelDBKey.VERSION.getKey(chunkX, chunkZ, this.level.getDimension()), new byte[]{LATEST_CHUNK_VERSION}); + batch.put(LevelDBKey.STATE_FINALIZATION.getKey(chunkX, chunkZ, this.level.getDimension()), + Unpooled.buffer(4).writeIntLE(chunk.getState() - 1).array()); + + BlockEntitySerializer.saveBlockEntities(batch, chunk); + EntitySerializer.saveEntities(batch, chunk); + return CompletableFuture.runAsync(() -> this.saveChunkCallback(batch, chunk), this.executor); + } + + public void saveChunkSync(int chunkX, int chunkZ, FullChunk chunk0) { + if (!(chunk0 instanceof LevelDBChunk)) throw new IllegalArgumentException("Only LevelDB chunks are supported"); + LevelDBChunk chunk = (LevelDBChunk) chunk0; + chunk.setX(chunkX); + chunk.setZ(chunkZ); + if (!chunk.isGenerated()) { + return; + } + chunk.setChanged(false); + + WriteBatch batch = this.db.createWriteBatch(); + ChunkSerializers.serializeChunk(batch, chunk, LATEST_CHUNK_VERSION); + if (chunk.has3dBiomes()) { + Data3dSerializer.serialize(batch, chunk); + } else { + Data2dSerializer.serialize(batch, chunk); + } + + batch.put(LevelDBKey.VERSION.getKey(chunkX, chunkZ, this.level.getDimension()), new byte[]{LATEST_CHUNK_VERSION}); + batch.put(LevelDBKey.STATE_FINALIZATION.getKey(chunkX, chunkZ, this.level.getDimension()), + Unpooled.buffer(4).writeIntLE(chunk.getState() - 1).array()); + + BlockEntitySerializer.saveBlockEntities(batch, chunk); + EntitySerializer.saveEntities(batch, chunk); + this.saveChunkCallback(batch, chunk); + } + + private void saveChunkCallback(WriteBatch batch, LevelDBChunk chunk) { + if (this.closed) { + return; + } + + chunk.writeLock().lock(); + try { + this.db.write(batch); + batch.close(); + } catch (IOException e) { + throw new RuntimeException("Failed to close WriteBatch for " + this.getName(), e); + } finally { + chunk.writeLock().unlock(); + } + } + + @Override + public void saveChunks() { + for (BaseFullChunk chunk : this.chunks.values()) { + if (chunk.hasChanged()) { + chunk.setChanged(false); + this.saveChunk(chunk.getX(), chunk.getZ(), chunk); + } + } + } + + @Override + public void unloadChunks() { + this.unloadChunksUnsafe(false); + } + + private void unloadChunksUnsafe(boolean wait) { + Iterator iterator = this.chunks.values().iterator(); + while (iterator.hasNext()) { + LevelDBChunk chunk = (LevelDBChunk) iterator.next(); + chunk.unload(level.isSaveOnUnloadEnabled(), false); + if (wait) { + if (!chunk.writeLock().tryLock()) { + chunk.writeLock().lock(); + } + chunk.writeLock().unlock(); + } + iterator.remove(); + } + } + + @Override + public boolean isChunkGenerated(int chunkX, int chunkZ) { + BaseFullChunk chunk = this.getChunk(chunkX, chunkZ); + return chunk != null && chunk.isGenerated(); + } + + @Override + public boolean isChunkPopulated(int chunkX, int chunkZ) { + BaseFullChunk chunk = this.getChunk(chunkX, chunkZ); + return chunk != null && chunk.isPopulated(); + } + + @Override + public LevelDBChunk getEmptyChunk(int x, int z) { + LevelDBChunk chunk = new LevelDBChunk(this, new LevelDBChunkSection[0]); + chunk.setPosition(x, z); + return chunk; + } + + @SuppressWarnings("unused") + public static LevelDBChunkSection createChunkSection(int y) { + LevelDBChunkSection section = new LevelDBChunkSection(y); + section.setHasSkyLight(true); + return section; + } + + @Override + public Map getLoadedChunks() { + return ImmutableMap.copyOf(this.chunks); + } + + @Override + public void requestChunkTask(int chunkX, int chunkZ) { + LevelDBChunk chunk = (LevelDBChunk) this.getChunk(chunkX, chunkZ, false); + if (chunk == null) { + throw new ChunkException("Invalid chunk"); + } + + long timestamp = chunk.getChanges(); + + level.asyncChunk(chunk.cloneForChunkSending(), timestamp, chunkX, chunkZ); + } + + @Override + public String getPath() { + return this.path.toString(); + } + + @Override + public String getGenerator() { + return this.levelData.getString("generatorName"); + } + + @Override + public Map getGeneratorOptions() { + Map options = new HashMap<>(); + options.put("preset", this.levelData.getString("generatorOptions")); + options.put("__LevelDB", true); + options.put("__Version", this.levelData.getInt("PM1EGen")); + return options; + } + + @Override + public String getName() { + return this.levelData.getString("LevelName"); + } + + @Override + public boolean isRaining() { + return this.levelData.getBoolean("raining"); + } + + @Override + public void setRaining(boolean raining) { + this.levelData.putBoolean("raining", raining); + } + + @Override + public int getRainTime() { + return this.levelData.getInt("rainTime"); + } + + @Override + public void setRainTime(int rainTime) { + this.levelData.putInt("rainTime", rainTime); + } + + @Override + public boolean isThundering() { + return this.levelData.getBoolean("thundering"); + } + + @Override + public void setThundering(boolean thundering) { + this.levelData.putBoolean("thundering", thundering); + } + + @Override + public int getThunderTime() { + return this.levelData.getInt("thunderTime"); + } + + @Override + public void setThunderTime(int thunderTime) { + this.levelData.putInt("thunderTime", thunderTime); + } + + @Override + public long getCurrentTick() { + return this.levelData.getLong("Time"); + } + + @Override + public void setCurrentTick(long currentTick) { + this.levelData.putLong("Time", currentTick); + } + + @Override + public long getTime() { + return this.levelData.getLong("DayTime"); + } + + @Override + public void setTime(long value) { + this.levelData.putLong("DayTime", value); + } + + @Override + public long getSeed() { + return this.levelData.getLong("RandomSeed"); + } + + @Override + public void setSeed(long value) { + this.levelData.putLong("RandomSeed", value); + } + + @Override + public Vector3 getSpawn() { + return this.spawn; + } + + @Override + public void setSpawn(Vector3 spawn) { + this.levelData.putInt("SpawnX", (int) spawn.getX()); + this.levelData.putInt("SpawnY", (int) spawn.getY()); + this.levelData.putInt("SpawnZ", (int) spawn.getZ()); + this.spawn = spawn; + } + + @Override + public Level getLevel() { + return this.level; + } + + @Override + public void updateLevelName(String name) { + this.levelData.putString("LevelName", name); + } + + @Override + public GameRules getGamerules() { + GameRules rules = GameRules.getDefault(); + rules.readNBT(this.levelData); + return rules; + } + + public void setLevelData(CompoundTag levelData, GameRules gameRules) { + this.levelData = levelData; + + this.setGameRules(gameRules); + } + + @Override + public void setGameRules(GameRules rules) { + for (Map.Entry entry : rules.getGameRules().entrySet()) { + String name = entry.getKey().getName().toLowerCase(); + + if (entry.getValue().getType() == GameRules.Type.BOOLEAN) { + this.levelData.putBoolean(name, rules.getBoolean(entry.getKey())); + } else if (entry.getValue().getType() == GameRules.Type.INTEGER) { + this.levelData.putInt(name, rules.getInteger(entry.getKey())); + } else if (entry.getValue().getType() == GameRules.Type.FLOAT) { + this.levelData.putFloat(name, rules.getFloat(entry.getKey())); + } + } + } + + private static CompoundTag loadLevelData(Path path) { + Path levelDat = path.resolve("level.dat"); + + try (NBTInputStream stream = new NBTInputStream(new DataInputStream(Files.newInputStream(levelDat)), ByteOrder.LITTLE_ENDIAN)) { + int version = stream.readInt(); + if (version != 8 && version != 9 && version != 10) { + throw new LevelException("Incompatible level.dat version: " + version); + } + + stream.readInt(); + return (CompoundTag) Tag.readNamedTag(stream); + } catch (Exception ex1) { + Server.getInstance().getLogger().error("Failed to load level.dat in " + path, ex1); + + Path backup = path.resolve("level.dat_old"); + if (Files.exists(backup)) { + Server.getInstance().getLogger().warning("Attempting to load level.dat_old in " + path); + + try { + // Save a copy of the corrupted one + Files.copy(levelDat, path.resolve("level.dat_invalid"), StandardCopyOption.REPLACE_EXISTING); + + // Replace the corrupted one with a backup + Files.copy(backup, levelDat, StandardCopyOption.REPLACE_EXISTING); + + try (NBTInputStream stream = new NBTInputStream(new DataInputStream(Files.newInputStream(levelDat)), ByteOrder.LITTLE_ENDIAN)) { + int version = stream.readInt(); + if (version != 8 && version != 9 && version != 10) { + throw new LevelException("Incompatible level.dat_old version: " + version); + } + + stream.readInt(); + return (CompoundTag) Tag.readNamedTag(stream); + } + } catch (Exception ex2) { + Server.getInstance().getLogger().error("Failed to load level.dat_old in " + path, ex1); + } + } + } + + throw new LevelException("Invalid level.dat"); + } + + private static void saveLevelData(CompoundTag levelData, Path path) { + // Update storage version values to latest supported + levelData.putInt("NetworkVersion", PALETTE_VERSION); + levelData.putInt("StorageVersion", LATEST_SUBCHUNK_VERSION); + + Path savePath = path.resolve("level.dat"); + try { + if (Files.exists(savePath)) { + Files.copy(savePath, path.resolve("level.dat_old"), StandardCopyOption.REPLACE_EXISTING); + } + + byte[] tagBytes; + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + NBTOutputStream stream = new NBTOutputStream(outputStream, ByteOrder.LITTLE_ENDIAN)) { + Tag.writeNamedTag(levelData, stream); + tagBytes = outputStream.toByteArray(); + } + + try (NBTOutputStream stream = new NBTOutputStream(Files.newOutputStream(savePath), ByteOrder.LITTLE_ENDIAN)) { + stream.writeInt(8); + stream.writeInt(tagBytes.length); + stream.write(tagBytes); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void saveLevelData() { + saveLevelData(this.levelData, this.path); + } + + @Override + public void close() { + if (this.closed) { + return; + } + + this.unloadChunksUnsafe(true); + this.closed = true; + this.level = null; + this.executor.shutdown(); + + try { + this.db.close(); + } catch (IOException e) { + throw new RuntimeException("Can not close database", e); + } + } + + @Override + public void doGarbageCollection() { + // Noop + } + + @Override + public void doGarbageCollection(long time) { + long start = System.currentTimeMillis(); + int maxIterations = this.chunks.size(); + if (this.lastGcPosition > maxIterations) { + this.lastGcPosition = 0; + } + + ObjectIterator iterator = chunks.values().iterator(); + if (this.lastGcPosition != 0) { + iterator.skip(lastGcPosition); + } + + int iterations; + for (iterations = 0; iterations < maxIterations; iterations++) { + if (!iterator.hasNext()) { + iterator = this.chunks.values().iterator(); + } + + if (!iterator.hasNext()) { + break; + } + + BaseFullChunk chunk = iterator.next(); + if (chunk instanceof LevelDBChunk && chunk.isGenerated() && chunk.isPopulated()) { + chunk.compress(); + if (System.currentTimeMillis() - start >= time) { + break; + } + } + } + this.lastGcPosition += iterations; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java new file mode 100644 index 00000000000..014c0b11f10 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java @@ -0,0 +1,59 @@ +package cn.nukkit.level.format.leveldb; + +import cn.nukkit.block.Block; +import cn.nukkit.level.GlobalBlockPalette; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.nbt.NbtUtils; + +import java.io.InputStream; +import java.util.List; + +public class NukkitLegacyMapper implements LegacyStateMapper { + + public static void registerStates(BlockStateMapping mapping) { + List states = loadBlockPalette(); + for (int i = 0; i < states.size(); i++) { + NbtMap state = states.get(i); + if (state.containsKey("name_hash") || state.containsKey("network_id")) { + NbtMapBuilder builder = NbtMapBuilder.from(state); + builder.remove("name_hash"); + builder.remove("network_id"); + state = builder.build(); + } + state.hashCode(); // cache hashCode + mapping.registerState(i, state); + } + } + + public static List loadBlockPalette() { + try (InputStream stream = NukkitLegacyMapper.class.getClassLoader().getResourceAsStream("leveldb_palette.nbt")) { + return ((NbtMap) NbtUtils.createGZIPReader(stream).readTag()).getList("blocks", NbtType.COMPOUND); + } catch (Exception e) { + throw new AssertionError("Error loading block palette leveldb_palette.nbt", e); + } + } + + @Override + public int legacyToRuntime(int legacyId, int meta) { + return GlobalBlockPalette.getLeveldbBlockPalette().getRuntimeId(legacyId, meta); + } + + @Override + public int runtimeToFullId(int runtimeId) { + return GlobalBlockPalette.getLeveldbBlockPalette().getLegacyFullId(runtimeId); + } + + @Override + public int runtimeToLegacyId(int runtimeId) { + int fullId = this.runtimeToFullId(runtimeId); + return fullId == -1 ? -1 : fullId >> 6; + } + + @Override + public int runtimeToLegacyData(int runtimeId) { + int fullId = this.runtimeToFullId(runtimeId); + return fullId == -1 ? -1 : (fullId & Block.DATA_MASK); + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/BaseKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/BaseKey.java deleted file mode 100644 index 660674497e2..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/BaseKey.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class BaseKey { - private final int chunkX; - private final int chunkZ; - private final byte type; - - public static final byte DATA_VERSION = 0x76; - public static final byte DATA_FLAGS = 0x66; - public static final byte DATA_EXTRA_DATA = 0x34; - public static final byte DATA_TICKS = 0x33; - public static final byte DATA_ENTITIES = 0x32; - public static final byte DATA_TILES = 0x31; - public static final byte DATA_TERRAIN = 0x30; - - protected BaseKey(int chunkX, int chunkZ, byte type) { - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.type = type; - } - - public byte[] toArray() { - return new byte[]{ - (byte) (this.chunkX & 0xff), - (byte) ((this.chunkX >>> 8) & 0xff), - (byte) ((this.chunkX >>> 16) & 0xff), - (byte) ((this.chunkX >>> 24) & 0xff), - (byte) (this.chunkZ & 0xff), - (byte) ((this.chunkZ >>> 8) & 0xff), - (byte) ((this.chunkZ >>> 16) & 0xff), - (byte) ((this.chunkZ >>> 24) & 0xff), - this.type - }; - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/EntitiesKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/EntitiesKey.java deleted file mode 100644 index 7ea9a9cd029..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/EntitiesKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class EntitiesKey extends BaseKey { - protected EntitiesKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_ENTITIES); - } - - public static EntitiesKey create(int chunkX, int chunkZ) { - return new EntitiesKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/ExtraDataKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/ExtraDataKey.java deleted file mode 100644 index 8632826bf6c..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/ExtraDataKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class ExtraDataKey extends BaseKey { - protected ExtraDataKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_EXTRA_DATA); - } - - public static ExtraDataKey create(int chunkX, int chunkZ) { - return new ExtraDataKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/FlagsKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/FlagsKey.java deleted file mode 100644 index 800442642e9..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/FlagsKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class FlagsKey extends BaseKey { - protected FlagsKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_FLAGS); - } - - public static FlagsKey create(int chunkX, int chunkZ) { - return new FlagsKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/TerrainKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/TerrainKey.java deleted file mode 100644 index 6536483313f..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/TerrainKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class TerrainKey extends BaseKey { - protected TerrainKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_TERRAIN); - } - - public static TerrainKey create(int chunkX, int chunkZ) { - return new TerrainKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/TicksKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/TicksKey.java deleted file mode 100644 index aa9bf1da317..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/TicksKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class TicksKey extends BaseKey { - protected TicksKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_TICKS); - } - - public static TicksKey create(int chunkX, int chunkZ) { - return new TicksKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/TilesKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/TilesKey.java deleted file mode 100644 index 8c1e4fa3218..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/TilesKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class TilesKey extends BaseKey { - protected TilesKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_TILES); - } - - public static TilesKey create(int chunkX, int chunkZ) { - return new TilesKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/key/VersionKey.java b/src/main/java/cn/nukkit/level/format/leveldb/key/VersionKey.java deleted file mode 100644 index 617989fcc45..00000000000 --- a/src/main/java/cn/nukkit/level/format/leveldb/key/VersionKey.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.nukkit.level.format.leveldb.key; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class VersionKey extends BaseKey { - protected VersionKey(int chunkX, int chunkZ) { - super(chunkX, chunkZ, DATA_VERSION); - } - - public static VersionKey create(int chunkX, int chunkZ) { - return new VersionKey(chunkX, chunkZ); - } -} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/BlockEntitySerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/BlockEntitySerializer.java new file mode 100644 index 00000000000..c18fa402791 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/BlockEntitySerializer.java @@ -0,0 +1,64 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.level.format.leveldb.LevelDBKey; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.Collection; +import java.util.List; + +public class BlockEntitySerializer { + + public static void loadBlockEntities(DB db, ChunkBuilder builder) { + byte[] key = LevelDBKey.BLOCK_ENTITIES.getKey(builder.getX(), builder.getZ(), builder.getProvider().getLevel().getDimension()); + + byte[] value = db.get(key); + if (value == null) { + return; + } + + List blockEntities = new ObjectArrayList<>(); + try (ByteArrayInputStream stream = new ByteArrayInputStream(value)) { + while (stream.available() > 0) { + blockEntities.add(NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN)); + } + builder.dataLoader((chunk, provider) -> chunk.setNbtBlockEntities(blockEntities)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void saveBlockEntities(WriteBatch db, LevelDBChunk chunk) { + byte[] key = LevelDBKey.BLOCK_ENTITIES.getKey(chunk.getX(), chunk.getZ(), chunk.getProvider().getLevel().getDimension()); + if (chunk.getBlockEntities().isEmpty()) { + db.delete(key); + return; + } + + Collection entities = chunk.getBlockEntities().values(); + + byte[] value; + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + for (BlockEntity blockEntity : entities) { + if (blockEntity.canSaveToStorage()) { + blockEntity.saveNBT(); + NBTIO.write(blockEntity.namedTag, stream, ByteOrder.LITTLE_ENDIAN); + } + } + value = stream.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + db.put(key, value); + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkDataLoader.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkDataLoader.java new file mode 100644 index 00000000000..2bcfa57f019 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkDataLoader.java @@ -0,0 +1,9 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.LevelDBProvider; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; + +public interface ChunkDataLoader { + + void initChunk(LevelDBChunk chunk, LevelDBProvider provider); +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializer.java new file mode 100644 index 00000000000..438b97beda4 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializer.java @@ -0,0 +1,12 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import io.netty.buffer.ByteBuf; + +interface ChunkSectionSerializer { + + void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection); + + StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder); +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV1.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV1.java new file mode 100644 index 00000000000..5a4cc784f36 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV1.java @@ -0,0 +1,23 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import io.netty.buffer.ByteBuf; + +public class ChunkSectionSerializerV1 implements ChunkSectionSerializer { + + public static final ChunkSectionSerializer INSTANCE = new ChunkSectionSerializerV1(); + + @Override + public void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection) { + throw new UnsupportedOperationException(); + } + + @Override + public StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder) { + StateBlockStorage[] storage = new StateBlockStorage[2]; + storage[0] = new StateBlockStorage(); + storage[0].readFromStorage(buf, builder); + return storage; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV7.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV7.java new file mode 100644 index 00000000000..e153beff7ef --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV7.java @@ -0,0 +1,41 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.anvil.util.NibbleArray; +import cn.nukkit.level.format.leveldb.BlockStateMapping; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import io.netty.buffer.ByteBuf; + +public class ChunkSectionSerializerV7 implements ChunkSectionSerializer { + + public static final ChunkSectionSerializer INSTANCE = new ChunkSectionSerializerV7(); + + @Override + public void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection) { + throw new UnsupportedOperationException(); + } + + @Override + public StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder) { + byte[] blockIds = new byte[4096]; + buf.readBytes(blockIds); + byte[] blockData = new byte[2048]; + buf.readBytes(blockData); + if (buf.isReadable(4096)) { + buf.skipBytes(4096); // light + } + + StateBlockStorage[] blockStorage = new StateBlockStorage[2]; + blockStorage[0] = fromXZY(blockIds, blockData); + return blockStorage; + } + + private static StateBlockStorage fromXZY(byte[] blockIds, byte[] blockData) { + NibbleArray data = new NibbleArray(blockData); + StateBlockStorage storage = new StateBlockStorage(); + for (int i = 0; i < 4096; i++) { + storage.setBlockStateUnsafe(i, BlockStateMapping.get().getState(blockIds[i], data.get(i))); + } + return storage; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV8.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV8.java new file mode 100644 index 00000000000..4e5c34b6097 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV8.java @@ -0,0 +1,30 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import io.netty.buffer.ByteBuf; + +public class ChunkSectionSerializerV8 implements ChunkSectionSerializer { + + public static final ChunkSectionSerializer INSTANCE = new ChunkSectionSerializerV8(); + + @Override + public void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection) { + buf.writeByte(storage.length); + for (StateBlockStorage blockStorage : storage) { + blockStorage.writeToStorage(buf); + } + } + + @Override + public StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder) { + int storageCount = buf.readUnsignedByte(); + + StateBlockStorage[] storage = new StateBlockStorage[Math.max(storageCount, 2)]; + for (int i = 0; i < storageCount; i++) { + storage[i] = new StateBlockStorage(); + storage[i].readFromStorage(buf, builder); + } + return storage; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV9.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV9.java new file mode 100644 index 00000000000..a8adcc3a3ee --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializerV9.java @@ -0,0 +1,32 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import io.netty.buffer.ByteBuf; + +public class ChunkSectionSerializerV9 implements ChunkSectionSerializer { + + public static final ChunkSectionSerializer INSTANCE = new ChunkSectionSerializerV9(); + + @Override + public void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection) { + buf.writeByte(storage.length); + buf.writeByte(ySection); + for (StateBlockStorage blockStorage : storage) { + blockStorage.writeToStorage(buf); + } + } + + @Override + public StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder) { + int storageCount = buf.readUnsignedByte(); + buf.readUnsignedByte(); // ySection + + StateBlockStorage[] storage = new StateBlockStorage[Math.max(storageCount, 2)]; + for (int i = 0; i < storageCount; i++) { + storage[i] = new StateBlockStorage(); + storage[i].readFromStorage(buf, builder); + } + return storage; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java new file mode 100644 index 00000000000..cbd9fb18553 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java @@ -0,0 +1,38 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import com.nukkitx.network.util.Preconditions; +import io.netty.buffer.ByteBuf; + +public class ChunkSectionSerializers { + private static final ChunkSectionSerializer[] SERIALIZERS = new ChunkSectionSerializer[10]; + + static { + SERIALIZERS[0] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[1] = ChunkSectionSerializerV1.INSTANCE; + SERIALIZERS[2] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[3] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[4] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[5] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[6] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[7] = ChunkSectionSerializerV7.INSTANCE; + SERIALIZERS[8] = ChunkSectionSerializerV8.INSTANCE; + SERIALIZERS[9] = ChunkSectionSerializerV9.INSTANCE; + } + + public static void serialize(ByteBuf buf, StateBlockStorage[] storage, int ySection, int version) { + getSerializer(version).serialize(buf, storage, ySection); + } + + public static StateBlockStorage[] deserialize(ByteBuf buf, ChunkBuilder builder, int version) { + return getSerializer(version).deserialize(buf, builder); + } + + public static ChunkSectionSerializer getSerializer(int version) { + Preconditions.checkElementIndex(version, SERIALIZERS.length, "Invalid sub-chunk version " + version); + ChunkSectionSerializer serializer = SERIALIZERS[version]; + if (serializer == null) throw new NullPointerException("No ChunkSectionSerializer for version " + version); + return serializer; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializer.java new file mode 100644 index 00000000000..81c8848ee9b --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializer.java @@ -0,0 +1,13 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.Chunk; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +public interface ChunkSerializer { + + void serialize(WriteBatch db, Chunk chunk); + + void deserialize(DB db, ChunkBuilder chunkBuilder); +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializerV3.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializerV3.java new file mode 100644 index 00000000000..40524320bb8 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializerV3.java @@ -0,0 +1,114 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.DimensionData; +import cn.nukkit.level.format.Chunk; +import cn.nukkit.level.format.leveldb.BlockStateMapping; +import cn.nukkit.level.format.leveldb.LevelDBKey; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunkSection; +import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; +import cn.nukkit.utils.ChunkException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ShortMap; +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; + +public class ChunkSerializerV3 implements ChunkSerializer { + public static final ChunkSerializer INSTANCE = new ChunkSerializerV3(); + + @Override + public void serialize(WriteBatch db, Chunk chunk) { + // Write chunk sections + DimensionData dimensionData = chunk.getProvider().getLevel().getDimensionData(); + int lowestSection = dimensionData.getMinHeight() >> 4; + int highestSection = dimensionData.getMaxHeight() >> 4; + + for (int ySection = lowestSection; ySection <= highestSection; ySection++) { + LevelDBChunkSection section = (LevelDBChunkSection) chunk.getSection(ySection); + if (section == null) { + continue; + } + + ByteBuf buffer = ByteBufAllocator.DEFAULT.ioBuffer(); + try { + buffer.writeByte(LATEST_SUBCHUNK_VERSION); + ChunkSectionSerializers.serialize(buffer, section.getStorages(), ySection, LATEST_SUBCHUNK_VERSION); + + byte[] payload = new byte[buffer.readableBytes()]; + buffer.readBytes(payload); + db.put(LevelDBKey.SUBCHUNK_PREFIX.getKey(chunk.getX(), chunk.getZ(), ySection, chunk.getProvider().getLevel().getDimension()), payload); + } finally { + buffer.release(); + } + } + } + + @Override + public void deserialize(DB db, ChunkBuilder chunkBuilder) { + int chunkX = chunkBuilder.getX(); + int chunkZ = chunkBuilder.getZ(); + + Int2ShortMap extraDataMap = null; + + byte[] extraData = db.get(LevelDBKey.BLOCK_EXTRA_DATA.getKey(chunkX, chunkZ, chunkBuilder.getProvider().getLevel().getDimension())); + if (extraData != null) { + extraDataMap = new Int2ShortOpenHashMap(); + ByteBuf extraDataBuf = Unpooled.wrappedBuffer(extraData); + + int count = extraDataBuf.readIntLE(); + for (int i = 0; i < count; i++) { + int key = extraDataBuf.readIntLE(); + short value = extraDataBuf.readShortLE(); + extraDataMap.put(key, value); + } + } + + DimensionData dimensionData = chunkBuilder.getProvider().getLevel().getDimensionData(); + int offset = dimensionData.getSectionOffset(); + int lowestSection = dimensionData.getMinHeight() >> 4; + int highestSection = dimensionData.getMaxHeight() >> 4; + + LevelDBChunkSection[] sections = new LevelDBChunkSection[dimensionData.getHeight() >> 4]; + + for (int ySection = lowestSection; ySection <= highestSection; ySection++) { + byte[] sectionData = db.get(LevelDBKey.SUBCHUNK_PREFIX.getKey(chunkX, chunkZ, ySection, chunkBuilder.getProvider().getLevel().getDimension())); + if (sectionData == null) { + continue; + } + ByteBuf buf = Unpooled.wrappedBuffer(sectionData); + if (!buf.isReadable()) { + throw new ChunkException("Empty sub-chunk " + ySection); + } + + int subChunkVersion = buf.readUnsignedByte(); + StateBlockStorage[] blockStorage = ChunkSectionSerializers.deserialize(buf, chunkBuilder, subChunkVersion); + + if (blockStorage[1] == null) { + blockStorage[1] = new StateBlockStorage(); + if (extraDataMap != null) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = ySection << 4, lim = y + 16; y < lim; y++) { + int key = ChunkBuilder.blockKey(x, y, z); + if (extraDataMap.containsKey(key)) { + short value = extraDataMap.get(ChunkBuilder.blockKey(x, y, z)); + int blockId = value & 0xff; + int blockData = (value >> 8) & 0xf; + blockStorage[1].setBlockStateUnsafe(ChunkBuilder.getSectionIndex(x, y, z), BlockStateMapping.get().getState(blockId, blockData)); + } + } + } + } + } + } + sections[ySection + offset] = new LevelDBChunkSection(ySection, blockStorage); + } + + chunkBuilder.sections(sections); + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializers.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializers.java new file mode 100644 index 00000000000..b44dbab5832 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSerializers.java @@ -0,0 +1,57 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +public class ChunkSerializers { + private static final IntObjectMap SERIALIZERS = new IntObjectHashMap<>(); + + static { + // SERIALIZERS.put(0, ChunkSerializerV1.INSTANCE); + // SERIALIZERS.put(1, ChunkSerializerV1.INSTANCE); + // SERIALIZERS.put(2, ChunkSerializerV1.INSTANCE); + SERIALIZERS.put(3, ChunkSerializerV3.INSTANCE); // v1_0_0 + SERIALIZERS.put(4, ChunkSerializerV3.INSTANCE); // v1_1_0 + SERIALIZERS.put(6, ChunkSerializerV3.INSTANCE); // v1_2_0_2_beta + SERIALIZERS.put(7, ChunkSerializerV3.INSTANCE); // v1_2_0 + SERIALIZERS.put(9, ChunkSerializerV3.INSTANCE); // v1_8_0 + SERIALIZERS.put(10, ChunkSerializerV3.INSTANCE); // v1_9_0 + SERIALIZERS.put(11, ChunkSerializerV3.INSTANCE); // v1_11_0_1_beta + SERIALIZERS.put(12, ChunkSerializerV3.INSTANCE); // v1_11_0_3_beta + SERIALIZERS.put(13, ChunkSerializerV3.INSTANCE); // v1_11_0_4_beta + SERIALIZERS.put(14, ChunkSerializerV3.INSTANCE); // v1_11_1 + SERIALIZERS.put(15, ChunkSerializerV3.INSTANCE); // v1_12_0_4_beta + SERIALIZERS.put(16, ChunkSerializerV3.INSTANCE); // v1_12_0_unused1 + SERIALIZERS.put(17, ChunkSerializerV3.INSTANCE); // v1_12_0_unused2 + SERIALIZERS.put(18, ChunkSerializerV3.INSTANCE); // v1_16_0_51_beta + SERIALIZERS.put(19, ChunkSerializerV3.INSTANCE); // v1_16_0 + SERIALIZERS.put(22, ChunkSerializerV3.INSTANCE); // v1_16_210 + // Here are some experimental Caves & Cliffs versions from 1.16 betas + SERIALIZERS.put(32, ChunkSerializerV3.INSTANCE); // v1_17_40_unused + SERIALIZERS.put(33, ChunkSerializerV3.INSTANCE); // v1_18_0_20_beta + SERIALIZERS.put(35, ChunkSerializerV3.INSTANCE); // v1_18_0_22_beta + SERIALIZERS.put(37, ChunkSerializerV3.INSTANCE); // v1_18_0_24_beta + SERIALIZERS.put(39, ChunkSerializerV3.INSTANCE); // v1_18_0_25_beta + SERIALIZERS.put(40, ChunkSerializerV3.INSTANCE); // v1_18_30 + } + + private static ChunkSerializer getChunkSerializer(int version) { + ChunkSerializer chunkSerializer = SERIALIZERS.get(version); + if (chunkSerializer == null) { + throw new IllegalArgumentException("Invalid chunk serialize version " + version); + } + return chunkSerializer; + } + + public static void serializeChunk(WriteBatch db, LevelDBChunk chunk, int version) { + getChunkSerializer(version).serialize(db, chunk); + } + + public static void deserializeChunk(DB db, ChunkBuilder chunkBuilder, int version) { + getChunkSerializer(version).deserialize(db, chunkBuilder); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data2dSerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data2dSerializer.java new file mode 100644 index 00000000000..b1505db8335 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data2dSerializer.java @@ -0,0 +1,45 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.format.leveldb.LevelDBKey; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +public class Data2dSerializer { + + public static void serialize(WriteBatch db, LevelDBChunk chunk) { + // Write height map and biomes. + byte[] data2d = new byte[768]; + ByteBuf buffer = Unpooled.wrappedBuffer(data2d); + buffer.writerIndex(0); + byte[] heightMap = chunk.getHeightMapArray(); + byte[] biomes = chunk.getBiomeIdArray(); + for (int height : heightMap) { + buffer.writeShortLE(height); + } + buffer.writeBytes(biomes); + + db.put(LevelDBKey.DATA_2D.getKey(chunk.getX(), chunk.getZ(), chunk.getProvider().getLevel().getDimension()), data2d); + } + + public static void deserialize(DB db, ChunkBuilder builder) { + byte[] data2d = db.get(LevelDBKey.DATA_2D.getKey(builder.getX(), builder.getZ(), builder.getProvider().getLevel().getDimension())); + int[] heightMap = new int[512]; + byte[] biomes = new byte[256]; + + if (data2d != null) { + ByteBuf buffer = Unpooled.wrappedBuffer(data2d); + + for (int i = 0; i < 256; i++) { + heightMap[i] = buffer.readUnsignedShortLE(); + } + buffer.readBytes(biomes); + } + + builder.heightMap(heightMap); + builder.biomes(biomes); + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data3dSerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data3dSerializer.java new file mode 100644 index 00000000000..94dcdd243f3 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/Data3dSerializer.java @@ -0,0 +1,90 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.level.DimensionData; +import cn.nukkit.level.format.leveldb.LevelDBKey; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.level.util.BitArrayVersion; +import cn.nukkit.level.util.PalettedBlockStorage; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +public class Data3dSerializer { + + public static void serialize(WriteBatch db, LevelDBChunk chunk) { + DimensionData dimensionData = chunk.getProvider().getLevel().getDimensionData(); + + ByteBuf buffer = ByteBufAllocator.DEFAULT.ioBuffer(); + try { + byte[] heightMap = chunk.getHeightMapArray(); + for (int height : heightMap) { + buffer.writeShortLE(height); + } + + for (int i = 0; i < dimensionData.getHeight() >> 4; i++) { + PalettedBlockStorage storage = chunk.getBiomeStorage(i); + storage.writeToStorage(buffer); + } + + byte[] data = new byte[buffer.readableBytes()]; + buffer.readBytes(data); + db.put(LevelDBKey.DATA_3D.getKey(chunk.getX(), chunk.getZ(), chunk.getProvider().getLevel().getDimension()), data); + } finally { + buffer.release(); + } + } + + public static void deserialize(DB db, ChunkBuilder builder) { + DimensionData dimensionData = builder.getProvider().getLevel().getDimensionData(); + + byte[] data3d = db.get(LevelDBKey.DATA_3D.getKey(builder.getX(), builder.getZ(), dimensionData.getDimensionId())); + if (data3d == null || data3d.length < 1) { + return; + } + + + int[] heightMap = new int[512]; + PalettedBlockStorage[] biomes = new PalettedBlockStorage[dimensionData.getHeight() >> 4]; + + ByteBuf buffer = ByteBufAllocator.DEFAULT.ioBuffer(data3d.length); + try { + buffer.writeBytes(data3d); + for (int i = 0; i < 256; i++) { + heightMap[i] = buffer.readUnsignedShortLE(); + } + + for (int i = 0; i < biomes.length; i++) { + PalettedBlockStorage storage = readPalettedBiomes(buffer); + if (storage == null && i == 0) { + throw new IllegalStateException("First biome palette can not point to previous!"); + } + + if (storage == null) { + storage = biomes[i - 1].copy(); + } + biomes[i] = storage; + } + } finally { + buffer.release(); + } + + builder.heightMap(heightMap); + builder.biomes3d(biomes); + } + + public static PalettedBlockStorage readPalettedBiomes(ByteBuf buffer) { + int index = buffer.readerIndex(); + int size = buffer.readUnsignedByte() >> 1; + if (size == 127) { + // This means this paletted storage had the flag pointing to the previous one + return null; + } + + buffer.readerIndex(index); + PalettedBlockStorage storage = PalettedBlockStorage.createWithDefaultState(BitArrayVersion.V0, 0); + storage.readFromStorage(buffer); + return storage; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/EntitySerializer.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/EntitySerializer.java new file mode 100644 index 00000000000..f1289100c07 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/EntitySerializer.java @@ -0,0 +1,88 @@ +package cn.nukkit.level.format.leveldb.serializer; + +import cn.nukkit.entity.Entity; +import cn.nukkit.level.format.leveldb.LevelDBKey; +import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.WriteBatch; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteOrder; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; + +public class EntitySerializer { + + public static void loadEntities(DB db, ChunkBuilder builder) { + byte[] key = LevelDBKey.ENTITIES.getKey(builder.getX(), builder.getZ(), builder.getProvider().getLevel().getDimension()); + + byte[] value = db.get(key); + if (value == null) { + return; + } + + List entityTags = new ObjectArrayList<>(); + try (ByteArrayInputStream stream = new ByteArrayInputStream(value)) { + while (stream.available() > 0) { + deserializeNbt(NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN), entityTags::add); + } + builder.dataLoader((chunk, provider) -> chunk.setNbtEntities(entityTags)); + } catch (IOException e) { + throw new RuntimeException("Unable to deserialize entity NBT", e); + } + } + + public static void saveEntities(WriteBatch db, LevelDBChunk chunk) { + byte[] key = LevelDBKey.ENTITIES.getKey(chunk.getX(), chunk.getZ(), chunk.getProvider().getLevel().getDimension()); + Collection entities = chunk.getEntities().values(); + if (entities.isEmpty()) { + db.delete(key); + return; + } + + byte[] value; + try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) { + for (Entity entity : entities) { + if (entity.canSaveToStorage() && !entity.closed) { + // Player data are still saved externally + entity.saveNBT(); + serializeNbt(entity.namedTag, nbt -> writeSilently(nbt, stream)); + } + } + value = stream.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Can not create out stream", e); + } + db.put(key, value); + } + + private static void deserializeNbt(CompoundTag nbt, Consumer handle) { + // TODO: convert LevelDB format to our legacy + if (!nbt.contains("id") || !nbt.contains("Pos")) { + return; + } + handle.accept(nbt); + } + + private static void serializeNbt(CompoundTag nbt, Consumer handle) { + // TODO: use identifiers instead of class names here + // might do some validation here too + handle.accept(nbt); + } + + private static void writeSilently(CompoundTag nbt, OutputStream stream) { + try { + NBTIO.write(nbt, stream, ByteOrder.LITTLE_ENDIAN); + } catch (IOException e) { + throw new RuntimeException("Unable to write entity NBT", e); + } + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java new file mode 100644 index 00000000000..263eab3a199 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java @@ -0,0 +1,51 @@ +package cn.nukkit.level.format.leveldb.structure; + +import cn.nukkit.level.format.leveldb.BlockStateMapping; +import com.nukkitx.nbt.NbtMap; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class BlockStateSnapshot { + private final NbtMap vanillaState; + private final int runtimeId; + private final int version; + + @Builder.Default + private boolean custom = false; + + @Builder.Default + private int legacyId = -1; + @Builder.Default + private int legacyData = -1; + + public int getLegacyId() { + if (this.legacyId != -1) { + return this.legacyId; + } + + int id = BlockStateMapping.get().getLegacyId(this.runtimeId); + if (this.version == BlockStateMapping.get().getVersion()) { + // Cache legacyId only if the mapping is same version, + // plugins might be passing custom states + // with unknown legacyId to vanilla mapping + this.legacyId = id; + } + return id; + } + + public int getLegacyData() { + if (this.legacyData != -1) { + return this.legacyData; + } + + int data = BlockStateMapping.get().getLegacyData(this.runtimeId); + if (this.version == BlockStateMapping.get().getVersion()) { + this.legacyData = data; + } + return data; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/ChunkBuilder.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/ChunkBuilder.java new file mode 100644 index 00000000000..3e2f0a82457 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/ChunkBuilder.java @@ -0,0 +1,171 @@ +package cn.nukkit.level.format.leveldb.structure; + +import cn.nukkit.level.format.leveldb.LevelDBConstants; +import cn.nukkit.level.format.leveldb.LevelDBProvider; +import cn.nukkit.level.format.leveldb.serializer.ChunkDataLoader; +import cn.nukkit.level.util.PalettedBlockStorage; +import cn.nukkit.math.Vector3; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.List; + +public class ChunkBuilder { + + public static final int STATE_NEW = 0; + public static final int STATE_GENERATED = 1; + public static final int STATE_POPULATED = 2; + public static final int STATE_FINISHED = 3; + + private final int x; + private final int z; + private final LevelDBProvider provider; + + private Int2IntMap extraData; + private final List chunkDataLoaders = new ObjectArrayList<>(); + private LevelDBChunkSection[] sections; + private byte[] biomes; + private PalettedBlockStorage[] biomes3d; + private boolean has3dBiomes; + private int[] heightMap; + private int state = STATE_NEW; + private int chunkVersion = LevelDBConstants.LATEST_CHUNK_VERSION; + + private boolean dirty; + + public ChunkBuilder(int x, int z, LevelDBProvider provider) { + this.x = x; + this.z = z; + if (provider == null) throw new NullPointerException(); + this.provider = provider; + } + + public static short blockKey(Vector3 vector) { + return blockKey((int) vector.getX(), (int) vector.getY(), (int) vector.getZ()); + } + + public static short blockKey(int x, int y, int z) { + return (short) ((x & 0xf) | ((z & 0xf) << 4) | ((y & 0xff) << 9)); + } + + public static Vector3 fromKey(long chunkKey, short blockKey) { + int x = (blockKey & 0xf) | (fromKeyX(chunkKey) << 4); + int z = ((blockKey >>> 4) & 0xf) | (fromKeyZ(chunkKey) << 4); + int y = (blockKey >>> 8) & 0xff; + return new Vector3(x, y, z); + } + + public static long key(int x, int z) { + return (((long) x) << 32) | (z & 0xffffffffL); + } + + public static int fromKeyX(long key) { + return (int) (key >> 32); + } + + public static int fromKeyZ(long key) { + return (int) key; + } + + public static int getSectionIndex(int x, int y, int z) { + return (x << 8) + (z << 4) + y; + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } + + public ChunkBuilder sections(LevelDBChunkSection[] sections) { + if (sections == null) throw new NullPointerException(); + this.sections = sections; + return this; + } + + public ChunkBuilder extraData(int key, short value) { + if (this.extraData == null) { + this.extraData = new Int2IntOpenHashMap(); + } + this.extraData.put(key, value); + return this; + } + + public ChunkBuilder biomes(byte[] biomes) { + if (biomes == null) throw new NullPointerException(); + this.biomes = biomes; + return this; + } + + public ChunkBuilder biomes3d(PalettedBlockStorage[] biomes) { + if (biomes == null) throw new NullPointerException(); + this.biomes3d = biomes; + this.has3dBiomes = true; + return this; + } + + public ChunkBuilder heightMap(int[] heightMap) { + if (heightMap == null) throw new NullPointerException(); + this.heightMap = heightMap; + return this; + } + + public ChunkBuilder dataLoader(ChunkDataLoader chunkDataLoader) { + if (chunkDataLoader == null) throw new NullPointerException(); + this.chunkDataLoaders.add(chunkDataLoader); + return this; + } + + public ChunkBuilder state(int state) { + this.state = state; + return this; + } + + public ChunkBuilder dirty() { + this.dirty = true; + return this; + } + + public ChunkBuilder chunkVersion(int chunkVersion) { + this.chunkVersion = chunkVersion; + return this; + } + + public LevelDBChunk build() { + if (this.sections == null) throw new NullPointerException("sections"); + if (this.biomes == null && this.biomes3d == null) throw new NullPointerException("biomes"); + if (this.heightMap == null) throw new NullPointerException("sections"); + + LevelDBChunk chunk = new LevelDBChunk(this.provider, this.sections, this.extraData, this.biomes, this.heightMap); + chunk.setPosition(this.x, this.z); + chunk.setState(this.state); + if (this.has3dBiomes) { + chunk.setBiomes3d(this.biomes3d); + } + + this.chunkDataLoaders.forEach(loader -> loader.initChunk(chunk, this.provider)); + if (this.dirty) { + chunk.setChanged(); + } + return chunk; + } + + public LevelDBProvider getProvider() { + return this.provider; + } + + public boolean has3dBiomes() { + return this.has3dBiomes; + } + + public int getChunkVersion() { + return this.chunkVersion; + } + + public String debugString() { + return this.provider.getName() + "(x=" + this.x + ", z=" + this.z + ")"; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunk.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunk.java new file mode 100644 index 00000000000..7d3b2f2c306 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunk.java @@ -0,0 +1,481 @@ +package cn.nukkit.level.format.leveldb.structure; + +import cn.nukkit.block.Block; +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.entity.Entity; +import cn.nukkit.level.DimensionData; +import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.format.ChunkSection; +import cn.nukkit.level.format.LevelProvider; +import cn.nukkit.level.format.generic.BaseChunk; +import cn.nukkit.level.format.generic.EmptyChunkSection; +import cn.nukkit.level.util.BitArrayVersion; +import cn.nukkit.level.util.PalettedBlockStorage; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.ByteTag; +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.ListTag; +import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.BlockUpdateEntry; +import cn.nukkit.utils.Zlib; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import lombok.Getter; +import lombok.Setter; + +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class LevelDBChunk extends BaseChunk { + + @Getter + @Setter + private int state = ChunkBuilder.STATE_NEW; + + private PalettedBlockStorage[] biomes3d; + + private final Lock writeLock = new ReentrantLock(); + + private final DimensionData dimensionData; + + public LevelDBChunk(Class providerClass) { + this(null, new LevelDBChunkSection[0], null, null, null); + this.providerClass = providerClass; + } + + public LevelDBChunk(LevelProvider provider, LevelDBChunkSection[] sections) { + this(provider, sections, null, null, null); + } + + public LevelDBChunk(LevelProvider provider, LevelDBChunkSection[] sections, Int2IntMap extraData, byte[] biomes, int[] heightMap) { + this.provider = provider; + if (provider != null) { + this.providerClass = provider.getClass(); + } + + this.dimensionData = provider == null ? DimensionData.LEGACY_DIMENSION : provider.getLevel().getDimensionData(); + int offset = dimensionData.getSectionOffset(); + int lowestSection = dimensionData.getMinHeight() >> 4; + int highestSection = dimensionData.getMaxHeight() >> 4; + + this.sections = new ChunkSection[dimensionData.getHeight() >> 4]; + for (int y = lowestSection; y <= highestSection; y++) { + int index = y + offset; + if (index < sections.length && sections[index] != null) { + this.sections[index] = sections[index]; + } else { + this.sections[index] = new LevelDBChunkSection(y); + } + } + + if (extraData != null && !extraData.isEmpty()) { + this.extraData = extraData; + } + + if (biomes == null) { + int biomePalettes = this.provider == null ? this.sections.length : dimensionData.getHeight() >> 4; + this.biomes3d = new PalettedBlockStorage[biomePalettes]; + this.biomes = new byte[256]; + } else { + this.biomes = biomes; + } + + this.heightMap = new byte[256]; + if (heightMap == null || heightMap.length != 256) { + Arrays.fill(this.heightMap, (byte) 255); + } else { + for (int i = 0; i < heightMap.length; i++) { + this.heightMap[i] = (byte) heightMap[i]; + } + } + } + + public void setNbtBlockEntities(List blockEntities) { + this.NBTtiles = blockEntities; + } + + public void setNbtEntities(List entities) { + this.NBTentities = entities; + } + + public void setBiomes3d(PalettedBlockStorage[] biomes3d) { + this.biomes3d = biomes3d; + } + + public void setBiomes3d(int y, PalettedBlockStorage biomes3d) { + if (!this.has3dBiomes()) { + this.convertBiomesTo3d(this.biomes); + } + + int index = y + this.getSectionOffset(); + if (index >= this.biomes3d.length) { + index = 0; + } + + this.biomes3d[index] = biomes3d; + } + + @Override + public void setBiomeIdArray(byte[] biomeIdArray) { + super.setBiomeIdArray(biomeIdArray); + if (this.has3dBiomes()) { + this.convertBiomesTo3d(biomeIdArray); + } + } + + @Override + public boolean has3dBiomes() { + return this.biomes3d != null && this.biomes3d.length > 0; + } + + @Override + public PalettedBlockStorage getBiomeStorage(int y) { + if (!this.has3dBiomes()) { + throw new IllegalStateException("Chunk does not have 3D biomes"); + } + + int index = y + this.getSectionOffset(); + if (index >= this.biomes3d.length) { + index = 0; // TODO: log + } + + if (this.biomes3d[index] == null) { + for (int i = index; i >= 0 ; i--) { + if (this.biomes3d[i] != null) { + this.biomes3d[index] = this.biomes3d[i].copy(); + break; + } + } + + if (this.biomes3d[index] == null) { + this.biomes3d[index] = PalettedBlockStorage.createWithDefaultState(BitArrayVersion.V0, 0); + } + } + return this.biomes3d[index]; + } + + private void convertBiomesTo3d(byte[] biomeIdArray) { + PalettedBlockStorage biomesStorage = PalettedBlockStorage.createWithDefaultState(BitArrayVersion.V0, Biome.getBiomeIdOrCorrect(biomeIdArray[0] & 0xFF)); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int biomeId = biomeIdArray[(x << 4) | z] & 0xFF; + for (int y = 0; y < 16; y++) { + biomesStorage.setBlock(x, y, z, biomeId); + } + } + } + + int biomePalettes = this.provider == null ? this.sections.length : provider.getLevel().getDimensionData().getHeight() >> 4; + PalettedBlockStorage[] biomes = new PalettedBlockStorage[biomePalettes]; + + for (int i = 0; i < biomePalettes; i++) { + biomes[i] = biomesStorage.copy(); + } + this.biomes3d = biomes; + this.setChanged(); + } + + @Override + public int getSectionOffset() { + return this.dimensionData.getSectionOffset(); + } + + @Override + public boolean isGenerated() { + return this.getState() >= ChunkBuilder.STATE_GENERATED; + } + + @Override + public void setGenerated() { + this.setGenerated(true); + } + + @Override + public void setGenerated(boolean value) { + if (this.isGenerated() == value) { + return; + } + this.setChanged(); + + if (value) { + this.setState(Math.max(this.getState(), ChunkBuilder.STATE_GENERATED)); + } else { + this.setState(ChunkBuilder.STATE_NEW); + } + } + + @Override + public boolean isPopulated() { + return this.state >= ChunkBuilder.STATE_POPULATED; + } + + @Override + public void setPopulated() { + this.setPopulated(true); + } + + @Override + public void setPopulated(boolean value) { + if (this.isPopulated() == value) { + return; + } + this.setChanged(); + + if (value) { + this.setState(Math.max(this.getState(), ChunkBuilder.STATE_POPULATED)); + } else { + this.setState(Math.max(this.getState(), ChunkBuilder.STATE_NEW)); + } + } + + @Override + public int getBlockSkyLight(int x, int y, int z) { + ChunkSection section0 = this.getSection(y >> 4); + if (!(section0 instanceof LevelDBChunkSection)) { + return section0.getBlockSkyLight(x, y & 0x0f, z); + } + + LevelDBChunkSection section = (LevelDBChunkSection) section0; + if (section.skyLight != null) { + return section.getBlockSkyLight(x, y & 0x0f, z); + } else if (!section.hasSkyLight) { + return 0; + } else { + int height = this.getHighestBlockAt(x, z); + if (height < y) { + return 15; + } else if (height == y) { + return Block.isBlockTransparentById(this.getBlockId(x, y, z)) ? 15 : 0; + } else { + return section.getBlockSkyLight(x, y & 0x0f, z); + } + } + } + + @Override + public int getBlockLight(int x, int y, int z) { + ChunkSection section0 = this.getSection(y >> 4); + if (!(section0 instanceof LevelDBChunkSection)) { + return section0.getBlockLight(x, y & 0x0f, z); + } + + LevelDBChunkSection section = (LevelDBChunkSection) section0; + if (section.blockLight != null) { + return section.getBlockLight(x, y & 0x0f, z); + } else if (!section.hasBlockLight) { + return 0; + } else { + return section.getBlockLight(x, y & 0x0f, z); + } + } + + @Override + public boolean compress() { + boolean result = super.compress(); + for (ChunkSection section : this.getSections()) { + if (section instanceof LevelDBChunkSection && !section.isEmpty()) { + result |= ((LevelDBChunkSection) section).compress(); + } + } + return result; + } + + @Override + @Deprecated + public byte[] toFastBinary() { + CompoundTag chunk = chunkNBT(); + + try { + return NBTIO.write(chunk, ByteOrder.BIG_ENDIAN); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + @Deprecated + public byte[] toBinary() { + CompoundTag chunk = chunkNBT(); + + try { + return Zlib.deflate(NBTIO.write(chunk, ByteOrder.BIG_ENDIAN), 7); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private CompoundTag chunkNBT() { + CompoundTag nbt = this.getNBT().copy(); + nbt.remove("BiomeColors"); + + nbt.putInt("xPos", this.getX()); + nbt.putInt("zPos", this.getZ()); + + ListTag sectionList = new ListTag<>("Sections"); + for (ChunkSection section : this.getSections()) { + if (section instanceof EmptyChunkSection) { + continue; + } + CompoundTag s = new CompoundTag(); + s.putByte("Y", (section.getY())); + s.putByteArray("Blocks", section.getIdArray()); + s.putByteArray("Data", section.getDataArray()); + s.putByteArray("BlockLight", section.getLightArray()); + s.putByteArray("SkyLight", section.getSkyLightArray()); + sectionList.add(s); + } + nbt.putList(sectionList); + + nbt.putByteArray("Biomes", this.getBiomeIdArray()); + + int[] heightInts = new int[256]; + byte[] heightBytes = this.getHeightMapArray(); + for (int i = 0; i < heightInts.length; i++) { + heightInts[i] = heightBytes[i] & 0xFF; + } + nbt.putIntArray("HeightMap", heightInts); + + ArrayList entities = new ArrayList<>(); + for (Entity entity : this.getEntities().values()) { + if (entity.canSaveToStorage() && !entity.closed) { + entity.saveNBT(); + entities.add(entity.namedTag); + } + } + + ListTag entityListTag = new ListTag<>("Entities"); + entityListTag.setAll(entities); + nbt.putList(entityListTag); + + ArrayList tiles = new ArrayList<>(); + for (BlockEntity blockEntity : this.getBlockEntities().values()) { + if (blockEntity.canSaveToStorage()) { + blockEntity.saveNBT(); + tiles.add(blockEntity.namedTag); + } + } + ListTag tileListTag = new ListTag<>("TileEntities"); + tileListTag.setAll(tiles); + nbt.putList(tileListTag); + + Set entries = this.provider.getLevel().getPendingBlockUpdates(this); + if (entries != null) { + ListTag tileTickTag = new ListTag<>("TileTicks"); + long totalTime = this.provider.getLevel().getCurrentTick(); + + for (BlockUpdateEntry entry : entries) { + CompoundTag entryNBT = new CompoundTag() + .putString("i", entry.block.getSaveId()) + .putInt("x", entry.pos.getFloorX()) + .putInt("y", entry.pos.getFloorY()) + .putInt("z", entry.pos.getFloorZ()) + .putInt("t", (int) (entry.delay - totalTime)) + .putInt("p", entry.priority); + tileTickTag.add(entryNBT); + } + + nbt.putList(tileTickTag); + } + + BinaryStream extraData = new BinaryStream(); + Map extraDataArray = this.getBlockExtraDataArray(); + extraData.putInt(extraDataArray.size()); + for (Map.Entry entry : extraDataArray.entrySet()) { + extraData.putInt(entry.getKey()); + extraData.putShort(entry.getValue()); + } + nbt.putByteArray("ExtraData", extraData.getBuffer()); + + CompoundTag chunk = new CompoundTag(""); + chunk.putCompound("Level", nbt); + return chunk; + } + + private CompoundTag getNBT() { + CompoundTag tag = new CompoundTag(); + tag.put("LightPopulated", new ByteTag("LightPopulated", (byte) (this.isLightPopulated() ? 1 : 0))); + tag.put("V", new ByteTag("V", (byte) 1)); + tag.put("TerrainGenerated", new ByteTag("TerrainGenerated", (byte) (this.isGenerated() ? 1 : 0))); + tag.put("TerrainPopulated", new ByteTag("TerrainPopulated", (byte) (this.isPopulated() ? 1 : 0))); + return tag; + } + + @Override + public LevelDBChunk clone() { + LevelDBChunk chunk = (LevelDBChunk) super.clone(); + if (this.has3dBiomes()) { + PalettedBlockStorage[] biomes = new PalettedBlockStorage[this.biomes3d.length]; + for (int i = 0; i < this.biomes3d.length; i++) { + biomes[i] = this.biomes3d[i].copy(); + } + chunk.setBiomes3d(biomes); + } + return chunk; + } + + @Override + public LevelDBChunk cloneForChunkSending() { + LevelDBChunk chunk = (LevelDBChunk) super.cloneForChunkSending(); + if (this.has3dBiomes()) { + PalettedBlockStorage[] biomes = new PalettedBlockStorage[this.biomes3d.length]; + for (int i = 0; i < this.biomes3d.length; i++) { + PalettedBlockStorage storage = this.biomes3d[i]; + if (storage != null) { + biomes[i] = storage.copy(); + } + } + chunk.setBiomes3d(biomes); + } + return chunk; + } + + @Override + public int getBiomeId(int x, int z) { + return this.getBiomeId(x, 0, z); + } + + @Override + public int getBiomeId(int x, int y, int z) { + if (this.has3dBiomes()) { + return this.getBiomeStorage(y >> 4).getBlock(x, y & 0x0f, z); + } else { + return super.getBiomeId(x, z); + } + } + + @Override + public void setBiomeId(int x, int z, byte biomeId) { + this.setBiomeId(x, z, (int) biomeId); + } + + @Override + public void setBiomeId(int x, int y, int z, byte biomeId) { + this.setBiomeId(x, y, z, ((int) biomeId) & 0xff); + } + + @Override + public void setBiomeId(int x, int z, int biomeId) { + for (int i = 0; i < this.sections.length; i++) { + int y = (i - this.getSectionOffset()) << 4; + for (int yy = 0; yy < 16; yy++) { + this.setBiomeId(x, y + yy, z, biomeId); + } + } + } + + @Override + public void setBiomeId(int x, int y, int z, int biomeId) { + if (!this.has3dBiomes()) { + this.convertBiomesTo3d(this.biomes); + } + + this.getBiomeStorage(y >> 4).setBlock(x, y & 0x0f, z, biomeId); + this.setChanged(); + } + + public Lock writeLock() { + return this.writeLock; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunkSection.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunkSection.java new file mode 100644 index 00000000000..01e338118b7 --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/LevelDBChunkSection.java @@ -0,0 +1,357 @@ +package cn.nukkit.level.format.leveldb.structure; + +import cn.nukkit.Server; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; +import cn.nukkit.level.format.ChunkSection; +import cn.nukkit.level.format.generic.EmptyChunkSection; +import cn.nukkit.utils.Binary; +import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.Utils; +import cn.nukkit.utils.Zlib; +import lombok.Getter; +import lombok.Setter; + +import java.io.IOException; +import java.util.Arrays; + +public class LevelDBChunkSection implements ChunkSection { + + @Getter + private final int y; + @Getter + private final StateBlockStorage[] storages; + + protected byte[] blockLight; + protected byte[] skyLight; + private byte[] compressedLight; + protected boolean hasBlockLight; + @Setter + protected boolean hasSkyLight; + + public LevelDBChunkSection(int y, StateBlockStorage[] storages, byte[] blockLight, byte[] skyLight, byte[] compressedLight, boolean hasBlockLight, boolean hasSkyLight) { + this.y = y; + this.storages = storages; + this.blockLight = blockLight; + this.skyLight = skyLight; + this.compressedLight = compressedLight; + this.hasBlockLight = hasBlockLight; + this.hasSkyLight = hasSkyLight; + } + + public LevelDBChunkSection(int y) { + this(y, new StateBlockStorage[]{new StateBlockStorage(), new StateBlockStorage()}); + } + + public LevelDBChunkSection(int y, StateBlockStorage[] storages) { + this.y = y; + this.storages = storages; + this.hasBlockLight = false; + this.hasSkyLight = false; + } + + private StateBlockStorage computeStorage(BlockLayer layer) { + if (this.storages.length <= layer.ordinal()) { + throw new IllegalArgumentException("Tried to get layer " + layer + " but LevelDBChunkSection has only " + this.storages.length); + } + + StateBlockStorage storage = this.storages[layer.ordinal()]; + if (storage == null) { + return this.storages[layer.ordinal()] = new StateBlockStorage(); + } + return storage; + } + + @Override + public int getBlockId(int x, int y, int z, BlockLayer layer) { + synchronized (this.storages) { + return this.computeStorage(layer).getBlockId(x, y, z); + } + } + + @Override + public void setBlockId(int x, int y, int z, BlockLayer layer, int id) { + synchronized (this.storages) { + this.computeStorage(layer).setBlockId(x, y, z, id); + if (layer != Block.LAYER_WATERLOGGED && id == 0 && this.hasSecondLayer()) { + this.computeStorage(Block.LAYER_WATERLOGGED).setBlockId(x, y, z, 0); + } + } + } + + @Override + public boolean setFullBlockId(int x, int y, int z, BlockLayer layer, int fullId) { + synchronized (this.storages) { + this.computeStorage(layer).setFullBlock(x, y, z, fullId); + if (layer != Block.LAYER_WATERLOGGED && fullId == 0 && this.hasSecondLayer()) { + this.computeStorage(Block.LAYER_WATERLOGGED).setFullBlock(x, y, z, 0); + } + } + return true; + } + + @Override + public int getBlockData(int x, int y, int z, BlockLayer layer) { + synchronized (this.storages) { + return this.computeStorage(layer).getBlockData(x, y, z); + } + } + + @Override + public void setBlockData(int x, int y, int z, BlockLayer layer, int data) { + synchronized (this.storages) { + this.computeStorage(layer).setBlockData(x, y, z, data); + } + } + + @Override + public int getFullBlock(int x, int y, int z, BlockLayer layer) { + synchronized (this.storages) { + return this.computeStorage(layer).getFullBlock(x, y, z); + } + } + + public Block getAndSetBlock(int x, int y, int z, BlockLayer layer, Block block) { + synchronized (this.storages) { + int fullId = this.computeStorage(layer).getAndSetFullBlock(x, y, z, block.getFullId()); + if (layer != Block.LAYER_WATERLOGGED && block.getId() == 0 && fullId != block.getFullId() && this.hasSecondLayer()) { + this.computeStorage(Block.LAYER_WATERLOGGED).setFullBlock(x, y, z, 0); + } + return Block.getUnsafe(fullId).clone(); + } + } + + @Override + public boolean setBlock(int x, int y, int z, int blockId) { + return this.setBlock(x, y, z, blockId, 0); + } + + @Override + public boolean setBlock(int x, int y, int z, int blockId, int meta) { + return this.setBlockAtLayer(x, y, z, Block.LAYER_NORMAL, blockId, meta); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId) { + return this.setBlockAtLayer(x, y, z, layer, blockId, 0); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int blockId, int meta) { + synchronized (this.storages) { + int fullId = (blockId << Block.DATA_BITS) | meta; + boolean success = this.computeStorage(layer).getAndSetFullBlock(x, y, z, fullId) != fullId; + if (layer != Block.LAYER_WATERLOGGED && blockId == 0 && this.hasSecondLayer()) { + this.computeStorage(Block.LAYER_WATERLOGGED).setFullBlock(x, y, z, 0); + } + return success; + } + } + + @Override + public int getBlockSkyLight(int x, int y, int z) { + if (this.skyLight == null) { + if (!hasSkyLight) { + return 0; + } else if (compressedLight == null) { + return 15; + } + } + this.skyLight = getSkyLightArray(); + int sl = this.skyLight[(y << 7) | (z << 3) | (x >> 1)] & 0xff; + if ((x & 1) == 0) { + return sl & 0x0f; + } + return sl >> 4; + } + + @Override + public void setBlockSkyLight(int x, int y, int z, int level) { + if (this.skyLight == null) { + if (hasSkyLight && compressedLight != null) { + this.skyLight = getSkyLightArray(); + } else if (level == (hasSkyLight ? 15 : 0)) { + return; + } else { + this.skyLight = new byte[2048]; + if (hasSkyLight) { + Arrays.fill(this.skyLight, (byte) 0xFF); + } + } + } + int i = (y << 7) | (z << 3) | (x >> 1); + int old = this.skyLight[i] & 0xff; + if ((x & 1) == 0) { + this.skyLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); + } else { + this.skyLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); + } + } + + @Override + public int getBlockLight(int x, int y, int z) { + if (blockLight == null && !hasBlockLight) return 0; + this.blockLight = getLightArray(); + int l = blockLight[(y << 7) | (z << 3) | (x >> 1)] & 0xff; + if ((x & 1) == 0) { + return l & 0x0f; + } + return l >> 4; + } + + @Override + public void setBlockLight(int x, int y, int z, int level) { + if (this.blockLight == null) { + if (hasBlockLight) { + this.blockLight = getLightArray(); + } else if (level == 0) { + return; + } else { + this.blockLight = new byte[2048]; + } + } + int i = (y << 7) | (z << 3) | (x >> 1); + int old = this.blockLight[i] & 0xff; + if ((x & 1) == 0) { + this.blockLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); + } else { + this.blockLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); + } + } + + @Override + public byte[] getIdArray() { + // We don't support old byte format + return new byte[0]; + } + + @Override + public byte[] getDataArray() { + // We don't support old byte format + return new byte[0]; + } + + @Override + public void writeTo(BinaryStream stream) { + synchronized (this.storages) { + boolean waterLogging = this.hasSecondLayer(); + stream.putByte((byte) 8); // Paletted chunk because Mojang messed up the old one + stream.putByte((byte) (waterLogging ? 2 : 1)); + this.storages[0].writeTo(stream); + if (waterLogging) { + this.storages[1].writeTo(stream); + } + } + } + + private boolean hasSecondLayer() { + return this.storages.length > 1 && this.storages[1] != null; + } + + @Override + public byte[] getSkyLightArray() { + if (this.skyLight != null) return this.skyLight; + if (this.hasSkyLight) { + if (this.compressedLight != null) { + this.inflate(); + if (this.skyLight != null) return this.skyLight; + } + } + return EmptyChunkSection.EMPTY_SKY_LIGHT_ARR; + } + + private void inflate() { + try { + if (compressedLight != null && compressedLight.length != 0) { + byte[] inflated = Zlib.inflate(compressedLight); + blockLight = Arrays.copyOfRange(inflated, 0, 2048); + if (inflated.length > 2048) { + skyLight = Arrays.copyOfRange(inflated, 2048, 4096); + } else { + skyLight = new byte[2048]; + if (hasSkyLight) { + Arrays.fill(skyLight, (byte) 0xFF); + } + } + compressedLight = null; + } else { + blockLight = new byte[2048]; + skyLight = new byte[2048]; + if (hasSkyLight) { + Arrays.fill(skyLight, (byte) 0xFF); + } + } + } catch (IOException e) { + Server.getInstance().getLogger().logException(e); + } + } + + @Override + public byte[] getLightArray() { + if (this.blockLight != null) return this.blockLight; + if (this.hasBlockLight) { + this.inflate(); + if (this.blockLight != null) return this.blockLight; + } + return EmptyChunkSection.EMPTY_LIGHT_ARR; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public ChunkSection copy() { + StateBlockStorage[] storages = new StateBlockStorage[this.storages.length]; + for (int i = 0; i < this.storages.length; i++) { + if (this.storages[i] != null) { + storages[i] = this.storages[i].copy(); + } + } + + return new LevelDBChunkSection( + this.y, + storages, + this.blockLight == null ? null : this.blockLight.clone(), + this.skyLight == null ? null : this.skyLight.clone(), + this.compressedLight == null ? null : this.compressedLight.clone(), + this.hasBlockLight, + this.hasSkyLight + ); + } + + public boolean compress() { + if (blockLight != null) { + byte[] arr1 = blockLight; + hasBlockLight = !Utils.isByteArrayEmpty(arr1); + byte[] arr2; + if (skyLight != null) { + arr2 = skyLight; + hasSkyLight = !Utils.isByteArrayEmpty(arr2); + } else if (hasSkyLight) { + arr2 = EmptyChunkSection.EMPTY_SKY_LIGHT_ARR; + } else { + arr2 = EmptyChunkSection.EMPTY_LIGHT_ARR; + hasSkyLight = false; + } + blockLight = null; + skyLight = null; + byte[] toDeflate = null; + if (hasBlockLight && hasSkyLight && arr2 != EmptyChunkSection.EMPTY_SKY_LIGHT_ARR) { + toDeflate = Binary.appendBytes(arr1, arr2); + } else if (hasBlockLight) { + toDeflate = arr1; + } + if (toDeflate != null) { + try { + compressedLight = Zlib.deflate(toDeflate, 1); + } catch (Exception e) { + Server.getInstance().getLogger().logException(e); + } + } + return true; + } + return false; + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java new file mode 100644 index 00000000000..8f36f10322a --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java @@ -0,0 +1,277 @@ +package cn.nukkit.level.format.leveldb.structure; + +import cn.nukkit.Nukkit; +import cn.nukkit.block.Block; +import cn.nukkit.level.GlobalBlockPalette; +import cn.nukkit.level.format.leveldb.BlockStateMapping; +import cn.nukkit.level.util.BitArray; +import cn.nukkit.level.util.BitArrayVersion; +import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.MainLogger; +import com.nukkitx.nbt.NBTInputStream; +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.List; +import java.util.function.Function; + +public class StateBlockStorage { + private static final Logger log = LogManager.getLogger("LevelDB-Logger"); + + private static final int SECTION_SIZE = 4096; + + private final List palette; + private BitArray bitArray; + + public StateBlockStorage() { + this(BitArrayVersion.V2); + } + + public StateBlockStorage(BitArrayVersion version) { + this.bitArray = version.createPalette(); + this.palette = new ObjectArrayList<>(16); + // Air is at the beginning of each palette + this.palette.add(BlockStateMapping.get().getState(0, 0)); + } + + public StateBlockStorage(BitArray bitArray, List palette) { + this.palette = palette; + this.bitArray = bitArray; + } + + private int getPaletteHeader(BitArrayVersion version, boolean runtime) { + return (version.getId() << 1) | (runtime ? 1 : 0); + } + + private static BitArrayVersion getVersionFromHeader(byte header) { + return BitArrayVersion.get(header >> 1, true); + } + + public void writeToStorage(ByteBuf buffer) { + int paletteSize = this.palette.size(); + BitArrayVersion version = paletteSize <= 1 ? BitArrayVersion.V0 : this.bitArray.getVersion(); + buffer.writeByte(getPaletteHeader(version, false)); + + if (version != BitArrayVersion.V0) { + for (int word : this.bitArray.getWords()) { + buffer.writeIntLE(word); + } + buffer.writeIntLE(paletteSize); + } + + try (ByteBufOutputStream stream = new ByteBufOutputStream(buffer); + NBTOutputStream nbtOutputStream = NbtUtils.createWriterLE(stream)) { + for (BlockStateSnapshot state : this.palette) { + nbtOutputStream.writeTag(state.getVanillaState()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void readFromStorage(ByteBuf buffer, ChunkBuilder chunkBuilder) { + BitArrayVersion version = getVersionFromHeader(buffer.readByte()); + this.palette.clear(); + int paletteSize = 1; + + if (version == BitArrayVersion.V0) { + this.bitArray = version.createPalette(SECTION_SIZE, null); + } else { + int expectedWordCount = version.getWordsForSize(SECTION_SIZE); + int[] words = new int[expectedWordCount]; + for (int i = 0; i < expectedWordCount; i++) { + words[i] = buffer.readIntLE(); + } + paletteSize = buffer.readIntLE(); + this.bitArray = version.createPalette(SECTION_SIZE, words); + } + + if (version.getMaxEntryValue() < paletteSize - 1) { + throw new IllegalArgumentException( + chunkBuilder.debugString() + " Palette (version " + version.name() + ") is too large. Max size " + version.getMaxEntryValue() + ". Actual size " + paletteSize + ); + } + + try (ByteBufInputStream stream = new ByteBufInputStream(buffer); + NBTInputStream nbtInputStream = NbtUtils.createReaderLE(stream)) { + for (int i = 0; i < paletteSize; i++) { + try { + NbtMap state = (NbtMap) nbtInputStream.readTag(); + state.hashCode(); // cache hashCode + + BlockStateSnapshot blockState = BlockStateMapping.get().getStateUnsafe(state); + if (blockState == null) { + NbtMap updatedState = BlockStateMapping.get().updateVanillaState(state); + blockState = BlockStateMapping.get().getUpdatedOrCustom(state, updatedState); + if (!blockState.isCustom()) { + if (Nukkit.DEBUG > 1) log.info("[{}] Updated unmapped block state: {} => {}", chunkBuilder.debugString(), state, blockState.getVanillaState()); + chunkBuilder.dirty(); + } + + if (Nukkit.DEBUG > 1 && blockState.getRuntimeId() == BlockStateMapping.get().getDefaultRuntimeId()) { + log.info("[{}] Chunk contains unknown block {} => {}", chunkBuilder.debugString(), state, updatedState); + } + } + + if (Nukkit.DEBUG > 1 && this.palette.contains(blockState)) { + log.info("[{}] Palette contains block state twice: {}", chunkBuilder.debugString(), state); + } + this.palette.add(blockState); + } catch (Exception e) { + MainLogger.getLogger().error("[" + chunkBuilder.debugString() + "] Unable to deserialize chunk block state", e); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void setBlockState(int index, NbtMap state) { + BlockStateSnapshot blockState = BlockStateMapping.get().getStateUnsafe(state); + if (blockState == null) { + blockState = BlockStateMapping.get().updateStateUnsafe(state); + } + this.setBlockStateUnsafe(index, blockState); + } + + public void setBlockStateUnsafe(int index, BlockStateSnapshot state) { + try { + int id = this.idFor(state); + this.bitArray.set(index, id); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unable to set block state: " + state + ", palette: " + palette, e); + } + } + + public BlockStateSnapshot getBlockState(int x, int y, int z) { + int index = ChunkBuilder.getSectionIndex(x, y, z); + return this.stateFor(this.bitArray.get(index)); + } + + public BlockStateSnapshot getBlockState(int index) { + return this.stateFor(this.bitArray.get(index)); + } + + public int getBlockData(int x, int y, int z) { + return this.getBlockState(x, y, z).getLegacyData(); + } + + public int getBlockId(int x, int y, int z) { + return this.getBlockState(x, y, z).getLegacyId(); + } + + public void setBlockId(int x, int y, int z, int id) { + int index = ChunkBuilder.getSectionIndex(x, y, z); + this.setBlockStateUnsafe(index, BlockStateMapping.get().getState(id, 0)); + } + + public void setBlockData(int x, int y, int z, int data) { + int index = ChunkBuilder.getSectionIndex(x, y, z); + this.setBlockStateUnsafe(index, BlockStateMapping.get().getState(this.getBlockId(x, y, z), data)); + } + + public int getFullBlock(int x, int y, int z) { + return getFullBlock(ChunkBuilder.getSectionIndex(x, y, z)); + } + + public void setFullBlock(int x, int y, int z, int value) { + this.setFullBlock(ChunkBuilder.getSectionIndex(x, y, z), value); + } + + public int getAndSetFullBlock(int x, int y, int z, int value) { + return getAndSetFullBlock(ChunkBuilder.getSectionIndex(x, y, z), value); + } + + private int getAndSetFullBlock(int index, int value) { + BlockStateSnapshot state = this.getBlockState(index); + int newBlock = value >> Block.DATA_BITS; + int newData = value & Block.DATA_MASK; + this.setBlockStateUnsafe(index, BlockStateMapping.get().getState(newBlock, newData)); + return (state.getLegacyId() << Block.DATA_BITS) | state.getLegacyData(); + } + + private int getFullBlock(int index) { + BlockStateSnapshot state = this.getBlockState(index); + return (state.getLegacyId() << Block.DATA_BITS) | state.getLegacyData(); + } + + public void setFullBlock(int index, int value) { + int block = value >> Block.DATA_BITS; + int data = value & Block.DATA_MASK; + this.setBlockStateUnsafe(index, BlockStateMapping.get().getState(block, data)); + } + + public void writeTo(BinaryStream stream) { + this.writeTo(stream, state -> GlobalBlockPalette.getOrCreateRuntimeId(state.getLegacyId(), state.getLegacyData())); + } + + private void writeTo(BinaryStream stream, Function mapping) { + BitArray bitArray = this.bitArray; + + stream.putByte((byte) this.getPaletteHeader(bitArray.getVersion(), true)); + if (bitArray.getVersion() != BitArrayVersion.V0) { + for (int word : bitArray.getWords()) { + stream.putLInt(word); + } + stream.putVarInt(this.palette.size()); + } + + for (BlockStateSnapshot state : this.palette) { + stream.putVarInt(mapping.apply(state)); + } + } + + private void onResize(BitArrayVersion version) { + BitArray newBitArray = version.createPalette(); + for (int i = 0; i < SECTION_SIZE; i++) { + newBitArray.set(i, this.bitArray.get(i)); + } + this.bitArray = newBitArray; + } + + private int idFor(BlockStateSnapshot state) { + int index = this.palette.indexOf(state); + if (index != -1) { + return index; + } + + index = this.palette.size(); + BitArrayVersion version = this.bitArray.getVersion(); + if (index > version.getMaxEntryValue()) { + BitArrayVersion next = version.next(); + if (next != null) { + this.onResize(next); + } + } + this.palette.add(state); + return index; + } + + private BlockStateSnapshot stateFor(int index) { + return this.palette.get(index); + } + + public boolean isEmpty() { + if (this.palette.size() == 1) { + return true; + } + for (int word : this.bitArray.getWords()) { + if (Integer.toUnsignedLong(word) != 0L) { + return false; + } + } + return true; + } + + public StateBlockStorage copy() { + return new StateBlockStorage(this.bitArray.copy(), new ObjectArrayList<>(this.palette)); + } +} diff --git a/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterChunker.java b/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterChunker.java new file mode 100644 index 00000000000..26a331f69ff --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterChunker.java @@ -0,0 +1,86 @@ +package cn.nukkit.level.format.leveldb.updater; + +import org.cloudburstmc.blockstateupdater.BlockStateUpdater; +import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BlockStateUpdaterChunker implements BlockStateUpdater { + + // This is updater for worlds converted using chunker.app + public static final BlockStateUpdater INSTANCE = new BlockStateUpdaterChunker(); + + @Override + public void registerUpdaters(CompoundTagUpdaterContext ctx) { + this.addProperty(ctx, "minecraft:anvil", "damage", "undamaged"); + this.addProperty(ctx, "minecraft:azalea_leaves", "update_bit", (byte) 0); + this.addProperty(ctx, "minecraft:azalea_leaves_flowered", "update_bit", (byte) 0); + this.addProperty(ctx, "minecraft:bamboo_sapling", "age_bit", (byte) 0); + this.addProperty(ctx, "minecraft:bedrock", "infiniburn_bit", (byte) 0); + this.addProperty(ctx, "minecraft:big_dripleaf", "big_dripleaf_tilt", "none"); + this.addProperty(ctx, "minecraft:blackstone_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:cauldron", "cauldron_liquid", "water"); + this.addProperty(ctx, "minecraft:cobbled_deepslate_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:coral", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:coral_block", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:coral_fan", "coral_fan_direction", (int) 0); + this.addProperty(ctx, "minecraft:coral_fan_dead", "coral_fan_direction", (int) 0); + this.addProperty(ctx, "minecraft:coral_fan_hang", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:coral_fan_hang2", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:coral_fan_hang3", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:crimson_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:double_wooden_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:deepslate_brick_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:deepslate_tile_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:dirt", "dirt_type", "normal"); + this.addProperty(ctx, "minecraft:double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:double_wooden_slab", "wood_type", "oak"); + this.addProperty(ctx, "minecraft:double_stone_block_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:double_stone_block_slab2", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:double_stone_block_slab3", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:double_stone_block_slab4", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:exposed_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:flower_pot", "update_bit", (byte) 0); + this.addProperty(ctx, "minecraft:frame", "item_frame_photo_bit", (byte) 0); + this.addProperty(ctx, "minecraft:frame", "item_frame_map_bit", (byte) 0); + this.addProperty(ctx, "minecraft:kelp", "kelp_age", (int) 0); + this.addProperty(ctx, "minecraft:lava_cauldron", "cauldron_liquid", "lava"); + this.addProperty(ctx, "minecraft:leaves", "update_bit", (byte) 0); + this.addProperty(ctx, "minecraft:leaves2", "update_bit", (byte) 0); + this.addProperty(ctx, "minecraft:oxidized_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:polished_blackstone_brick_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:polished_blackstone_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:polished_deepslate_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:prismarine", "prismarine_block_type", "default"); + this.addProperty(ctx, "minecraft:purpur_block", "chisel_type", "default"); + this.addProperty(ctx, "minecraft:red_sandstone", "sand_stone_type", "default"); + this.addProperty(ctx, "minecraft:sand", "sand_type", "normal"); + this.addProperty(ctx, "minecraft:sandstone", "sand_stone_type", "default"); + this.addProperty(ctx, "minecraft:sea_pickle", "dead_bit", (byte) 0); + this.addProperty(ctx, "minecraft:soul_fire", "age", (int) 0); + this.addProperty(ctx, "minecraft:sponge", "sponge_type", "dry"); + this.addProperty(ctx, "minecraft:structure_void", "structure_void_type", "void"); + this.addProperty(ctx, "minecraft:tnt", "allow_underwater_bit", (byte) 0); + this.addProperty(ctx, "minecraft:trip_wire", "suspended_bit", (byte) 0); + this.addProperty(ctx, "minecraft:warped_double_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:water", "liquid_depth", (int) 0); + this.addProperty(ctx, "minecraft:waxed_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:waxed_exposed_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:waxed_oxidized_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:waxed_weathered_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:weathered_double_cut_copper_slab", "top_slot_bit", (byte) 0); + this.addProperty(ctx, "minecraft:wooden_slab", "wood_type", "oak"); + this.addProperty(ctx, "minecraft:quartz_block", "chisel_type", "default"); + this.addProperty(ctx, "minecraft:quartz_block", "pillar_axis", "y"); + } + + private void addProperty(CompoundTagUpdaterContext ctx, String identifier, String propertyName, Object value) { + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", identifier) + .visit("states") + .tryAdd(propertyName, value); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterVanilla.java b/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterVanilla.java new file mode 100644 index 00000000000..6b2d2512b2b --- /dev/null +++ b/src/main/java/cn/nukkit/level/format/leveldb/updater/BlockStateUpdaterVanilla.java @@ -0,0 +1,55 @@ +package cn.nukkit.level.format.leveldb.updater; + +import org.cloudburstmc.blockstateupdater.BlockStateUpdater; +import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class BlockStateUpdaterVanilla implements BlockStateUpdater { + + public static final BlockStateUpdater INSTANCE = new BlockStateUpdaterVanilla(); + + @Override + public void registerUpdaters(CompoundTagUpdaterContext ctx) { + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", "minecraft:water") + .visit("states") + .tryAdd("liquid_depth", (int) 0); + + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", "minecraft:polished_blackstone_double_slab") + .visit("states") + .tryAdd("top_slot_bit", (byte) 0); + + this.replaceState(ctx, "minecraft:wood", "pillar_axis", "y"); + this.removeConnections(ctx, "minecraft:cobblestone_wall"); + } + + private void replaceState(CompoundTagUpdaterContext ctx, String identifier, String propertyName, Object value) { + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", identifier) + .visit("states") + .edit(propertyName, helper -> helper.replaceWith(propertyName, value)); + } + + private void removeConnections(CompoundTagUpdaterContext ctx, String identifier) { + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", identifier) + .visit("states") + .edit("wall_connection_type_east", helper -> helper.replaceWith("wall_connection_type_east", "none")) + .edit("wall_connection_type_north", helper -> helper.replaceWith("wall_connection_type_north", "none")) + .edit("wall_connection_type_south", helper -> helper.replaceWith("wall_connection_type_south", "none")) + .edit("wall_connection_type_west", helper -> helper.replaceWith("wall_connection_type_west", "none")) + .edit("wall_post_bit", helper -> helper.replaceWith("wall_post_bit", (byte) 0)); + } + + private void addProperty(CompoundTagUpdaterContext ctx, String identifier, String propertyName, Object value) { + ctx.addUpdater(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION, true) + .match("name", identifier) + .visit("states") + .tryAdd(propertyName, value); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/mcregion/Chunk.java b/src/main/java/cn/nukkit/level/format/mcregion/Chunk.java deleted file mode 100644 index b738ee83b12..00000000000 --- a/src/main/java/cn/nukkit/level/format/mcregion/Chunk.java +++ /dev/null @@ -1,538 +0,0 @@ -package cn.nukkit.level.format.mcregion; - -import cn.nukkit.Player; -import cn.nukkit.block.Block; -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.entity.Entity; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.anvil.palette.BiomePalette; -import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.ByteArrayTag; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.nbt.tag.IntArrayTag; -import cn.nukkit.nbt.tag.ListTag; -import cn.nukkit.utils.Binary; -import cn.nukkit.utils.BinaryStream; -import cn.nukkit.utils.Zlib; - -import java.io.ByteArrayInputStream; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class Chunk extends BaseFullChunk { - - private CompoundTag nbt; - - public Chunk(LevelProvider level) { - this(level, null); - } - - public Chunk(Class providerClass) { - this((LevelProvider) null, null); - this.providerClass = providerClass; - } - - public Chunk(Class providerClass, CompoundTag nbt) { - this((LevelProvider) null, nbt); - this.providerClass = providerClass; - } - - public Chunk(LevelProvider level, CompoundTag nbt) { - this.provider = level; - if (level != null) { - this.providerClass = level.getClass(); - } - - if (nbt == null) { - return; - } - - this.nbt = nbt; - - if (!(this.nbt.contains("Entities") && (this.nbt.get("Entities") instanceof ListTag))) { - this.nbt.putList(new ListTag("Entities")); - } - - if (!(this.nbt.contains("TileEntities") && (this.nbt.get("TileEntities") instanceof ListTag))) { - this.nbt.putList(new ListTag("TileEntities")); - } - - if (!(this.nbt.contains("TileTicks") && (this.nbt.get("TileTicks") instanceof ListTag))) { - this.nbt.putList(new ListTag("TileTicks")); - } - - if (!(this.nbt.contains("Biomes") && (this.nbt.get("Biomes") instanceof ByteArrayTag))) { - this.nbt.putByteArray("Biomes", new byte[256]); - } - - if (!(this.nbt.contains("HeightMap") && (this.nbt.get("HeightMap") instanceof IntArrayTag))) { - this.nbt.putIntArray("HeightMap", new int[256]); - } - - if (!(this.nbt.contains("Blocks"))) { - this.nbt.putByteArray("Blocks", new byte[32768]); - } - - if (!(this.nbt.contains("Data"))) { - this.nbt.putByteArray("Data", new byte[16384]); - this.nbt.putByteArray("SkyLight", new byte[16384]); - this.nbt.putByteArray("BlockLight", new byte[16384]); - } - - Map extraData = new HashMap<>(); - - if (!this.nbt.contains("ExtraData") || !(this.nbt.get("ExtraData") instanceof ByteArrayTag)) { - this.nbt.putByteArray("ExtraData", Binary.writeInt(0)); - } else { - BinaryStream stream = new BinaryStream(this.nbt.getByteArray("ExtraData")); - for (int i = 0; i < stream.getInt(); i++) { - int key = stream.getInt(); - extraData.put(key, stream.getShort()); - } - } - - this.setPosition(this.nbt.getInt("xPos"), this.nbt.getInt("zPos")); - this.blocks = this.nbt.getByteArray("Blocks"); - this.data = this.nbt.getByteArray("Data"); - this.skyLight = this.nbt.getByteArray("SkyLight"); - this.blockLight = this.nbt.getByteArray("BlockLight"); - - if (this.nbt.contains("BiomeColors")) { - this.biomes = new byte[16 * 16]; - int[] biomeColors = this.nbt.getIntArray("BiomeColors"); - if (biomeColors.length == 256) { - BiomePalette palette = new BiomePalette(biomeColors); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - this.biomes[(x << 4) | z] = (byte) (palette.get(x, z) >> 24); - } - } - } - } else { - this.biomes = this.nbt.getByteArray("Biomes"); - } - - int[] heightMap = this.nbt.getIntArray("HeightMap"); - this.heightMap = new byte[256]; - if (heightMap.length != 256) { - Arrays.fill(this.heightMap, (byte) 255); - } else { - for (int i = 0; i < heightMap.length; i++) { - this.heightMap[i] = (byte) heightMap[i]; - } - } - - if (!extraData.isEmpty()) this.extraData = extraData; - - this.NBTentities = ((ListTag) this.nbt.getList("Entities")).getAll(); - this.NBTtiles = ((ListTag) this.nbt.getList("TileEntities")).getAll(); - - this.nbt.remove("Blocks"); - this.nbt.remove("Data"); - this.nbt.remove("SkyLight"); - this.nbt.remove("BlockLight"); - this.nbt.remove("BiomeColors"); - this.nbt.remove("HeightMap"); - this.nbt.remove("Biomes"); - } - - @Override - public int getBlockId(int x, int y, int z) { - return this.blocks[(x << 11) | (z << 7) | y] & 0xff; - } - - @Override - public void setBlockId(int x, int y, int z, int id) { - this.blocks[(x << 11) | (z << 7) | y] = (byte) id; - setChanged(); - } - - @Override - public int getBlockData(int x, int y, int z) { - int b = this.data[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return b & 0x0f; - } else { - return b >> 4; - } - } - - @Override - public void setBlockData(int x, int y, int z, int data) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - this.data[i] = (byte) ((old & 0xf0) | (data & 0x0f)); - } else { - this.data[i] = (byte) (((data & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public int getFullBlock(int x, int y, int z) { - int i = (x << 11) | (z << 7) | y; - int block = this.blocks[i] & 0xff; - int data = this.data[i >> 1] & 0xff; - if ((y & 1) == 0) { - return (block << 4) | (data & 0x0f); - } else { - return (block << 4) | (data >> 4); - } - } - - @Override - public boolean setBlock(int x, int y, int z, int blockId) { - return setBlock(x, y, z, blockId, 0); - } - - @Override - public boolean setBlock(int x, int y, int z, int blockId, int meta) { - int i = (x << 11) | (z << 7) | y; - boolean changed = false; - byte id = (byte) blockId; - if (this.blocks[i] != id) { - this.blocks[i] = id; - changed = true; - } - - if (Block.hasMeta[blockId]) { - i >>= 1; - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - this.data[i] = (byte) ((old & 0xf0) | (meta & 0x0f)); - if ((old & 0x0f) != meta) { - changed = true; - } - } else { - this.data[i] = (byte) (((meta & 0x0f) << 4) | (old & 0x0f)); - if (meta != (old >> 4)) { - changed = true; - } - } - } - - if (changed) { - setChanged(); - } - return changed; - } - - @Override - public Block getAndSetBlock(int x, int y, int z, Block block) { - int i = (x << 11) | (z << 7) | y; - boolean changed = false; - byte id = (byte) block.getId(); - - byte previousId = this.blocks[i]; - - if (previousId != id) { - this.blocks[i] = id; - changed = true; - } - - int previousData; - i >>= 1; - int old = this.data[i] & 0xff; - if ((y & 1) == 0) { - previousData = old & 0x0f; - if (Block.hasMeta[block.getId()]) { - this.data[i] = (byte) ((old & 0xf0) | (block.getDamage() & 0x0f)); - if (block.getDamage() != previousData) { - changed = true; - } - } - } else { - previousData = old >> 4; - if (Block.hasMeta[block.getId()]) { - this.data[i] = (byte) (((block.getDamage() & 0x0f) << 4) | (old & 0x0f)); - if (block.getDamage() != previousData) { - changed = true; - } - } - } - - if (changed) { - setChanged(); - } - return Block.get(previousId, previousData); - } - - @Override - public int getBlockSkyLight(int x, int y, int z) { - int sl = this.skyLight[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return sl & 0x0f; - } else { - return sl >> 4; - } - } - - @Override - public void setBlockSkyLight(int x, int y, int z, int level) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.skyLight[i] & 0xff; - if ((y & 1) == 0) { - this.skyLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); - } else { - this.skyLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public int getBlockLight(int x, int y, int z) { - int b = this.blockLight[(x << 10) | (z << 6) | (y >> 1)] & 0xff; - if ((y & 1) == 0) { - return b & 0x0f; - } else { - return b >> 4; - } - } - - @Override - public void setBlockLight(int x, int y, int z, int level) { - int i = (x << 10) | (z << 6) | (y >> 1); - int old = this.blockLight[i] & 0xff; - if ((y & 1) == 0) { - this.blockLight[i] = (byte) ((old & 0xf0) | (level & 0x0f)); - } else { - this.blockLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f)); - } - setChanged(); - } - - @Override - public boolean isLightPopulated() { - return this.nbt.getBoolean("LightPopulated"); - } - - @Override - public void setLightPopulated() { - this.setLightPopulated(true); - } - - @Override - public void setLightPopulated(boolean value) { - this.nbt.putBoolean("LightPopulated", value); - setChanged(); - } - - @Override - public boolean isPopulated() { - return this.nbt.contains("TerrainPopulated") && this.nbt.getBoolean("TerrainPopulated"); - } - - @Override - public void setPopulated() { - this.setPopulated(true); - } - - @Override - public void setPopulated(boolean value) { - this.nbt.putBoolean("TerrainPopulated", value); - setChanged(); - } - - @Override - public boolean isGenerated() { - if (this.nbt.contains("TerrainGenerated")) { - return this.nbt.getBoolean("TerrainGenerated"); - } else if (this.nbt.contains("TerrainPopulated")) { - return this.nbt.getBoolean("TerrainPopulated"); - } - return false; - } - - @Override - public void setGenerated() { - this.setGenerated(true); - } - - @Override - public void setGenerated(boolean value) { - this.nbt.putBoolean("TerrainGenerated", value); - setChanged(); - } - - public static Chunk fromBinary(byte[] data) { - return fromBinary(data, null); - } - - public static Chunk fromBinary(byte[] data, LevelProvider provider) { - try { - CompoundTag chunk = NBTIO.read(new ByteArrayInputStream(Zlib.inflate(data)), ByteOrder.BIG_ENDIAN); - - if (!chunk.contains("Level") || !(chunk.get("Level") instanceof CompoundTag)) { - return null; - } - return new Chunk(provider != null ? provider : McRegion.class.newInstance(), chunk.getCompound("Level")); - } catch (Exception e) { - return null; - } - } - - public static Chunk fromFastBinary(byte[] data) { - return fromFastBinary(data, null); - } - - public static Chunk fromFastBinary(byte[] data, LevelProvider provider) { - try { - int offset = 0; - Chunk chunk = new Chunk(provider != null ? provider : McRegion.class.newInstance(), null); - chunk.provider = provider; - int chunkX = (Binary.readInt(Arrays.copyOfRange(data, offset, offset + 3))); - offset += 4; - int chunkZ = (Binary.readInt(Arrays.copyOfRange(data, offset, offset + 3))); - chunk.setPosition(chunkX, chunkZ); - offset += 4; - chunk.blocks = Arrays.copyOfRange(data, offset, offset + 32767); - offset += 32768; - chunk.data = Arrays.copyOfRange(data, offset, offset + 16383); - offset += 16384; - chunk.skyLight = Arrays.copyOfRange(data, offset, offset + 16383); - offset += 16384; - chunk.blockLight = Arrays.copyOfRange(data, offset, offset + 16383); - offset += 16384; - chunk.heightMap = Arrays.copyOfRange(data, offset, offset + 256); - offset += 256; - chunk.biomes = Arrays.copyOfRange(data, offset, offset + 256); - offset += 256; - byte flags = data[offset++]; - chunk.nbt.putByte("TerrainGenerated", (flags & 0b1)); - chunk.nbt.putByte("TerrainPopulated", ((flags >> 1) & 0b1)); - chunk.nbt.putByte("LightPopulated", ((flags >> 2) & 0b1)); - return chunk; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - - @Override - public byte[] toFastBinary() { - BinaryStream stream = new BinaryStream(new byte[65536]); - stream.put(Binary.writeInt(this.getX())); - stream.put(Binary.writeInt(this.getZ())); - stream.put(this.getBlockIdArray()); - stream.put(this.getBlockDataArray()); - stream.put(this.getBlockSkyLightArray()); - stream.put(this.getBlockLightArray()); - stream.put(this.getHeightMapArray()); - stream.put(this.getBiomeIdArray()); - stream.putByte((byte) ((this.isLightPopulated() ? 1 << 2 : 0) + (this.isPopulated() ? 1 << 2 : 0) + (this.isGenerated() ? 1 : 0))); - return stream.getBuffer(); - } - - @Override - public byte[] toBinary() { - CompoundTag nbt = this.getNBT().copy(); - nbt.remove("BiomeColors"); - - nbt.putInt("xPos", this.getX()); - nbt.putInt("zPos", this.getZ()); - - if (this.isGenerated()) { - nbt.putByteArray("Blocks", this.getBlockIdArray()); - nbt.putByteArray("Data", this.getBlockDataArray()); - nbt.putByteArray("SkyLight", this.getBlockSkyLightArray()); - nbt.putByteArray("BlockLight", this.getBlockLightArray()); - nbt.putByteArray("Biomes", this.getBiomeIdArray()); - - int[] heightInts = new int[256]; - byte[] heightBytes = this.getHeightMapArray(); - for (int i = 0; i < heightInts.length; i++) { - heightInts[i] = heightBytes[i] & 0xFF; - } - nbt.putIntArray("HeightMap", heightInts); - } - - - ArrayList entities = new ArrayList<>(); - for (Entity entity : this.getEntities().values()) { - if (!(entity instanceof Player) && !entity.closed) { - entity.saveNBT(); - entities.add(entity.namedTag); - } - } - ListTag entityListTag = new ListTag<>("Entities"); - entityListTag.setAll(entities); - nbt.putList(entityListTag); - - ArrayList tiles = new ArrayList<>(); - for (BlockEntity blockEntity : this.getBlockEntities().values()) { - blockEntity.saveNBT(); - tiles.add(blockEntity.namedTag); - } - ListTag tileListTag = new ListTag<>("TileEntities"); - tileListTag.setAll(tiles); - nbt.putList(tileListTag); - - BinaryStream extraData = new BinaryStream(); - Map extraDataArray = this.getBlockExtraDataArray(); - extraData.putInt(extraDataArray.size()); - for (Integer key : extraDataArray.keySet()) { - extraData.putInt(key); - extraData.putShort(extraDataArray.get(key)); - } - - nbt.putByteArray("ExtraData", extraData.getBuffer()); - - CompoundTag chunk = new CompoundTag(""); - chunk.putCompound("Level", nbt); - - try { - return Zlib.deflate(NBTIO.write(chunk, ByteOrder.BIG_ENDIAN), RegionLoader.COMPRESSION_LEVEL); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public CompoundTag getNBT() { - return nbt; - } - - public static Chunk getEmptyChunk(int chunkX, int chunkZ) { - return getEmptyChunk(chunkX, chunkZ, null); - } - - public static Chunk getEmptyChunk(int chunkX, int chunkZ, LevelProvider provider) { - try { - Chunk chunk; - if (provider != null) { - chunk = new Chunk(provider, null); - } else { - chunk = new Chunk(McRegion.class, null); - } - - chunk.setPosition(chunkX, chunkZ); - chunk.data = new byte[16384]; - chunk.blocks = new byte[32768]; - byte[] skyLight = new byte[16384]; - Arrays.fill(skyLight, (byte) 0xff); - chunk.skyLight = skyLight; - chunk.blockLight = chunk.data; - - chunk.heightMap = new byte[256]; - chunk.biomes = new byte[16 * 16]; - - chunk.nbt.putByte("V", 1); - chunk.nbt.putLong("InhabitedTime", 0); - chunk.nbt.putBoolean("TerrainGenerated", false); - chunk.nbt.putBoolean("TerrainPopulated", false); - chunk.nbt.putBoolean("LightPopulated", false); - - return chunk; - } catch (Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java b/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java deleted file mode 100644 index ae9f0ad5366..00000000000 --- a/src/main/java/cn/nukkit/level/format/mcregion/McRegion.java +++ /dev/null @@ -1,220 +0,0 @@ -package cn.nukkit.level.format.mcregion; - -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.blockentity.BlockEntitySpawnable; -import cn.nukkit.level.Level; -import cn.nukkit.level.format.ChunkSection; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.format.generic.BaseFullChunk; -import cn.nukkit.level.format.generic.BaseLevelProvider; -import cn.nukkit.level.format.generic.BaseRegionLoader; -import cn.nukkit.level.generator.Generator; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.scheduler.AsyncTask; -import cn.nukkit.utils.BinaryStream; -import cn.nukkit.utils.ChunkException; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Pattern; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class McRegion extends BaseLevelProvider { - - public McRegion(Level level, String path) throws IOException { - super(level, path); - } - - public static String getProviderName() { - return "mcregion"; - } - - public static byte getProviderOrder() { - return ORDER_ZXY; - } - - public static boolean usesChunkSection() { - return false; - } - - public static boolean isValid(String path) { - boolean isValid = (new File(path + "/level.dat").exists()) && new File(path + "/region/").isDirectory(); - if (isValid) { - for (File file : new File(path + "/region/").listFiles((dir, name) -> Pattern.matches("^.+\\.mc[r|a]$", name))) { - if (!file.getName().endsWith(".mcr")) { - isValid = false; - break; - } - } - } - return isValid; - } - - public static void generate(String path, String name, long seed, Class generator) throws IOException { - generate(path, name, seed, generator, new HashMap<>()); - } - - public static void generate(String path, String name, long seed, Class generator, Map options) throws IOException { - if (!new File(path + "/region").exists()) { - new File(path + "/region").mkdirs(); - } - - CompoundTag levelData = new CompoundTag("Data") - .putCompound("GameRules", new CompoundTag()) - - .putLong("DayTime", 0) - .putInt("GameType", 0) - .putString("generatorName", Generator.getGeneratorName(generator)) - .putString("generatorOptions", options.getOrDefault("preset", "")) - .putInt("generatorVersion", 1) - .putBoolean("hardcore", false) - .putBoolean("initialized", true) - .putLong("LastPlayed", System.currentTimeMillis() / 1000) - .putString("LevelName", name) - .putBoolean("raining", false) - .putInt("rainTime", 0) - .putLong("RandomSeed", seed) - .putInt("SpawnX", 128) - .putInt("SpawnY", 70) - .putInt("SpawnZ", 128) - .putBoolean("thundering", false) - .putInt("thunderTime", 0) - .putInt("version", 19133) - .putLong("Time", 0) - .putLong("SizeOnDisk", 0); - - NBTIO.writeGZIPCompressed(new CompoundTag().putCompound("Data", levelData), new FileOutputStream(path + "level.dat"), ByteOrder.BIG_ENDIAN); - } - - @Override - public AsyncTask requestChunkTask(int x, int z) throws ChunkException { - BaseFullChunk chunk = this.getChunk(x, z, false); - if (chunk == null) { - throw new ChunkException("Invalid Chunk Sent"); - } - - long timestamp = chunk.getChanges(); - - BinaryStream stream = new BinaryStream(); - stream.putByte((byte) 0); // subchunk version - - stream.put(chunk.getBlockIdArray()); - stream.put(chunk.getBlockDataArray()); - stream.put(chunk.getBlockSkyLightArray()); - stream.put(chunk.getBlockLightArray()); - stream.put(chunk.getHeightMapArray()); - stream.put(chunk.getBiomeIdArray()); - - Map extra = chunk.getBlockExtraDataArray(); - stream.putLInt(extra.size()); - if (!extra.isEmpty()) { - for (Integer key : extra.values()) { - stream.putLInt(key); - stream.putLShort(extra.get(key)); - } - } - - if (!chunk.getBlockEntities().isEmpty()) { - List tagList = new ArrayList<>(); - - for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { - if (blockEntity instanceof BlockEntitySpawnable) { - tagList.add(((BlockEntitySpawnable) blockEntity).getSpawnCompound()); - } - } - - try { - stream.put(NBTIO.write(tagList, ByteOrder.LITTLE_ENDIAN)); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - return null; - } - - @Override - public BaseFullChunk loadChunk(long index, int chunkX, int chunkZ, boolean create) { - int regionX = getRegionIndexX(chunkX); - int regionZ = getRegionIndexZ(chunkZ); - BaseRegionLoader region = this.loadRegion(regionX, regionZ); - this.level.timings.syncChunkLoadDataTimer.startTiming(); - BaseFullChunk chunk; - try { - chunk = region.readChunk(chunkX - regionX * 32, chunkZ - regionZ * 32); - } catch (IOException e) { - throw new RuntimeException(e); - } - if (chunk == null) { - if (create) { - chunk = this.getEmptyChunk(chunkX, chunkZ); - putChunk(index, chunk); - } - } else { - putChunk(index, chunk); - } - this.level.timings.syncChunkLoadDataTimer.stopTiming(); - return chunk; - } - - @Override - public Chunk getEmptyChunk(int chunkX, int chunkZ) { - return Chunk.getEmptyChunk(chunkX, chunkZ, this); - } - - @Override - public void saveChunk(int x, int z) { - if (this.isChunkLoaded(x, z)) { - try { - this.getRegion(x >> 5, z >> 5).writeChunk(this.getChunk(x, z)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - @Override - public void saveChunk(int x, int z, FullChunk chunk) { - if (!(chunk instanceof Chunk)) { - throw new ChunkException("Invalid Chunk class"); - } - this.loadRegion(x >> 5, z >> 5); - chunk.setPosition(x, z); - try { - this.getRegion(x >> 5, z >> 5).writeChunk(chunk); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static ChunkSection createChunkSection(int y) { - return null; - } - - protected BaseRegionLoader loadRegion(int x, int z) { - BaseRegionLoader tmp = lastRegion.get(); - if (tmp != null && x == tmp.getX() && z == tmp.getZ()) { - return tmp; - } - long index = Level.chunkHash(x, z); - synchronized (regions) { - BaseRegionLoader region = this.regions.get(index); - if (region == null) { - region = new RegionLoader(this, x, z); - this.regions.put(index, region); - } - lastRegion.set(region); - return region; - } - } -} diff --git a/src/main/java/cn/nukkit/level/format/mcregion/RegionLoader.java b/src/main/java/cn/nukkit/level/format/mcregion/RegionLoader.java deleted file mode 100644 index 4963f942c72..00000000000 --- a/src/main/java/cn/nukkit/level/format/mcregion/RegionLoader.java +++ /dev/null @@ -1,322 +0,0 @@ -package cn.nukkit.level.format.mcregion; - -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.format.LevelProvider; -import cn.nukkit.level.format.generic.BaseRegionLoader; -import cn.nukkit.utils.*; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; -import java.util.TreeMap; - -/** - * author: MagicDroidX - * Nukkit Project - */ - -public class RegionLoader extends BaseRegionLoader { - - public RegionLoader(LevelProvider level, int regionX, int regionZ) { - super(level, regionX, regionZ, "mcr"); - } - - @Override - protected boolean isChunkGenerated(int index) { - Integer[] array = this.locationTable.get(index); - return !(array[0] == 0 || array[1] == 0); - } - - public Chunk readChunk(int x, int z) throws IOException { - int index = getChunkOffset(x, z); - if (index < 0 || index >= 4096) { - return null; - } - - this.lastUsed = System.currentTimeMillis(); - - if (!this.isChunkGenerated(index)) { - return null; - } - - Integer[] table = this.locationTable.get(index); - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(table[0] << 12); - int length = raf.readInt(); - if (length <= 0 || length >= MAX_SECTOR_LENGTH) { - if (length >= MAX_SECTOR_LENGTH) { - table[0] = ++this.lastSector; - table[1] = 1; - this.locationTable.put(index, table); - MainLogger.getLogger().error("Corrupted chunk header detected"); - } - return null; - } - - byte compression = raf.readByte(); - if (length > (table[1] << 12)) { - MainLogger.getLogger().error("Corrupted bigger chunk detected"); - table[1] = length >> 12; - this.locationTable.put(index, table); - this.writeLocationIndex(index); - } else if (compression != COMPRESSION_ZLIB && compression != COMPRESSION_GZIP) { - MainLogger.getLogger().error("Invalid compression type"); - return null; - } - - byte[] data = new byte[length - 1]; - raf.readFully(data); - Chunk chunk = this.unserializeChunk(data); - if (chunk != null) { - return chunk; - } else { - MainLogger.getLogger().error("Corrupted chunk detected"); - return null; - } - } - - @Override - protected Chunk unserializeChunk(byte[] data) { - return Chunk.fromBinary(data, this.levelProvider); - } - - @Override - public boolean chunkExists(int x, int z) { - return this.isChunkGenerated(getChunkOffset(x, z)); - } - - @Override - protected void saveChunk(int x, int z, byte[] chunkData) throws IOException { - int length = chunkData.length + 1; - if (length + 4 > MAX_SECTOR_LENGTH) { - throw new ChunkException("Chunk is too big! " + (length + 4) + " > " + MAX_SECTOR_LENGTH); - } - int sectors = (int) Math.ceil((length + 4) / 4096d); - int index = getChunkOffset(x, z); - boolean indexChanged = false; - Integer[] table = this.locationTable.get(index); - - if (table[1] < sectors) { - table[0] = this.lastSector + 1; - this.locationTable.put(index, table); - this.lastSector += sectors; - indexChanged = true; - } else if (table[1] != sectors) { - indexChanged = true; - } - - table[1] = sectors; - table[2] = (int) (System.currentTimeMillis() / 1000d); - - this.locationTable.put(index, table); - - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(table[0] << 12); - - BinaryStream stream = new BinaryStream(); - stream.put(Binary.writeInt(length)); - stream.putByte(COMPRESSION_ZLIB); - stream.put(chunkData); - byte[] data = stream.getBuffer(); - if (data.length < sectors << 12) { - byte[] newData = new byte[sectors << 12]; - System.arraycopy(data, 0, newData, 0, data.length); - data = newData; - } - - raf.write(data); - - if (indexChanged) { - this.writeLocationIndex(index); - } - - } - - @Override - public void removeChunk(int x, int z) { - int index = getChunkOffset(x, z); - Integer[] table = this.locationTable.get(0); - table[0] = 0; - table[1] = 0; - this.locationTable.put(index, table); - } - - @Override - public void writeChunk(FullChunk chunk) throws Exception { - this.lastUsed = System.currentTimeMillis(); - byte[] chunkData = chunk.toBinary(); - this.saveChunk(chunk.getX() & 0x1f, chunk.getZ() & 0x1f, chunkData); - } - - protected static int getChunkOffset(int x, int z) { - return x | (z << 5); - } - - @Override - public void close() throws IOException { - this.writeLocationTable(); - this.levelProvider = null; - super.close(); - } - - @Override - public int doSlowCleanUp() throws Exception { - RandomAccessFile raf = this.getRandomAccessFile(); - for (int i = 0; i < 1024; i++) { - Integer[] table = this.locationTable.get(i); - if (table[0] == 0 || table[1] == 0) { - continue; - } - raf.seek(table[0] << 12); - byte[] chunk = new byte[table[1] << 12]; - raf.readFully(chunk); - int length = Binary.readInt(Arrays.copyOfRange(chunk, 0, 3)); - if (length <= 1) { - this.locationTable.put(i, (table = new Integer[]{0, 0, 0})); - } - try { - chunk = Zlib.inflate(Arrays.copyOf(chunk, 5)); - } catch (Exception e) { - this.locationTable.put(i, new Integer[]{0, 0, 0}); - continue; - } - chunk = Zlib.deflate(chunk, 9); - ByteBuffer buffer = ByteBuffer.allocate(4 + 1 + chunk.length); - buffer.put(Binary.writeInt(chunk.length + 1)); - buffer.put(COMPRESSION_ZLIB); - buffer.put(chunk); - chunk = buffer.array(); - int sectors = (int) Math.ceil(chunk.length / 4096d); - if (sectors > table[1]) { - table[0] = this.lastSector + 1; - this.lastSector += sectors; - this.locationTable.put(i, table); - } - raf.seek(table[0] << 12); - byte[] bytes = new byte[sectors << 12]; - ByteBuffer buffer1 = ByteBuffer.wrap(bytes); - buffer1.put(chunk); - raf.write(buffer1.array()); - } - this.writeLocationTable(); - int n = this.cleanGarbage(); - this.writeLocationTable(); - return n; - } - - @Override - protected void loadLocationTable() throws IOException { - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(0); - this.lastSector = 1; - int[] data = new int[1024 * 2]; //1024 records * 2 times - for (int i = 0; i < 1024 * 2; i++) { - data[i] = raf.readInt(); - } - for (int i = 0; i < 1024; ++i) { - int index = data[i]; - this.locationTable.put(i, new Integer[]{index >> 8, index & 0xff, data[1024 + i]}); - int value = this.locationTable.get(i)[0] + this.locationTable.get(i)[1] - 1; - if (value > this.lastSector) { - this.lastSector = value; - } - } - } - - private void writeLocationTable() throws IOException { - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(0); - for (int i = 0; i < 1024; ++i) { - Integer[] array = this.locationTable.get(i); - raf.writeInt((array[0] << 8) | array[1]); - } - for (int i = 0; i < 1024; ++i) { - Integer[] array = this.locationTable.get(i); - raf.writeInt(array[2]); - } - } - - private int cleanGarbage() throws IOException { - Map sectors = new TreeMap<>(); - for (int index : new ArrayList<>(this.locationTable.keySet())) { - Integer[] data = this.locationTable.get(index); - if (data[0] == 0 || data[1] == 0) { - this.locationTable.put(index, new Integer[]{0, 0, 0}); - continue; - } - sectors.put(data[0], index); - } - - if (sectors.size() == (this.lastSector - 2)) { - return 0; - } - - int shift = 0; - int lastSector = 1; - - RandomAccessFile raf = this.getRandomAccessFile(); - raf.seek(8192); - int s = 2; - for (int sector : sectors.keySet()) { - s = sector; - int index = sectors.get(sector); - if ((sector - lastSector) > 1) { - shift += sector - lastSector - 1; - } - if (shift > 0) { - raf.seek(sector << 12); - byte[] old = new byte[4096]; - raf.readFully(old); - raf.seek((sector - shift) << 12); - raf.write(old); - } - Integer[] v = this.locationTable.get(index); - v[0] -= shift; - this.locationTable.put(index, v); - this.lastSector = sector; - } - raf.setLength((s + 1) << 12); - return shift; - } - - @Override - protected void writeLocationIndex(int index) throws IOException { - Integer[] array = this.locationTable.get(index); - RandomAccessFile raf = this.getRandomAccessFile(); - - raf.seek(index << 2); - raf.writeInt((array[0] << 8) | array[1]); - raf.seek(4096 + (index << 2)); - raf.writeInt(array[2]); - } - - @Override - protected void createBlank() throws IOException { - RandomAccessFile raf = this.getRandomAccessFile(); - - raf.seek(0); - raf.setLength(0); - this.lastSector = 1; - int time = (int) (System.currentTimeMillis() / 1000d); - for (int i = 0; i < 1024; ++i) { - this.locationTable.put(i, new Integer[]{0, 0, time}); - raf.writeInt(0); - } - for (int i = 0; i < 1024; ++i) { - raf.writeInt(time); - } - } - - @Override - public int getX() { - return x; - } - - @Override - public int getZ() { - return z; - } - -} diff --git a/src/main/java/cn/nukkit/level/generator/End.java b/src/main/java/cn/nukkit/level/generator/End.java new file mode 100644 index 00000000000..344b90b8aa9 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/End.java @@ -0,0 +1,71 @@ +package cn.nukkit.level.generator; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; +import cn.nukkit.level.biome.EnumBiome; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +import java.util.Collections; +import java.util.Map; + +public class End extends Generator { + + private ChunkManager level; + + public End() { + //this(Collections.emptyMap()); + } + + public End(Map options) { + } + + @Override + public int getId() { + return Generator.TYPE_THE_END; + } + + @Override + public int getDimension() { + return Level.DIMENSION_THE_END; + } + + @Override + public ChunkManager getChunkManager() { + return level; + } + + @Override + public Map getSettings() { + return Collections.emptyMap(); + } + + @Override + public String getName() { + return "the_end"; + } + + @Override + public void init(ChunkManager level, NukkitRandom random) { + this.level = level; + } + + @Override + public void generateChunk(int chunkX, int chunkZ) { + BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); + for (int x = 0; x < 16; ++x) { + for (int z = 0; z < 16; ++z) { + chunk.setBiomeId(x, z, EnumBiome.END.biome.getId()); + } + } + } + + @Override + public void populateChunk(int chunkX, int chunkZ) { + } + + public Vector3 getSpawn() { + return new Vector3(100.5, 49, 0.5); + } +} diff --git a/src/main/java/cn/nukkit/level/generator/Flat.java b/src/main/java/cn/nukkit/level/generator/Flat.java index dd963702f07..57ffd7def1e 100644 --- a/src/main/java/cn/nukkit/level/generator/Flat.java +++ b/src/main/java/cn/nukkit/level/generator/Flat.java @@ -1,7 +1,8 @@ package cn.nukkit.level.generator; import cn.nukkit.Server; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.format.generic.BaseFullChunk; @@ -11,14 +12,11 @@ import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Flat extends Generator { @@ -62,7 +60,7 @@ public String getName() { } public Flat() { - this(new HashMap<>()); + this(Collections.emptyMap()); } public Flat(Map options) { @@ -70,25 +68,24 @@ public Flat(Map options) { this.options = options; if (this.options.containsKey("decoration")) { - PopulatorOre ores = new PopulatorOre(BlockID.STONE, new OreType[]{ - new OreType(Block.get(BlockID.COAL_ORE), 20, 16, 0, 128), - new OreType(Block.get(BlockID.IRON_ORE), 20, 8, 0, 64), - new OreType(Block.get(BlockID.REDSTONE_ORE), 8, 7, 0, 16), - new OreType(Block.get(BlockID.LAPIS_ORE), 1, 6, 0, 32), - new OreType(Block.get(BlockID.GOLD_ORE), 2, 8, 0, 32), - new OreType(Block.get(BlockID.DIAMOND_ORE), 1, 7, 0, 16), - new OreType(Block.get(BlockID.DIRT), 20, 32, 0, 128), - new OreType(Block.get(BlockID.GRAVEL), 20, 16, 0, 128), + PopulatorOre ores = new PopulatorOre(STONE, new OreType[]{ + new OreType(Block.get(BlockID.COAL_ORE), 20, 17, 0, 128), + new OreType(Block.get(BlockID.IRON_ORE), 20, 9, 0, 64), + new OreType(Block.get(BlockID.REDSTONE_ORE), 8, 8, 0, 16), + new OreType(Block.get(BlockID.LAPIS_ORE), 1, 7, 0, 30), + new OreType(Block.get(BlockID.GOLD_ORE), 2, 9, 0, 32), + new OreType(Block.get(BlockID.DIAMOND_ORE), 1, 8, 0, 16), + new OreType(Block.get(BlockID.DIRT), 10, 33, 0, 128), + new OreType(Block.get(BlockID.GRAVEL), 8, 33, 0, 128) }); this.populators.add(ores); } } - protected void parsePreset(String preset, int chunkX, int chunkZ) { + protected void parsePreset(String preset) { try { this.preset = preset; String[] presetArray = preset.split(";"); - int version = Integer.parseInt(presetArray[0]); String blocks = presetArray.length > 1 ? presetArray[1] : ""; this.biome = presetArray.length > 2 ? Integer.parseInt(presetArray[2]) : 1; String options = presetArray.length > 3 ? presetArray[1] : ""; @@ -129,8 +126,8 @@ protected void parsePreset(String preset, int chunkX, int chunkZ) { if (Pattern.matches("^[0-9a-z_]+$", option)) { this.options.put(option, true); } else if (Pattern.matches("^[0-9a-z_]+\\([0-9a-z_ =]+\\)$", option)) { - String name = option.substring(0, option.indexOf("(")); - String extra = option.substring(option.indexOf("(") + 1, option.indexOf(")")); + String name = option.substring(0, option.indexOf('(')); + String extra = option.substring(option.indexOf('(') + 1, option.indexOf(')')); Map map = new HashMap<>(); for (String kv : extra.split(" ")) { String[] data = kv.split("="); @@ -140,7 +137,7 @@ protected void parsePreset(String preset, int chunkX, int chunkZ) { } } } catch (Exception e) { - Server.getInstance().getLogger().error("error while parsing the preset", e); + Server.getInstance().getLogger().error("Error while parsing the preset", e); throw new RuntimeException(e); } } @@ -156,9 +153,9 @@ public void generateChunk(int chunkX, int chunkZ) { if (!this.init) { init = true; if (this.options.containsKey("preset") && !"".equals(this.options.get("preset"))) { - this.parsePreset((String) this.options.get("preset"), chunkX, chunkZ); + this.parsePreset((String) this.options.get("preset")); } else { - this.parsePreset(this.preset, chunkX, chunkZ); + this.parsePreset(this.preset); } } this.generateChunk(level.getChunk(chunkX, chunkZ)); @@ -172,8 +169,6 @@ private void generateChunk(FullChunk chunk) { chunk.setBiomeId(X, Z, biome); for (int y = 0; y < 256; ++y) { - int k = this.structure[y][0]; - int l = this.structure[y][1]; chunk.setBlock(X, y, Z, this.structure[y][0], this.structure[y][1]); } } @@ -191,6 +186,6 @@ public void populateChunk(int chunkX, int chunkZ) { @Override public Vector3 getSpawn() { - return new Vector3(128, this.floorLevel, 128); + return new Vector3(0.5, this.floorLevel, 0.5); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/Generator.java b/src/main/java/cn/nukkit/level/generator/Generator.java index ea53d7eef79..6a9976a03ef 100644 --- a/src/main/java/cn/nukkit/level/generator/Generator.java +++ b/src/main/java/cn/nukkit/level/generator/Generator.java @@ -1,5 +1,6 @@ package cn.nukkit.level.generator; +import cn.nukkit.Server; import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.DimensionData; @@ -7,30 +8,34 @@ import cn.nukkit.level.Level; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; + import java.util.HashMap; import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Generator implements BlockID { + public static final int TYPE_OLD = 0; public static final int TYPE_INFINITE = 1; public static final int TYPE_FLAT = 2; public static final int TYPE_NETHER = 3; + public static final int TYPE_THE_END = 4; + public static final int TYPE_VOID = 5; public abstract int getId(); public DimensionData getDimensionData() { DimensionData dimensionData = DimensionEnum.getDataFromId(this.getDimension()); if (dimensionData == null) { + Server.getInstance().getLogger().warning("Invalid DimensionData for Generator " + this.getClass().getName()); dimensionData = DimensionEnum.OVERWORLD.getDimensionData(); } return dimensionData; } - @Deprecated public int getDimension() { return Level.DIMENSION_OVERWORLD; } @@ -72,18 +77,18 @@ public static Class getGenerator(int type) { } public static String getGeneratorName(Class c) { - for (String key : Generator.nameList.keySet()) { - if (Generator.nameList.get(key).equals(c)) { - return key; + for (Map.Entry> entry : Generator.nameList.entrySet()) { + if (entry.getValue() == c) { + return entry.getKey(); } } return "unknown"; } public static int getGeneratorType(Class c) { - for (int key : Generator.typeList.keySet()) { - if (Generator.typeList.get(key).equals(c)) { - return key; + for (Map.Entry> entry : Generator.typeList.entrySet()) { + if (entry.getValue() == c) { + return entry.getKey(); } } return Generator.TYPE_INFINITE; diff --git a/src/main/java/cn/nukkit/level/generator/GeneratorTaskFactory.java b/src/main/java/cn/nukkit/level/generator/GeneratorTaskFactory.java new file mode 100644 index 00000000000..54518d46f4a --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/GeneratorTaskFactory.java @@ -0,0 +1,11 @@ +package cn.nukkit.level.generator; + +import cn.nukkit.level.Level; +import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.scheduler.AsyncTask; + +public interface GeneratorTaskFactory { + + AsyncTask populateChunkTask(BaseFullChunk chunk, Level level); + AsyncTask generateChunkTask(BaseFullChunk chunk, Level level); +} diff --git a/src/main/java/cn/nukkit/level/generator/Nether.java b/src/main/java/cn/nukkit/level/generator/Nether.java index 64d7a9b08cd..21ef1b7526e 100644 --- a/src/main/java/cn/nukkit/level/generator/Nether.java +++ b/src/main/java/cn/nukkit/level/generator/Nether.java @@ -1,45 +1,72 @@ package cn.nukkit.level.generator; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.Level; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.biome.EnumBiome; +import cn.nukkit.level.biome.type.CoveredBiome; import cn.nukkit.level.format.generic.BaseFullChunk; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; import cn.nukkit.level.generator.noise.nukkit.f.SimplexF; import cn.nukkit.level.generator.object.ore.OreType; -import cn.nukkit.level.generator.populator.impl.PopulatorGlowStone; -import cn.nukkit.level.generator.populator.impl.PopulatorGroundFire; -import cn.nukkit.level.generator.populator.impl.PopulatorLava; -import cn.nukkit.level.generator.populator.impl.PopulatorOre; +import cn.nukkit.level.generator.populator.impl.*; import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; +import net.daporkchop.lib.common.ref.Ref; +import net.daporkchop.lib.common.ref.ThreadRef; +import net.daporkchop.lib.common.util.PValidation; +import net.daporkchop.lib.noise.engine.PerlinNoiseEngine; +import net.daporkchop.lib.noise.filter.ScaleOctavesOffsetFilter; +import net.daporkchop.lib.random.PRandom; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; public class Nether extends Generator { + + private final boolean legacy; + private final int version; + private ChunkManager level; - /** - * @var Random - */ private NukkitRandom nukkitRandom; - private Random random; - private double lavaHeight = 32; - private double bedrockDepth = 5; - private SimplexF[] noiseGen = new SimplexF[3]; + private static final int lavaHeight = 32; // should be 31 + private final SimplexF[] noiseGen = new SimplexF[3]; private final List populators = new ArrayList<>(); - private List generationPopulators = new ArrayList<>(); + private final List generationPopulators = new ArrayList<>(); private long localSeed1; private long localSeed2; + private SimplexF biomeNoise; + + private static final int STEP_X = 4; + private static final int STEP_Y = 8; + private static final int STEP_Z = STEP_X; + private static final int SAMPLES_X = 16 / STEP_X; + private static final int SAMPLES_Y = 256 / STEP_Y; + private static final int SAMPLES_Z = 16 / STEP_Z; + private static final int CACHE_X = SAMPLES_X + 1; + private static final int CACHE_Y = SAMPLES_Y + 1; + private static final int CACHE_Z = SAMPLES_Z + 1; + private static final double SCALE_X = 1.0d / STEP_X; + private static final double SCALE_Y = 1.0d / STEP_Y; + private static final double SCALE_Z = 1.0d / STEP_Z; + private static final double NOISE_SCALE_FACTOR = ((1 << 16) - 1.0d) / 512.0d; + private static final Ref THREAD_DATA_CACHE = ThreadRef.soft(ThreadData::new); + private ScaleOctavesOffsetFilter selector; + private ScaleOctavesOffsetFilter low; + private ScaleOctavesOffsetFilter high; public Nether() { - this(new HashMap<>()); + this(Collections.emptyMap()); } public Nether(Map options) { - //Nothing here. Just used for future update. + this.legacy = !options.containsKey("__LevelDB"); + this.version = (int) options.getOrDefault("__Version", 0); } @Override @@ -59,7 +86,7 @@ public String getName() { @Override public Map getSettings() { - return new HashMap<>(); + return Collections.emptyMap(); } @Override @@ -70,43 +97,59 @@ public ChunkManager getChunkManager() { @Override public void init(ChunkManager level, NukkitRandom random) { this.level = level; + this.nukkitRandom = random; - this.random = new Random(); this.nukkitRandom.setSeed(this.level.getSeed()); - for (int i = 0; i < noiseGen.length; i++) { - noiseGen[i] = new SimplexF(nukkitRandom, 4, 1 / 4f, 1 / 64f); + if (this.legacy || this.version < 2) { + for (int i = 0; i < noiseGen.length; i++) { + noiseGen[i] = new SimplexF(this.nukkitRandom, 4, 1 / 4f, 1 / 64f); + } + } else { + this.selector = new ScaleOctavesOffsetFilter(new PerlinNoiseEngine(PRandom.wrap(new Random(this.level.getSeed()))), 0.01670927734375, 0.0334185546875, 0.01670927734375, 8, 12.75, 0.5); + this.low = new ScaleOctavesOffsetFilter(new PerlinNoiseEngine(PRandom.wrap(new Random(this.level.getSeed()))), 0.005221649169921875, 0.0078324737548828125, 0.005221649169921875, 16, 1.0, 0); + this.high = new ScaleOctavesOffsetFilter(new PerlinNoiseEngine(PRandom.wrap(new Random(this.level.getSeed()))), 0.005221649169921875, 0.0078324737548828125, 0.005221649169921875, 16, 1.0, 0); } this.nukkitRandom.setSeed(this.level.getSeed()); - this.localSeed1 = this.random.nextLong(); - this.localSeed2 = this.random.nextLong(); - - PopulatorOre ores = new PopulatorOre(Block.NETHERRACK, new OreType[]{ - new OreType(Block.get(BlockID.QUARTZ_ORE), 20, 16, 0, 128), - new OreType(Block.get(BlockID.SOUL_SAND), 5, 64, 0, 128), - new OreType(Block.get(BlockID.GRAVEL), 5, 64, 0, 128), - new OreType(Block.get(BlockID.LAVA), 1, 16, 0, (int) this.lavaHeight), - }); + this.localSeed1 = ThreadLocalRandom.current().nextLong(); + this.localSeed2 = ThreadLocalRandom.current().nextLong(); + + this.biomeNoise = new SimplexF(this.nukkitRandom, 2F, 1F / 8F, 1F / 2048f); + + PopulatorBedrock bedrock = new PopulatorBedrock(true); + this.generationPopulators.add(bedrock); + + PopulatorOre ores; + if (this.legacy) { + ores = new PopulatorOre(NETHERRACK, new OreType[]{ + new OreType(Block.get(BlockID.QUARTZ_ORE), 16, 24, 10, 117, NETHERRACK), + new OreType(Block.get(BlockID.SOUL_SAND), 12, 23, 0, /*31*/ 105, NETHERRACK), + new OreType(Block.get(BlockID.GRAVEL), 2, 64, 5, /*41*/ 105, NETHERRACK), + new OreType(Block.get(BlockID.MAGMA), 4, 64, 26, /*37*/ lavaHeight + 1, NETHERRACK), + new OreType(Block.get(BlockID.LAVA), 1, 16, 0, lavaHeight, NETHERRACK), + }); + } else { + ores = new PopulatorOre(NETHERRACK, new OreType[]{ + new OreType(Block.get(BlockID.QUARTZ_ORE), 16, 24, 10, 117, NETHERRACK), + new OreType(Block.get(BlockID.SOUL_SAND), 12, 23, 0, /*31*/ 105, NETHERRACK), + new OreType(Block.get(BlockID.GRAVEL), 2, 64, 5, /*41*/ 105, NETHERRACK), + new OreType(Block.get(BlockID.MAGMA), 4, 64, 26, /*37*/ lavaHeight + 1, NETHERRACK), + new OreType(Block.get(BlockID.LAVA), 1, 16, 0, lavaHeight, NETHERRACK), + new OreType(Block.get(BlockID.NETHER_GOLD_ORE), 10, 16, 10, 117, NETHERRACK), + new OreType(Block.get(BlockID.ANCIENT_DEBRIS), 2, 3, 8, 23, NETHERRACK), + new OreType(Block.get(BlockID.ANCIENT_DEBRIS), 3, 2, 8, 119, NETHERRACK), + }); + } this.populators.add(ores); - PopulatorGroundFire groundFire = new PopulatorGroundFire(); - groundFire.setBaseAmount(1); - groundFire.setRandomAmount(1); - this.populators.add(groundFire); + this.populators.add(new PopulatorNetherFire(FIRE, NETHERRACK)); PopulatorLava lava = new PopulatorLava(); - lava.setBaseAmount(1); lava.setRandomAmount(2); this.populators.add(lava); + this.populators.add(new PopulatorGlowStone()); - PopulatorOre ore = new PopulatorOre(Block.NETHERRACK, new OreType[]{ - new OreType(Block.get(BlockID.QUARTZ_ORE), 40, 16, 0, 128, NETHERRACK), - new OreType(Block.get(BlockID.SOUL_SAND), 1, 64, 30, 35, NETHERRACK), - new OreType(Block.get(BlockID.LAVA), 32, 1, 0, 32, NETHERRACK), - new OreType(Block.get(BlockID.MAGMA), 32, 16, 26, 37, NETHERRACK), - }); - this.populators.add(ore); } @Override @@ -117,45 +160,209 @@ public void generateChunk(int chunkX, int chunkZ) { BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); - for (int x = 0; x < 16; ++x) { - for (int z = 0; z < 16; ++z) { - Biome biome = EnumBiome.HELL.biome; - chunk.setBiomeId(x, z, biome.getId()); + if (this.version < 2 || !(chunk instanceof LevelDBChunk)) { + for (int x = 0; x < 16; ++x) { + for (int z = 0; z < 16; ++z) { + chunk.setBiomeId(x, z, EnumBiome.HELL.biome.getId()); - chunk.setBlockId(x, 0, z, Block.BEDROCK); - for (int y = 115; y < 127; ++y) { - chunk.setBlockId(x, y, z, Block.NETHERRACK); - } - chunk.setBlockId(x, 127, z, Block.BEDROCK); - for (int y = 1; y < 127; ++y) { - if (getNoise(baseX | x, y, baseZ | z) > 0) { + for (int y = 115; y < 127; ++y) { chunk.setBlockId(x, y, z, Block.NETHERRACK); - } else if (y <= this.lavaHeight) { - chunk.setBlockId(x, y, z, Block.STILL_LAVA); - chunk.setBlockLight(x, y + 1, z, 15); + } + + for (int y = 1; y < 127; ++y) { + if (getNoise(baseX | x, y, baseZ | z) > 0) { + chunk.setBlockId(x, y, z, Block.NETHERRACK); + } else if (y <= lavaHeight) { + chunk.setBlockId(x, y, z, Block.STILL_LAVA); + chunk.setBlockLight(x, y + 1, z, 15); + } + } + } + } + } else { // use generator from Cloudburst + CoveredBiome[][] biomes = new CoveredBiome[16][16]; + + for (int x = 0; x < 16; ++x) { + for (int z = 0; z < 16; ++z) { + CoveredBiome biome = (CoveredBiome) pickBiome(baseX + x, baseZ + z); + chunk.setBiomeId(x, z, biome.getId()); + biomes[x][z] = biome; + } + } + + final ThreadData threadData = THREAD_DATA_CACHE.get(); + final double[] densityCache = threadData.densityCache = densityGet(threadData.densityCache, baseX, 0, baseZ); + + for (int i = 0, sectionX = 0; sectionX < SAMPLES_X; sectionX++) { + for (int sectionZ = 0; sectionZ < SAMPLES_Z; sectionZ++) { + for (int sectionY = 0; sectionY < SAMPLES_Y; sectionY++, i++) { + double dxyz = densityCache[i]; + double dxYz = densityCache[i + 1]; + double dxyZ = densityCache[i + CACHE_Y]; + double dxYZ = densityCache[i + CACHE_Y + 1]; + double dXyz = densityCache[i + CACHE_Y * CACHE_Z]; + double dXYz = densityCache[i + CACHE_Y * CACHE_Z + 1]; + double dXyZ = densityCache[i + CACHE_Y * CACHE_Z + CACHE_Y]; + double dXYZ = densityCache[i + CACHE_Y * CACHE_Z + CACHE_Y + 1]; + + double bx00 = dxyz; + double bx01 = dxyZ; + double bx10 = dxYz; + double bx11 = dxYZ; + double gx00 = (dXyz - dxyz) * SCALE_X; + double gx01 = (dXyZ - dxyZ) * SCALE_X; + double gx10 = (dXYz - dxYz) * SCALE_X; + double gx11 = (dXYZ - dxYZ) * SCALE_X; + + for (int stepX = 0; stepX < STEP_X; stepX++) { + double ix00 = bx00 + gx00 * stepX; + double ix01 = bx01 + gx01 * stepX; + double ix10 = bx10 + gx10 * stepX; + double ix11 = bx11 + gx11 * stepX; + + double by0 = ix00; + double by1 = ix01; + double gy0 = (ix10 - ix00) * SCALE_Y; + double gy1 = (ix11 - ix01) * SCALE_Y; + + for (int stepY = 0; stepY < STEP_Y; stepY++) { + double iy0 = by0 + gy0 * stepY; + double iy1 = by1 + gy1 * stepY; + + double bz = iy0; + double gz = (iy1 - iy0) * SCALE_Z; + + for (int stepZ = 0; stepZ < STEP_Z; stepZ++) { + double iz = bz + gz * stepZ; + + int blockX = sectionX * STEP_X | stepX; + int blockY = sectionY * STEP_Y | stepY; + int blockZ = sectionZ * STEP_Z | stepZ; + + if (blockY < 127 && iz > 0.0d) { + CoveredBiome biome = biomes[blockX][blockZ]; + chunk.setFullBlockId(blockX, blockY, blockZ, biome.getGroundId(blockX, 0, blockZ)); + } else if (blockY <= lavaHeight) { + chunk.setBlockId(blockX, blockY, blockZ, Block.STILL_LAVA); + chunk.setBlockLight(blockX, blockY + 1, blockZ, 15); + } + } + } + } + } + + i++; + } + + i += CACHE_Y; + } + + // Populate ground cover + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + CoveredBiome biome = biomes[x][z]; + + int ground = biome.getGroundId(x, 0, z); + int surface = biome.getSurfaceId(x, 0, z); + + if (ground != surface) { + int previous = chunk.getBlockId(x, 126, z); + for (int y = 125; y > 1; --y) { + int id = chunk.getFullBlock(x, y, z); + if (id == ground && previous == AIR) { + chunk.setFullBlockId(x, y, z, surface); + } + previous = id; + } } } } } + + // Populate bedrock for (Populator populator : this.generationPopulators) { populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk); } } + private static double lerp(double a, double b, double t) { + return a + (b - a) * t; + } + + private double densityGet(int x, int y, int z) { + if (y >= 128) { + return 0.0d; + } + + double selector = NukkitMath.clamp(this.selector.get(x, y, (double) z), 0.0d, 1.0d); + double low = this.low.get(x, y, (double) z) * NOISE_SCALE_FACTOR; + double high = this.high.get(x, y, (double) z) * NOISE_SCALE_FACTOR; + + double outputNoise = lerp(low, high, selector); + + double threshold = y * 0.125d; + double offset = Math.cos(threshold * Math.PI * 6.0d / 17.0d) * 2.0d; + + if (threshold > 8.0d) { + threshold = 16.0d - threshold; + } + if (threshold < 4.0d) { + threshold = 4.0d - threshold; + offset -= threshold * threshold * threshold * 10.0d; + } + + return outputNoise - offset; + } + + private double[] densityGet(double[] arr, int x, int y, int z) { + int totalSize = PValidation.positive(Nether.CACHE_X) * PValidation.positive(Nether.CACHE_Y) * PValidation.positive(Nether.CACHE_Z) + PValidation.notNegative(0); + if (arr == null || arr.length < totalSize) { + arr = new double[totalSize]; + } + + for (int i = 0, dx = 0; dx < Nether.CACHE_X; dx++) { + for (int dz = 0; dz < Nether.CACHE_Z; dz++) { + for (int dy = 0; dy < Nether.CACHE_Y; dy++) { + arr[i++] = densityGet(x + dx * Nether.STEP_X, y + dy * Nether.STEP_Y, z + dz * Nether.STEP_Z); + } + } + } + return arr; + } + + private Biome pickBiome(int x, int z) { + float value = this.biomeNoise.noise2D(x, z, true); + float secondaryValue = this.biomeNoise.noise2D(z, x, true); // Reversed x & z is intentional here + + if (value >= 1 / 3f) { + return secondaryValue >= 0 ? EnumBiome.WARPED_FOREST.biome : EnumBiome.CRIMSON_FOREST.biome; + } else if (value >= -1 / 3f) { + return EnumBiome.HELL.biome; + } else { + return secondaryValue >= 0 ? EnumBiome.BASALT_DELTAS.biome : EnumBiome.SOULSAND_VALLEY.biome; + } + } + @Override public void populateChunk(int chunkX, int chunkZ) { BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); + this.nukkitRandom.setSeed(0xdeadbeef ^ (chunkX << 8) ^ chunkZ ^ this.level.getSeed()); + for (Populator populator : this.populators) { populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk); } - Biome biome = EnumBiome.getBiome(chunk.getBiomeId(7, 7)); - biome.populateChunk(this.level, chunkX, chunkZ, this.nukkitRandom); + if (!(chunk instanceof LevelDBChunk)) { + EnumBiome.HELL.biome.populateChunk(this.level, chunkX, chunkZ, this.nukkitRandom); + } else { + Biome biome = Biome.getBiome(chunk.getBiomeId(7, 7)); + biome.populateChunk(this.level, chunkX, chunkZ, this.nukkitRandom); + } } public Vector3 getSpawn() { - return new Vector3(0, 64, 0); + return new Vector3(0.5, 64, 0.5); } public float getNoise(int x, int y, int z) { @@ -165,4 +372,8 @@ public float getNoise(int x, int y, int z) { } return val; } -} + + private static final class ThreadData { + private double[] densityCache; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/Normal.java b/src/main/java/cn/nukkit/level/generator/Normal.java index 61ced5c4966..4ef30f527cf 100644 --- a/src/main/java/cn/nukkit/level/generator/Normal.java +++ b/src/main/java/cn/nukkit/level/generator/Normal.java @@ -1,13 +1,18 @@ package cn.nukkit.level.generator; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockStone; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.biome.BiomeSelector; import cn.nukkit.level.biome.EnumBiome; +import cn.nukkit.level.biome.impl.beach.BeachBiome; +import cn.nukkit.level.biome.impl.mushroom.MushroomIslandBiome; +import cn.nukkit.level.biome.impl.swamp.SwampBiome; +import cn.nukkit.level.biome.type.WateryBiome; import cn.nukkit.level.format.generic.BaseFullChunk; import cn.nukkit.level.generator.noise.vanilla.f.NoiseGeneratorOctavesF; -import cn.nukkit.level.generator.noise.vanilla.f.NoiseGeneratorPerlinF; import cn.nukkit.level.generator.object.ore.OreType; import cn.nukkit.level.generator.populator.impl.*; import cn.nukkit.level.generator.populator.type.Populator; @@ -16,91 +21,18 @@ import cn.nukkit.math.Vector3; import com.google.common.collect.ImmutableList; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.SplittableRandom; /** * Nukkit's terrain generator * Originally adapted from the PocketMine-MP generator by NycuRO and CreeperFace * Mostly rewritten by DaPorkchop_ - *

- * The following classes, and others related to terrain generation are theirs and are intended for NUKKIT USAGE and should not be copied/translated to other server software - * such as BukkitPE, ClearSky, Genisys, PocketMine-MP, or others - *

- * Normal.java - * MushroomPopulator.java - * DarkOakTreePopulator.java - * JungleBigTreePopulator.java - * JungleTreePopulaotr.java - * SavannaTreePopulator.java - * SwampTreePopulator.java - * BasicPopulator.java - * TreeGenerator.java - * HugeTreesGenerator.java - * BeachBiome.java - * ColdBeachBiome.java - * DesertBiome.java - * DesertHillsBiome.java - * DesertMBiome.java - * ExtremeHillsBiome.java - * ExtremeHillsEdgeBiome.java - * ExtremeHillsMBiome.java - * ExtremeHillsPlusBiome.java - * ExtremeHillsPlusMBiome.java - * StoneBeachBiome.java - * FlowerForestBiome.java - * ForestBiome.java - * ForestHillsBiome.java - * IcePlainsBiome.java - * IcePlainsSpikesBiome.java - * JungleBiome.java - * JungleEdgeBiome.java - * JungleEdgeMBiome.java - * JungleHillsBiome.java - * JungleMBiome.java - * MesaBiome.java - * MesaBryceBiome.java - * MesaPlateauBiome.java - * MesaPlateauFBiome.java - * MesaPlateauFMBiome.java - * MesaPlateauMBiome.java - * MushroomIslandBiome.java - * MushroomIslandShoreBiome.java - * DeepOceanBiome.java - * FrozenOceanBiome.java - * OceanBiome.java - * PlainsBiome.java - * SunflowerPlainsBiome.java - * FrozenRiverBiome.java - * RiverBiome.java - * RoofedForestBiome.java - * RoofedForestMBiome.java - * SavannaBiome.java - * SavannaMBiome.java - * SavannaPlateauBiome.java - * SavannaPlateauMBiome.java - * SwampBiome.java - * SwamplandMBiome.java - * ColdTaigaBiome.java - * ColdTaigaHillsBiome.java - * ColdTaigaMBiome.java - * MegaSpruceTaigaBiome.java - * MegaTaigaBiome.java - * MegaTagaHillsBiome.java - * TaigaBiome.java - * TaigaHillsBiome.java - * TaigaMBiome.java - * CoveredBiome.java - * GrassyBiome.java - * SandyBiome.java - * WateryBiome.java - * EnumBiomeBiome.java - * PopulatorCount.java - * PopulatorSurfaceBlock.java - * Normal.java - * Nether.java - * End.java */ public class Normal extends Generator { + private static final float[] biomeWeights = new float[25]; static { @@ -113,32 +45,27 @@ public class Normal extends Generator { private List populators = Collections.emptyList(); private List generationPopulators = Collections.emptyList(); - public static final int seaHeight = 64; - public NoiseGeneratorOctavesF scaleNoise; + public static int seaHeight = 64; // should be 62 public NoiseGeneratorOctavesF depthNoise; private ChunkManager level; - private Random random; private NukkitRandom nukkitRandom; private long localSeed1; private long localSeed2; private BiomeSelector selector; - private ThreadLocal biomes = ThreadLocal.withInitial(() -> new Biome[10 * 10]); - private ThreadLocal depthRegion = ThreadLocal.withInitial(() -> null); - private ThreadLocal mainNoiseRegion = ThreadLocal.withInitial(() -> null); - private ThreadLocal minLimitRegion = ThreadLocal.withInitial(() -> null); - private ThreadLocal maxLimitRegion = ThreadLocal.withInitial(() -> null); - private ThreadLocal heightMap = ThreadLocal.withInitial(() -> new float[825]); + private final ThreadLocal depthRegion = ThreadLocal.withInitial(() -> null); + private final ThreadLocal mainNoiseRegion = ThreadLocal.withInitial(() -> null); + private final ThreadLocal minLimitRegion = ThreadLocal.withInitial(() -> null); + private final ThreadLocal maxLimitRegion = ThreadLocal.withInitial(() -> null); + private final ThreadLocal heightMap = ThreadLocal.withInitial(() -> new float[825]); private NoiseGeneratorOctavesF minLimitPerlinNoise; private NoiseGeneratorOctavesF maxLimitPerlinNoise; private NoiseGeneratorOctavesF mainPerlinNoise; - private NoiseGeneratorPerlinF surfaceNoise; public Normal() { - this(Collections.emptyMap()); + //this(Collections.emptyMap()); } public Normal(Map options) { - //Nothing here. Just used for future update. } @Override @@ -148,7 +75,7 @@ public int getId() { @Override public ChunkManager getChunkManager() { - return this.level; + return level; } @Override @@ -169,18 +96,16 @@ public Biome pickBiome(int x, int z) { public void init(ChunkManager level, NukkitRandom random) { this.level = level; this.nukkitRandom = random; - this.random = new Random(); + SplittableRandom random1 = new SplittableRandom(); this.nukkitRandom.setSeed(this.level.getSeed()); - this.localSeed1 = this.random.nextLong(); - this.localSeed2 = this.random.nextLong(); + this.localSeed1 = random1.nextLong(); + this.localSeed2 = random1.nextLong(); this.nukkitRandom.setSeed(this.level.getSeed()); this.selector = new BiomeSelector(this.nukkitRandom); this.minLimitPerlinNoise = new NoiseGeneratorOctavesF(random, 16); this.maxLimitPerlinNoise = new NoiseGeneratorOctavesF(random, 16); this.mainPerlinNoise = new NoiseGeneratorOctavesF(random, 8); - this.surfaceNoise = new NoiseGeneratorPerlinF(random, 4); - this.scaleNoise = new NoiseGeneratorOctavesF(random, 10); this.depthNoise = new NoiseGeneratorOctavesF(random, 16); //this should run before all other populators so that we don't do things like generate ground cover on bedrock or something @@ -194,7 +119,7 @@ public void init(ChunkManager level, NukkitRandom random) { new OreType(Block.get(BlockID.COAL_ORE), 20, 17, 0, 128), new OreType(Block.get(BlockID.IRON_ORE), 20, 9, 0, 64), new OreType(Block.get(BlockID.REDSTONE_ORE), 8, 8, 0, 16), - new OreType(Block.get(BlockID.LAPIS_ORE), 1, 7, 0, 16), + new OreType(Block.get(BlockID.LAPIS_ORE), 1, 7, 0, 30), new OreType(Block.get(BlockID.GOLD_ORE), 2, 9, 0, 32), new OreType(Block.get(BlockID.DIAMOND_ORE), 1, 8, 0, 16), new OreType(Block.get(BlockID.DIRT), 10, 33, 0, 128), @@ -203,8 +128,10 @@ public void init(ChunkManager level, NukkitRandom random) { new OreType(Block.get(BlockID.STONE, BlockStone.DIORITE), 10, 33, 0, 80), new OreType(Block.get(BlockID.STONE, BlockStone.ANDESITE), 10, 33, 0, 80) }), - new PopulatorCaves()//, - //new PopulatorRavines() + new PopulatorCaves(), + new WaterIcePopulator(), // Populate water ice here to avoid sharp corners when the chunk is not fully on a freezing biome + new PopulatorSpring(BlockID.WATER, BlockID.STONE, 15, 8, 255), + new PopulatorSpring(BlockID.LAVA, BlockID.STONE, 10, 16, 255) ); } @@ -212,18 +139,18 @@ public void init(ChunkManager level, NukkitRandom random) { public void generateChunk(final int chunkX, final int chunkZ) { int baseX = chunkX << 4; int baseZ = chunkZ << 4; - this.nukkitRandom.setSeed(chunkX * this.localSeed1 ^ chunkZ * this.localSeed2 ^ this.level.getSeed()); + this.nukkitRandom.setSeed(chunkX * localSeed1 ^ chunkZ * localSeed2 ^ this.level.getSeed()); - BaseFullChunk chunk = this.level.getChunk(chunkX, chunkZ); + BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); //generate base noise values - float[] depthRegion = this.depthNoise.generateNoiseOctaves(this.depthRegion.get(), chunkX * 4, chunkZ * 4, 5, 5, 200f, 200f, 0.5f); + float[] depthRegion = this.depthNoise.generateNoiseOctaves(this.depthRegion.get(), chunkX << 2, chunkZ << 2, 5, 5, 200f, 200f, 0.5f); this.depthRegion.set(depthRegion); - float[] mainNoiseRegion = this.mainPerlinNoise.generateNoiseOctaves(this.mainNoiseRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f / 60f, 684.412f / 160f, 684.412f / 60f); + float[] mainNoiseRegion = this.mainPerlinNoise.generateNoiseOctaves(this.mainNoiseRegion.get(), chunkX << 2, 0, chunkZ << 2, 5, 33, 5, 11.406866f, 4.277575f, 11.406866f); this.mainNoiseRegion.set(mainNoiseRegion); - float[] minLimitRegion = this.minLimitPerlinNoise.generateNoiseOctaves(this.minLimitRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f, 684.412f, 684.412f); + float[] minLimitRegion = this.minLimitPerlinNoise.generateNoiseOctaves(this.minLimitRegion.get(), chunkX << 2, 0, chunkZ << 2, 5, 33, 5, 684.412f, 684.412f, 684.412f); this.minLimitRegion.set(minLimitRegion); - float[] maxLimitRegion = this.maxLimitPerlinNoise.generateNoiseOctaves(this.maxLimitRegion.get(), chunkX * 4, 0, chunkZ * 4, 5, 33, 5, 684.412f, 684.412f, 684.412f); + float[] maxLimitRegion = this.maxLimitPerlinNoise.generateNoiseOctaves(this.maxLimitRegion.get(), chunkX << 2, 0, chunkZ << 2, 5, 33, 5, 684.412f, 684.412f, 684.412f); this.maxLimitRegion.set(maxLimitRegion); float[] heightMap = this.heightMap.get(); @@ -235,11 +162,11 @@ public void generateChunk(final int chunkX, final int chunkZ) { float heightVariationSum = 0.0F; float baseHeightSum = 0.0F; float biomeWeightSum = 0.0F; - Biome biome = this.pickBiome(baseX + (xSeg * 4), baseZ + (zSeg * 4)); + Biome biome = pickBiome(baseX + (xSeg << 2), baseZ + (zSeg << 2)); for (int xSmooth = -2; xSmooth <= 2; ++xSmooth) { for (int zSmooth = -2; zSmooth <= 2; ++zSmooth) { - Biome biome1 = this.pickBiome(baseX + (xSeg * 4) + xSmooth, baseZ + (zSeg * 4) + zSmooth); + Biome biome1 = pickBiome(baseX + (xSeg << 2) + xSmooth, baseZ + (zSeg << 2) + zSmooth); float baseHeight = biome1.getBaseHeight(); float heightVariation = biome1.getHeightVariation(); @@ -314,6 +241,12 @@ public void generateChunk(final int chunkX, final int chunkZ) { } } + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + chunk.setBiome(x, z, selector.pickBiome(baseX | x, baseZ | z)); + } + } + //place blocks for (int xSeg = 0; xSeg < 4; ++xSeg) { int xScale = xSeg * 5; @@ -346,10 +279,21 @@ public void generateChunk(final int chunkX, final int chunkZ) { double scaleZ2 = baseIncr - scaleZ; for (int xIn = 0; xIn < 4; ++xIn) { - if ((scaleZ2 += scaleZ) > 0.0f) { - chunk.setBlockId(xSeg * 4 + zIn, ySeg * 8 + yIn, zSeg * 4 + xIn, STONE); - } else if (ySeg * 8 + yIn <= seaHeight) { - chunk.setBlockId(xSeg * 4 + zIn, ySeg * 8 + yIn, zSeg * 4 + xIn, STILL_WATER); + int xxx = (xSeg << 2) + zIn; + int yyy = (ySeg << 3) + yIn; + int zzz = (zSeg << 2) + xIn; + + if ((scaleZ2 += scaleZ) > 0.0) { + chunk.setBlockId(xxx, yyy, zzz, STONE); + } else if (yyy <= seaHeight) { + chunk.setBlockId(xxx, yyy, zzz, STILL_WATER); + + if (yyy < seaHeight) { + Biome biome = Biome.getBiome(chunk.getBiomeId(xxx, zzz)); + if (!(biome instanceof WateryBiome) && !(biome instanceof BeachBiome) && !(biome instanceof SwampBiome) && !(biome instanceof MushroomIslandBiome)) { // don't replace shore + chunk.setBiome(xxx, zzz, biome.isFreezing() ? EnumBiome.FROZEN_RIVER.biome : EnumBiome.RIVER.biome); + } + } } } @@ -366,14 +310,6 @@ public void generateChunk(final int chunkX, final int chunkZ) { } } - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - Biome biome = this.selector.pickBiome(baseX | x, baseZ | z); - - chunk.setBiome(x, z, biome); - } - } - //populate chunk for (Populator populator : this.generationPopulators) { populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk); @@ -382,14 +318,15 @@ public void generateChunk(final int chunkX, final int chunkZ) { @Override public void populateChunk(int chunkX, int chunkZ) { - BaseFullChunk chunk = this.level.getChunk(chunkX, chunkZ); + BaseFullChunk chunk = level.getChunk(chunkX, chunkZ); + this.nukkitRandom.setSeed(0xdeadbeef ^ (chunkX << 8) ^ chunkZ ^ this.level.getSeed()); + for (Populator populator : this.populators) { populator.populate(this.level, chunkX, chunkZ, this.nukkitRandom, chunk); } - @SuppressWarnings("deprecation") - Biome biome = EnumBiome.getBiome(chunk.getBiomeId(7, 7)); + Biome biome = Biome.getBiome(chunk.getBiomeId(7, 7)); biome.populateChunk(this.level, chunkX, chunkZ, this.nukkitRandom); } diff --git a/src/main/java/cn/nukkit/level/generator/PopChunkManager.java b/src/main/java/cn/nukkit/level/generator/PopChunkManager.java index d879c2fcf73..db8f3a556ee 100644 --- a/src/main/java/cn/nukkit/level/generator/PopChunkManager.java +++ b/src/main/java/cn/nukkit/level/generator/PopChunkManager.java @@ -1,16 +1,26 @@ package cn.nukkit.level.generator; +import cn.nukkit.level.DimensionData; import cn.nukkit.level.format.generic.BaseFullChunk; import java.util.Arrays; +import java.util.function.Supplier; public class PopChunkManager extends SimpleChunkManager { + private boolean clean = true; private final BaseFullChunk[] chunks = new BaseFullChunk[9]; private int CX = Integer.MAX_VALUE; private int CZ = Integer.MAX_VALUE; + private final Supplier dimensionDataSupplier; + public PopChunkManager(long seed) { + this(seed, () -> DimensionData.LEGACY_DIMENSION); + } + + public PopChunkManager(long seed, Supplier dimensionDataSupplier) { super(seed); + this.dimensionDataSupplier = dimensionDataSupplier; } @Override @@ -90,4 +100,14 @@ public void setChunk(int chunkX, int chunkZ, BaseFullChunk chunk) { clean = false; chunks[index] = chunk; } + + @Override + public int getMaxBlockY() { + return this.dimensionDataSupplier.get().getMaxHeight(); + } + + @Override + public int getMinBlockY() { + return this.dimensionDataSupplier.get().getMinHeight(); + } } diff --git a/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java b/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java index 5ff6692c8a3..731385ca9bc 100644 --- a/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java +++ b/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java @@ -1,10 +1,11 @@ package cn.nukkit.level.generator; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class SimpleChunkManager implements ChunkManager { @@ -16,54 +17,86 @@ public SimpleChunkManager(long seed) { } @Override - public int getBlockIdAt(int x, int y, int z) { + public int getBlockIdAt(int x, int y, int z, BlockLayer layer) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - return chunk.getBlockId(x & 0xf, y & 0xff, z & 0xf); + return chunk.getBlockId(x & 0xf, y, z & 0xf, layer); } return 0; } @Override - public void setBlockIdAt(int x, int y, int z, int id) { + public void setBlockIdAt(int x, int y, int z, BlockLayer layer, int id) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - chunk.setBlockId(x & 0xf, y & 0xff, z & 0xf, id); + chunk.setBlockId(x & 0xf, y, z & 0xf, layer, id); } } @Override public void setBlockAt(int x, int y, int z, int id, int data) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } + FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - chunk.setBlock(x & 0xf, y & 0xff, z & 0xf, id, data); + chunk.setBlock(x & 0xf, y, z & 0xf, id, data); } } - @Override - public void setBlockFullIdAt(int x, int y, int z, int fullId) { + public void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, int fullId) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return; + } FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - chunk.setFullBlockId(x & 0xf, y & 0xff, z & 0xf, fullId); + chunk.setFullBlockId(x & 0xf, y, z & 0xf, layer, fullId); } } @Override - public int getBlockDataAt(int x, int y, int z) { + public int getBlockDataAt(int x, int y, int z, BlockLayer layer) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return 0; + } FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - return chunk.getBlockData(x & 0xf, y & 0xff, z & 0xf); + return chunk.getBlockData(x & 0xf, y, z & 0xf, layer); } return 0; } @Override - public void setBlockDataAt(int x, int y, int z, int data) { + public void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data) { + FullChunk chunk = this.getChunk(x >> 4, z >> 4); + if (chunk != null) { + chunk.setBlockData(x & 0xf, y & 0xff, z & 0xf, layer, data); + } + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id) { + return this.setBlockAtLayer(x, y, z, layer, id, 0); + } + + @Override + public boolean setBlockAtLayer(int x, int y, int z, BlockLayer layer, int id, int data) { + if (y < this.getMinBlockY() || y > this.getMaxBlockY()) { + return false; + } FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - chunk.setBlockData(x & 0xf, y & 0xff, z & 0xf, data); + return chunk.setBlockAtLayer(x, y, z, layer, id, data); } + return true; } @Override @@ -83,4 +116,7 @@ public void setSeed(long seed) { public void cleanChunks(long seed) { this.seed = seed; } + + public abstract int getMinBlockY(); + public abstract int getMaxBlockY(); } diff --git a/src/main/java/cn/nukkit/level/generator/SingleChunkManager.java b/src/main/java/cn/nukkit/level/generator/SingleChunkManager.java index 3567dd5a4ee..69eacf94acd 100644 --- a/src/main/java/cn/nukkit/level/generator/SingleChunkManager.java +++ b/src/main/java/cn/nukkit/level/generator/SingleChunkManager.java @@ -3,6 +3,7 @@ import cn.nukkit.level.format.generic.BaseFullChunk; public class SingleChunkManager extends SimpleChunkManager { + private int CX = Integer.MAX_VALUE; private int CZ = Integer.MAX_VALUE; private BaseFullChunk chunk; @@ -41,4 +42,14 @@ public void cleanChunks(long seed) { CX = Integer.MAX_VALUE; CZ = Integer.MAX_VALUE; } + + @Override + public int getMinBlockY() { + return 0; + } + + @Override + public int getMaxBlockY() { + return 255; + } } diff --git a/src/main/java/cn/nukkit/level/generator/Void.java b/src/main/java/cn/nukkit/level/generator/Void.java new file mode 100644 index 00000000000..8a0979c476a --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/Void.java @@ -0,0 +1,57 @@ +package cn.nukkit.level.generator; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +import java.util.Map; + +public class Void extends Generator { + + private ChunkManager level; + + public Void() { + //this(Collections.emptyMap()); + } + + public Void(Map options) { + } + + @Override + public int getId() { + return TYPE_VOID; + } + + @Override + public ChunkManager getChunkManager() { + return level; + } + + @Override + public void init(ChunkManager level, NukkitRandom random) { + this.level = level; + } + + @Override + public void generateChunk(int chX, int chZ) { + } + + @Override + public void populateChunk(int i, int i1) { + } + + @Override + public Map getSettings() { + return null; + } + + @Override + public String getName() { + return "void"; + } + + @Override + public Vector3 getSpawn() { + return new Vector3(0.5, 64, 0.5); + } +} diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/NoiseD.java b/src/main/java/cn/nukkit/level/generator/noise/Noise.java similarity index 97% rename from src/main/java/cn/nukkit/level/generator/noise/nukkit/d/NoiseD.java rename to src/main/java/cn/nukkit/level/generator/noise/Noise.java index 04b2c400c46..88ee18a49ff 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/NoiseD.java +++ b/src/main/java/cn/nukkit/level/generator/noise/Noise.java @@ -1,10 +1,10 @@ -package cn.nukkit.level.generator.noise.nukkit.d; +package cn.nukkit.level.generator.noise; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public abstract class NoiseD { +public abstract class Noise { protected int[] perm; protected double offsetX = 0; protected double offsetY = 0; diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/PerlinD.java b/src/main/java/cn/nukkit/level/generator/noise/Perlin.java similarity index 83% rename from src/main/java/cn/nukkit/level/generator/noise/nukkit/d/PerlinD.java rename to src/main/java/cn/nukkit/level/generator/noise/Perlin.java index 6989ea5bf3d..dbf8ad919f2 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/PerlinD.java +++ b/src/main/java/cn/nukkit/level/generator/noise/Perlin.java @@ -1,17 +1,23 @@ -package cn.nukkit.level.generator.noise.nukkit.d; +package cn.nukkit.level.generator.noise; import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class PerlinD extends NoiseD { - public PerlinD(NukkitRandom random, double octaves, double persistence) { +public class Perlin extends Noise { + public static final int[][] grad3 = { + {1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, + {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, + {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1} + }; + + public Perlin(NukkitRandom random, double octaves, double persistence) { this(random, octaves, persistence, 1); } - public PerlinD(NukkitRandom random, double octaves, double persistence, double expansion) { + public Perlin(NukkitRandom random, double octaves, double persistence, double expansion) { this.octaves = octaves; this.persistence = persistence; this.expansion = expansion; diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/SimplexD.java b/src/main/java/cn/nukkit/level/generator/noise/Simplex.java similarity index 80% rename from src/main/java/cn/nukkit/level/generator/noise/nukkit/d/SimplexD.java rename to src/main/java/cn/nukkit/level/generator/noise/Simplex.java index 34f20ecddee..b2b8c73679f 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/d/SimplexD.java +++ b/src/main/java/cn/nukkit/level/generator/noise/Simplex.java @@ -1,12 +1,12 @@ -package cn.nukkit.level.generator.noise.nukkit.d; +package cn.nukkit.level.generator.noise; import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -public class SimplexD extends PerlinD { +public class Simplex extends Perlin { protected static double SQRT_3; protected static double SQRT_5; protected static double F2; @@ -19,14 +19,26 @@ public class SimplexD extends PerlinD { protected static double G42; protected static double G43; protected static double G44; - public static final int[][] grad3 = { - {1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, - {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, - {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1} - }; + protected static final int[][] grad4 = {{0, 1, 1, 1}, {0, 1, 1, -1}, {0, 1, -1, 1}, {0, 1, -1, -1}, + {0, -1, 1, 1}, {0, -1, 1, -1}, {0, -1, -1, 1}, {0, -1, -1, -1}, + {1, 0, 1, 1}, {1, 0, 1, -1}, {1, 0, -1, 1}, {1, 0, -1, -1}, + {-1, 0, 1, 1}, {-1, 0, 1, -1}, {-1, 0, -1, 1}, {-1, 0, -1, -1}, + {1, 1, 0, 1}, {1, 1, 0, -1}, {1, -1, 0, 1}, {1, -1, 0, -1}, + {-1, 1, 0, 1}, {-1, 1, 0, -1}, {-1, -1, 0, 1}, {-1, -1, 0, -1}, + {1, 1, 1, 0}, {1, 1, -1, 0}, {1, -1, 1, 0}, {1, -1, -1, 0}, + {-1, 1, 1, 0}, {-1, 1, -1, 0}, {-1, -1, 1, 0}, {-1, -1, -1, 0}}; + protected static final int[][] simplex = { + {0, 1, 2, 3}, {0, 1, 3, 2}, {0, 0, 0, 0}, {0, 2, 3, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 2, 3, 0}, + {0, 2, 1, 3}, {0, 0, 0, 0}, {0, 3, 1, 2}, {0, 3, 2, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 3, 2, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {1, 2, 0, 3}, {0, 0, 0, 0}, {1, 3, 0, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, + {1, 0, 2, 3}, {1, 0, 3, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {2, 0, 3, 1}, {0, 0, 0, 0}, {2, 1, 3, 0}, + {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, + {2, 0, 1, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {0, 0, 0, 0}, {3, 1, 2, 0}, + {2, 1, 0, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {3, 1, 0, 2}, {0, 0, 0, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}; protected final double offsetW; - public SimplexD(NukkitRandom random, double octaves, double persistence) { + public Simplex(NukkitRandom random, double octaves, double persistence) { super(random, octaves, persistence); this.offsetW = random.nextDouble() * 256; SQRT_3 = Math.sqrt(3); @@ -34,8 +46,8 @@ public SimplexD(NukkitRandom random, double octaves, double persistence) { F2 = 0.5 * (SQRT_3 - 1); G2 = (3 - SQRT_3) / 6; G22 = G2 * 2.0 - 1; - F3 = 1.0 / 3.0; - G3 = 1.0 / 6.0; + F3 = 0.3333333333333333; + G3 = 0.16666666666666666; F4 = (SQRT_5 - 1.0) / 4.0; G4 = (5.0 - SQRT_5) / 20.0; G42 = G4 * 2.0; @@ -43,7 +55,7 @@ public SimplexD(NukkitRandom random, double octaves, double persistence) { G44 = G4 * 4.0 - 1.0; } - public SimplexD(NukkitRandom random, double octaves, double persistence, double expansion) { + public Simplex(NukkitRandom random, double octaves, double persistence, double expansion) { super(random, octaves, persistence, expansion); this.offsetW = random.nextDouble() * 256; SQRT_3 = Math.sqrt(3); @@ -51,8 +63,8 @@ public SimplexD(NukkitRandom random, double octaves, double persistence, double F2 = 0.5 * (SQRT_3 - 1); G2 = (3 - SQRT_3) / 6; G22 = G2 * 2.0 - 1; - F3 = 1.0 / 3.0; - G3 = 1.0 / 6.0; + F3 = 0.3333333333333333; + G3 = 0.16666666666666666; F4 = (SQRT_5 - 1.0) / 4.0; G4 = (5.0 - SQRT_5) / 20.0; G42 = G4 * 2.0; diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/NoiseF.java b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/NoiseF.java index ff0cc7146c1..5fe0f025c00 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/NoiseF.java +++ b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/NoiseF.java @@ -1,7 +1,7 @@ package cn.nukkit.level.generator.noise.nukkit.f; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public abstract class NoiseF { diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/PerlinF.java b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/PerlinF.java index 491778ffd06..50c09297935 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/PerlinF.java +++ b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/PerlinF.java @@ -3,7 +3,7 @@ import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class PerlinF extends NoiseF { diff --git a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/SimplexF.java b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/SimplexF.java index bfb15848cc0..960e82d553b 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/SimplexF.java +++ b/src/main/java/cn/nukkit/level/generator/noise/nukkit/f/SimplexF.java @@ -3,7 +3,7 @@ import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class SimplexF extends PerlinF { diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorImprovedD.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorImprovedD.java deleted file mode 100644 index 6ac9aa097c3..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorImprovedD.java +++ /dev/null @@ -1,177 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.d; - -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorImprovedD { - private static final double[] GRAD_X = new double[]{1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D, 0.0D, 0.0D, 0.0D, 0.0D, 1.0D, 0.0D, -1.0D, 0.0D}; - private static final double[] GRAD_Y = new double[]{1.0D, 1.0D, -1.0D, -1.0D, 0.0D, 0.0D, 0.0D, 0.0D, 1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D}; - private static final double[] GRAD_Z = new double[]{0.0D, 0.0D, 0.0D, 0.0D, 1.0D, 1.0D, -1.0D, -1.0D, 1.0D, 1.0D, -1.0D, -1.0D, 0.0D, 1.0D, 0.0D, -1.0D}; - private static final double[] GRAD_2X = new double[]{1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D, 1.0D, -1.0D, 0.0D, 0.0D, 0.0D, 0.0D, 1.0D, 0.0D, -1.0D, 0.0D}; - private static final double[] GRAD_2Z = new double[]{0.0D, 0.0D, 0.0D, 0.0D, 1.0D, 1.0D, -1.0D, -1.0D, 1.0D, 1.0D, -1.0D, -1.0D, 0.0D, 1.0D, 0.0D, -1.0D}; - private final int[] permutations; - public double xCoord; - public double yCoord; - public double zCoord; - - public NoiseGeneratorImprovedD() { - this(new NukkitRandom(System.currentTimeMillis())); - } - - public NoiseGeneratorImprovedD(NukkitRandom random) { - this.permutations = new int[512]; - this.xCoord = random.nextDouble() * 256.0D; - this.yCoord = random.nextDouble() * 256.0D; - this.zCoord = random.nextDouble() * 256.0D; - - int i = 0; - while (i < 256) { - this.permutations[i] = i++; - } - - for (int l = 0; l < 256; ++l) { - int j = random.nextBoundedInt(256 - l) + l; - int k = this.permutations[l]; - this.permutations[l] = this.permutations[j]; - this.permutations[j] = k; - this.permutations[l + 256] = this.permutations[l]; - } - } - - public final double lerp(double p_76311_1_, double p_76311_3_, double p_76311_5_) { - return p_76311_3_ + p_76311_1_ * (p_76311_5_ - p_76311_3_); - } - - public final double grad2(int p_76309_1_, double p_76309_2_, double p_76309_4_) { - int i = p_76309_1_ & 15; - return GRAD_2X[i] * p_76309_2_ + GRAD_2Z[i] * p_76309_4_; - } - - public final double grad(int p_76310_1_, double p_76310_2_, double p_76310_4_, double p_76310_6_) { - int i = p_76310_1_ & 15; - return GRAD_X[i] * p_76310_2_ + GRAD_Y[i] * p_76310_4_ + GRAD_Z[i] * p_76310_6_; - } - - /* - * noiseArray should be xSize*ySize*zSize in size - */ - public void populateNoiseArray(double[] noiseArray, double xOffset, double yOffset, double zOffset, int xSize, int ySize, int zSize, double xScale, double yScale, double zScale, double noiseScale) { - if (ySize == 1) { - int i5 = 0; - int j5 = 0; - int j = 0; - int k5 = 0; - double d14 = 0.0D; - double d15 = 0.0D; - int l5 = 0; - double d16 = 1.0D / noiseScale; - - for (int j2 = 0; j2 < xSize; ++j2) { - double d17 = xOffset + (double) j2 * xScale + this.xCoord; - int i6 = (int) d17; - - if (d17 < (double) i6) { - --i6; - } - - int k2 = i6 & 255; - d17 = d17 - (double) i6; - double d18 = d17 * d17 * d17 * (d17 * (d17 * 6.0D - 15.0D) + 10.0D); - - for (int j6 = 0; j6 < zSize; ++j6) { - double d19 = zOffset + (double) j6 * zScale + this.zCoord; - int k6 = (int) d19; - - if (d19 < (double) k6) { - --k6; - } - - int l6 = k6 & 255; - d19 = d19 - (double) k6; - double d20 = d19 * d19 * d19 * (d19 * (d19 * 6.0D - 15.0D) + 10.0D); - i5 = this.permutations[k2]; - j5 = this.permutations[i5] + l6; - j = this.permutations[k2 + 1]; - k5 = this.permutations[j] + l6; - d14 = this.lerp(d18, this.grad2(this.permutations[j5], d17, d19), this.grad(this.permutations[k5], d17 - 1.0D, 0.0D, d19)); - d15 = this.lerp(d18, this.grad(this.permutations[j5 + 1], d17, 0.0D, d19 - 1.0D), this.grad(this.permutations[k5 + 1], d17 - 1.0D, 0.0D, d19 - 1.0D)); - double d21 = this.lerp(d20, d14, d15); - int i7 = l5++; - noiseArray[i7] += d21 * d16; - } - } - } else { - int i = 0; - double d0 = 1.0D / noiseScale; - int k = -1; - int l = 0; - int i1 = 0; - int j1 = 0; - int k1 = 0; - int l1 = 0; - int i2 = 0; - double d1 = 0.0D; - double d2 = 0.0D; - double d3 = 0.0D; - double d4 = 0.0D; - - for (int l2 = 0; l2 < xSize; ++l2) { - double d5 = xOffset + (double) l2 * xScale + this.xCoord; - int i3 = (int) d5; - - if (d5 < (double) i3) { - --i3; - } - - int j3 = i3 & 255; - d5 = d5 - (double) i3; - double d6 = d5 * d5 * d5 * (d5 * (d5 * 6.0D - 15.0D) + 10.0D); - - for (int k3 = 0; k3 < zSize; ++k3) { - double d7 = zOffset + (double) k3 * zScale + this.zCoord; - int l3 = (int) d7; - - if (d7 < (double) l3) { - --l3; - } - - int i4 = l3 & 255; - d7 = d7 - (double) l3; - double d8 = d7 * d7 * d7 * (d7 * (d7 * 6.0D - 15.0D) + 10.0D); - - for (int j4 = 0; j4 < ySize; ++j4) { - double d9 = yOffset + (double) j4 * yScale + this.yCoord; - int k4 = (int) d9; - - if (d9 < (double) k4) { - --k4; - } - - int l4 = k4 & 255; - d9 = d9 - (double) k4; - double d10 = d9 * d9 * d9 * (d9 * (d9 * 6.0D - 15.0D) + 10.0D); - - if (j4 == 0 || l4 != k) { - k = l4; - l = this.permutations[j3] + l4; - i1 = this.permutations[l] + i4; - j1 = this.permutations[l + 1] + i4; - k1 = this.permutations[j3 + 1] + l4; - l1 = this.permutations[k1] + i4; - i2 = this.permutations[k1 + 1] + i4; - d1 = this.lerp(d6, this.grad(this.permutations[i1], d5, d9, d7), this.grad(this.permutations[l1], d5 - 1.0D, d9, d7)); - d2 = this.lerp(d6, this.grad(this.permutations[j1], d5, d9 - 1.0D, d7), this.grad(this.permutations[i2], d5 - 1.0D, d9 - 1.0D, d7)); - d3 = this.lerp(d6, this.grad(this.permutations[i1 + 1], d5, d9, d7 - 1.0D), this.grad(this.permutations[l1 + 1], d5 - 1.0D, d9, d7 - 1.0D)); - d4 = this.lerp(d6, this.grad(this.permutations[j1 + 1], d5, d9 - 1.0D, d7 - 1.0D), this.grad(this.permutations[i2 + 1], d5 - 1.0D, d9 - 1.0D, d7 - 1.0D)); - } - - double d11 = this.lerp(d10, d1, d2); - double d12 = this.lerp(d10, d3, d4); - double d13 = this.lerp(d8, d11, d12); - int j7 = i++; - noiseArray[j7] += d13 * d0; - } - } - } - } - } -} diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorOctavesD.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorOctavesD.java deleted file mode 100644 index 76d10f240c6..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorOctavesD.java +++ /dev/null @@ -1,62 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.d; - -import cn.nukkit.math.MathHelper; -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorOctavesD { - /** - * Collection of noise generation functions. Output is combined to produce different octaves of noise. - */ - private final NoiseGeneratorImprovedD[] generatorCollection; - private final int octaves; - - public NoiseGeneratorOctavesD(NukkitRandom seed, int octavesIn) { - this.octaves = octavesIn; - this.generatorCollection = new NoiseGeneratorImprovedD[octavesIn]; - - for (int i = 0; i < octavesIn; ++i) { - this.generatorCollection[i] = new NoiseGeneratorImprovedD(seed); - } - } - - /* - * pars:(par2,3,4=noiseOffset ; so that adjacent noise segments connect) (pars5,6,7=x,y,zArraySize),(pars8,10,12 = - * x,y,z noiseScale) - */ - public double[] generateNoiseOctaves(double[] noiseArray, int xOffset, int yOffset, int zOffset, int xSize, int ySize, int zSize, double xScale, double yScale, double zScale) { - if (noiseArray == null) { - noiseArray = new double[xSize * ySize * zSize]; - } else { - for (int i = 0; i < noiseArray.length; ++i) { - noiseArray[i] = 0.0D; - } - } - - double d3 = 1.0D; - - for (int j = 0; j < this.octaves; ++j) { - double d0 = (double) xOffset * d3 * xScale; - double d1 = (double) yOffset * d3 * yScale; - double d2 = (double) zOffset * d3 * zScale; - long k = MathHelper.floor_double_long(d0); - long l = MathHelper.floor_double_long(d2); - d0 = d0 - (double) k; - d2 = d2 - (double) l; - k = k % 16777216L; - l = l % 16777216L; - d0 = d0 + (double) k; - d2 = d2 + (double) l; - this.generatorCollection[j].populateNoiseArray(noiseArray, d0, d1, d2, xSize, ySize, zSize, xScale * d3, yScale * d3, zScale * d3, d3); - d3 /= 2.0D; - } - - return noiseArray; - } - - /* - * Bouncer function to the main one with some default arguments. - */ - public double[] generateNoiseOctaves(double[] noiseArray, int xOffset, int zOffset, int xSize, int zSize, double xScale, double zScale, double p_76305_10_) { - return this.generateNoiseOctaves(noiseArray, xOffset, 10, zOffset, xSize, 1, zSize, xScale, 1.0D, zScale); - } -} diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorPerlinD.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorPerlinD.java deleted file mode 100644 index 6bf2f310f57..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorPerlinD.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.d; - -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorPerlinD { - private final NoiseGeneratorSimplexD[] noiseLevels; - private final int levels; - - public NoiseGeneratorPerlinD(NukkitRandom p_i45470_1_, int p_i45470_2_) { - this.levels = p_i45470_2_; - this.noiseLevels = new NoiseGeneratorSimplexD[p_i45470_2_]; - - for (int i = 0; i < p_i45470_2_; ++i) { - this.noiseLevels[i] = new NoiseGeneratorSimplexD(p_i45470_1_); - } - } - - public double getValue(double p_151601_1_, double p_151601_3_) { - double d0 = 0.0D; - double d1 = 1.0D; - - for (int i = 0; i < this.levels; ++i) { - d0 += this.noiseLevels[i].getValue(p_151601_1_ * d1, p_151601_3_ * d1) / d1; - d1 /= 2.0D; - } - - return d0; - } - - public double[] getRegion(double[] p_151599_1_, double p_151599_2_, double p_151599_4_, int p_151599_6_, int p_151599_7_, double p_151599_8_, double p_151599_10_, double p_151599_12_) { - return this.getRegion(p_151599_1_, p_151599_2_, p_151599_4_, p_151599_6_, p_151599_7_, p_151599_8_, p_151599_10_, p_151599_12_, 0.5D); - } - - public double[] getRegion(double[] p_151600_1_, double p_151600_2_, double p_151600_4_, int p_151600_6_, int p_151600_7_, double p_151600_8_, double p_151600_10_, double p_151600_12_, double p_151600_14_) { - if (p_151600_1_ != null && p_151600_1_.length >= p_151600_6_ * p_151600_7_) { - for (int i = 0; i < p_151600_1_.length; ++i) { - p_151600_1_[i] = 0.0D; - } - } else { - p_151600_1_ = new double[p_151600_6_ * p_151600_7_]; - } - - double d1 = 1.0D; - double d0 = 1.0D; - - for (int j = 0; j < this.levels; ++j) { - this.noiseLevels[j].add(p_151600_1_, p_151600_2_, p_151600_4_, p_151600_6_, p_151600_7_, p_151600_8_ * d0 * d1, p_151600_10_ * d0 * d1, 0.55D / d1); - d0 *= p_151600_12_; - d1 *= p_151600_14_; - } - - return p_151600_1_; - } -} diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorSimplexD.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorSimplexD.java deleted file mode 100644 index 78ca1ecbf1d..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/d/NoiseGeneratorSimplexD.java +++ /dev/null @@ -1,182 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.d; - -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorSimplexD { - public static final double SQRT_3 = Math.sqrt(3.0D); - private static final int[][] grad3 = new int[][]{{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}}; - private static final double F2 = 0.5D * (SQRT_3 - 1.0D); - private static final double G2 = (3.0D - SQRT_3) / 6.0D; - private final int[] p; - public double xo; - public double yo; - public double zo; - - public NoiseGeneratorSimplexD() { - this(new NukkitRandom(System.currentTimeMillis())); - } - - public NoiseGeneratorSimplexD(NukkitRandom p_i45471_1_) { - this.p = new int[512]; - this.xo = p_i45471_1_.nextDouble() * 256.0D; - this.yo = p_i45471_1_.nextDouble() * 256.0D; - this.zo = p_i45471_1_.nextDouble() * 256.0D; - - int i = 0; - while (i < 256) { - this.p[i] = i++; - } - - for (int l = 0; l < 256; ++l) { - int j = p_i45471_1_.nextBoundedInt(256 - l) + l; - int k = this.p[l]; - this.p[l] = this.p[j]; - this.p[j] = k; - this.p[l + 256] = this.p[l]; - } - } - - private static int fastFloor(double value) { - return value > 0.0D ? (int) value : (int) value - 1; - } - - private static double dot(int[] p_151604_0_, double p_151604_1_, double p_151604_3_) { - return (double) p_151604_0_[0] * p_151604_1_ + (double) p_151604_0_[1] * p_151604_3_; - } - - public double getValue(double p_151605_1_, double p_151605_3_) { - double d3 = 0.5D * (SQRT_3 - 1.0D); - double d4 = (p_151605_1_ + p_151605_3_) * d3; - int i = fastFloor(p_151605_1_ + d4); - int j = fastFloor(p_151605_3_ + d4); - double d5 = (3.0D - SQRT_3) / 6.0D; - double d6 = (double) (i + j) * d5; - double d7 = (double) i - d6; - double d8 = (double) j - d6; - double d9 = p_151605_1_ - d7; - double d10 = p_151605_3_ - d8; - int k; - int l; - - if (d9 > d10) { - k = 1; - l = 0; - } else { - k = 0; - l = 1; - } - - double d11 = d9 - (double) k + d5; - double d12 = d10 - (double) l + d5; - double d13 = d9 - 1.0D + 2.0D * d5; - double d14 = d10 - 1.0D + 2.0D * d5; - int i1 = i & 255; - int j1 = j & 255; - int k1 = this.p[i1 + this.p[j1]] % 12; - int l1 = this.p[i1 + k + this.p[j1 + l]] % 12; - int i2 = this.p[i1 + 1 + this.p[j1 + 1]] % 12; - double d15 = 0.5D - d9 * d9 - d10 * d10; - double d0; - - if (d15 < 0.0D) { - d0 = 0.0D; - } else { - d15 = d15 * d15; - d0 = d15 * d15 * dot(grad3[k1], d9, d10); - } - - double d16 = 0.5D - d11 * d11 - d12 * d12; - double d1; - - if (d16 < 0.0D) { - d1 = 0.0D; - } else { - d16 = d16 * d16; - d1 = d16 * d16 * dot(grad3[l1], d11, d12); - } - - double d17 = 0.5D - d13 * d13 - d14 * d14; - double d2; - - if (d17 < 0.0D) { - d2 = 0.0D; - } else { - d17 = d17 * d17; - d2 = d17 * d17 * dot(grad3[i2], d13, d14); - } - - return 70.0D * (d0 + d1 + d2); - } - - public void add(double[] p_151606_1_, double p_151606_2_, double p_151606_4_, int p_151606_6_, int p_151606_7_, double p_151606_8_, double p_151606_10_, double p_151606_12_) { - int i = 0; - - for (int j = 0; j < p_151606_7_; ++j) { - double d0 = (p_151606_4_ + (double) j) * p_151606_10_ + this.yo; - - for (int k = 0; k < p_151606_6_; ++k) { - double d1 = (p_151606_2_ + (double) k) * p_151606_8_ + this.xo; - double d5 = (d1 + d0) * F2; - int l = fastFloor(d1 + d5); - int i1 = fastFloor(d0 + d5); - double d6 = (double) (l + i1) * G2; - double d7 = (double) l - d6; - double d8 = (double) i1 - d6; - double d9 = d1 - d7; - double d10 = d0 - d8; - int j1; - int k1; - - if (d9 > d10) { - j1 = 1; - k1 = 0; - } else { - j1 = 0; - k1 = 1; - } - - double d11 = d9 - (double) j1 + G2; - double d12 = d10 - (double) k1 + G2; - double d13 = d9 - 1.0D + 2.0D * G2; - double d14 = d10 - 1.0D + 2.0D * G2; - int l1 = l & 255; - int i2 = i1 & 255; - int j2 = this.p[l1 + this.p[i2]] % 12; - int k2 = this.p[l1 + j1 + this.p[i2 + k1]] % 12; - int l2 = this.p[l1 + 1 + this.p[i2 + 1]] % 12; - double d15 = 0.5D - d9 * d9 - d10 * d10; - double d2; - - if (d15 < 0.0D) { - d2 = 0.0D; - } else { - d15 = d15 * d15; - d2 = d15 * d15 * dot(grad3[j2], d9, d10); - } - - double d16 = 0.5D - d11 * d11 - d12 * d12; - double d3; - - if (d16 < 0.0D) { - d3 = 0.0D; - } else { - d16 = d16 * d16; - d3 = d16 * d16 * dot(grad3[k2], d11, d12); - } - - double d17 = 0.5D - d13 * d13 - d14 * d14; - double d4; - - if (d17 < 0.0D) { - d4 = 0.0D; - } else { - d17 = d17 * d17; - d4 = d17 * d17 * dot(grad3[l2], d13, d14); - } - - int i3 = i++; - p_151606_1_[i3] += 70.0D * (d2 + d3 + d4) * p_151606_12_; - } - } - } -} diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorImprovedF.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorImprovedF.java index 954a3da6d12..10cd733a8ba 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorImprovedF.java +++ b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorImprovedF.java @@ -3,11 +3,11 @@ import cn.nukkit.math.NukkitRandom; public class NoiseGeneratorImprovedF { - private static final float[] GRAD_X = new float[]{1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f}; - private static final float[] GRAD_Y = new float[]{1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f}; - private static final float[] GRAD_Z = new float[]{0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f}; - private static final float[] GRAD_2X = new float[]{1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f}; - private static final float[] GRAD_2Z = new float[]{0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f}; + private static final float[] GRAD_X = {1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f}; + private static final float[] GRAD_Y = {1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f}; + private static final float[] GRAD_Z = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f}; + private static final float[] GRAD_2X = {1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f}; + private static final float[] GRAD_2Z = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f}; private final int[] permutations; public float xCoord; public float yCoord; diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorOctavesF.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorOctavesF.java index 605fc378dfb..d01b0f256e3 100644 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorOctavesF.java +++ b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorOctavesF.java @@ -3,6 +3,8 @@ import cn.nukkit.math.MathHelper; import cn.nukkit.math.NukkitRandom; +import java.util.Arrays; + public class NoiseGeneratorOctavesF { /** * Collection of noise generation functions. Output is combined to produce different octaves of noise. @@ -27,9 +29,7 @@ public float[] generateNoiseOctaves(float[] noiseArray, int xOffset, int yOffset if (noiseArray == null) { noiseArray = new float[xSize * ySize * zSize]; } else { - for (int i = 0; i < noiseArray.length; ++i) { - noiseArray[i] = 0.0f; - } + Arrays.fill(noiseArray, 0.0f); } float d3 = 1.0f; diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorPerlinF.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorPerlinF.java deleted file mode 100644 index 947bf5b6533..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorPerlinF.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.f; - -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorPerlinF { - private final NoiseGeneratorSimplexF[] noiseLevels; - private final int levels; - - public NoiseGeneratorPerlinF(NukkitRandom p_i45470_1_, int p_i45470_2_) { - this.levels = p_i45470_2_; - this.noiseLevels = new NoiseGeneratorSimplexF[p_i45470_2_]; - - for (int i = 0; i < p_i45470_2_; ++i) { - this.noiseLevels[i] = new NoiseGeneratorSimplexF(p_i45470_1_); - } - } - - public float getValue(float p_151601_1_, float p_151601_3_) { - float d0 = 0.0f; - float d1 = 1.0f; - - for (int i = 0; i < this.levels; ++i) { - d0 += this.noiseLevels[i].getValue(p_151601_1_ * d1, p_151601_3_ * d1) / d1; - d1 /= 2.0f; - } - - return d0; - } - - public float[] getRegion(float[] p_151599_1_, float p_151599_2_, float p_151599_4_, int p_151599_6_, int p_151599_7_, float p_151599_8_, float p_151599_10_, float p_151599_12_) { - return this.getRegion(p_151599_1_, p_151599_2_, p_151599_4_, p_151599_6_, p_151599_7_, p_151599_8_, p_151599_10_, p_151599_12_, 0.5f); - } - - public float[] getRegion(float[] p_151600_1_, float p_151600_2_, float p_151600_4_, int p_151600_6_, int p_151600_7_, float p_151600_8_, float p_151600_10_, float p_151600_12_, float p_151600_14_) { - if (p_151600_1_ != null && p_151600_1_.length >= p_151600_6_ * p_151600_7_) { - for (int i = 0; i < p_151600_1_.length; ++i) { - p_151600_1_[i] = 0.0f; - } - } else { - p_151600_1_ = new float[p_151600_6_ * p_151600_7_]; - } - - float d1 = 1.0f; - float d0 = 1.0f; - - for (int j = 0; j < this.levels; ++j) { - this.noiseLevels[j].add(p_151600_1_, p_151600_2_, p_151600_4_, p_151600_6_, p_151600_7_, p_151600_8_ * d0 * d1, p_151600_10_ * d0 * d1, 0.55f / d1); - d0 *= p_151600_12_; - d1 *= p_151600_14_; - } - - return p_151600_1_; - } -} diff --git a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorSimplexF.java b/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorSimplexF.java deleted file mode 100644 index d55d59e4105..00000000000 --- a/src/main/java/cn/nukkit/level/generator/noise/vanilla/f/NoiseGeneratorSimplexF.java +++ /dev/null @@ -1,182 +0,0 @@ -package cn.nukkit.level.generator.noise.vanilla.f; - -import cn.nukkit.math.NukkitRandom; - -public class NoiseGeneratorSimplexF { - public static final float SQRT_3 = (float) Math.sqrt(3.0f); - private static final int[][] grad3 = new int[][]{{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}}; - private static final float F2 = 0.5f * (SQRT_3 - 1.0f); - private static final float G2 = (3.0f - SQRT_3) / 6.0f; - private final int[] p; - public float xo; - public float yo; - public float zo; - - public NoiseGeneratorSimplexF() { - this(new NukkitRandom(System.currentTimeMillis())); - } - - public NoiseGeneratorSimplexF(NukkitRandom p_i45471_1_) { - this.p = new int[512]; - this.xo = p_i45471_1_.nextFloat() * 256.0f; - this.yo = p_i45471_1_.nextFloat() * 256.0f; - this.zo = p_i45471_1_.nextFloat() * 256.0f; - - int i = 0; - while (i < 256) { - this.p[i] = i++; - } - - for (int l = 0; l < 256; ++l) { - int j = p_i45471_1_.nextBoundedInt(256 - l) + l; - int k = this.p[l]; - this.p[l] = this.p[j]; - this.p[j] = k; - this.p[l + 256] = this.p[l]; - } - } - - private static int fastFloor(float value) { - return value > 0.0f ? (int) value : (int) value - 1; - } - - private static float dot(int[] p_151604_0_, float p_151604_1_, float p_151604_3_) { - return (float) p_151604_0_[0] * p_151604_1_ + (float) p_151604_0_[1] * p_151604_3_; - } - - public float getValue(float p_151605_1_, float p_151605_3_) { - float d3 = 0.5f * (SQRT_3 - 1.0f); - float d4 = (p_151605_1_ + p_151605_3_) * d3; - int i = fastFloor(p_151605_1_ + d4); - int j = fastFloor(p_151605_3_ + d4); - float d5 = (3.0f - SQRT_3) / 6.0f; - float d6 = (float) (i + j) * d5; - float d7 = (float) i - d6; - float d8 = (float) j - d6; - float d9 = p_151605_1_ - d7; - float d10 = p_151605_3_ - d8; - int k; - int l; - - if (d9 > d10) { - k = 1; - l = 0; - } else { - k = 0; - l = 1; - } - - float d11 = d9 - (float) k + d5; - float d12 = d10 - (float) l + d5; - float d13 = d9 - 1.0f + 2.0f * d5; - float d14 = d10 - 1.0f + 2.0f * d5; - int i1 = i & 255; - int j1 = j & 255; - int k1 = this.p[i1 + this.p[j1]] % 12; - int l1 = this.p[i1 + k + this.p[j1 + l]] % 12; - int i2 = this.p[i1 + 1 + this.p[j1 + 1]] % 12; - float d15 = 0.5f - d9 * d9 - d10 * d10; - float d0; - - if (d15 < 0.0f) { - d0 = 0.0f; - } else { - d15 = d15 * d15; - d0 = d15 * d15 * dot(grad3[k1], d9, d10); - } - - float d16 = 0.5f - d11 * d11 - d12 * d12; - float d1; - - if (d16 < 0.0f) { - d1 = 0.0f; - } else { - d16 = d16 * d16; - d1 = d16 * d16 * dot(grad3[l1], d11, d12); - } - - float d17 = 0.5f - d13 * d13 - d14 * d14; - float d2; - - if (d17 < 0.0f) { - d2 = 0.0f; - } else { - d17 = d17 * d17; - d2 = d17 * d17 * dot(grad3[i2], d13, d14); - } - - return 70.0f * (d0 + d1 + d2); - } - - public void add(float[] p_151606_1_, float p_151606_2_, float p_151606_4_, int p_151606_6_, int p_151606_7_, float p_151606_8_, float p_151606_10_, float p_151606_12_) { - int i = 0; - - for (int j = 0; j < p_151606_7_; ++j) { - float d0 = (p_151606_4_ + (float) j) * p_151606_10_ + this.yo; - - for (int k = 0; k < p_151606_6_; ++k) { - float d1 = (p_151606_2_ + (float) k) * p_151606_8_ + this.xo; - float d5 = (d1 + d0) * F2; - int l = fastFloor(d1 + d5); - int i1 = fastFloor(d0 + d5); - float d6 = (float) (l + i1) * G2; - float d7 = (float) l - d6; - float d8 = (float) i1 - d6; - float d9 = d1 - d7; - float d10 = d0 - d8; - int j1; - int k1; - - if (d9 > d10) { - j1 = 1; - k1 = 0; - } else { - j1 = 0; - k1 = 1; - } - - float d11 = d9 - (float) j1 + G2; - float d12 = d10 - (float) k1 + G2; - float d13 = d9 - 1.0f + 2.0f * G2; - float d14 = d10 - 1.0f + 2.0f * G2; - int l1 = l & 255; - int i2 = i1 & 255; - int j2 = this.p[l1 + this.p[i2]] % 12; - int k2 = this.p[l1 + j1 + this.p[i2 + k1]] % 12; - int l2 = this.p[l1 + 1 + this.p[i2 + 1]] % 12; - float d15 = 0.5f - d9 * d9 - d10 * d10; - float d2; - - if (d15 < 0.0f) { - d2 = 0.0f; - } else { - d15 = d15 * d15; - d2 = d15 * d15 * dot(grad3[j2], d9, d10); - } - - float d16 = 0.5f - d11 * d11 - d12 * d12; - float d3; - - if (d16 < 0.0f) { - d3 = 0.0f; - } else { - d16 = d16 * d16; - d3 = d16 * d16 * dot(grad3[k2], d11, d12); - } - - float d17 = 0.5f - d13 * d13 - d14 * d14; - float d4; - - if (d17 < 0.0f) { - d4 = 0.0f; - } else { - d17 = d17 * d17; - d4 = d17 * d17 * dot(grad3[l2], d13, d14); - } - - int i3 = i++; - p_151606_1_[i3] += 70.0f * (d2 + d3 + d4) * p_151606_12_; - } - } - } -} diff --git a/src/main/java/cn/nukkit/level/generator/object/BasicGenerator.java b/src/main/java/cn/nukkit/level/generator/object/BasicGenerator.java index 840f3f9b276..60da60ff5d4 100644 --- a/src/main/java/cn/nukkit/level/generator/object/BasicGenerator.java +++ b/src/main/java/cn/nukkit/level/generator/object/BasicGenerator.java @@ -11,9 +11,6 @@ public abstract class BasicGenerator { //also autism, see below public abstract boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position); - public void setDecorationDefaults() { - } - protected void setBlockAndNotifyAdequately(ChunkManager level, BlockVector3 pos, Block state) { setBlock(level, new Vector3(pos.x, pos.y, pos.z), state); } @@ -26,4 +23,4 @@ protected void setBlockAndNotifyAdequately(ChunkManager level, Vector3 pos, Bloc protected void setBlock(ChunkManager level, Vector3 v, Block b) { level.setBlockAt((int) v.x, (int) v.y, (int) v.z, b.getId(), b.getDamage()); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/object/ObjectTallGrass.java b/src/main/java/cn/nukkit/level/generator/object/ObjectTallGrass.java index 1d96514f673..9c432741e02 100644 --- a/src/main/java/cn/nukkit/level/generator/object/ObjectTallGrass.java +++ b/src/main/java/cn/nukkit/level/generator/object/ObjectTallGrass.java @@ -1,16 +1,28 @@ package cn.nukkit.level.generator.object; import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; +import cn.nukkit.utils.Utils; /** - * author: ItsLucas + * @author ItsLucas * Nukkit Project */ public class ObjectTallGrass { + + @SuppressWarnings("unused") // Backwards compatibility public static void growGrass(ChunkManager level, Vector3 pos, NukkitRandom random) { + growGrass(level, pos); + } + + public static void growGrass(ChunkManager level, Vector3 pos) { + int maxBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 255; + int minBlockY = level instanceof Level ? ((Level) level).getMinBlockY() : 0; + for (int i = 0; i < 128; ++i) { int num = 0; @@ -19,12 +31,14 @@ public static void growGrass(ChunkManager level, Vector3 pos, NukkitRandom rando int z = pos.getFloorZ(); while (true) { - if (num >= i / 16) { + if (num >= i >> 4) { if (level.getBlockIdAt(x, y, z) == Block.AIR) { - if (random.nextBoundedInt(8) == 0) { - //porktodo: biomes have specific flower types that can grow in them - if (random.nextBoolean()) { + if (Utils.random.nextInt(8) == 0) { + //TODO: biomes have specific flower types that can grow in them + if (Utils.rand()) { level.setBlockAt(x, y, z, Block.DANDELION); + } else if (Utils.rand()) { + level.setBlockAt(x, y, z, Block.TALL_GRASS, 2); // fern } else { level.setBlockAt(x, y, z, Block.POPPY); } @@ -36,11 +50,51 @@ public static void growGrass(ChunkManager level, Vector3 pos, NukkitRandom rando break; } - x += random.nextRange(-1, 1); - y += random.nextRange(-1, 1) * random.nextBoundedInt(3) / 2; - z += random.nextRange(-1, 1); + x += Utils.rand(-1, 1); + y += Utils.rand(-1, 1) * Utils.random.nextInt(3) >> 1; + z += Utils.rand(-1, 1); + + if (y > maxBlockY || y < minBlockY || level.getBlockIdAt(x, y - 1, z) != Block.GRASS) { + break; + } + + ++num; + } + } + } + + public static void growSeagrass(ChunkManager level, Vector3 pos) { + int maxBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 255; + int minBlockY = level instanceof Level ? ((Level) level).getMinBlockY() : 0; + + for (int i = 0; i < 48; ++i) { + int num = 0; + + int x = pos.getFloorX(); + int y = pos.getFloorY() + 1; + int z = pos.getFloorZ(); + + while (true) { + if (num >= i >> 4) { + int block = level.getBlockIdAt(x, y, z); + if (block == Block.WATER || block == Block.STILL_WATER) { + //if (Utils.random.nextInt(8) == 0) { + // TODO: coral & tall seagrass + //} else { + level.setBlockAt(x, y, z, Block.SEAGRASS, 0); + level.setBlockAtLayer(x, y, z, BlockLayer.WATERLOGGED, Block.WATER, 0); + //} + } + + break; + } + + x += Utils.rand(-1, 1); + y += Utils.rand(-1, 1) * Utils.random.nextInt(3) >> 1; + z += Utils.rand(-1, 1); - if (level.getBlockIdAt(x, y - 1, z) != Block.GRASS || y > 255 || y < 0) { + int block; + if (y > maxBlockY || y < minBlockY || ((block = level.getBlockIdAt(x, y - 1, z)) != Block.DIRT && block != Block.SAND && block != Block.GRAVEL)) { break; } diff --git a/src/main/java/cn/nukkit/level/generator/object/mushroom/BigMushroom.java b/src/main/java/cn/nukkit/level/generator/object/mushroom/BigMushroom.java index 19d261ef173..6317e6100aa 100644 --- a/src/main/java/cn/nukkit/level/generator/object/mushroom/BigMushroom.java +++ b/src/main/java/cn/nukkit/level/generator/object/mushroom/BigMushroom.java @@ -3,6 +3,7 @@ import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.level.generator.object.BasicGenerator; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; @@ -27,7 +28,7 @@ public class BigMushroom extends BasicGenerator { /** * The mushroom type. 0 for brown, 1 for red. */ - private int mushroomType; + private final int mushroomType; public BigMushroom(int mushroomType) { this.mushroomType = mushroomType; @@ -48,12 +49,15 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) int i = rand.nextBoundedInt(3) + 4; if (rand.nextBoundedInt(12) == 0) { - i *= 2; + i <<= 1; } boolean flag = true; - if (position.getY() >= 1 && position.getY() + i + 1 < 256) { + int maxY = level instanceof Level ? ((Level) level).getMaxBlockY() + 1 : 256; + int minBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 0; + + if (position.getY() > minBlockY && position.getY() + i + 1 < maxY) { for (int j = position.getFloorY(); j <= position.getY() + 1 + i; ++j) { int k = 3; @@ -65,7 +69,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) for (int l = position.getFloorX() - k; l <= position.getX() + k && flag; ++l) { for (int i1 = position.getFloorZ() - k; i1 <= position.getZ() + k && flag; ++i1) { - if (j >= 0 && j < 256) { + if (j >= 0 && j < maxY) { pos.setComponents(l, j, i1); int material = level.getBlockIdAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()); @@ -173,7 +177,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) if (position.getY() >= position.getY() + i - 1 || meta != ALL_INSIDE) { Vector3 blockPos = new Vector3(l1, l2, i2); - if (!Block.solid[level.getBlockIdAt(blockPos.getFloorX(), blockPos.getFloorY(), blockPos.getFloorZ())]) { + if (!Block.isBlockSolidById(level.getBlockIdAt(blockPos.getFloorX(), blockPos.getFloorY(), blockPos.getFloorZ()))) { mushroom.setDamage(meta); this.setBlockAndNotifyAdequately(level, blockPos, mushroom); } @@ -186,7 +190,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) Vector3 pos = position.up(i3); int id = level.getBlockIdAt(pos.getFloorX(), pos.getFloorY(), pos.getFloorZ()); - if (!Block.solid[id]) { + if (!Block.isBlockSolidById(id)) { mushroom.setDamage(STEM); this.setBlockAndNotifyAdequately(level, pos, mushroom); } diff --git a/src/main/java/cn/nukkit/level/generator/object/ore/OreType.java b/src/main/java/cn/nukkit/level/generator/object/ore/OreType.java index ce318bae2c3..2e06274aa6f 100644 --- a/src/main/java/cn/nukkit/level/generator/object/ore/OreType.java +++ b/src/main/java/cn/nukkit/level/generator/object/ore/OreType.java @@ -8,11 +8,11 @@ import static cn.nukkit.block.BlockID.STONE; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ -//porktodo: rewrite this, the whole class is terrible and generated ores look stupid public class OreType { + public final int fullId; public final int clusterCount; public final int clusterSize; @@ -79,7 +79,7 @@ public boolean spawn(ChunkManager level, NukkitRandom rand, int replaceId, int x } } } - + return true; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/HugeTreesGenerator.java b/src/main/java/cn/nukkit/level/generator/object/tree/HugeTreesGenerator.java index ab582cf98e9..7015aa98b60 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/HugeTreesGenerator.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/HugeTreesGenerator.java @@ -2,6 +2,8 @@ import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; @@ -29,7 +31,7 @@ public HugeTreesGenerator(int baseHeightIn, int extraRandomHeightIn, Block woodM this.leavesMetadata = leavesMetadataIn; } - /* + /** * Calculates the height based on this trees base height and its extra random height */ protected int getHeight(NukkitRandom rand) { @@ -42,13 +44,16 @@ protected int getHeight(NukkitRandom rand) { return i; } - /* + /** * returns whether or not there is space for a tree to grow at a certain position */ private boolean isSpaceAt(ChunkManager worldIn, Vector3 leavesPos, int height) { boolean flag = true; - if (leavesPos.getY() >= 1 && leavesPos.getY() + height + 1 <= 256) { + int maxY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() + 1 : 256; + int minBlockY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() : 0; + + if (leavesPos.getY() > minBlockY && leavesPos.getY() + height + 1 <= maxY) { for (int i = 0; i <= 1 + height; ++i) { int j = 2; @@ -61,7 +66,7 @@ private boolean isSpaceAt(ChunkManager worldIn, Vector3 leavesPos, int height) { for (int k = -j; k <= j && flag; ++k) { for (int l = -j; l <= j && flag; ++l) { Vector3 blockPos = leavesPos.add(k, i, l); - if (leavesPos.getY() + i < 0 || leavesPos.getY() + i >= 256 || !this.canGrowInto(worldIn.getBlockIdAt((int) blockPos.x, (int) blockPos.y, (int) blockPos.z))) { + if (leavesPos.getY() + i < 0 || leavesPos.getY() + i >= maxY || !this.canGrowInto(worldIn.getBlockIdAt((int) blockPos.x, (int) blockPos.y, (int) blockPos.z))) { flag = false; } } @@ -74,12 +79,12 @@ private boolean isSpaceAt(ChunkManager worldIn, Vector3 leavesPos, int height) { } } - /* + /** * returns whether or not there is dirt underneath the block where the tree will be grown. * It also generates dirt around the block in a 2x2 square if there is dirt underneath the blockpos. */ private boolean ensureDirtsUnderneath(Vector3 pos, ChunkManager worldIn) { - Vector3 blockpos = pos.down(); + Vector3 blockpos = pos.getSideVec(BlockFace.DOWN); int block = worldIn.getBlockIdAt((int) blockpos.x, (int) blockpos.y, (int) blockpos.z); if ((block == Block.GRASS || block == Block.DIRT) && pos.getY() >= 2) { @@ -93,15 +98,15 @@ private boolean ensureDirtsUnderneath(Vector3 pos, ChunkManager worldIn) { } } - /* + /** * returns whether or not a tree can grow at a specific position. * If it can, it generates surrounding dirt underneath. */ - protected boolean ensureGrowable(ChunkManager worldIn, NukkitRandom rand, Vector3 treePos, int p_175929_4_) { - return this.isSpaceAt(worldIn, treePos, p_175929_4_) && this.ensureDirtsUnderneath(treePos, worldIn); + protected boolean ensureGrowable(ChunkManager worldIn, Vector3 treePos, int height) { + return this.isSpaceAt(worldIn, treePos, height) && this.ensureDirtsUnderneath(treePos, worldIn); } - /* + /** * grow leaves in a circle with the outsides being within the circle */ protected void growLeavesLayerStrict(ChunkManager worldIn, Vector3 layerCenter, int width) { @@ -124,7 +129,7 @@ protected void growLeavesLayerStrict(ChunkManager worldIn, Vector3 layerCenter, } } - /* + /** * grow leaves in a circle */ protected void growLeavesLayer(ChunkManager worldIn, Vector3 layerCenter, int width) { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java index f9b1cfc579e..7c6dec21f07 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/NewJungleTree.java @@ -1,7 +1,11 @@ package cn.nukkit.level.generator.object.tree; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockLeaves; +import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockVector3; import cn.nukkit.math.NukkitRandom; @@ -17,6 +21,9 @@ public class NewJungleTree extends TreeGenerator { */ private final int minTreeHeight; + /** + * The maxium height of a generated tree. + */ private final int maxTreeHeight; /** @@ -29,6 +36,10 @@ public class NewJungleTree extends TreeGenerator { */ private final Block metaLeaves = Block.get(BlockID.LEAVES, BlockLeaves.JUNGLE); + public NewJungleTree(int minTreeHeight) { + this(minTreeHeight, 3); + } + public NewJungleTree(int minTreeHeight, int maxTreeHeight) { this.minTreeHeight = minTreeHeight; this.maxTreeHeight = maxTreeHeight; @@ -41,7 +52,10 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP int i = rand.nextBoundedInt(maxTreeHeight) + this.minTreeHeight; boolean flag = true; - if (position.getY() >= 1 && position.getY() + i + 1 <= 256) { + int maxY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() + 1 : 256; + int minBlockY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() : 0; + + if (position.getY() > minBlockY && position.getY() + i + 1 <= maxY) { for (int j = position.getY(); j <= position.getY() + 1 + i; ++j) { int k = 1; @@ -57,7 +71,7 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP for (int l = position.getX() - k; l <= position.getX() + k && flag; ++l) { for (int i1 = position.getZ() - k; i1 <= position.getZ() + k && flag; ++i1) { - if (j >= 0 && j < 256) { + if (j >= 0 && j < maxY) { pos2.setComponents(l, j, i1); if (!this.canGrowInto(worldIn.getBlockIdAt(pos2.x, pos2.y, pos2.z))) { flag = false; @@ -75,14 +89,12 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP BlockVector3 down = position.down(); int block = worldIn.getBlockIdAt(down.x, down.y, down.z); - if ((block == Block.GRASS || block == Block.DIRT || block == Block.FARMLAND) && position.getY() < 256 - i - 1) { + if ((block == Block.GRASS || block == Block.DIRT || block == Block.FARMLAND) && position.getY() < maxY - i - 1) { this.setDirtAt(worldIn, down); - int k2 = 3; - int l2 = 0; for (int i3 = position.getY() - 3 + i; i3 <= position.getY() + i; ++i3) { int i4 = i3 - (position.getY() + i); - int j1 = 1 - i4 / 2; + int j1 = 1 - (i4 >> 1); for (int k1 = position.getX() - j1; k1 <= position.getX() + j1; ++k1) { int l1 = k1 - position.getX(); @@ -131,7 +143,7 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP for (int k3 = position.getY() - 3 + i; k3 <= position.getY() + i; ++k3) { int j4 = k3 - (position.getY() + i); - int k4 = 2 - j4 / 2; + int k4 = 2 - (j4 >> 1); BlockVector3 pos2 = new BlockVector3(); for (int l4 = position.getX() - k4; l4 <= position.getX() + k4; ++l4) { @@ -169,7 +181,7 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP for (BlockFace enumfacing : BlockFace.Plane.HORIZONTAL) { if (rand.nextBoundedInt(4 - l3) == 0) { BlockFace enumfacing1 = enumfacing.getOpposite(); - this.placeCocoa(worldIn, rand.nextBoundedInt(3), position.add(enumfacing1.getXOffset(), i - 5 + l3, enumfacing1.getZOffset()), enumfacing); + this.placeCocoa(worldIn, /*rand.nextBoundedInt(3),*/ position.add(enumfacing1.getXOffset(), i - 5 + l3, enumfacing1.getZOffset()), enumfacing); } } } @@ -185,10 +197,8 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP } } - private void placeCocoa(ChunkManager worldIn, int age, BlockVector3 pos, BlockFace side) { - int meta = getCocoaMeta(age, side.getIndex()); - - this.setBlockAndNotifyAdequately(worldIn, pos, new BlockUnknown(127, meta)); + private void placeCocoa(ChunkManager worldIn, /*int age,*/ BlockVector3 pos, BlockFace side) { + this.setBlockAndNotifyAdequately(worldIn, pos, Block.get(BlockID.COCOA_BLOCK, getCocoaMeta(side.getIndex()))); } private void addVine(ChunkManager worldIn, BlockVector3 pos, int meta) { @@ -205,12 +215,12 @@ private void addHangingVine(ChunkManager worldIn, BlockVector3 pos, int meta) { } } - private boolean isAirBlock(ChunkManager level, BlockVector3 v) { + private static boolean isAirBlock(ChunkManager level, BlockVector3 v) { return level.getBlockIdAt(v.x, v.y, v.z) == 0; } - private int getCocoaMeta(int age, int side) { - int meta = age * 4; + private static int getCocoaMeta(int side) { + int meta = 0; //3 4 2 5 switch (side) { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBigSpruceTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBigSpruceTree.java index 890bf1c8db2..183143e969e 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBigSpruceTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBigSpruceTree.java @@ -3,23 +3,31 @@ import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class ObjectBigSpruceTree extends ObjectSpruceTree { + private final float leafStartHeightMultiplier; private final int baseLeafRadius; + private final boolean fromSapling; public ObjectBigSpruceTree(float leafStartHeightMultiplier, int baseLeafRadius) { + this(leafStartHeightMultiplier, baseLeafRadius, false); + } + + public ObjectBigSpruceTree(float leafStartHeightMultiplier, int baseLeafRadius, boolean fromSapling) { this.leafStartHeightMultiplier = leafStartHeightMultiplier; this.baseLeafRadius = baseLeafRadius; + this.fromSapling = fromSapling; } @Override public void placeObject(ChunkManager level, int x, int y, int z, NukkitRandom random) { - this.treeHeight = random.nextBoundedInt(15) + 20; + this.treeHeight = fromSapling ? Utils.rand(15, 20) : random.nextBoundedInt(15) + 20; int topSize = this.treeHeight - (int) (this.treeHeight * leafStartHeightMultiplier); int lRadius = baseLeafRadius + random.nextBoundedInt(2); @@ -46,4 +54,51 @@ protected void placeTrunk(ChunkManager level, int x, int y, int z, NukkitRandom } } } + + @Override + public void placeLeaves(ChunkManager level, int topSize, int lRadius, int x, int y, int z, NukkitRandom random) { + int radius = random.nextBoundedInt(2); + int maxR = 1; + int minR = 0; + + for (int yy = 0; yy <= topSize; ++yy) { + int yyy = y + this.treeHeight - yy; + + for (int xx = x - radius; xx <= x + radius + 1; ++xx) { + for (int zz = z - radius; zz <= z + radius + 1; ++zz) { + if (topSize - yy > 1 && radius > 0) { + if (Math.abs(xx - x) > radius && Math.abs(zz - z) > radius) { + continue; + } + + if (xx - x <= -radius && zz - z <= -radius) { + continue; + } + + if (Math.abs(xx - x) > radius && zz - z <= -radius) { + continue; + } + + if (xx - x <= -radius && Math.abs(zz - z) > radius) { + continue; + } + } + + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yyy, zz))) { + level.setBlockAt(xx, yyy, zz, this.getLeafBlock(), this.getType()); + } + } + } + + if (radius >= maxR) { + radius = minR; + minR = 1; + if (++maxR > lRadius) { + maxR = lRadius; + } + } else { + ++radius; + } + } + } } diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBirchTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBirchTree.java index 6b6ed4a7d16..81701139e80 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBirchTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectBirchTree.java @@ -1,26 +1,16 @@ package cn.nukkit.level.generator.object.tree; -import cn.nukkit.block.Block; import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ObjectBirchTree extends ObjectTree { - protected int treeHeight = 7; - - @Override - public int getTrunkBlock() { - return Block.LOG; - } - @Override - public int getLeafBlock() { - return Block.LEAVES; - } + protected int treeHeight = 7; @Override public int getType() { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectCrimsonTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectCrimsonTree.java new file mode 100644 index 00000000000..77cf2a1f4e1 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectCrimsonTree.java @@ -0,0 +1,19 @@ +package cn.nukkit.level.generator.object.tree; + +import cn.nukkit.block.Block; + +/** + * @author FlamingKnight + */ +public class ObjectCrimsonTree extends ObjectNetherTree { + + @Override + public int getTrunkBlock() { + return Block.CRIMSON_STEM; + } + + @Override + public int getLeafBlock() { + return Block.BLOCK_NETHER_WART_BLOCK; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectDarkOakTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectDarkOakTree.java index e4fa570ba13..1994a3f7530 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectDarkOakTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectDarkOakTree.java @@ -5,6 +5,7 @@ import cn.nukkit.block.BlockLeaves2; import cn.nukkit.block.BlockWood2; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; @@ -13,6 +14,7 @@ * Created by CreeperFace on 23. 10. 2016. */ public class ObjectDarkOakTree extends TreeGenerator { + private static final Block DARK_OAK_LOG = Block.get(BlockID.WOOD2, BlockWood2.DARK_OAK); private static final Block DARK_OAK_LEAVES = Block.get(BlockID.LEAVES2, BlockLeaves2.DARK_OAK); @@ -23,7 +25,8 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) int k = position.getFloorY(); int l = position.getFloorZ(); - if (k >= 1 && k + i + 1 < 256) { + int maxY = level instanceof Level ? ((Level) level).getMaxBlockY() + 1 : 256; + if (k >= 1 && k + i + 1 < maxY) { Vector3 blockpos = position.down(); int block = level.getBlockIdAt(blockpos.getFloorX(), blockpos.getFloorY(), blockpos.getFloorZ()); diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectFallenTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectFallenTree.java new file mode 100644 index 00000000000..e57bd6ff840 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectFallenTree.java @@ -0,0 +1,61 @@ +package cn.nukkit.level.generator.object.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.math.NukkitRandom; + +public class ObjectFallenTree { + + public ObjectFallenTree(ChunkManager level, int x, int y, int z, int type, NukkitRandom random) { + placeObject(level, x, y, z, type, random); + } + + public void placeObject(ChunkManager level, int x, int y, int z, int type, NukkitRandom random) { + int direction = random.nextRange(1, 2); + if (direction == 1 && type == 0) { //oak tree + //tree + level.setBlockAt(x - 1, y, z, Block.WOOD); + level.setBlockAt(x + 1, y, z, Block.WOOD, 12); + level.setBlockAt(x + 2, y, z, Block.WOOD, 12); + //vines + level.setBlockAt(x - 2, y, z, Block.VINES, 8); + level.setBlockAt(x - 1, y, z + 1, Block.VINES, 4); + level.setBlockAt(x - 1, y, z - 1, Block.VINES, 1); + } else if (direction == 1 && type == 1) { //birch + //tree + level.setBlockAt(x - 1, y, z, Block.WOOD, 2); + level.setBlockAt(x + 1, y, z, Block.WOOD, 14); + level.setBlockAt(x + 2, y, z, Block.WOOD, 14); + level.setBlockAt(x + 3, y, z, Block.WOOD, 14); + //vines + level.setBlockAt(x - 2, y, z, Block.VINES, 8); + level.setBlockAt(x - 1, y, z + 1, Block.VINES, 4); + level.setBlockAt(x - 1, y, z - 1, Block.VINES, 1); + //mushrooms + level.setBlockAt(x + 1, y + 1, z, Block.BROWN_MUSHROOM); + level.setBlockAt(x + 3, y + 1, z, Block.RED_MUSHROOM); + } else if (direction == 2 && type == 0) { //oak tree + //tree + level.setBlockAt(x, y, z - 1, Block.WOOD); + level.setBlockAt(x, y, z + 1, Block.WOOD, 12); + level.setBlockAt(x, y, z + 2, Block.WOOD, 12); + //vines + level.setBlockAt(x, y, z - 2, Block.VINES, 1); + level.setBlockAt(x + 1, y, z - 1, Block.VINES, 2); + level.setBlockAt(x - 1, y, z - 1, Block.VINES, 8); + } else if (direction == 2 && type == 1) { //birch + //tree + level.setBlockAt(x, y, z - 1, Block.WOOD, 2); + level.setBlockAt(x, y, z + 1, Block.WOOD, 14); + level.setBlockAt(x, y, z + 2, Block.WOOD, 14); + level.setBlockAt(x, y, z + 3, Block.WOOD, 14); + //vines + level.setBlockAt(x, y, z - 2, Block.VINES, 1); + level.setBlockAt(x + 1, y, z - 1, Block.VINES, 2); + level.setBlockAt(x - 1, y, z - 1, Block.VINES, 8); + //mushrooms + level.setBlockAt(x, y + 1, z + 1, Block.BROWN_MUSHROOM); + level.setBlockAt(x, y + 1, z + 3, Block.RED_MUSHROOM); + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleBigTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleBigTree.java index 4a4d7727c79..7457447e27f 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleBigTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleBigTree.java @@ -3,7 +3,6 @@ import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.level.ChunkManager; -import cn.nukkit.math.BlockVector3; import cn.nukkit.math.MathHelper; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; @@ -16,27 +15,27 @@ public ObjectJungleBigTree(int baseHeightIn, int extraRandomHeight, Block woodMe public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) { int height = this.getHeight(rand); - if (!this.ensureGrowable(level, rand, position, height)) { + if (!this.ensureGrowable(level, position, height)) { return false; } else { this.createCrown(level, position.up(height), 2); - for (int j = (int) position.getY() + height - 2 - rand.nextBoundedInt(4); j > position.getY() + height / 2; j -= 2 + rand.nextBoundedInt(4)) { - float f = rand.nextFloat() * ((float) Math.PI * 2F); + for (int j = (int) position.getY() + height - 2 - rand.nextBoundedInt(4); j > position.getY() + (height >> 1); j -= 2 + rand.nextBoundedInt(4)) { + float f = rand.nextFloat() * (6.2831855f); int k = (int) (position.getX() + (0.5F + MathHelper.cos(f) * 4.0F)); int l = (int) (position.getZ() + (0.5F + MathHelper.sin(f) * 4.0F)); for (int i1 = 0; i1 < 5; ++i1) { k = (int) (position.getX() + (1.5F + MathHelper.cos(f) * (float) i1)); l = (int) (position.getZ() + (1.5F + MathHelper.sin(f) * (float) i1)); - this.setBlockAndNotifyAdequately(level, new BlockVector3(k, j - 3 + i1 / 2, l), this.woodMetadata); + this.setBlockAndNotifyAdequately(level, new Vector3(k, j - 3 + (i1 >> 1), l - 1), this.woodMetadata); } int j2 = 1 + rand.nextBoundedInt(2); for (int k1 = j - j2; k1 <= j; ++k1) { int l1 = k1 - j; - this.growLeavesLayer(level, new Vector3(k, k1, l), 1 - l1); + this.growLeavesLayer(level, new Vector3(k, k1, l - 1), 1 - l1); } } @@ -94,7 +93,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) private void placeVine(ChunkManager level, NukkitRandom random, Vector3 pos, int meta) { if (random.nextBoundedInt(3) > 0 && level.getBlockIdAt((int) pos.x, (int) pos.y, (int) pos.z) == 0) { - this.setBlockAndNotifyAdequately(level, pos, Block.get(BlockID.VINE, meta)); + this.setBlockAndNotifyAdequately(level, pos, Block.get(BlockID.VINES, meta)); } } diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleTree.java index c50f8fe2905..e3a0e4e6735 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectJungleTree.java @@ -1,27 +1,16 @@ package cn.nukkit.level.generator.object.tree; -import cn.nukkit.block.Block; import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ObjectJungleTree extends ObjectTree { private int treeHeight = 8; - @Override - public int getTrunkBlock() { - return Block.LOG; - } - - @Override - public int getLeafBlock() { - return Block.LEAVES; - } - @Override public int getType() { return BlockWood.JUNGLE; diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectNetherTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectNetherTree.java new file mode 100644 index 00000000000..e0f93b3a047 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectNetherTree.java @@ -0,0 +1,131 @@ +package cn.nukkit.level.generator.object.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; +import cn.nukkit.math.NukkitRandom; + +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author FlamingKnight + */ +public abstract class ObjectNetherTree extends ObjectTree { + + private final int treeHeight; + + public ObjectNetherTree() { + this(ThreadLocalRandom.current().nextInt(9) + 4); + } + + public ObjectNetherTree(int treeHeight) { + this.treeHeight = treeHeight; + } + + @Override + public int getTreeHeight() { + return treeHeight; + } + + @Override + public void placeObject(ChunkManager level, int x, int y, int z, NukkitRandom random) { + int maxBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 255; + + if (y >= maxBlockY) { + return; + } + + this.placeTrunk(level, x, y, z, random, this.getTreeHeight()); + + int mid = 2; + for (int yy = y - 3 + treeHeight; yy <= y + this.treeHeight - 1; ++yy) { + if (yy >= maxBlockY) { + continue; + } + + for (int xx = x - mid; xx <= x + mid; xx++) { + int xOff = Math.abs(xx - x); + for (int zz = z - mid; zz <= z + mid; zz += mid * 2) { + int zOff = Math.abs(zz - z); + if (xOff == mid && zOff == mid && random.nextBoundedInt(2) == 0) { + continue; + } + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy, zz))) { + if (random.nextBoundedInt(20) == 0) level.setBlockAt(xx, yy, zz, Block.SHROOMLIGHT); + else level.setBlockAt(xx, yy, zz, this.getLeafBlock()); + } + } + } + + for (int zz = z - mid; zz <= z + mid; zz++) { + int zOff = Math.abs(zz - z); + for (int xx = x - mid; xx <= x + mid; xx += mid * 2) { + int xOff = Math.abs(xx - x); + if (xOff == mid && zOff == mid && (random.nextBoundedInt(2) == 0)) { + continue; + } + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy, zz))) { + if (random.nextBoundedInt(20) == 0) level.setBlockAt(xx, yy, zz, Block.SHROOMLIGHT); + else level.setBlockAt(xx, yy, zz, this.getLeafBlock()); + } + } + } + } + + for (int yy = y - 4 + treeHeight; yy <= y + this.treeHeight - 3; ++yy) { + if (yy >= maxBlockY) { + continue; + } + + for (int xx = x - mid; xx <= x + mid; xx++) { + for (int zz = z - mid; zz <= z + mid; zz += mid * 2) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy, zz))) { + if (random.nextBoundedInt(3) == 0) { + for (int i = 0; i < random.nextBoundedInt(5); i++) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy - i, zz))) + level.setBlockAt(xx, yy - i, zz, getLeafBlock()); + } + } + } + } + } + + for (int zz = z - mid; zz <= z + mid; zz++) { + for (int xx = x - mid; xx <= x + mid; xx += mid * 2) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy, zz))) { + if (random.nextBoundedInt(3) == 0) { + for (int i = 0; i < random.nextBoundedInt(4); i++) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy - i, zz))) + level.setBlockAt(xx, yy - i, zz, getLeafBlock()); + } + } + } + } + } + } + + for (int xCanopy = x - mid + 1; xCanopy <= x + mid - 1; xCanopy++) { + for (int zCanopy = z - mid + 1; zCanopy <= z + mid - 1; zCanopy++) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xCanopy, y + treeHeight, zCanopy))) + level.setBlockAt(xCanopy, y + treeHeight, zCanopy, getLeafBlock()); + } + } + } + + @Override + protected void placeTrunk(ChunkManager level, int x, int y, int z, NukkitRandom random, int trunkHeight) { + int maxBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 255; + + level.setBlockAt(x, y, z, getTrunkBlock()); + + for (int yy = 0; yy < trunkHeight; ++yy) { + if (y + yy >= maxBlockY) { + continue; + } + int blockId = level.getBlockIdAt(x, y + yy, z); + if (this.overridable(blockId)) { + level.setBlockAt(x, y + yy, z, this.getTrunkBlock()); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectOakTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectOakTree.java index 66127162d52..35592f7ee4e 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectOakTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectOakTree.java @@ -1,26 +1,16 @@ package cn.nukkit.level.generator.object.tree; -import cn.nukkit.block.Block; import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ObjectOakTree extends ObjectTree { - private int treeHeight = 7; - - @Override - public int getTrunkBlock() { - return Block.LOG; - } - @Override - public int getLeafBlock() { - return Block.LEAVES; - } + private int treeHeight = 7; @Override public int getType() { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSavannaTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSavannaTree.java index 71226eb541f..d9cd6555775 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSavannaTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSavannaTree.java @@ -5,11 +5,13 @@ import cn.nukkit.block.BlockLeaves2; import cn.nukkit.block.BlockWood2; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; public class ObjectSavannaTree extends TreeGenerator { + private static final Block TRUNK = Block.get(BlockID.WOOD2, BlockWood2.ACACIA); private static final Block LEAF = Block.get(BlockID.LEAVES2, BlockLeaves2.ACACIA); @@ -17,7 +19,10 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) int i = rand.nextBoundedInt(3) + rand.nextBoundedInt(3) + 5; boolean flag = true; - if (position.getY() >= 1 && position.getY() + i + 1 <= 256) { + int maxY = level instanceof Level ? ((Level) level).getMaxBlockY() + 1 : 256; + int minBlockY = level instanceof Level ? ((Level) level).getMaxBlockY() : 0; + + if (position.getY() > minBlockY && position.getY() + i + 1 <= maxY) { for (int j = (int) position.getY(); j <= position.getY() + 1 + i; ++j) { int k = 1; @@ -33,7 +38,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) for (int l = (int) position.getX() - k; l <= position.getX() + k && flag; ++l) { for (int i1 = (int) position.getZ() - k; i1 <= position.getZ() + k && flag; ++i1) { - if (j >= 0 && j < 256) { + if (j >= 0 && j < maxY) { vector3.setComponents(l, j, i1); if (!this.canGrowInto(level.getBlockIdAt((int) vector3.x, (int) vector3.y, (int) vector3.z))) { @@ -52,7 +57,7 @@ public boolean generate(ChunkManager level, NukkitRandom rand, Vector3 position) Vector3 down = position.down(); int block = level.getBlockIdAt(down.getFloorX(), down.getFloorY(), down.getFloorZ()); - if ((block == Block.GRASS || block == Block.DIRT) && position.getY() < 256 - i - 1) { + if ((block == Block.GRASS || block == Block.DIRT) && position.getY() < maxY - i - 1) { this.setDirtAt(level, position.down()); BlockFace face = BlockFace.Plane.HORIZONTAL.random(rand); int k2 = i - rand.nextBoundedInt(4) - 1; diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java index 89c4f7792ef..020e7282507 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java @@ -6,21 +6,12 @@ import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ObjectSpruceTree extends ObjectTree { - protected int treeHeight; - @Override - public int getTrunkBlock() { - return Block.LOG; - } - - @Override - public int getLeafBlock() { - return Block.LEAVES; - } + protected int treeHeight = 15; @Override public int getType() { @@ -36,10 +27,10 @@ public int getTreeHeight() { public void placeObject(ChunkManager level, int x, int y, int z, NukkitRandom random) { this.treeHeight = random.nextBoundedInt(4) + 6; - int topSize = this.getTreeHeight() - (1 + random.nextBoundedInt(2)); + int topSize = this.treeHeight - (1 + random.nextBoundedInt(2)); int lRadius = 2 + random.nextBoundedInt(2); - this.placeTrunk(level, x, y, z, random, this.getTreeHeight() - random.nextBoundedInt(3)); + this.placeTrunk(level, x, y, z, random, this.treeHeight - random.nextBoundedInt(2)); this.placeLeaves(level, topSize, lRadius, x, y, z, random); } @@ -60,7 +51,7 @@ public void placeLeaves(ChunkManager level, int topSize, int lRadius, int x, int continue; } - if (!Block.solid[level.getBlockIdAt(xx, yyy, zz)]) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yyy, zz))) { level.setBlockAt(xx, yyy, zz, this.getLeafBlock(), this.getType()); } } diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSwampTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSwampTree.java index bdc871c38d7..51426ef3ac3 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSwampTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSwampTree.java @@ -1,7 +1,11 @@ package cn.nukkit.level.generator.object.tree; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.block.BlockLeaves; +import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockVector3; import cn.nukkit.math.NukkitRandom; import cn.nukkit.math.Vector3; @@ -25,7 +29,10 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP int i = rand.nextBoundedInt(4) + 5; boolean flag = true; - if (position.getY() >= 1 && position.getY() + i + 1 <= 256) { + int maxY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() + 1 : 256; + int minBlockY = worldIn instanceof Level ? ((Level) worldIn).getMaxBlockY() : 0; + + if (position.getY() > minBlockY && position.getY() + i + 1 <= maxY) { for (int j = position.getY(); j <= position.getY() + 1 + i; ++j) { int k = 1; @@ -41,7 +48,7 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP for (int l = position.getX() - k; l <= position.getX() + k && flag; ++l) { for (int i1 = position.getZ() - k; i1 <= position.getZ() + k && flag; ++i1) { - if (j >= 0 && j < 256) { + if (j >= 0 && j < maxY) { pos2.setComponents(l, j, i1); if (!this.canGrowInto(worldIn.getBlockIdAt(pos2.x, pos2.y, pos2.z))) { flag = false; @@ -59,12 +66,12 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP BlockVector3 down = position.down(); int block = worldIn.getBlockIdAt(down.x, down.y, down.z); - if ((block == Block.GRASS || block == Block.DIRT) && position.getY() < 256 - i - 1) { + if ((block == Block.GRASS || block == Block.DIRT) && position.getY() < maxY - i - 1) { this.setDirtAt(worldIn, down); for (int k1 = position.getY() - 3 + i; k1 <= position.getY() + i; ++k1) { int j2 = k1 - (position.getY() + i); - int l2 = 2 - j2 / 2; + int l2 = 2 - (j2 >> 1); for (int j3 = position.getX() - l2; j3 <= position.getX() + l2; ++j3) { int k3 = j3 - position.getX(); @@ -95,7 +102,7 @@ public boolean generate(ChunkManager worldIn, NukkitRandom rand, Vector3 vectorP for (int i2 = position.getY() - 3 + i; i2 <= position.getY() + i; ++i2) { int k2 = i2 - (position.getY() + i); - int i3 = 2 - k2 / 2; + int i3 = 2 - (k2 >> 1); BlockVector3 pos2 = new BlockVector3(); for (int l3 = position.getX() - i3; l3 <= position.getX() + i3; ++l3) { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTallBirchTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTallBirchTree.java index 72759ea56b7..72741d1218a 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTallBirchTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTallBirchTree.java @@ -4,7 +4,7 @@ import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class ObjectTallBirchTree extends ObjectBirchTree { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTree.java index 49da3dfc6b6..81229dbff2b 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectTree.java @@ -6,10 +6,11 @@ import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class ObjectTree { + protected boolean overridable(int id) { switch (id) { case Block.AIR: @@ -78,6 +79,7 @@ public boolean canPlaceObject(ChunkManager level, int x, int y, int z, NukkitRan for (int yy = 0; yy < this.getTreeHeight() + 3; ++yy) { if (yy == 1 || yy == this.getTreeHeight()) { ++radiusToCheck; + continue; } for (int xx = -radiusToCheck; xx < (radiusToCheck + 1); ++xx) { for (int zz = -radiusToCheck; zz < (radiusToCheck + 1); ++zz) { @@ -105,7 +107,7 @@ public void placeObject(ChunkManager level, int x, int y, int z, NukkitRandom ra if (xOff == mid && zOff == mid && (yOff == 0 || random.nextBoundedInt(2) == 0)) { continue; } - if (!Block.solid[level.getBlockIdAt(xx, yy, zz)]) { + if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yy, zz))) { level.setBlockAt(xx, yy, zz, this.getLeafBlock(), this.getType()); } } @@ -124,4 +126,4 @@ protected void placeTrunk(ChunkManager level, int x, int y, int z, NukkitRandom } } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectWarpedTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectWarpedTree.java new file mode 100644 index 00000000000..4456e840884 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectWarpedTree.java @@ -0,0 +1,19 @@ +package cn.nukkit.level.generator.object.tree; + +import cn.nukkit.block.Block; + +/** + * @author FlamingKnight + */ +public class ObjectWarpedTree extends ObjectNetherTree { + + @Override + public int getTrunkBlock() { + return Block.WARPED_STEM; + } + + @Override + public int getLeafBlock() { + return Block.WARPED_WART_BLOCK; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/TreeGenerator.java b/src/main/java/cn/nukkit/level/generator/object/tree/TreeGenerator.java index 06e2314e990..6aaa25faa70 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/TreeGenerator.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/TreeGenerator.java @@ -12,7 +12,7 @@ public abstract class TreeGenerator extends cn.nukkit.level.generator.object.BasicGenerator { - /* + /** * returns whether or not a tree can grow into a block * For example, a tree will not grow into stone */ @@ -27,7 +27,7 @@ protected void setDirtAt(ChunkManager level, BlockVector3 pos) { setDirtAt(level, new Vector3(pos.x, pos.y, pos.z)); } - /* + /** * sets dirt at a specific location if it isn't already dirt */ protected void setDirtAt(ChunkManager level, Vector3 pos) { diff --git a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureBelow.java b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureBelow.java index 710b26a7d56..661ea75a8c5 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureBelow.java +++ b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureBelow.java @@ -6,7 +6,8 @@ * @author DaPorkchop_ */ public interface EnsureBelow { - static boolean ensureBelow(int x, int y, int z, int id, FullChunk chunk) { + + static boolean ensureBelow(int x, int y, int z, int id, FullChunk chunk) { return chunk.getBlockId(x, y - 1, z) == id; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureCover.java b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureCover.java index 3bf11d72ef8..071e29934ff 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureCover.java +++ b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureCover.java @@ -2,15 +2,21 @@ import cn.nukkit.level.format.FullChunk; -import static cn.nukkit.block.BlockID.AIR; -import static cn.nukkit.block.BlockID.SNOW_LAYER; +import static cn.nukkit.block.BlockID.*; /** * @author DaPorkchop_ */ public interface EnsureCover { - static boolean ensureCover(int x, int y, int z, FullChunk chunk) { + + static boolean ensureCover(int x, int y, int z, FullChunk chunk) { + if (y > 255) return false; int id = chunk.getBlockId(x, y, z); return id == AIR || id == SNOW_LAYER; } + + static boolean ensureWaterCover(int x, int y, int z, FullChunk chunk) { + if (y > 255) return false; + return chunk.getBlockId(x, y, z) == STILL_WATER; + } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureGrassBelow.java b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureGrassBelow.java index 773ea9f794a..c8084dacfdc 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureGrassBelow.java +++ b/src/main/java/cn/nukkit/level/generator/populator/helper/EnsureGrassBelow.java @@ -1,14 +1,16 @@ package cn.nukkit.level.generator.populator.helper; +import cn.nukkit.block.BlockID; import cn.nukkit.level.format.FullChunk; -import static cn.nukkit.block.BlockID.GRASS; - /** * @author DaPorkchop_ */ public interface EnsureGrassBelow { + static boolean ensureGrassBelow(int x, int y, int z, FullChunk chunk) { - return EnsureBelow.ensureBelow(x, y, z, GRASS, chunk); + int bid = chunk.getBlockId(x, y - 1, z); + return bid == BlockID.GRASS || bid == BlockID.PODZOL; + //return EnsureBelow.ensureBelow(x, y, z, GRASS, chunk); } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/helper/PopulatorHelpers.java b/src/main/java/cn/nukkit/level/generator/populator/helper/PopulatorHelpers.java index 7cad9dfdb41..e8fd53ca12e 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/helper/PopulatorHelpers.java +++ b/src/main/java/cn/nukkit/level/generator/populator/helper/PopulatorHelpers.java @@ -9,13 +9,23 @@ * @author DaPorkchop_ */ public final class PopulatorHelpers implements BlockID { + private static final IntSet nonSolidBlocks = new IntOpenHashSet(); + private static final IntSet nonOceanSolidBlocks = new IntOpenHashSet(); static { nonSolidBlocks.add(AIR); nonSolidBlocks.add(LEAVES); nonSolidBlocks.add(LEAVES2); nonSolidBlocks.add(SNOW_LAYER); + nonSolidBlocks.add(TALL_GRASS); + + nonOceanSolidBlocks.add(AIR); + nonOceanSolidBlocks.add(WATER); + nonOceanSolidBlocks.add(STILL_WATER); + nonOceanSolidBlocks.add(ICE); + nonOceanSolidBlocks.add(PACKED_ICE); + nonOceanSolidBlocks.add(BLUE_ICE); } private PopulatorHelpers() { @@ -28,4 +38,8 @@ public static boolean canGrassStay(int x, int y, int z, FullChunk chunk) { public static boolean isNonSolid(int id) { return nonSolidBlocks.contains(id); } + + public static boolean isNonOceanSolid(int id) { + return nonOceanSolidBlocks.contains(id); + } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/helper/package-info.java b/src/main/java/cn/nukkit/level/generator/populator/helper/package-info.java deleted file mode 100644 index 45d449289ec..00000000000 --- a/src/main/java/cn/nukkit/level/generator/populator/helper/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package has lots of helper interfaces for frequently repeated methods - */ -package cn.nukkit.level.generator.populator.helper; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/MushroomPopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/MushroomPopulator.java index feda79d36f1..4cb0d7c6707 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/MushroomPopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/MushroomPopulator.java @@ -12,6 +12,7 @@ * @author DaPorkchop_ */ public class MushroomPopulator extends PopulatorCount { + private final int type; public MushroomPopulator() { @@ -39,7 +40,7 @@ protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChun z &= 0xF; for (y = 254; y > 0; --y) { int b = chunk.getBlockId(x, y, z); - if (b == Block.DIRT || b == Block.GRASS) { + if (b == Block.DIRT || b == Block.GRASS || b == Block.MYCELIUM) { break; } else if (b != Block.AIR && b != Block.SNOW_LAYER) { return -1; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBamboo.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBamboo.java new file mode 100644 index 00000000000..9291ba009cf --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBamboo.java @@ -0,0 +1,45 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.helper.EnsureBelow; +import cn.nukkit.level.generator.populator.helper.EnsureCover; +import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; + +public class PopulatorBamboo extends PopulatorSurfaceBlock { + + @Override + protected boolean canStay(int x, int y, int z, FullChunk chunk) { + if (chunk instanceof cn.nukkit.level.format.anvil.Chunk) return false; + return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, GRASS, chunk); + } + + @Override + protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { + return Block.BAMBOO << Block.DATA_BITS; + } + + @Override + protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { + int height = Utils.rand(1, 10); + int part = 0; + while (part < height) { + int yy = y + part; + if (yy < 256 && chunk.getBlockId(x, yy, z) == AIR) { + super.placeBlock(x, yy, z, id, chunk, random); + } else { + break; + } + part++; + } + // Top part + int yy = y + part; + if (yy < 256 && chunk.getBlockId(x, yy, z) == AIR) { + chunk.setBlock(x, yy, z, BAMBOO, 2); + } else { + chunk.setBlock(x, yy - 1, z, BAMBOO, 2); + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaLava.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaLava.java new file mode 100644 index 00000000000..9afe52df594 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaLava.java @@ -0,0 +1,45 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorBasaltDeltaLava extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.BASALT || b == Block.BLACKSTONE) && + level.getBlockIdAt(x, y + 1, z) == 0 && + level.getBlockIdAt(x + 1, y, z) != 0 && + level.getBlockIdAt(x - 1, y, z) != 0 && + level.getBlockIdAt(x, y, z + 1) != 0 && + level.getBlockIdAt(x, y, z - 1) != 0 + ) { + blockYs.add(y); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(64) + 64; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + level.setBlockAt(x, y, z, BlockID.STILL_LAVA); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaMagma.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaMagma.java new file mode 100644 index 00000000000..a6cfe81b0e9 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaMagma.java @@ -0,0 +1,45 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorBasaltDeltaMagma extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.BASALT || b == Block.BLACKSTONE) && + level.getBlockIdAt(x, y + 1, z) == 0 && ( + level.getBlockIdAt(x + 1, y, z) == BlockID.STILL_LAVA || + level.getBlockIdAt(x - 1, y, z) == BlockID.STILL_LAVA || + level.getBlockIdAt(x, y, z + 1) == BlockID.STILL_LAVA || + level.getBlockIdAt(x, y, z - 1) == BlockID.STILL_LAVA) + ) { + blockYs.add(y); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(4) + 20; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + level.setBlockAt(x, y, z, BlockID.MAGMA); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaPillar.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaPillar.java new file mode 100644 index 00000000000..1d9368e2637 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBasaltDeltaPillar.java @@ -0,0 +1,47 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorBasaltDeltaPillar extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.BASALT || b == Block.BLACKSTONE) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(128) + 128; + + IntArrayList visited = new IntArrayList(); + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + int pos = (x >> 1) + (z << 1); + if (visited.contains(pos)) continue; + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + if (random.nextBoundedInt(5) == 0) continue; + for (int randomHeight = 0; randomHeight < random.nextBoundedInt(5) + 1; randomHeight++) { + int placeLocation = y + randomHeight; + level.setBlockAt(x, placeLocation, z, BlockID.BASALT); + } + visited.add(pos); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBedrock.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBedrock.java index 6c231cb29c4..03b22372f79 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBedrock.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorBedrock.java @@ -5,24 +5,44 @@ import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; -import static cn.nukkit.block.BlockID.BEDROCK; - /** * @author DaPorkchop_ * * Places bedrock on the bottom of the world */ public class PopulatorBedrock extends Populator { + + private final boolean nether; + + public PopulatorBedrock() { + this(false); + } + + public PopulatorBedrock(boolean nether) { + this.nether = nether; + } + @Override public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { chunk.setBlockId(x, 0, z, BEDROCK); + for (int i = 1; i < 5; i++) { - if (random.nextBoundedInt(i) == 0) { //decreasing amount + if (random.nextBoundedInt(i) == 0) { // decreasing amount chunk.setBlockId(x, i, z, BEDROCK); } } + + if (this.nether) { + chunk.setBlockId(x, 127, z, BEDROCK); + + for (int i = 126; i > 122; i--) { + if (random.nextBoundedInt(127 - i) == 0) { // decreasing amount + chunk.setBlockId(x, i, z, BEDROCK); + } + } + } } } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCactus.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCactus.java index b6057b2ec8d..4d14b3e2447 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCactus.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCactus.java @@ -1,24 +1,47 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureBelow; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; -import static cn.nukkit.block.BlockID.CACTUS; +import java.util.concurrent.ThreadLocalRandom; /** * @author DaPorkchop_ */ public class PopulatorCactus extends PopulatorSurfaceBlock { + + private boolean checkSurroundingBlocks(int x, int y, int z, FullChunk chunk) { + int b = chunk.getBlockId(x + BlockFace.NORTH.getXOffset() & 0xF, y, z + BlockFace.NORTH.getZOffset() & 0xF); + if (b != Block.AIR) return false; + b = chunk.getBlockId(x + BlockFace.EAST.getXOffset() & 0xF, y, z + BlockFace.EAST.getZOffset() & 0xF); + if (b != Block.AIR) return false; + b = chunk.getBlockId(x + BlockFace.SOUTH.getXOffset() & 0xF, y, z + BlockFace.SOUTH.getZOffset() & 0xF); + if (b != Block.AIR) return false; + b = chunk.getBlockId(x + BlockFace.WEST.getXOffset() & 0xF, y, z + BlockFace.WEST.getZOffset() & 0xF); + return b == Block.AIR; + } + @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { - return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, SAND, chunk); + return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, SAND, chunk) && checkSurroundingBlocks(x, y, z, chunk); } @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return (CACTUS << 4) | 1; + return (Block.CACTUS << Block.DATA_BITS) | 1; + } + + @Override + protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { + int height = ThreadLocalRandom.current().nextInt(3) + 1; + if (y + height > 255) return; + for (int i = 0; i < height; i++) { + chunk.setFullBlockId(x, y + i, z, id); + } } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCaves.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCaves.java index 59eaed143aa..5b2b156b94c 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCaves.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCaves.java @@ -2,7 +2,6 @@ import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; -import cn.nukkit.level.biome.EnumBiome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.biome.type.CoveredBiome; @@ -13,7 +12,7 @@ import java.util.Random; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class PopulatorCaves extends Populator { @@ -22,11 +21,11 @@ public class PopulatorCaves extends Populator { private Random random; - public static int caveRarity = 7;//7 - public static int caveFrequency = 40;//40 + public static int caveRarity = 7; + public static int caveFrequency = 40; public static int caveMinAltitude = 8; public static int caveMaxAltitude = 67; - public static int individualCaveRarity = 25;//25 + public static int individualCaveRarity = 25; public static int caveSystemFrequency = 1; public static int caveSystemPocketChance = 0; public static int caveSystemPocketMinSize = 0; @@ -61,8 +60,8 @@ protected void generateCaveNode(long seed, FullChunk chunk, double x, double y, int chunkX = chunk.getX(); int chunkZ = chunk.getZ(); - double realX = chunkX * 16 + 8; - double realZ = chunkZ * 16 + 8; + double realX = (chunkX << 4) + 8; + double realZ = (chunkZ << 4) + 8; float f1 = 0.0F; float f2 = 0.0F; @@ -70,17 +69,17 @@ protected void generateCaveNode(long seed, FullChunk chunk, double x, double y, Random localRandom = new Random(seed); if (maxAngle <= 0) { - int checkAreaSize = this.checkAreaSize * 16 - 16; - maxAngle = checkAreaSize - localRandom.nextInt(checkAreaSize / 4); + int checkAreaSize = (this.checkAreaSize << 4) - 16; + maxAngle = checkAreaSize - localRandom.nextInt(checkAreaSize >> 2); } boolean isLargeCave = false; if (angle == -1) { - angle = maxAngle / 2; + angle = maxAngle >> 1; isLargeCave = true; } - int randomAngel = localRandom.nextInt(maxAngle / 2) + maxAngle / 4; + int randomAngel = localRandom.nextInt(maxAngle >> 1) + (maxAngle >> 2); boolean bigAngel = localRandom.nextInt(6) == 0; for (; angle < maxAngle; angle++) { @@ -129,14 +128,14 @@ protected void generateCaveNode(long seed, FullChunk chunk, double x, double y, continue; - int xFrom = MathHelper.floor(x - offsetXZ) - chunkX * 16 - 1; - int xTo = MathHelper.floor(x + offsetXZ) - chunkX * 16 + 1; + int xFrom = MathHelper.floor(x - offsetXZ) - (chunkX << 4) - 1; + int xTo = MathHelper.floor(x + offsetXZ) - (chunkX << 4) + 1; int yFrom = MathHelper.floor(y - offsetY) - 1; int yTo = MathHelper.floor(y + offsetY) + 1; - int zFrom = MathHelper.floor(z - offsetXZ) - chunkZ * 16 - 1; - int zTo = MathHelper.floor(z + offsetXZ) - chunkZ * 16 + 1; + int zFrom = MathHelper.floor(z - offsetXZ) - (chunkZ << 4) - 1; + int zTo = MathHelper.floor(z + offsetXZ) - (chunkZ << 4) + 1; if (xFrom < 0) xFrom = 0; @@ -176,22 +175,21 @@ protected void generateCaveNode(long seed, FullChunk chunk, double x, double y, // Generate cave for (int xx = xFrom; xx < xTo; xx++) { - double modX = (xx + chunkX * 16 + 0.5D - x) / offsetXZ; + double modX = (xx + (chunkX << 4) + 0.5D - x) / offsetXZ; for (int zz = zFrom; zz < zTo; zz++) { - double modZ = (zz + chunkZ * 16 + 0.5D - z) / offsetXZ; + double modZ = (zz + (chunkZ << 4) + 0.5D - z) / offsetXZ; boolean grassFound = false; if (modX * modX + modZ * modZ < 1.0D) { for (int yy = yTo; yy > yFrom; yy--) { double modY = ((yy - 1) + 0.5D - y) / offsetY; - if ((modY > -0.7D) && (modX * modX + modY * modY + modZ * modZ < 1.0D)) { - Biome biome = EnumBiome.getBiome(chunk.getBiomeId(xx, zz)); + if ((modY > -0.7) && (modX * modX + modY * modY + modZ * modZ < 1.0D)) { + Biome biome = Biome.getBiome(chunk.getBiomeId(xx, zz)); if (!(biome instanceof CoveredBiome)) { continue; } int material = chunk.getBlockId(xx, yy, zz); - int materialAbove = chunk.getBlockId(xx, yy + 1, zz); if (material == Block.GRASS || material == Block.MYCELIUM) { grassFound = true; } @@ -230,16 +228,16 @@ protected void generateChunk(int chunkX, int chunkZ, FullChunk generatingChunkBu i = 0; for (int j = 0; j < i; j++) { - double x = chunkX * 16 + this.random.nextInt(16); + double x = (chunkX << 4) + this.random.nextInt(16); double y; if (evenCaveDistribution) - y = numberInRange(random, caveMinAltitude, caveMaxAltitude); + y = MathHelper.getRandomNumberInRange(random, caveMinAltitude, caveMaxAltitude); else y = this.random.nextInt(this.random.nextInt(caveMaxAltitude - caveMinAltitude + 1) + 1) + caveMinAltitude; - double z = chunkZ * 16 + this.random.nextInt(16); + double z = (chunkZ << 4) + this.random.nextInt(16); int count = caveSystemFrequency; boolean largeCaveSpawned = false; @@ -249,11 +247,11 @@ protected void generateChunk(int chunkX, int chunkZ, FullChunk generatingChunkBu } if ((largeCaveSpawned) || (this.random.nextInt(100) <= caveSystemPocketChance - 1)) { - count += numberInRange(random, caveSystemPocketMinSize, caveSystemPocketMaxSize); + count += MathHelper.getRandomNumberInRange(random, caveSystemPocketMinSize, caveSystemPocketMaxSize); } while (count > 0) { count--; - float f1 = this.random.nextFloat() * 3.141593F * 2.0F; + float f1 = this.random.nextFloat() * 6.283186f; float f2 = (this.random.nextFloat() - 0.5F) * 2.0F / 8.0F; float f3 = this.random.nextFloat() * 2.0F + this.random.nextFloat(); @@ -261,8 +259,4 @@ protected void generateChunk(int chunkX, int chunkZ, FullChunk generatingChunkBu } } } - - public static int numberInRange(Random random, int min, int max) { - return min + random.nextInt(max - min + 1); - } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonForestGround.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonForestGround.java new file mode 100644 index 00000000000..50ad9c8f112 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonForestGround.java @@ -0,0 +1,50 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorCrimsonForestGround extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.CRIMSON_NYLIUM) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(64) + 32; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + + int blockID; + if (random.nextBoundedInt(6) == 0) { + if (random.nextBoundedInt(8) == 1) { + blockID = WARPED_FUNGUS; + } else { + blockID = CRIMSON_FUNGUS; + } + } else { + blockID = CRIMSON_ROOTS; + } + + level.setBlockAt(x, y, z, blockID); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonFungus.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonFungus.java new file mode 100644 index 00000000000..46d5c18aada --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorCrimsonFungus.java @@ -0,0 +1,40 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectCrimsonTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorCrimsonFungus extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.CRIMSON_NYLIUM) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(6) + 3; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + if (random.nextBoundedInt(4) == 1) continue; + new ObjectCrimsonTree().placeObject(level, x, y, z, random); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDeadBush.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDeadBush.java index 2b9e09e9812..5d3a97094ea 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDeadBush.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDeadBush.java @@ -1,5 +1,6 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureBelow; import cn.nukkit.level.generator.populator.helper.EnsureCover; @@ -18,6 +19,6 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return DEAD_BUSH << 4; + return Block.DEAD_BUSH << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDoublePlant.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDoublePlant.java index 9218e54171f..9cb0aa323be 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDoublePlant.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorDoublePlant.java @@ -1,32 +1,32 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.helper.EnsureGrassBelow; import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; import cn.nukkit.math.NukkitRandom; -import static cn.nukkit.block.BlockID.DOUBLE_PLANT; - /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class PopulatorDoublePlant extends PopulatorSurfaceBlock { - private int type; - public PopulatorDoublePlant(int type) { + private final int type; + + public PopulatorDoublePlant(int type) { this.type = type; } @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { - return EnsureCover.ensureCover(x, y, z, chunk) && EnsureCover.ensureCover(x, y + 1, z, chunk) && EnsureGrassBelow.ensureGrassBelow(x, y, z, chunk); + return y < 255 && EnsureCover.ensureCover(x, y, z, chunk) && EnsureCover.ensureCover(x, y + 1, z, chunk) && EnsureGrassBelow.ensureGrassBelow(x, y, z, chunk); } @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return (DOUBLE_PLANT << 4) | type; + return (Block.DOUBLE_PLANT << Block.DATA_BITS) | type; } @Override diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFallenTree.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFallenTree.java new file mode 100644 index 00000000000..9e9ae786c69 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFallenTree.java @@ -0,0 +1,69 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectFallenTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; + +public class PopulatorFallenTree extends Populator { + + private ChunkManager level; + private int type; + + public void setType(int type) { //0 = oak, 1 = birch + this.type = type; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextRange(0, 3) != 1) return; + this.level = level; + int amount = 1; + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + if (level.getBlockIdAt(x, y, z) != Block.AIR || + level.getBlockIdAt(x + 1, y, z) != Block.AIR || + level.getBlockIdAt(x, y, z + 1) != Block.AIR || + level.getBlockIdAt(x - 1, y, z) != Block.AIR || + level.getBlockIdAt(x, y, z - 1) != Block.AIR || + level.getBlockIdAt(x + 1, y - 1, z) != Block.GRASS || + level.getBlockIdAt(x, y - 1, z + 1) != Block.GRASS || + level.getBlockIdAt(x - 1, y - 1, z) != Block.GRASS || + level.getBlockIdAt(x, y - 1, z - 1) != Block.GRASS || + level.getBlockIdAt(x + 2, y - 1, z) != Block.GRASS || + level.getBlockIdAt(x, y - 1, z + 2) != Block.GRASS || + level.getBlockIdAt(x - 2, y - 1, z) != Block.GRASS || + level.getBlockIdAt(x, y - 1, z - 2) != Block.GRASS || + level.getBlockIdAt(x - 3, y - 1, z) != Block.GRASS || + level.getBlockIdAt(x, y - 1, z - 3) != Block.GRASS || + level.getBlockIdAt(x + 2, y, z) != Block.AIR || + level.getBlockIdAt(x, y, z + 2) != Block.AIR || + level.getBlockIdAt(x + 3, y, z) != Block.AIR || + level.getBlockIdAt(x, y, z + 3) != Block.AIR) { + continue; + } + new ObjectFallenTree(this.level, x, y, z, type, random); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.GRASS) { + break; + } + } + + return ++y; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFlower.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFlower.java index 77d3f09513e..b73700c359b 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFlower.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorFlower.java @@ -1,14 +1,15 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.helper.EnsureGrassBelow; import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; /** * @author Angelic47, Niall Lindsay (Niall7459) @@ -17,6 +18,7 @@ *

*/ public class PopulatorFlower extends PopulatorSurfaceBlock { + private final List flowerTypes = new ArrayList<>(); public void addType(int a, int b) { @@ -32,11 +34,11 @@ public List getTypes() { @Override protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { - if (flowerTypes.size() != 0) { - int[] type = flowerTypes.get(ThreadLocalRandom.current().nextInt(flowerTypes.size())); - chunk.setFullBlockId(x, y, z, (type[0] << 4) | type[1]); + if (!flowerTypes.isEmpty()) { + int[] type = flowerTypes.get(Utils.random.nextInt(flowerTypes.size())); + chunk.setFullBlockId(x, y, z, (type[0] << Block.DATA_BITS) | type[1]); if (type[0] == DOUBLE_PLANT) { - chunk.setFullBlockId(x, y + 1, z, (type[0] << 4) | (8 | type[1])); + chunk.setFullBlockId(x, y + 1, z, (type[0] << Block.DATA_BITS) | (8 | type[1])); } } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorForestRock.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorForestRock.java new file mode 100644 index 00000000000..c9485ffc5d5 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorForestRock.java @@ -0,0 +1,71 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.helper.EnsureBelow; +import cn.nukkit.level.generator.populator.helper.EnsureCover; +import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; +import cn.nukkit.math.NukkitRandom; + +public class PopulatorForestRock extends PopulatorSurfaceBlock { + + @Override + protected boolean canStay(int x, int y, int z, FullChunk chunk) { + return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, PODZOL, chunk); + } + + @Override + protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { + return MOSSY_STONE << 4; + } + + @Override + protected void populateCount(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int radiusX = random.nextBoundedInt(2); + int radiusZ = random.nextBoundedInt(2); + int radiusY = random.nextBoundedInt(2); + float f = (radiusX + radiusZ + radiusY) * 0.333F + 0.5F; + float fsquared = f * f; + if (fsquared <= 0.7) return; // Skip one block sized ones + + int rx = random.nextBoundedInt(16); + int rz = random.nextBoundedInt(16); + int sourceX = (chunkX << 4) + rx; + int sourceZ = (chunkZ << 4) + rz; + int sourceY = getHighestWorkableBlock(level, rx, rz, chunk); + + boolean groundReached = false; + while (sourceY > 3) { + sourceY--; + int block = level.getBlockIdAt(sourceX, sourceY, sourceZ); + if (block == 0) { + continue; + } + if (block == PODZOL || block == DIRT || block == STONE) { + groundReached = true; + sourceY++; + break; + } + } + + if (!groundReached || level.getBlockIdAt(sourceX, sourceY, sourceZ) != 0) { + return; + } + + for (int x = -radiusX; x <= radiusX; x++) { + float xsquared = x * x; + for (int z = -radiusZ; z <= radiusZ; z++) { + float zsquared = z * z; + for (int y = -radiusY; y <= radiusY; y++) { + if (xsquared + zsquared + y * y > fsquared) { + continue; + } + int bid = level.getBlockIdAt(sourceX + x, sourceY + y, sourceZ + z); + if (bid == AIR || bid == PODZOL || bid == DIRT || bid == STONE) { + level.setBlockFullIdAt(sourceX + x, sourceY + y, sourceZ + z, MOSSY_STONE << 4); + } + } + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGlowStone.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGlowStone.java index 7d0575fb869..ab7ed3f671c 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGlowStone.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGlowStone.java @@ -2,28 +2,41 @@ import cn.nukkit.block.Block; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.biome.EnumBiome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitMath; import cn.nukkit.math.NukkitRandom; public class PopulatorGlowStone extends Populator { + @Override public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextBoundedInt(9) < 8 || Biome.getBiome(chunk.getBiomeId(7, 7)).getId() == EnumBiome.SOULSAND_VALLEY.id) return; int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); - int y = this.getHighestWorkableBlock(chunk, x & 0xF, z & 0xF); - if (y != -1 && level.getBlockIdAt(x, y, z) != NETHERRACK) { - int count = NukkitMath.randomRange(random, 40, 60); + int y = getHighestWorkableBlock(chunk, x & 0xF, z & 0xF) + 1; + boolean vertical = random.nextBoolean(); + if (y != -1) { + int size = random.nextRange(3, 6); + int count = NukkitMath.randomRange(random, 20 + (size << 1), 30 + (size << 1)); + if (vertical && size > 3) size--; for (int i = 0; i < count; i++) { - level.setBlockAt(x + (random.nextBoundedInt(7) - 3), y + (random.nextBoundedInt(9) - 4), z + (random.nextBoundedInt(7) - 3), GLOWSTONE); + int yy = y + (random.nextBoundedInt(size) - random.nextRange(3, vertical ? 5 : 3)); + if (yy <= 0) continue; + int xx = x + (random.nextBoundedInt(size) - 3); + int zz = z + (random.nextBoundedInt(size) - 3); + if (chunk.getBlockId(xx & 0x0f, yy & 0x0ff, zz & 0x0f) == AIR) { + level.setBlockAt(xx, yy, zz, GLOWSTONE); + } } } } - private int getHighestWorkableBlock(FullChunk chunk, int x, int z) { + private static int getHighestWorkableBlock(FullChunk chunk, int x, int z) { int y; - //start scanning a bit lower down to allow space for placing on top + // Start scanning a bit lower down to allow space for placing on top for (y = 120; y >= 0; y--) { int b = chunk.getBlockId(x, y, z); if (b == Block.AIR) { diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGrass.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGrass.java index d381a3dbdb3..518a69eecaf 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGrass.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGrass.java @@ -1,15 +1,17 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.PopulatorHelpers; import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class PopulatorGrass extends PopulatorSurfaceBlock { + @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { return PopulatorHelpers.canGrassStay(x, y, z, chunk); @@ -17,6 +19,6 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return (TALL_GRASS << 4) | 1; + return (TALL_GRASS << Block.DATA_BITS) | 1; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java index 3feec7fa3b1..b3b9744bdbe 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundCover.java @@ -1,7 +1,6 @@ package cn.nukkit.level.generator.populator.impl; import cn.nukkit.level.ChunkManager; -import cn.nukkit.level.biome.EnumBiome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.biome.Biome; import cn.nukkit.level.biome.type.CoveredBiome; @@ -10,18 +9,19 @@ /** * @author DaPorkchop_ + * Nukkit Project */ public class PopulatorGroundCover extends Populator { + @Override public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { - //reverse iteration to 0 is faster for (int x = 15; x >= 0; x--) { for (int z = 15; z >= 0; z--) { - Biome realBiome = EnumBiome.getBiome(chunk.getBiomeId(x, z)); + Biome realBiome = Biome.getBiome(chunk.getBiomeId(x, z)); if (realBiome instanceof CoveredBiome) { ((CoveredBiome) realBiome).doCover(x, z, chunk); } } } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundFire.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundFire.java index 3c93cb88e48..4da61438333 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundFire.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorGroundFire.java @@ -12,6 +12,7 @@ * @author DaPorkchop_ */ public class PopulatorGroundFire extends PopulatorSurfaceBlock { + @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, NETHERRACK, chunk); @@ -19,24 +20,25 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return FIRE << 4; + return Block.FIRE << Block.DATA_BITS; } @Override protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { super.placeBlock(x, y, z, id, chunk, random); - chunk.setBlockLight(x, y, z, Block.light[FIRE]); + chunk.setBlockLight(x, y, z, Block.getBlockLight(FIRE)); } @Override protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { - int y; - for (y = 0; y <= 127; ++y) { - int b = chunk.getBlockId(x, y, z); - if (b == Block.AIR) { + int height = 0; + for (int y = 0; y < 127; ++y) { + height = y; + int blockId = chunk.getBlockId(x, y, z); + if (blockId == Block.AIR) { break; } } - return y == 0 ? -1 : y; + return height == 0 ? -1 : height; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorKelp.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorKelp.java new file mode 100644 index 00000000000..b5d553db6e6 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorKelp.java @@ -0,0 +1,48 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.leveldb.structure.LevelDBChunk; +import cn.nukkit.level.generator.populator.helper.EnsureBelow; +import cn.nukkit.level.generator.populator.helper.EnsureCover; +import cn.nukkit.level.generator.populator.type.PopulatorOceanFloorSurfaceBlock; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; + +public class PopulatorKelp extends PopulatorOceanFloorSurfaceBlock { + + @Override + protected boolean canStay(int x, int y, int z, FullChunk chunk) { + if (chunk instanceof cn.nukkit.level.format.anvil.Chunk) return false; + return EnsureCover.ensureWaterCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, GRAVEL, chunk); + } + + @Override + protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { + return Block.BLOCK_KELP << Block.DATA_BITS; + } + + @Override + protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { + int height = Utils.rand(1, 25); + int lastTop = y; + + boolean leveldb = chunk instanceof LevelDBChunk; + + for (int part = 0; part < height; part++) { + int yy = y + part; + if (yy < 256 && chunk.getBlockId(x, yy, z) == STILL_WATER && chunk.getBlockId(x, yy + 1, z) == STILL_WATER) { + chunk.setBlock(x, yy, z, Block.BLOCK_KELP, leveldb ? part : (int) (part / 1.6)); + chunk.setFullBlockId(x, yy, z, BlockLayer.WATERLOGGED, Block.STILL_WATER << Block.DATA_BITS); + lastTop = yy; + } else { + lastTop = yy - 1; + break; + } + } + + // Stop top part growing + chunk.setBlockData(x, lastTop, z, leveldb ? 24 : 15); + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLava.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLava.java index c2f954a9474..73db47f2958 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLava.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLava.java @@ -7,6 +7,7 @@ import cn.nukkit.math.NukkitRandom; public class PopulatorLava extends Populator { + private ChunkManager level; private int randomAmount; private int baseAmount; @@ -28,15 +29,13 @@ public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom ra int amount = random.nextRange(0, this.randomAmount + 1) + this.baseAmount; int bx = chunkX << 4; int bz = chunkZ << 4; - int tx = bx + 15; - int tz = bz + 15; for (int i = 0; i < amount; ++i) { int x = random.nextRange(0, 15); int z = random.nextRange(0, 15); - int y = this.getHighestWorkableBlock(chunk, x, z); + int y = getHighestWorkableBlock(chunk, x, z); if (y != -1 && chunk.getBlockId(x, y, z) == Block.AIR) { chunk.setBlock(x, y, z, Block.LAVA); - chunk.setBlockLight(x, y, z, Block.light[Block.LAVA]); + chunk.setBlockLight(x, y, z, Block.getBlockLight(Block.LAVA)); this.lavaSpread(bx + x, y, bz + z); } } @@ -141,7 +140,6 @@ private int calculateFlowCost(int xx, int yy, int zz, int accumulatedCost, int p (j == 3 && previousDirection == 2) ) { int x = xx; - int y = yy; int z = zz; if (j == 0) { --x; @@ -152,17 +150,17 @@ private int calculateFlowCost(int xx, int yy, int zz, int accumulatedCost, int p } else if (j == 3) { ++z; } - if (!this.canFlowInto(x, y, z)) { + if (!this.canFlowInto(x, yy, z)) { continue; - } else if (this.canFlowInto(x, y, z) && this.level.getBlockDataAt(x, y, z) == 0) { + } else if (this.canFlowInto(x, yy, z) && this.level.getBlockDataAt(x, yy, z) == 0) { continue; - } else if (this.canFlowInto(x, y - 1, z)) { + } else if (this.canFlowInto(x, yy - 1, z)) { return accumulatedCost; } if (accumulatedCost >= 4) { continue; } - int realCost = this.calculateFlowCost(x, y, z, accumulatedCost + 1, j); + int realCost = this.calculateFlowCost(x, yy, z, accumulatedCost + 1, j); if (realCost < cost) { cost = realCost; } @@ -177,7 +175,6 @@ private boolean[] getOptimalFlowDirections(int xx, int yy, int zz) { for (int j = 0; j < 4; ++j) { flowCost[j] = 1000; int x = xx; - int y = yy; int z = zz; if (j == 0) { --x; @@ -188,10 +185,10 @@ private boolean[] getOptimalFlowDirections(int xx, int yy, int zz) { } else if (j == 3) { ++z; } - if (this.canFlowInto(x, y - 1, z)) { + if (this.canFlowInto(x, yy - 1, z)) { flowCost[j] = 0; } else { - flowCost[j] = this.calculateFlowCost(x, y, z, 1, j); + flowCost[j] = this.calculateFlowCost(x, yy, z, 1, j); } } int minCost = flowCost[0]; @@ -217,7 +214,7 @@ private int getSmallestFlowDecay(int x1, int y1, int z1, int x2, int y2, int z2, } - private int getHighestWorkableBlock(FullChunk chunk, int x, int z) { + private static int getHighestWorkableBlock(FullChunk chunk, int x, int z) { int y; for (y = 127; y >= 0; y--) { int b = chunk.getBlockId(x, y, z); diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLilyPad.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLilyPad.java index d6fd6d0ed17..fc0241ad472 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLilyPad.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorLilyPad.java @@ -1,5 +1,6 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureBelow; import cn.nukkit.level.generator.populator.helper.EnsureCover; @@ -18,6 +19,6 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return WATER_LILY << 4; + return Block.LILY_PAD << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMelon.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMelon.java index e3271cad822..ca4d65dcf47 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMelon.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMelon.java @@ -1,5 +1,6 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.helper.EnsureGrassBelow; @@ -10,6 +11,7 @@ * @author DaPorkchop_ */ public class PopulatorMelon extends PopulatorSurfaceBlock { + @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { return EnsureCover.ensureCover(x, y, z, chunk) && EnsureGrassBelow.ensureGrassBelow(x, y, z, chunk); @@ -17,6 +19,6 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return MELON_BLOCK << 4; + return Block.MELON_BLOCK << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMineshaft.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMineshaft.java deleted file mode 100644 index f5f970201fe..00000000000 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorMineshaft.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.nukkit.level.generator.populator.impl; - -import cn.nukkit.level.ChunkManager; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.generator.populator.type.Populator; -import cn.nukkit.math.NukkitRandom; - -public class PopulatorMineshaft extends Populator { - - /** - * @author Niall Lindsay (Niall7459) - *

- * Nukkit Project - *

- * - * WIP - */ - - @Override - public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { - // TODO Auto-generated method stub - - } - -} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorNetherFire.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorNetherFire.java new file mode 100644 index 00000000000..b44ee25ab66 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorNetherFire.java @@ -0,0 +1,47 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorNetherFire extends Populator { + + private final int fireBlock; + private final int replaceBlock; + + public PopulatorNetherFire(int fireBlock, int replaceBlock) { + this.fireBlock = fireBlock; + this.replaceBlock = replaceBlock; + } + + private IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 127; y > 0; y--) { + int b = level.getBlockIdAt(x, y, z); + if ((b == this.replaceBlock) && level.getBlockIdAt(x, y + 1, z) == AIR) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (this.replaceBlock == NETHERRACK && random.nextBoundedInt(8) < 7) return; + + int amount = random.nextBoundedInt(4) + 4; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + level.setBlockAt(x, y, z, this.fireBlock); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java index 56474e4870b..2fcae2710da 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorOre.java @@ -11,6 +11,7 @@ * @author DaPorkchop_ */ public class PopulatorOre extends Populator { + private final int replaceId; private final OreType[] oreTypes; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorPumpkin.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorPumpkin.java new file mode 100644 index 00000000000..628ffe5cfbc --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorPumpkin.java @@ -0,0 +1,39 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.utils.Utils; + +public class PopulatorPumpkin extends Populator { + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (Utils.rand(0, 10) == 5) { + int x = random.nextRange(0, 15); + int z = random.nextRange(0, 15); + int y = getHighestWorkableBlock(chunk, x, z); + if (y != -1 && canPumpkinStay(chunk, x, y, z)) { + chunk.setBlock(x, y, z, Block.PUMPKIN); + } + } + } + + private static boolean canPumpkinStay(FullChunk chunk, int x, int y, int z) { + int b = chunk.getBlockId(x, y, z); + return (b == Block.AIR) && chunk.getBlockId(x, y - 1, z) == Block.GRASS; + } + + private static int getHighestWorkableBlock(FullChunk chunk, int x, int z) { + int y; + for (y = 0; y <= 127; ++y) { + int b = chunk.getBlockId(x, y, z); + if (b == Block.AIR) { + break; + } + } + return y == 0 ? -1 : y; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorRavines.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorRavines.java deleted file mode 100644 index f04068d7eee..00000000000 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorRavines.java +++ /dev/null @@ -1,205 +0,0 @@ -package cn.nukkit.level.generator.populator.impl; - -import cn.nukkit.block.Block; -import cn.nukkit.level.ChunkManager; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.level.generator.populator.type.Populator; -import cn.nukkit.math.MathHelper; -import cn.nukkit.math.NukkitRandom; - -import java.util.Random; - -public class PopulatorRavines extends Populator { - - protected int checkAreaSize = 8; - - private Random random; - private long worldLong1; - private long worldLong2; - - private int ravineRarity = 1;//2 - private int ravineMinAltitude = 20; - private int ravineMaxAltitude = 67; - private int ravineMinLength = 84; - private int ravineMaxLength = 111; - - private double ravineDepth = 3; - - private int worldHeightCap = 1 << 8; - - private float[] a = new float[1024]; - - @Override - public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { - this.random = new Random(); - this.random.setSeed(level.getSeed()); - worldLong1 = this.random.nextLong(); - worldLong2 = this.random.nextLong(); - - int i = this.checkAreaSize; - - for (int x = chunkX - i; x <= chunkX + i; x++) - for (int z = chunkZ - i; z <= chunkZ + i; z++) { - long l3 = x * worldLong1; - long l4 = z * worldLong2; - this.random.setSeed(l3 ^ l4 ^ level.getSeed()); - generateChunk(chunkX, chunkZ, level.getChunk(chunkX, chunkZ)); - } - } - - protected void generateChunk(int chunkX, int chunkZ, FullChunk generatingChunkBuffer) { - if (this.random.nextInt(300) >= this.ravineRarity) - return; - double d1 = (chunkX * 16) + this.random.nextInt(16); - double d2 = numberInRange(random, this.ravineMinAltitude, this.ravineMaxAltitude); - double d3 = (chunkZ * 16) + this.random.nextInt(16); - - int i = 1; - - for (int j = 0; j < i; j++) { - float f1 = this.random.nextFloat() * 3.141593F * 2.0F; - float f2 = (this.random.nextFloat() - 0.5F) * 2.0F / 8.0F; - float f3 = (this.random.nextFloat() * 2.0F + this.random.nextFloat()) * 2.0F; - - int size = numberInRange(random, this.ravineMinLength, this.ravineMaxLength); - - createRavine(this.random.nextLong(), generatingChunkBuffer, d1, d2, d3, f3, f1, f2, size, this.ravineDepth); - } - } - - protected void createRavine(long paramLong, FullChunk generatingChunkBuffer, double paramDouble1, double paramDouble2, double paramDouble3, - float paramFloat1, float paramFloat2, float paramFloat3, int size, double paramDouble4) { - Random localRandom = new Random(paramLong); - - int chunkX = generatingChunkBuffer.getX(); - int chunkZ = generatingChunkBuffer.getZ(); - - double d1 = chunkX * 16 + 8; - double d2 = chunkZ * 16 + 8; - - float f1 = 0.0F; - float f2 = 0.0F; - - int i = 0; - - float f3 = 1.0F; - for (int j = 0; ; j++) { - if (j >= worldHeightCap) - break; - if ((j == 0) || (localRandom.nextInt(3) == 0)) { - f3 = 1.0F + localRandom.nextFloat() * localRandom.nextFloat() * 1.0F; - } - this.a[j] = (f3 * f3); - } - - for (int stepCount = 0; stepCount < size; stepCount++) { - double d3 = 1.5D + MathHelper.sin(stepCount * 3.141593F / size) * paramFloat1 * 1.0F; - double d4 = d3 * paramDouble4; - - d3 *= (localRandom.nextFloat() * 0.25D + 0.75D); - d4 *= (localRandom.nextFloat() * 0.25D + 0.75D); - - float f4 = MathHelper.cos(paramFloat3); - float f5 = MathHelper.sin(paramFloat3); - paramDouble1 += MathHelper.cos(paramFloat2) * f4; - paramDouble2 += f5; - paramDouble3 += MathHelper.sin(paramFloat2) * f4; - - paramFloat3 *= 0.7F; - - paramFloat3 += f2 * 0.05F; - paramFloat2 += f1 * 0.05F; - - f2 *= 0.8F; - f1 *= 0.5F; - f2 += (localRandom.nextFloat() - localRandom.nextFloat()) * localRandom.nextFloat() * 2.0F; - f1 += (localRandom.nextFloat() - localRandom.nextFloat()) * localRandom.nextFloat() * 4.0F; - - if ((i == 0) && (localRandom.nextInt(4) == 0)) { - continue; - } - double d5 = paramDouble1 - d1; - double d6 = paramDouble3 - d2; - double d7 = size - stepCount; - double d8 = paramFloat1 + 2.0F + 16.0F; - if (d5 * d5 + d6 * d6 - d7 * d7 > d8 * d8) { - return; - } - - if ((paramDouble1 < d1 - 16.0D - d3 * 2.0D) || (paramDouble3 < d2 - 16.0D - d3 * 2.0D) || (paramDouble1 > d1 + 16.0D + d3 * 2.0D) || (paramDouble3 > d2 + 16.0D + d3 * 2.0D)) - continue; - int k = MathHelper.floor(paramDouble1 - d3) - (chunkX * 16) - 1; - int m = MathHelper.floor(paramDouble1 + d3) - (chunkZ * 16) + 1; - - int maxY = MathHelper.floor(paramDouble2 - d4) - 1; - int minY = MathHelper.floor(paramDouble2 + d4) + 1; - - int i2 = MathHelper.floor(paramDouble3 - d3) - (chunkX * 16) - 1; - int i3 = MathHelper.floor(paramDouble3 + d3) - (chunkZ * 16) + 1; - - if (k < 0) - k = 0; - if (m > 16) - m = 16; - - if (maxY < 1) - maxY = 1; - if (minY > this.worldHeightCap - 8) - minY = this.worldHeightCap - 8; - - if (i2 < 0) - i2 = 0; - if (i3 > 16) - i3 = 16; - - int i4 = 0; - for (int localX = k; (i4 == 0) && (localX < m); localX++) { - for (int localZ = i2; (i4 == 0) && (localZ < i3); localZ++) { - for (int localY = minY + 1; (i4 == 0) && (localY >= maxY - 1); localY--) { - if (localY < 0) - continue; - if (localY < this.worldHeightCap) { - int materialAtPosition = generatingChunkBuffer.getBlockId(localX, localY, localZ); - if (materialAtPosition == Block.WATER - || materialAtPosition == Block.STILL_WATER) { - i4 = 1; - } - if ((localY != maxY - 1) && (localX != k) && (localX != m - 1) && (localZ != i2) && (localZ != i3 - 1)) - localY = maxY; - } - } - } - } - if (i4 != 0) { - continue; - } - for (int localX = k; localX < m; localX++) { - double d9 = (localX + (chunkX * 16) + 0.5D - paramDouble1) / d3; - for (int localZ = i2; localZ < i3; localZ++) { - double d10 = (localZ + (chunkZ * 16) + 0.5D - paramDouble3) / d3; - if (d9 * d9 + d10 * d10 < 1.0D) { - for (int localY = minY; localY >= maxY; localY--) { - double d11 = ((localY - 1) + 0.5D - paramDouble2) / d4; - if ((d9 * d9 + d10 * d10) * this.a[localY - 1] + d11 * d11 / 6.0D < 1.0D) { - int material = generatingChunkBuffer.getBlockId(localX, localY, localZ); - if (material == Block.GRASS) { - if (localY - 1 < 10) { - generatingChunkBuffer.setBlock(localX, localY, localZ, Block.LAVA); - } else { - generatingChunkBuffer.setBlock(localX, localY, localZ, Block.AIR); - } - } - } - } - } - } - } - if (i != 0) - break; - } - } - - public static int numberInRange(Random random, int min, int max) { - return min + random.nextInt(max - min + 1); - } -} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSeagrass.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSeagrass.java new file mode 100644 index 00000000000..4f6f498f288 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSeagrass.java @@ -0,0 +1,36 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLayer; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.helper.EnsureCover; +import cn.nukkit.level.generator.populator.type.PopulatorOceanFloorSurfaceBlock; +import cn.nukkit.math.NukkitRandom; + +public class PopulatorSeagrass extends PopulatorOceanFloorSurfaceBlock { + + @Override + protected boolean canStay(int x, int y, int z, FullChunk chunk) { + if (chunk instanceof cn.nukkit.level.format.anvil.Chunk) return false; + int down; + return EnsureCover.ensureWaterCover(x, y, z, chunk) && ((down = chunk.getBlockId(x, y - 1, z)) == DIRT || down == SAND || down == GRAVEL); + } + + @Override + protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { + return Block.SEAGRASS << Block.DATA_BITS; + } + + @Override + protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { + if (y < 255 && random.nextDouble() < 0.3 && chunk.getBlockId(x, y + 1, z) == STILL_WATER) { + chunk.setBlock(x, y, z, SEAGRASS, 2); // tall seagrass bottom + chunk.setBlock(x, y + 1, z, SEAGRASS, 1); // tall seagrass top + chunk.setFullBlockId(x, y, z, BlockLayer.WATERLOGGED, Block.STILL_WATER << Block.DATA_BITS); + chunk.setFullBlockId(x, y + 1, z, BlockLayer.WATERLOGGED, Block.STILL_WATER << Block.DATA_BITS); + } else { + chunk.setFullBlockId(x, y, z, id); + chunk.setFullBlockId(x, y, z, BlockLayer.WATERLOGGED, Block.STILL_WATER << Block.DATA_BITS); + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSmallMushroom.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSmallMushroom.java index 4269946967a..32921198eeb 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSmallMushroom.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSmallMushroom.java @@ -1,5 +1,6 @@ package cn.nukkit.level.generator.populator.impl; +import cn.nukkit.block.Block; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.helper.EnsureGrassBelow; @@ -18,6 +19,6 @@ protected boolean canStay(int x, int y, int z, FullChunk chunk) { @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return BROWN_MUSHROOM << 4; + return Block.BROWN_MUSHROOM << Block.DATA_BITS; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSoulSandValleyGround.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSoulSandValleyGround.java new file mode 100644 index 00000000000..859370a3fc3 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSoulSandValleyGround.java @@ -0,0 +1,41 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorSoulSandValleyGround extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 127; y > 0; y--) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.SOUL_SOIL || b == Block.SOUL_SAND) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextBoundedInt(15) != 1) return; + + int block = random.nextBoolean() ? CRIMSON_ROOTS : random.nextBoolean() ? RED_MUSHROOM : BROWN_MUSHROOM; + + int amount = random.nextBoundedInt(7) + 3; + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + level.setBlockAt(x, y, z, block); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSpring.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSpring.java new file mode 100644 index 00000000000..f33de854149 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSpring.java @@ -0,0 +1,110 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.format.LevelProvider; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; + +public class PopulatorSpring extends Populator { + + private final int block; + private final int replaceId; + private final int attempts; + private final int minHeight; + private final int maxHeight; + + public PopulatorSpring(int block, int replaceId, int attempts, int minHeight, int maxHeight) { + this.block = block; + this.replaceId = replaceId; + this.attempts = attempts; + this.minHeight = minHeight; + this.maxHeight = maxHeight; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int sourceX = chunkX << 4; + int sourceZ = chunkZ << 4; + + for (int i = 0; i < attempts; i++) { + int x = sourceX + random.nextBoundedInt(16); + int z = sourceZ + random.nextBoundedInt(16); + int y = random.nextRange(minHeight, maxHeight); + + int blockXYZ = level.getBlockIdAt(x, y, z); + if (!(blockXYZ == BlockID.AIR || blockXYZ == this.replaceId)) { + continue; + } + + // We don't want to trigger block update next to a fallable block + + int blockX_1YZ = level.getBlockIdAt(x, y - 1, z); + if (blockX_1YZ == BlockID.SAND || blockX_1YZ == BlockID.GRAVEL) { + continue; + } + + int blockX1YZ = level.getBlockIdAt(x, y + 1, z); + if (blockX1YZ == BlockID.SAND || blockX1YZ == BlockID.GRAVEL) { + continue; + } + + if (!(blockX_1YZ == this.replaceId || blockX1YZ == this.replaceId)) { + continue; + } + + int block1XYZ = level.getBlockIdAt(x + 1, y, z); + if (block1XYZ == BlockID.SAND || block1XYZ == BlockID.GRAVEL) { + continue; + } + int block_1XYZ = level.getBlockIdAt(x - 1, y, z); + if (block_1XYZ == BlockID.SAND || block_1XYZ == BlockID.GRAVEL) { + continue; + } + int blockXY1Z = level.getBlockIdAt(x, y, z + 1); + if (blockXY1Z == BlockID.SAND || blockXY1Z == BlockID.GRAVEL) { + continue; + } + int blockXY_1Z = level.getBlockIdAt(x, y, z - 1); + if (blockXY_1Z == BlockID.SAND || blockXY_1Z == BlockID.GRAVEL) { + continue; + } + + int surroundCount = 0; + if (block1XYZ == this.replaceId) surroundCount++; + if (block_1XYZ == this.replaceId) surroundCount++; + if (blockXY1Z == this.replaceId) surroundCount++; + if (blockXY_1Z == this.replaceId) surroundCount++; + + if (surroundCount != 3) { + continue; + } + + int airCount = 0; + if (block1XYZ == AIR) airCount++; + if (block_1XYZ == AIR) airCount++; + if (blockXY1Z == AIR) airCount++; + if (blockXY_1Z == AIR) airCount++; + + if (airCount != 1) { + continue; + } + + level.setBlockAt(x, y, z, this.block); + + LevelProvider provider = chunk.getProvider(); + if (provider != null) { + Block state = Block.fullList[this.block << Block.DATA_BITS].clone(); + state.x = x; + state.y = y; + state.z = z; + state.level = provider.getLevel(); + if (state.level != null) { + state.level.scheduleUpdate(state, state, 1, 0, false); + } + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSugarcane.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSugarcane.java index 1b1bf160af3..cc1120ba504 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSugarcane.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSugarcane.java @@ -1,12 +1,12 @@ package cn.nukkit.level.generator.populator.impl; import cn.nukkit.block.Block; -import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.helper.EnsureBelow; import cn.nukkit.level.generator.populator.helper.EnsureCover; import cn.nukkit.level.generator.populator.helper.EnsureGrassBelow; import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; +import cn.nukkit.math.BlockFace; import cn.nukkit.math.NukkitRandom; /** @@ -17,29 +17,24 @@ */ public class PopulatorSugarcane extends PopulatorSurfaceBlock { - private boolean findWater(int x, int y, int z, Level level) { - int count = 0; - for (int i = x - 4; i < (x + 4); i++) { - for (int j = z - 4; j < (z + 4); j++) { - int b = level.getBlockIdAt(i, y, j); - if (b == Block.WATER || b == Block.STILL_WATER) { - count++; - } - if (count > 10) { - return true; - } - } - } - return (count > 10); + private boolean findWater(int x, int y, int z, FullChunk chunk) { + int b = chunk.getBlockId(x + BlockFace.NORTH.getXOffset() & 0xF, y, z + BlockFace.NORTH.getZOffset() & 0xF); + if (b == Block.WATER || b == Block.STILL_WATER) return true; + b = chunk.getBlockId(x + BlockFace.EAST.getXOffset() & 0xF, y, z + BlockFace.EAST.getZOffset() & 0xF); + if (b == Block.WATER || b == Block.STILL_WATER) return true; + b = chunk.getBlockId(x + BlockFace.SOUTH.getXOffset() & 0xF, y, z + BlockFace.SOUTH.getZOffset() & 0xF); + if (b == Block.WATER || b == Block.STILL_WATER) return true; + b = chunk.getBlockId(x + BlockFace.WEST.getXOffset() & 0xF, y, z + BlockFace.WEST.getZOffset() & 0xF); + return b == Block.WATER || b == Block.STILL_WATER; } @Override protected boolean canStay(int x, int y, int z, FullChunk chunk) { - return EnsureCover.ensureCover(x, y, z, chunk) && (EnsureGrassBelow.ensureGrassBelow(x, y, z, chunk) || EnsureBelow.ensureBelow(x, y, z, SAND, chunk)) && findWater(x, y - 1, z, chunk.getProvider().getLevel()); + return EnsureCover.ensureCover(x, y, z, chunk) && (EnsureGrassBelow.ensureGrassBelow(x, y, z, chunk) || EnsureBelow.ensureBelow(x, y, z, SAND, chunk)) && findWater(x, y - 1, z, chunk); } @Override protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { - return (SUGARCANE_BLOCK << 4) | 1; + return (Block.SUGARCANE_BLOCK << Block.DATA_BITS) | 1; } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSweetBerryBush.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSweetBerryBush.java new file mode 100644 index 00000000000..06370dd5b1a --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorSweetBerryBush.java @@ -0,0 +1,25 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockID; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.helper.EnsureBelow; +import cn.nukkit.level.generator.populator.helper.EnsureCover; +import cn.nukkit.level.generator.populator.type.PopulatorSurfaceBlock; +import cn.nukkit.math.NukkitRandom; + +import java.util.concurrent.ThreadLocalRandom; + +public class PopulatorSweetBerryBush extends PopulatorSurfaceBlock { + + @Override + protected boolean canStay(int x, int y, int z, FullChunk chunk) { + if (chunk instanceof cn.nukkit.level.format.anvil.Chunk) return false; + return EnsureCover.ensureCover(x, y, z, chunk) && EnsureBelow.ensureBelow(x, y, z, GRASS, chunk); + } + + @Override + protected int getBlockId(int x, int z, NukkitRandom random, FullChunk chunk) { + return (BlockID.SWEET_BERRY_BUSH << Block.DATA_BITS) + ThreadLocalRandom.current().nextInt(3); + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTallSugarcane.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTallSugarcane.java index 84aa9a0002d..ef7382822aa 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTallSugarcane.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTallSugarcane.java @@ -16,6 +16,7 @@ public class PopulatorTallSugarcane extends PopulatorSugarcane { @Override protected void placeBlock(int x, int y, int z, int id, FullChunk chunk, NukkitRandom random) { int height = ThreadLocalRandom.current().nextInt(3) + 1; + if (y + height > 255) return; for (int i = 0; i < height; i++) { chunk.setFullBlockId(x, y + i, z, id); } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTree.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTree.java index a68ffa676a3..681755dca95 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTree.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTree.java @@ -10,7 +10,7 @@ import cn.nukkit.math.NukkitRandom; /** - * author: DaPorkchop_ + * @author DaPorkchop_ * Nukkit Project */ public class PopulatorTree extends PopulatorCount { @@ -42,7 +42,7 @@ private int getHighestWorkableBlock(int x, int z) { int y; for (y = 254; y > 0; --y) { int b = this.level.getBlockIdAt(x, y, z); - if (b == Block.DIRT || b == Block.GRASS) { + if (b == Block.DIRT || b == Block.GRASS || (this.type == BlockSapling.SPRUCE && b == Block.PODZOL)) { break; } else if (b != Block.AIR && b != Block.SNOW_LAYER) { return -1; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTwistingVines.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTwistingVines.java new file mode 100644 index 00000000000..bd125127e51 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorTwistingVines.java @@ -0,0 +1,62 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorTwistingVines extends Populator { + + private static int getHighestEndingBlock(ChunkManager level, int x, int y, int z) { + for (; y < 128; ++y) { + int b = level.getBlockIdAt(x, y, z); + int above = level.getBlockIdAt(x, y + 1, z); + if (b == 0 && ( + above == NETHERRACK || above == WARPED_NYLIUM || above == WARPED_WART_BLOCK || + above == STILL_LAVA || above == LAVA || + above == WARPED_FUNGUS || above == WARPED_ROOTS || + above == QUARTZ_ORE || above == NETHER_GOLD_ORE || above == ANCIENT_DEBRIS)) { + break; + } + } + + return y; + } + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.WARPED_NYLIUM || b == Block.NETHERRACK) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextBoundedInt(8) < 7) return; + + int amount = random.nextBoundedInt(5) + 2; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + if (random.nextBoundedInt(4) == 0) continue; + int endY = getHighestEndingBlock(level, x, y, z); + int amountToDecrease = Math.min(random.nextBoundedInt(endY - y), 15); + for (int yPos = y; yPos < y + (amountToDecrease / 2); yPos++) { + level.setBlockAt(x, yPos, z, TWISTING_VINES); + } + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorUnderwaterFloor.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorUnderwaterFloor.java new file mode 100644 index 00000000000..83216b5f07c --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorUnderwaterFloor.java @@ -0,0 +1,77 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.BlockID; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.Normal; +import cn.nukkit.level.generator.populator.helper.PopulatorHelpers; +import cn.nukkit.level.generator.populator.type.PopulatorCount; +import cn.nukkit.math.NukkitRandom; + +import java.util.List; + +public class PopulatorUnderwaterFloor extends PopulatorCount { + + private final double probability; + private final int block; + private final int radiusMin; + private final int radiusMax; + private final int radiusY; + private final List replaceBlocks; + + public PopulatorUnderwaterFloor(double probability, int block, int radiusMin, int radiusMax, int radiusY, List replaceBlocks) { + this.probability = probability; + this.block = block; + this.radiusMin = radiusMin; + this.radiusMax = radiusMax; + this.radiusY = radiusY; + this.replaceBlocks = replaceBlocks; + } + + @Override + public void populateCount(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextDouble() >= probability) { + return; + } + + int sourceX = (chunkX << 4) + random.nextBoundedInt(16); + int sourceZ = (chunkZ << 4) + random.nextBoundedInt(16); + int sourceY = getHighestWorkableBlock(level, sourceX, sourceZ, chunk) - 1; + if (sourceY < radiusY) { + return; + } + + if (level.getBlockIdAt(sourceX, sourceY + 1, sourceZ) != BlockID.STILL_WATER) { + return; + } + + int radius = random.nextRange(radiusMin, radiusMax); + for (int x = sourceX - radius; x <= sourceX + radius; x++) { + for (int z = sourceZ - radius; z <= sourceZ + radius; z++) { + if ((x - sourceX) * (x - sourceX) + (z - sourceZ) * (z - sourceZ) <= radius * radius) { + for (int y = sourceY - radiusY; y <= sourceY + radiusY; y++) { + for (int replaceBlockState : replaceBlocks) { + if (level.getBlockIdAt(x, y, z) == replaceBlockState) { + level.setBlockAt(x, y, z, block, 0); + } + } + } + } + } + } + } + + @Override + protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { + int y; + x &= 0xF; + z &= 0xF; + for (y = Normal.seaHeight - 1; y >= 0; --y) { + if (!PopulatorHelpers.isNonOceanSolid(chunk.getBlockId(x, y, z))) { + break; + } + } + + return y == 0 ? -1 : ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedForestGround.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedForestGround.java new file mode 100644 index 00000000000..a7fa22f96ce --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedForestGround.java @@ -0,0 +1,47 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorWarpedForestGround extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.WARPED_NYLIUM) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(64) + 32; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + if (random.nextBoundedInt(4) == 0) continue; + + int blockID; + if (random.nextBoundedInt(6) == 0) { + blockID = WARPED_FUNGUS; + } else { + blockID = WARPED_ROOTS; + } + + level.setBlockAt(x, y, z, blockID); + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedFungus.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedFungus.java new file mode 100644 index 00000000000..3b63da5dfc8 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWarpedFungus.java @@ -0,0 +1,40 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectWarpedTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorWarpedFungus extends Populator { + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.WARPED_NYLIUM) && level.getBlockIdAt(x, y + 1, z) == 0) { + blockYs.add(y + 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + int amount = random.nextBoundedInt(6) + 3; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + if (random.nextBoundedInt(4) == 0) continue; + new ObjectWarpedTree().placeObject(level, x, y, z, random); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWeepingVines.java b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWeepingVines.java new file mode 100644 index 00000000000..88be7ce9e6b --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/PopulatorWeepingVines.java @@ -0,0 +1,60 @@ +package cn.nukkit.level.generator.populator.impl; + +import cn.nukkit.block.Block; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitRandom; +import it.unimi.dsi.fastutil.ints.IntArrayList; + +public class PopulatorWeepingVines extends Populator { + + private static int getHighestEndingBlock(ChunkManager level, int x, int y, int z) { + for (; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + int above = level.getBlockIdAt(x, y + 1, z); + if (above == 0 && ( + b == NETHERRACK || b == CRIMSON_NYLIUM || b == BLOCK_NETHER_WART_BLOCK || + b == STILL_LAVA || b == LAVA || + b == CRIMSON_FUNGUS || b == CRIMSON_ROOTS || + b == QUARTZ_ORE || b == NETHER_GOLD_ORE || b == ANCIENT_DEBRIS)) { + break; + } + } + + return ++y; + } + + private static IntArrayList getHighestWorkableBlocks(ChunkManager level, int x, int z) { + int y; + IntArrayList blockYs = new IntArrayList(); + for (y = 128; y > 0; --y) { + int b = level.getBlockIdAt(x, y, z); + if ((b == Block.CRIMSON_NYLIUM || b == Block.NETHERRACK) && level.getBlockIdAt(x, y - 1, z) == 0) { + blockYs.add(y - 1); + } + } + return blockYs; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + if (random.nextBoundedInt(8) < 7) return; + + int amount = random.nextBoundedInt(5) + 1; + + for (int i = 0; i < amount; ++i) { + int x = random.nextRange(chunkX << 4, (chunkX << 4) + 15); + int z = random.nextRange(chunkZ << 4, (chunkZ << 4) + 15); + IntArrayList ys = getHighestWorkableBlocks(level, x, z); + for (int y : ys) { + if (y <= 1) continue; + int endY = getHighestEndingBlock(level, x, y, z); + int amountToDecrease = Math.min(random.nextBoundedInt(y - endY), 10); + for (int yPos = y; yPos > y - amountToDecrease; yPos--) { + level.setBlockAt(x, yPos, z, WEEPING_VINES); + } + } + } + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/WaterIcePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/WaterIcePopulator.java index dfe92b8f5b3..e3d74147726 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/WaterIcePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/WaterIcePopulator.java @@ -2,23 +2,20 @@ import cn.nukkit.level.ChunkManager; import cn.nukkit.level.biome.Biome; -import cn.nukkit.level.biome.EnumBiome; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.populator.type.Populator; import cn.nukkit.math.NukkitRandom; -import static cn.nukkit.block.BlockID.ICE; -import static cn.nukkit.block.BlockID.STILL_WATER; - public class WaterIcePopulator extends Populator { + @Override public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - Biome biome = EnumBiome.getBiome(chunk.getBiomeId(x, z)); + Biome biome = Biome.getBiome(chunk.getBiomeId(x, z)); if (biome.isFreezing()) { int topBlock = chunk.getHighestBlockAt(x, z); - if (chunk.getBlockId(x, topBlock, z) == STILL_WATER) { + if (chunk.getBlockId(x, topBlock, z) == STILL_WATER) { chunk.setBlockId(x, topBlock, z, ICE); } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/DarkOakTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/DarkOakTreePopulator.java index 0b0aa540db7..8482a7dcc66 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/DarkOakTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/DarkOakTreePopulator.java @@ -15,15 +15,11 @@ public class DarkOakTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public DarkOakTreePopulator() { this(BlockSapling.DARK_OAK); } - public DarkOakTreePopulator(int type) { - this.type = type; - } + public DarkOakTreePopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleBigTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleBigTreePopulator.java index 394a97c581e..05f7f1c293f 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleBigTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleBigTreePopulator.java @@ -1,6 +1,9 @@ package cn.nukkit.level.generator.populator.impl.tree; -import cn.nukkit.block.*; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockLeaves; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.object.tree.ObjectJungleBigTree; @@ -15,15 +18,11 @@ public class JungleBigTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public JungleBigTreePopulator() { this(BlockSapling.JUNGLE); } - public JungleBigTreePopulator(int type) { - this.type = type; - } + public JungleBigTreePopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; @@ -46,7 +45,7 @@ public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom ra if (y == -1) { continue; } - new ObjectJungleBigTree(10, 20, Block.get(BlockID.WOOD, BlockWood.JUNGLE), Block.get(BlockID.LEAVES, BlockLeaves.JUNGLE)).generate(this.level, random, v.setComponents(x, y, z)); + new ObjectJungleBigTree(10, 20, Block.get(WOOD, BlockWood.JUNGLE), Block.get(LEAVES, BlockLeaves.JUNGLE)).generate(this.level, random, v.setComponents(x, y, z)); } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleFloorPopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleFloorPopulator.java index e886c4da580..91e2b158ed6 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleFloorPopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleFloorPopulator.java @@ -20,15 +20,11 @@ public class JungleFloorPopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public JungleFloorPopulator() { this(BlockSapling.JUNGLE); } - public JungleFloorPopulator(int type) { - this.type = type; - } + public JungleFloorPopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleTreePopulator.java index 94f77c34461..1018a3f7073 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/JungleTreePopulator.java @@ -15,15 +15,11 @@ public class JungleTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public JungleTreePopulator() { this(BlockSapling.JUNGLE); } - public JungleTreePopulator(int type) { - this.type = type; - } + public JungleTreePopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SavannaTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SavannaTreePopulator.java index 11911087f7f..e83a8b572e7 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SavannaTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SavannaTreePopulator.java @@ -16,15 +16,11 @@ public class SavannaTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public SavannaTreePopulator() { this(BlockSapling.ACACIA); } - public SavannaTreePopulator(int type) { - this.type = type; - } + public SavannaTreePopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceBigTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceBigTreePopulator.java index 360f83db0e8..d626f085112 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceBigTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceBigTreePopulator.java @@ -1,7 +1,6 @@ package cn.nukkit.level.generator.populator.impl.tree; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockSapling; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.object.tree.ObjectBigSpruceTree; @@ -16,14 +15,7 @@ public class SpruceBigTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public SpruceBigTreePopulator() { - this(BlockSapling.SPRUCE); - } - - private SpruceBigTreePopulator(int type) { - this.type = type; } public void setRandomAmount(int randomAmount) { @@ -47,7 +39,7 @@ public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom ra if (y == -1) { continue; } - new ObjectBigSpruceTree(3 / 4f, 4).placeObject(this.level, (int) (v.x = x), (int) (v.y = y), (int) (v.z = z), random); + new ObjectBigSpruceTree(0.75f, 4).placeObject(this.level, (int) (v.x = x), (int) (v.y = y), (int) (v.z = z), random); } } @@ -55,7 +47,7 @@ private int getHighestWorkableBlock(int x, int z) { int y; for (y = 255; y > 0; --y) { int b = this.level.getBlockIdAt(x, y, z); - if (b == Block.DIRT || b == Block.GRASS) { + if (b == Block.DIRT || b == Block.GRASS || b == Block.PODZOL) { break; } else if (b != Block.AIR && b != Block.SNOW_LAYER) { return -1; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceMegaTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceMegaTreePopulator.java index 9e6c67f7e38..982676cfde6 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceMegaTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SpruceMegaTreePopulator.java @@ -1,7 +1,6 @@ package cn.nukkit.level.generator.populator.impl.tree; import cn.nukkit.block.Block; -import cn.nukkit.block.BlockSapling; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.format.FullChunk; import cn.nukkit.level.generator.object.tree.ObjectBigSpruceTree; @@ -18,14 +17,7 @@ public class SpruceMegaTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public SpruceMegaTreePopulator() { - this(BlockSapling.SPRUCE); - } - - private SpruceMegaTreePopulator(int type) { - this.type = type; } public void setRandomAmount(int randomAmount) { @@ -49,7 +41,7 @@ public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom ra if (y == -1) { continue; } - new ObjectBigSpruceTree(1 / 4f, 5).placeObject(this.level, (int) (v.x = x), (int) (v.y = y), (int) (v.z = z), random); + new ObjectBigSpruceTree(0.25f, 5).placeObject(this.level, (int) (v.x = x), (int) (v.y = y), (int) (v.z = z), random); } } @@ -57,7 +49,7 @@ private int getHighestWorkableBlock(int x, int z) { int y; for (y = 255; y > 0; --y) { int b = this.level.getBlockIdAt(x, y, z); - if (b == Block.DIRT || b == Block.GRASS) { + if (b == Block.DIRT || b == Block.GRASS || b == Block.PODZOL) { break; } else if (b != Block.AIR && b != Block.SNOW_LAYER) { return -1; diff --git a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SwampTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SwampTreePopulator.java index 631c931d514..50828a5a234 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SwampTreePopulator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/impl/tree/SwampTreePopulator.java @@ -15,15 +15,11 @@ public class SwampTreePopulator extends Populator { private int randomAmount; private int baseAmount; - private final int type; - public SwampTreePopulator() { this(BlockSapling.OAK); } - public SwampTreePopulator(int type) { - this.type = type; - } + public SwampTreePopulator(int type) {} public void setRandomAmount(int randomAmount) { this.randomAmount = randomAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/tree/DarkOakTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/tree/DarkOakTreePopulator.java new file mode 100644 index 00000000000..b85f34d20e7 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/tree/DarkOakTreePopulator.java @@ -0,0 +1,64 @@ +package cn.nukkit.level.generator.populator.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectDarkOakTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +public class DarkOakTreePopulator extends Populator { + + private ChunkManager level; + private int randomAmount; + private int baseAmount; + + public DarkOakTreePopulator() { + this(BlockSapling.DARK_OAK); + } + + public DarkOakTreePopulator(int type) {} + + public void setRandomAmount(int randomAmount) { + this.randomAmount = randomAmount; + } + + public void setBaseAmount(int baseAmount) { + this.baseAmount = baseAmount; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + this.level = level; + int amount = random.nextBoundedInt(this.randomAmount + 1) + this.baseAmount; + Vector3 v = new Vector3(); + + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + + new ObjectDarkOakTree().generate(level, random, v.setComponents(x, y, z)); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.DIRT || b == Block.GRASS) { + break; + } else if (b != Block.AIR && b != Block.SNOW_LAYER) { + return -1; + } + } + + return ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/tree/JungleBigTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/tree/JungleBigTreePopulator.java new file mode 100644 index 00000000000..65dd8f65d16 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/tree/JungleBigTreePopulator.java @@ -0,0 +1,62 @@ +package cn.nukkit.level.generator.populator.tree; + +import cn.nukkit.block.*; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectJungleBigTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +public class JungleBigTreePopulator extends Populator { + + private ChunkManager level; + private int randomAmount; + private int baseAmount; + + public JungleBigTreePopulator() { + this(BlockSapling.JUNGLE); + } + + public JungleBigTreePopulator(int type) {} + + public void setRandomAmount(int randomAmount) { + this.randomAmount = randomAmount; + } + + public void setBaseAmount(int baseAmount) { + this.baseAmount = baseAmount; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + this.level = level; + int amount = random.nextBoundedInt(this.randomAmount + 1) + this.baseAmount; + Vector3 v = new Vector3(); + + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + new ObjectJungleBigTree(10, 20, Block.get(BlockID.WOOD, BlockWood.JUNGLE), Block.get(BlockID.LEAVES, BlockLeaves.JUNGLE)).generate(this.level, random, v.setComponents(x, y, z)); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.DIRT || b == Block.GRASS) { + break; + } else if (b != Block.AIR && b != Block.SNOW_LAYER) { + return -1; + } + } + + return ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/tree/JungleTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/tree/JungleTreePopulator.java new file mode 100644 index 00000000000..4882257ea2a --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/tree/JungleTreePopulator.java @@ -0,0 +1,63 @@ +package cn.nukkit.level.generator.populator.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.NewJungleTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +public class JungleTreePopulator extends Populator { + + private ChunkManager level; + private int randomAmount; + private int baseAmount; + + public JungleTreePopulator() { + this(BlockSapling.JUNGLE); + } + + public JungleTreePopulator(int type) {} + + public void setRandomAmount(int randomAmount) { + this.randomAmount = randomAmount; + } + + public void setBaseAmount(int baseAmount) { + this.baseAmount = baseAmount; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + this.level = level; + int amount = random.nextBoundedInt(this.randomAmount + 1) + this.baseAmount; + Vector3 v = new Vector3(); + + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + new NewJungleTree(4 + random.nextBoundedInt(7)).generate(level, random, v.setComponents(x, y, z)); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.DIRT || b == Block.GRASS) { + break; + } else if (b != Block.AIR && b != Block.SNOW_LAYER) { + return -1; + } + } + + return ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/tree/SavannaTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/tree/SavannaTreePopulator.java new file mode 100644 index 00000000000..d4d55213d79 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/tree/SavannaTreePopulator.java @@ -0,0 +1,63 @@ +package cn.nukkit.level.generator.populator.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectSavannaTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +public class SavannaTreePopulator extends Populator { + + private ChunkManager level; + private int randomAmount; + private int baseAmount; + + public SavannaTreePopulator() { + this(BlockSapling.ACACIA); + } + + public SavannaTreePopulator(int type) {} + + public void setRandomAmount(int randomAmount) { + this.randomAmount = randomAmount; + } + + public void setBaseAmount(int baseAmount) { + this.baseAmount = baseAmount; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + this.level = level; + int amount = random.nextBoundedInt(this.randomAmount + 1) + this.baseAmount; + Vector3 v = new Vector3(); + + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + new ObjectSavannaTree().generate(level, random, v.setComponents(x, y, z)); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.DIRT || b == Block.GRASS) { + break; + } else if (b != Block.AIR && b != Block.SNOW_LAYER) { + return -1; + } + } + + return ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/tree/SwampTreePopulator.java b/src/main/java/cn/nukkit/level/generator/populator/tree/SwampTreePopulator.java new file mode 100644 index 00000000000..ab09530fe79 --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/tree/SwampTreePopulator.java @@ -0,0 +1,63 @@ +package cn.nukkit.level.generator.populator.tree; + +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockSapling; +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.object.tree.ObjectSwampTree; +import cn.nukkit.level.generator.populator.type.Populator; +import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.math.Vector3; + +public class SwampTreePopulator extends Populator { + + private ChunkManager level; + private int randomAmount; + private int baseAmount; + + public SwampTreePopulator() { + this(BlockSapling.OAK); + } + + public SwampTreePopulator(int type) {} + + public void setRandomAmount(int randomAmount) { + this.randomAmount = randomAmount; + } + + public void setBaseAmount(int baseAmount) { + this.baseAmount = baseAmount; + } + + @Override + public void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { + this.level = level; + int amount = random.nextBoundedInt(this.randomAmount + 1) + this.baseAmount; + Vector3 v = new Vector3(); + + for (int i = 0; i < amount; ++i) { + int x = NukkitMath.randomRange(random, chunkX << 4, (chunkX << 4) + 15); + int z = NukkitMath.randomRange(random, chunkZ << 4, (chunkZ << 4) + 15); + int y = this.getHighestWorkableBlock(x, z); + if (y == -1) { + continue; + } + new ObjectSwampTree().generate(level, random, v.setComponents(x, y, z)); + } + } + + private int getHighestWorkableBlock(int x, int z) { + int y; + for (y = 127; y > 0; --y) { + int b = this.level.getBlockIdAt(x, y, z); + if (b == Block.DIRT || b == Block.GRASS) { + break; + } else if (b != Block.AIR && b != Block.SNOW_LAYER) { + return -1; + } + } + + return ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/type/Populator.java b/src/main/java/cn/nukkit/level/generator/populator/type/Populator.java index 481d85f7f12..16ecb4165fb 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/type/Populator.java +++ b/src/main/java/cn/nukkit/level/generator/populator/type/Populator.java @@ -6,13 +6,14 @@ import cn.nukkit.math.NukkitRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Populator implements BlockID { + public abstract void populate(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk); - protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { + protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { return chunk.getHighestBlockAt(x & 0xF, z & 0xF); } } diff --git a/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorCount.java b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorCount.java index b15bba7c42d..c537997267f 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorCount.java +++ b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorCount.java @@ -11,6 +11,7 @@ * This prevents the exact same code from being repeated in nearly every single populator */ public abstract class PopulatorCount extends Populator { + private int randomAmount; private int baseAmount; diff --git a/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorOceanFloorSurfaceBlock.java b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorOceanFloorSurfaceBlock.java new file mode 100644 index 00000000000..395857449cb --- /dev/null +++ b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorOceanFloorSurfaceBlock.java @@ -0,0 +1,20 @@ +package cn.nukkit.level.generator.populator.type; + +import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.format.FullChunk; +import cn.nukkit.level.generator.Normal; +import cn.nukkit.level.generator.populator.helper.PopulatorHelpers; + +public abstract class PopulatorOceanFloorSurfaceBlock extends PopulatorSurfaceBlock { + + @Override + protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { + int y; + for (y = Normal.seaHeight - 1; y >= 0; --y) { + if (!PopulatorHelpers.isNonOceanSolid(chunk.getBlockId(x, y, z))) { + break; + } + } + return y == 0 ? -1 : ++y; + } +} diff --git a/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorSurfaceBlock.java b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorSurfaceBlock.java index e649eb7e314..8cd56ac3245 100644 --- a/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorSurfaceBlock.java +++ b/src/main/java/cn/nukkit/level/generator/populator/type/PopulatorSurfaceBlock.java @@ -11,6 +11,7 @@ * A populator that populates a single block type. */ public abstract class PopulatorSurfaceBlock extends PopulatorCount { + @Override protected void populateCount(ChunkManager level, int chunkX, int chunkZ, NukkitRandom random, FullChunk chunk) { int x = random.nextBoundedInt(16); @@ -28,7 +29,7 @@ protected void populateCount(ChunkManager level, int chunkX, int chunkZ, NukkitR @Override protected int getHighestWorkableBlock(ChunkManager level, int x, int z, FullChunk chunk) { int y; - //start at 254 because we add one afterwards + // Start at 254 because we add one afterwards for (y = 254; y >= 0; --y) { if (!PopulatorHelpers.isNonSolid(chunk.getBlockId(x, y, z))) { break; diff --git a/src/main/java/cn/nukkit/level/generator/task/GenerationTask.java b/src/main/java/cn/nukkit/level/generator/task/GenerationTask.java index 4d4654e227b..f9c7784e1fd 100644 --- a/src/main/java/cn/nukkit/level/generator/task/GenerationTask.java +++ b/src/main/java/cn/nukkit/level/generator/task/GenerationTask.java @@ -8,15 +8,15 @@ import cn.nukkit.scheduler.AsyncTask; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class GenerationTask extends AsyncTask { + private final Level level; public boolean state; private BaseFullChunk chunk; - public GenerationTask(Level level, BaseFullChunk chunk) { this.state = true; this.chunk = chunk; @@ -25,16 +25,17 @@ public GenerationTask(Level level, BaseFullChunk chunk) { @Override public void onRun() { - Generator generator = level.getGenerator(); this.state = false; + Generator generator = level.getGenerator(); if (generator == null) { + Server.getInstance().getLogger().debug(level.getFolderName() + "/GenerationTask: generator == null"); return; } SimpleChunkManager manager = (SimpleChunkManager) generator.getChunkManager(); if (manager == null) { - this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/GenerationTask: manager == null"); return; } @@ -49,6 +50,12 @@ public void onRun() { synchronized (chunk) { if (!chunk.isGenerated()) { + if (level.getProvider() == null) { + this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/GenerationTask: provider == null"); + return; + } + manager.setChunk(chunk.getX(), chunk.getZ(), chunk); generator.generateChunk(chunk.getX(), chunk.getZ()); chunk = manager.getChunk(chunk.getX(), chunk.getZ()); @@ -56,12 +63,11 @@ public void onRun() { } } this.chunk = chunk; - state = true; + this.state = true; } finally { manager.cleanChunks(level.getSeed()); } } - } @Override @@ -80,4 +86,4 @@ public void onCompletion(Server server) { level.generateChunkCallback(chunk.getX(), chunk.getZ(), chunk); } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/task/LightPopulationTask.java b/src/main/java/cn/nukkit/level/generator/task/LightPopulationTask.java index d1efa9fddd8..00700354ff8 100644 --- a/src/main/java/cn/nukkit/level/generator/task/LightPopulationTask.java +++ b/src/main/java/cn/nukkit/level/generator/task/LightPopulationTask.java @@ -6,7 +6,7 @@ import cn.nukkit.scheduler.AsyncTask; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LightPopulationTask extends AsyncTask { @@ -37,8 +37,8 @@ public void onRun() { public void onCompletion(Server server) { Level level = server.getLevel(this.levelId); - BaseFullChunk chunk = this.chunk.clone(); if (level != null) { + BaseFullChunk chunk = this.chunk.clone(); if (chunk == null) { return; } @@ -46,4 +46,4 @@ public void onCompletion(Server server) { level.generateChunkCallback(chunk.getX(), chunk.getZ(), chunk); } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/generator/task/PopulationTask.java b/src/main/java/cn/nukkit/level/generator/task/PopulationTask.java index 078dc8a4dbb..61c155d2c04 100644 --- a/src/main/java/cn/nukkit/level/generator/task/PopulationTask.java +++ b/src/main/java/cn/nukkit/level/generator/task/PopulationTask.java @@ -8,10 +8,11 @@ import cn.nukkit.scheduler.AsyncTask; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PopulationTask extends AsyncTask { + private final long seed; private final Level level; private boolean state; @@ -61,13 +62,14 @@ private void generationTask() { this.state = false; Generator generator = level.getGenerator(); if (generator == null) { + Server.getInstance().getLogger().debug(level.getFolderName() + "/PopulationTask: generator == null"); return; } SimpleChunkManager manager = (SimpleChunkManager) generator.getChunkManager(); if (manager == null) { - this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/PopulationTask: manager == null"); return; } @@ -85,27 +87,46 @@ private void generationTask() { for (int z = -1; z < 2; z++, index++) { BaseFullChunk ck = this.chunks[index]; if (ck == centerChunk) continue; + + if (level.getProvider() == null) { + this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/PopulationTask: provider == null"); + return; + } + if (ck == null) { - try { - this.chunks[index] = (BaseFullChunk) centerChunk.getClass().getMethod("getEmptyChunk", int.class, int.class).invoke(null, centerChunk.getX() + x, centerChunk.getZ() + z); - } catch (Exception e) { - throw new RuntimeException(e); - } + //try { + //this.chunks[index] = (BaseFullChunk) centerChunk.getClass().getMethod("getEmptyChunk", int.class, int.class).invoke(null, centerChunk.getX() + x, centerChunk.getZ() + z); + this.chunks[index] = level.getProvider().getEmptyChunk(centerChunk.getX() + x, centerChunk.getZ() + z); + //} catch (Exception e) { + // throw new RuntimeException(e); + //} } else { this.chunks[index] = ck; } - } } for (BaseFullChunk chunk : this.chunks) { + if (level.getProvider() == null) { + this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/PopulationTask: provider == null"); + return; + } + manager.setChunk(chunk.getX(), chunk.getZ(), chunk); if (!chunk.isGenerated()) { generator.generateChunk(chunk.getX(), chunk.getZ()); BaseFullChunk newChunk = manager.getChunk(chunk.getX(), chunk.getZ()); newChunk.setGenerated(); if (newChunk != chunk) manager.setChunk(chunk.getX(), chunk.getZ(), newChunk); - } + } + } + + if (level.getProvider() == null) { + this.state = false; + Server.getInstance().getLogger().debug(level.getFolderName() + "/PopulationTask: provider == null"); + return; } isPopulated = centerChunk.isPopulated(); @@ -131,7 +152,6 @@ private void generationTask() { chunks[index] = newChunk; } } - } } this.state = true; @@ -163,4 +183,4 @@ public void onCompletion(Server server) { level.generateChunkCallback(centerChunk.getX(), centerChunk.getZ(), centerChunk, isPopulated); } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/particle/AngryVillagerParticle.java b/src/main/java/cn/nukkit/level/particle/AngryVillagerParticle.java index 9af7ff473a3..42506adab7e 100644 --- a/src/main/java/cn/nukkit/level/particle/AngryVillagerParticle.java +++ b/src/main/java/cn/nukkit/level/particle/AngryVillagerParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class AngryVillagerParticle extends GenericParticle { + public AngryVillagerParticle(Vector3 pos) { super(pos, Particle.TYPE_VILLAGER_ANGRY); } diff --git a/src/main/java/cn/nukkit/level/particle/BlockForceFieldParticle.java b/src/main/java/cn/nukkit/level/particle/BlockForceFieldParticle.java index 3b307c292b0..fd84ef2c69d 100644 --- a/src/main/java/cn/nukkit/level/particle/BlockForceFieldParticle.java +++ b/src/main/java/cn/nukkit/level/particle/BlockForceFieldParticle.java @@ -3,6 +3,7 @@ import cn.nukkit.math.Vector3; public class BlockForceFieldParticle extends GenericParticle { + public BlockForceFieldParticle(Vector3 pos) { this(pos, 0); } diff --git a/src/main/java/cn/nukkit/level/particle/BoneMealParticle.java b/src/main/java/cn/nukkit/level/particle/BoneMealParticle.java index 0ff4decf824..b62afd681d3 100644 --- a/src/main/java/cn/nukkit/level/particle/BoneMealParticle.java +++ b/src/main/java/cn/nukkit/level/particle/BoneMealParticle.java @@ -9,21 +9,19 @@ */ public class BoneMealParticle extends Particle { - private Vector3 position; - public BoneMealParticle(Vector3 pos) { super(pos.x, pos.y, pos.z); } @Override public DataPacket[] encode() { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = LevelEventPacket.EVENT_PARTICLE_BONEMEAL; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.data = 0; - - return new DataPacket[]{pk}; + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_BONEMEAL; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = 0; + packet.tryEncode(); + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/BubbleParticle.java b/src/main/java/cn/nukkit/level/particle/BubbleParticle.java index e4013afb21a..2991032c11e 100644 --- a/src/main/java/cn/nukkit/level/particle/BubbleParticle.java +++ b/src/main/java/cn/nukkit/level/particle/BubbleParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class BubbleParticle extends GenericParticle { + public BubbleParticle(Vector3 pos) { super(pos, Particle.TYPE_BUBBLE); } diff --git a/src/main/java/cn/nukkit/level/particle/CriticalParticle.java b/src/main/java/cn/nukkit/level/particle/CriticalParticle.java index 12c670fbf88..088230e1347 100644 --- a/src/main/java/cn/nukkit/level/particle/CriticalParticle.java +++ b/src/main/java/cn/nukkit/level/particle/CriticalParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class CriticalParticle extends GenericParticle { + public CriticalParticle(Vector3 pos) { this(pos, 2); } diff --git a/src/main/java/cn/nukkit/level/particle/DestroyBlockParticle.java b/src/main/java/cn/nukkit/level/particle/DestroyBlockParticle.java index 4f81b7e4784..6436a6fef2e 100644 --- a/src/main/java/cn/nukkit/level/particle/DestroyBlockParticle.java +++ b/src/main/java/cn/nukkit/level/particle/DestroyBlockParticle.java @@ -21,13 +21,14 @@ public DestroyBlockParticle(Vector3 pos, Block block) { @Override public DataPacket[] encode() { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = LevelEventPacket.EVENT_PARTICLE_DESTROY; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.data = this.data; + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_DESTROY; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; - return new DataPacket[]{pk}; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/ElectricSparkParticle.java b/src/main/java/cn/nukkit/level/particle/ElectricSparkParticle.java new file mode 100644 index 00000000000..03c0f7e6a8c --- /dev/null +++ b/src/main/java/cn/nukkit/level/particle/ElectricSparkParticle.java @@ -0,0 +1,24 @@ +package cn.nukkit.level.particle; + +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.DataPacket; +import cn.nukkit.network.protocol.LevelEventPacket; + +public class ElectricSparkParticle extends GenericParticle { + + public ElectricSparkParticle(Vector3 pos) { + super(pos, Particle.TYPE_WAX); + } + + @Override + public DataPacket[] encode() { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_ELECTRIC_SPARK; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/particle/EnchantParticle.java b/src/main/java/cn/nukkit/level/particle/EnchantParticle.java index dec924f536f..87d26ef2063 100644 --- a/src/main/java/cn/nukkit/level/particle/EnchantParticle.java +++ b/src/main/java/cn/nukkit/level/particle/EnchantParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class EnchantParticle extends GenericParticle { + public EnchantParticle(Vector3 pos) { super(pos, Particle.TYPE_MOB_SPELL); } diff --git a/src/main/java/cn/nukkit/level/particle/EnchantmentTableParticle.java b/src/main/java/cn/nukkit/level/particle/EnchantmentTableParticle.java index a5499a9a069..2d793706085 100644 --- a/src/main/java/cn/nukkit/level/particle/EnchantmentTableParticle.java +++ b/src/main/java/cn/nukkit/level/particle/EnchantmentTableParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class EnchantmentTableParticle extends GenericParticle { + public EnchantmentTableParticle(Vector3 pos) { super(pos, Particle.TYPE_ENCHANTMENT_TABLE); } diff --git a/src/main/java/cn/nukkit/level/particle/EntityFlameParticle.java b/src/main/java/cn/nukkit/level/particle/EntityFlameParticle.java index dbc74dd6dcb..e67c4526721 100644 --- a/src/main/java/cn/nukkit/level/particle/EntityFlameParticle.java +++ b/src/main/java/cn/nukkit/level/particle/EntityFlameParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class EntityFlameParticle extends GenericParticle { + public EntityFlameParticle(Vector3 pos) { super(pos, Particle.TYPE_MOB_FLAME); } diff --git a/src/main/java/cn/nukkit/level/particle/ExplodeParticle.java b/src/main/java/cn/nukkit/level/particle/ExplodeParticle.java index a1bb3ef92ea..a70ab352e62 100644 --- a/src/main/java/cn/nukkit/level/particle/ExplodeParticle.java +++ b/src/main/java/cn/nukkit/level/particle/ExplodeParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class ExplodeParticle extends GenericParticle { + public ExplodeParticle(Vector3 pos) { super(pos, Particle.TYPE_EXPLODE); } diff --git a/src/main/java/cn/nukkit/level/particle/FlameParticle.java b/src/main/java/cn/nukkit/level/particle/FlameParticle.java index 9ae63d42ee7..b4a4cabd3ee 100644 --- a/src/main/java/cn/nukkit/level/particle/FlameParticle.java +++ b/src/main/java/cn/nukkit/level/particle/FlameParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class FlameParticle extends GenericParticle { + public FlameParticle(Vector3 pos) { super(pos, Particle.TYPE_FLAME); } diff --git a/src/main/java/cn/nukkit/level/particle/FloatingTextParticle.java b/src/main/java/cn/nukkit/level/particle/FloatingTextParticle.java index b9b6d849f57..2e51ead5d65 100644 --- a/src/main/java/cn/nukkit/level/particle/FloatingTextParticle.java +++ b/src/main/java/cn/nukkit/level/particle/FloatingTextParticle.java @@ -8,24 +8,31 @@ import cn.nukkit.level.Location; import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.*; +import cn.nukkit.utils.Binary; import cn.nukkit.utils.SerializedImage; +import cn.nukkit.utils.Utils; import com.google.common.base.Strings; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; /** * Created on 2015/11/21 by xtypr. * Package cn.nukkit.level.particle in project Nukkit . */ public class FloatingTextParticle extends Particle { + private static final Skin EMPTY_SKIN = new Skin(); private static final SerializedImage SKIN_DATA = SerializedImage.fromLegacy(new byte[8192]); + private static final UUID SKIN_UUID = UUID.nameUUIDFromBytes(Binary.appendBytes(Skin.GEOMETRY_CUSTOM.getBytes(StandardCharsets.UTF_8), SKIN_DATA.data)); static { EMPTY_SKIN.setSkinData(SKIN_DATA); - EMPTY_SKIN.generateSkinId("FloatingText"); + EMPTY_SKIN.setSkinResourcePatch(Skin.GEOMETRY_CUSTOM); + EMPTY_SKIN.setSkinId(SKIN_UUID + ".FloatingText"); + EMPTY_SKIN.setCapeData(SerializedImage.EMPTY); + EMPTY_SKIN.setCapeId(""); } protected UUID uuid = UUID.randomUUID(); @@ -54,12 +61,9 @@ private FloatingTextParticle(Level level, Vector3 pos, String title, String text super(pos.x, pos.y, pos.z); this.level = level; - long flags = ( - 1L << Entity.DATA_FLAG_NO_AI - ); - metadata.putLong(Entity.DATA_FLAGS, flags) + metadata.putLong(Entity.DATA_FLAGS, 65536L) .putLong(Entity.DATA_LEAD_HOLDER_EID,-1) - .putFloat(Entity.DATA_SCALE, 0.01f) //zero causes problems on debug builds? + .putFloat(Entity.DATA_SCALE, 0.01f) .putFloat(Entity.DATA_BOUNDING_BOX_HEIGHT, 0.01f) .putFloat(Entity.DATA_BOUNDING_BOX_WIDTH, 0.01f); if (!Strings.isNullOrEmpty(title)) { @@ -108,21 +112,20 @@ public void setInvisible(boolean invisible) { public void setInvisible() { this.setInvisible(true); } - + public long getEntityId() { - return entityId; + return entityId; } @Override public DataPacket[] encode() { ArrayList packets = new ArrayList<>(); - if (this.entityId == -1) { - this.entityId = 1095216660480L + ThreadLocalRandom.current().nextLong(0, 0x7fffffffL); + this.entityId = 1095216660480L + Utils.random.nextLong(0, 0x7fffffffL); } else { RemoveEntityPacket pk = new RemoveEntityPacket(); pk.eid = this.entityId; - + pk.tryEncode(); packets.add(pk); } @@ -132,6 +135,7 @@ public DataPacket[] encode() { PlayerListPacket playerAdd = new PlayerListPacket(); playerAdd.entries = entry; playerAdd.type = PlayerListPacket.TYPE_ADD; + playerAdd.tryEncode(); packets.add(playerAdd); AddPlayerPacket pk = new AddPlayerPacket(); @@ -149,14 +153,15 @@ public DataPacket[] encode() { pk.pitch = 0; pk.metadata = this.metadata; pk.item = Item.get(Item.AIR); + pk.tryEncode(); packets.add(pk); PlayerListPacket playerRemove = new PlayerListPacket(); playerRemove.entries = entry; playerRemove.type = PlayerListPacket.TYPE_REMOVE; + playerRemove.tryEncode(); packets.add(playerRemove); } - return packets.toArray(new DataPacket[0]); } } diff --git a/src/main/java/cn/nukkit/level/particle/GenericParticle.java b/src/main/java/cn/nukkit/level/particle/GenericParticle.java index 870c4f3f80c..72909bceaca 100644 --- a/src/main/java/cn/nukkit/level/particle/GenericParticle.java +++ b/src/main/java/cn/nukkit/level/particle/GenericParticle.java @@ -10,7 +10,7 @@ */ public class GenericParticle extends Particle { - protected int id = 0; + protected int id; protected final int data; @@ -26,13 +26,13 @@ public GenericParticle(Vector3 pos, int id, int data) { @Override public DataPacket[] encode() { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = (short) (LevelEventPacket.EVENT_ADD_PARTICLE_MASK | this.id); - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.data = this.data; - - return new DataPacket[]{pk}; + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = (short) (LevelEventPacket.EVENT_ADD_PARTICLE_MASK | this.id); + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/HappyVillagerParticle.java b/src/main/java/cn/nukkit/level/particle/HappyVillagerParticle.java index 494d38a64b4..0750ab67f62 100644 --- a/src/main/java/cn/nukkit/level/particle/HappyVillagerParticle.java +++ b/src/main/java/cn/nukkit/level/particle/HappyVillagerParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class HappyVillagerParticle extends GenericParticle { + public HappyVillagerParticle(Vector3 pos) { super(pos, Particle.TYPE_VILLAGER_HAPPY); } diff --git a/src/main/java/cn/nukkit/level/particle/HeartParticle.java b/src/main/java/cn/nukkit/level/particle/HeartParticle.java index 1d94e017c35..90640170386 100644 --- a/src/main/java/cn/nukkit/level/particle/HeartParticle.java +++ b/src/main/java/cn/nukkit/level/particle/HeartParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class HeartParticle extends GenericParticle { + public HeartParticle(Vector3 pos) { this(pos, 0); } diff --git a/src/main/java/cn/nukkit/level/particle/HugeExplodeParticle.java b/src/main/java/cn/nukkit/level/particle/HugeExplodeParticle.java index af056ffb04d..ad310911605 100644 --- a/src/main/java/cn/nukkit/level/particle/HugeExplodeParticle.java +++ b/src/main/java/cn/nukkit/level/particle/HugeExplodeParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class HugeExplodeParticle extends GenericParticle { + public HugeExplodeParticle(Vector3 pos) { super(pos, Particle.TYPE_HUGE_EXPLODE); } diff --git a/src/main/java/cn/nukkit/level/particle/HugeExplodeSeedParticle.java b/src/main/java/cn/nukkit/level/particle/HugeExplodeSeedParticle.java index a5fc23b480a..c3bd7105e61 100644 --- a/src/main/java/cn/nukkit/level/particle/HugeExplodeSeedParticle.java +++ b/src/main/java/cn/nukkit/level/particle/HugeExplodeSeedParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class HugeExplodeSeedParticle extends GenericParticle { + public HugeExplodeSeedParticle(Vector3 pos) { super(pos, Particle.TYPE_HUGE_EXPLODE_SEED); } diff --git a/src/main/java/cn/nukkit/level/particle/InstantEnchantParticle.java b/src/main/java/cn/nukkit/level/particle/InstantEnchantParticle.java index 751ee650079..d5d46cdabf1 100644 --- a/src/main/java/cn/nukkit/level/particle/InstantEnchantParticle.java +++ b/src/main/java/cn/nukkit/level/particle/InstantEnchantParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class InstantEnchantParticle extends GenericParticle { + public InstantEnchantParticle(Vector3 pos) { super(pos, Particle.TYPE_MOB_SPELL_INSTANTANEOUS); } diff --git a/src/main/java/cn/nukkit/level/particle/InstantSpellParticle.java b/src/main/java/cn/nukkit/level/particle/InstantSpellParticle.java index 80c29518eca..c8693d69533 100644 --- a/src/main/java/cn/nukkit/level/particle/InstantSpellParticle.java +++ b/src/main/java/cn/nukkit/level/particle/InstantSpellParticle.java @@ -8,7 +8,6 @@ * Package cn.nukkit.level.particle in project nukkit . */ public class InstantSpellParticle extends SpellParticle { - protected int data; public InstantSpellParticle(Vector3 pos) { this(pos, 0); @@ -27,5 +26,4 @@ public InstantSpellParticle(Vector3 pos, int r, int g, int b) { //this 0x01 is the only difference between instant spell and non-instant one super(pos, r, g, b, 0x01); } - } diff --git a/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java b/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java index b8722427d86..375c21b9998 100644 --- a/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java +++ b/src/main/java/cn/nukkit/level/particle/ItemBreakParticle.java @@ -26,6 +26,7 @@ public DataPacket[] encode() { packet.y = (float) this.y; packet.z = (float) this.z; packet.data = this.data; + packet.tryEncode(); return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/LavaDripParticle.java b/src/main/java/cn/nukkit/level/particle/LavaDripParticle.java index 8397fa9e43c..89b57d03b43 100644 --- a/src/main/java/cn/nukkit/level/particle/LavaDripParticle.java +++ b/src/main/java/cn/nukkit/level/particle/LavaDripParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class LavaDripParticle extends GenericParticle { + public LavaDripParticle(Vector3 pos) { super(pos, Particle.TYPE_DRIP_LAVA); } diff --git a/src/main/java/cn/nukkit/level/particle/LavaParticle.java b/src/main/java/cn/nukkit/level/particle/LavaParticle.java index d64fb7ad7ec..eb7bc8f2cc6 100644 --- a/src/main/java/cn/nukkit/level/particle/LavaParticle.java +++ b/src/main/java/cn/nukkit/level/particle/LavaParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class LavaParticle extends GenericParticle { + public LavaParticle(Vector3 pos) { super(pos, Particle.TYPE_LAVA); } diff --git a/src/main/java/cn/nukkit/level/particle/MobSpawnParticle.java b/src/main/java/cn/nukkit/level/particle/MobSpawnParticle.java index 56827e741e8..191875970d6 100644 --- a/src/main/java/cn/nukkit/level/particle/MobSpawnParticle.java +++ b/src/main/java/cn/nukkit/level/particle/MobSpawnParticle.java @@ -27,7 +27,7 @@ public DataPacket[] encode() { packet.y = (float) this.y; packet.z = (float) this.z; packet.data = (this.width & 0xff) + ((this.height & 0xff) << 8); - + packet.tryEncode(); return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/Particle.java b/src/main/java/cn/nukkit/level/particle/Particle.java index eda418475a4..2384de13c3a 100644 --- a/src/main/java/cn/nukkit/level/particle/Particle.java +++ b/src/main/java/cn/nukkit/level/particle/Particle.java @@ -6,7 +6,7 @@ import java.lang.reflect.Field; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class Particle extends Vector3 { @@ -100,6 +100,24 @@ public abstract class Particle extends Vector3 { public static final int TYPE_DUST_PLUME = 88; public static final int TYPE_WHITE_SMOKE = 89; + public Particle() { + super(0, 0, 0); + } + + public Particle(double x) { + super(x, 0, 0); + } + + public Particle(double x, double y) { + super(x, y, 0); + } + + public Particle(double x, double y, double z) { + super(x, y, z); + } + + public abstract DataPacket[] encode(); + public static Integer getParticleIdByName(String name) { name = name.toUpperCase(); @@ -111,31 +129,11 @@ public static Integer getParticleIdByName(String name) { if (type == int.class) { return field.getInt(null); } - } catch (NoSuchFieldException | IllegalAccessException e) { - // ignore - } + } catch (NoSuchFieldException | IllegalAccessException ignored) {} return null; } public static boolean particleExists(String name) { return getParticleIdByName(name) != null; } - - public Particle() { - super(0, 0, 0); - } - - public Particle(double x) { - super(x, 0, 0); - } - - public Particle(double x, double y) { - super(x, y, 0); - } - - public Particle(double x, double y, double z) { - super(x, y, z); - } - - abstract public DataPacket[] encode(); } diff --git a/src/main/java/cn/nukkit/level/particle/PortalParticle.java b/src/main/java/cn/nukkit/level/particle/PortalParticle.java index 282130b6486..d3a8473efd0 100644 --- a/src/main/java/cn/nukkit/level/particle/PortalParticle.java +++ b/src/main/java/cn/nukkit/level/particle/PortalParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class PortalParticle extends GenericParticle { + public PortalParticle(Vector3 pos) { super(pos, Particle.TYPE_PORTAL); } diff --git a/src/main/java/cn/nukkit/level/particle/PunchBlockParticle.java b/src/main/java/cn/nukkit/level/particle/PunchBlockParticle.java index f7ce141abcb..f4f14a5aa62 100644 --- a/src/main/java/cn/nukkit/level/particle/PunchBlockParticle.java +++ b/src/main/java/cn/nukkit/level/particle/PunchBlockParticle.java @@ -9,7 +9,10 @@ public class PunchBlockParticle extends Particle { - protected final int data; + protected final int blockId; + protected final int blockDamage; + protected final int index; + protected final int face; public PunchBlockParticle(Vector3 pos, Block block, BlockFace face) { this(pos, block.getId(), block.getDamage(), face); @@ -17,18 +20,21 @@ public PunchBlockParticle(Vector3 pos, Block block, BlockFace face) { public PunchBlockParticle(Vector3 pos, int blockId, int blockDamage, BlockFace face) { super(pos.x, pos.y, pos.z); - this.data = GlobalBlockPalette.getOrCreateRuntimeId(blockId, blockDamage) | (face.getIndex() << 24); + this.blockId = blockId; + this.blockDamage = blockDamage; + this.face = face.getIndex(); + this.index = this.face << 24; } @Override public DataPacket[] encode() { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = LevelEventPacket.EVENT_PARTICLE_PUNCH_BLOCK; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.data = this.data; - - return new DataPacket[]{pk}; + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_PUNCH_BLOCK; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = GlobalBlockPalette.getOrCreateRuntimeId(blockId, blockDamage) | index; + packet.tryEncode(); + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/RainSplashParticle.java b/src/main/java/cn/nukkit/level/particle/RainSplashParticle.java index 5149c748a5d..86730109567 100644 --- a/src/main/java/cn/nukkit/level/particle/RainSplashParticle.java +++ b/src/main/java/cn/nukkit/level/particle/RainSplashParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class RainSplashParticle extends GenericParticle { + public RainSplashParticle(Vector3 pos) { super(pos, Particle.TYPE_RAIN_SPLASH); } diff --git a/src/main/java/cn/nukkit/level/particle/RedstoneParticle.java b/src/main/java/cn/nukkit/level/particle/RedstoneParticle.java index 7effbfa34b7..c815dd7f469 100644 --- a/src/main/java/cn/nukkit/level/particle/RedstoneParticle.java +++ b/src/main/java/cn/nukkit/level/particle/RedstoneParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class RedstoneParticle extends GenericParticle { + public RedstoneParticle(Vector3 pos) { this(pos, 1); } diff --git a/src/main/java/cn/nukkit/level/particle/ScrapeParticle.java b/src/main/java/cn/nukkit/level/particle/ScrapeParticle.java new file mode 100644 index 00000000000..50cb24ef19c --- /dev/null +++ b/src/main/java/cn/nukkit/level/particle/ScrapeParticle.java @@ -0,0 +1,24 @@ +package cn.nukkit.level.particle; + +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.DataPacket; +import cn.nukkit.network.protocol.LevelEventPacket; + +public class ScrapeParticle extends GenericParticle { + + public ScrapeParticle(Vector3 pos) { + super(pos, Particle.TYPE_WAX); + } + + @Override + public DataPacket[] encode() { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_SCRAPE; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/particle/SmokeParticle.java b/src/main/java/cn/nukkit/level/particle/SmokeParticle.java index 2544f836717..5db5fe0044e 100644 --- a/src/main/java/cn/nukkit/level/particle/SmokeParticle.java +++ b/src/main/java/cn/nukkit/level/particle/SmokeParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class SmokeParticle extends GenericParticle { + public SmokeParticle(Vector3 pos) { this(pos, 0); } diff --git a/src/main/java/cn/nukkit/level/particle/SpellParticle.java b/src/main/java/cn/nukkit/level/particle/SpellParticle.java index 038b9273dc6..629ec65a518 100644 --- a/src/main/java/cn/nukkit/level/particle/SpellParticle.java +++ b/src/main/java/cn/nukkit/level/particle/SpellParticle.java @@ -38,13 +38,13 @@ protected SpellParticle(Vector3 pos, int r, int g, int b, int a) { @Override public DataPacket[] encode() { - LevelEventPacket pk = new LevelEventPacket(); - pk.evid = LevelEventPacket.EVENT_PARTICLE_SPLASH; - pk.x = (float) this.x; - pk.y = (float) this.y; - pk.z = (float) this.z; - pk.data = this.data; - - return new DataPacket[]{pk}; + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_SPLASH; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; } } diff --git a/src/main/java/cn/nukkit/level/particle/SplashParticle.java b/src/main/java/cn/nukkit/level/particle/SplashParticle.java index 5128cc75be5..8e8f825f042 100644 --- a/src/main/java/cn/nukkit/level/particle/SplashParticle.java +++ b/src/main/java/cn/nukkit/level/particle/SplashParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class SplashParticle extends GenericParticle { + public SplashParticle(Vector3 pos) { super(pos, Particle.TYPE_WATER_SPLASH); } diff --git a/src/main/java/cn/nukkit/level/particle/SporeParticle.java b/src/main/java/cn/nukkit/level/particle/SporeParticle.java index 78b223b8dac..3557c1d8469 100644 --- a/src/main/java/cn/nukkit/level/particle/SporeParticle.java +++ b/src/main/java/cn/nukkit/level/particle/SporeParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class SporeParticle extends GenericParticle { + public SporeParticle(Vector3 pos) { super(pos, Particle.TYPE_TOWN_AURA); } diff --git a/src/main/java/cn/nukkit/level/particle/TerrainParticle.java b/src/main/java/cn/nukkit/level/particle/TerrainParticle.java index 1041dc4d232..d55caecea6e 100644 --- a/src/main/java/cn/nukkit/level/particle/TerrainParticle.java +++ b/src/main/java/cn/nukkit/level/particle/TerrainParticle.java @@ -9,6 +9,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class TerrainParticle extends GenericParticle { + public TerrainParticle(Vector3 pos, Block block) { super(pos, Particle.TYPE_TERRAIN, GlobalBlockPalette.getOrCreateRuntimeId(block.getId(), block.getDamage())); } diff --git a/src/main/java/cn/nukkit/level/particle/WaterDripParticle.java b/src/main/java/cn/nukkit/level/particle/WaterDripParticle.java index 9c7b34536ac..b8c780387b3 100644 --- a/src/main/java/cn/nukkit/level/particle/WaterDripParticle.java +++ b/src/main/java/cn/nukkit/level/particle/WaterDripParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class WaterDripParticle extends GenericParticle { + public WaterDripParticle(Vector3 pos) { super(pos, Particle.TYPE_DRIP_WATER); } diff --git a/src/main/java/cn/nukkit/level/particle/WaterParticle.java b/src/main/java/cn/nukkit/level/particle/WaterParticle.java index 7f6a1d92393..7446fe1c86b 100644 --- a/src/main/java/cn/nukkit/level/particle/WaterParticle.java +++ b/src/main/java/cn/nukkit/level/particle/WaterParticle.java @@ -7,6 +7,7 @@ * Package cn.nukkit.level.particle in project Nukkit . */ public class WaterParticle extends GenericParticle { + public WaterParticle(Vector3 pos) { super(pos, Particle.TYPE_WATER_WAKE); } diff --git a/src/main/java/cn/nukkit/level/particle/WaxOffParticle.java b/src/main/java/cn/nukkit/level/particle/WaxOffParticle.java new file mode 100644 index 00000000000..09af7240d5f --- /dev/null +++ b/src/main/java/cn/nukkit/level/particle/WaxOffParticle.java @@ -0,0 +1,24 @@ +package cn.nukkit.level.particle; + +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.DataPacket; +import cn.nukkit.network.protocol.LevelEventPacket; + +public class WaxOffParticle extends GenericParticle { + + public WaxOffParticle(Vector3 pos) { + super(pos, Particle.TYPE_WAX); + } + + @Override + public DataPacket[] encode() { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_WAX_OFF; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/particle/WaxOnParticle.java b/src/main/java/cn/nukkit/level/particle/WaxOnParticle.java new file mode 100644 index 00000000000..f74e4fdc42d --- /dev/null +++ b/src/main/java/cn/nukkit/level/particle/WaxOnParticle.java @@ -0,0 +1,24 @@ +package cn.nukkit.level.particle; + +import cn.nukkit.math.Vector3; +import cn.nukkit.network.protocol.DataPacket; +import cn.nukkit.network.protocol.LevelEventPacket; + +public class WaxOnParticle extends GenericParticle { + + public WaxOnParticle(Vector3 pos) { + super(pos, Particle.TYPE_WAX); + } + + @Override + public DataPacket[] encode() { + LevelEventPacket packet = new LevelEventPacket(); + packet.evid = LevelEventPacket.EVENT_PARTICLE_WAX_ON; + packet.x = (float) this.x; + packet.y = (float) this.y; + packet.z = (float) this.z; + packet.data = this.data; + packet.tryEncode(); + return new DataPacket[]{packet}; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/persistence/ImmutableCompoundTag.java b/src/main/java/cn/nukkit/level/persistence/ImmutableCompoundTag.java new file mode 100644 index 00000000000..35034ef5c6a --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/ImmutableCompoundTag.java @@ -0,0 +1,241 @@ +package cn.nukkit.level.persistence; + +import cn.nukkit.nbt.stream.NBTInputStream; +import cn.nukkit.nbt.tag.*; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Collection; +import java.util.Map; + +public class ImmutableCompoundTag extends CompoundTag { + + public static final CompoundTag EMPTY = new ImmutableCompoundTag(new CompoundTag()); + + public static CompoundTag of(CompoundTag tag) { + return new ImmutableCompoundTag(tag); + } + + private final CompoundTag delegate; + + private ImmutableCompoundTag(CompoundTag delegate) { + this.delegate = delegate; + } + + @Override + public void load(NBTInputStream dis) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag put(String name, Tag tag) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putByte(String name, int value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putShort(String name, int value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putInt(String name, int value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putLong(String name, long value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putFloat(String name, float value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putDouble(String name, double value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putString(String name, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putByteArray(String name, byte[] value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putIntArray(String name, int[] value) { + throw new UnsupportedOperationException(); + } + @Override + + public CompoundTag putList(ListTag listTag) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putCompound(String name, CompoundTag value) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag putBoolean(String string, boolean val) { + throw new UnsupportedOperationException(); + } + + @Override + public CompoundTag remove(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public T removeAndGet(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(String name) { + return this.delegate.contains(name); + } + + @Override + public Collection getAllTags() { + return this.delegate.getAllTags(); + } + + @Override + public int getByte(String name) { + return this.delegate.getByte(name); + } + + @Override + public int getShort(String name) { + return this.delegate.getShort(name); + } + + @Override + public int getInt(String name) { + return this.delegate.getInt(name); + } + @Override + public long getLong(String name) { + return this.delegate.getLong(name); + } + + @Override + public float getFloat(String name) { + return this.delegate.getFloat(name); + } + + @Override + public double getDouble(String name) { + return this.delegate.getDouble(name); + } + + @Override + public String getString(String name) { + return this.delegate.getString(name); + } + + @Override + public byte[] getByteArray(String name) { + return this.delegate.getByteArray(name); + } + + @Override + public byte[] getByteArray(String name, int defaultSize) { + return this.delegate.getByteArray(name, defaultSize); + } + + @Override + public int[] getIntArray(String name) { + return this.delegate.getIntArray(name); + } + + @Override + public CompoundTag getCompound(String name) { + return this.delegate.getCompound(name); + } + + @Override + public ListTag getList(String name) { + return this.delegate.getList(name); + } + + @Override + public ListTag getList(String name, Class type) { + return this.delegate.getList(name, type); + } + + @Override + public Map getTags() { + return this.delegate.getTags(); + } + + @Override + public Map parseValue() { + return this.delegate.parseValue(); + } + + @Override + public boolean getBoolean(String name) { + return this.delegate.getBoolean(name); + } + + @Override + public boolean getBoolean(String name, boolean def) { + return this.delegate.getBoolean(name, def); + } + + @Override + public String toString() { + return this.delegate.toString(); + } + + @Override + public void print(PrintStream out) { + this.delegate.print(out); + } + + @Override + public void print(String prefix, PrintStream out) { + this.delegate.print(prefix, out); + } + + @Override + public boolean isEmpty() { + return this.delegate.isEmpty(); + } + + @Override + public CompoundTag copy() { + return this; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ImmutableCompoundTag) { + return this.delegate.equals(((ImmutableCompoundTag) obj).delegate); + } + return this.delegate.equals(obj); + } + + @Override + public boolean exist(String name) { + return this.delegate.exist(name); + } + + @Override + public CompoundTag clone() { + return this; + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/PersistentDataContainer.java b/src/main/java/cn/nukkit/level/persistence/PersistentDataContainer.java new file mode 100644 index 00000000000..0a4da27564c --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/PersistentDataContainer.java @@ -0,0 +1,58 @@ +package cn.nukkit.level.persistence; + +import cn.nukkit.nbt.tag.CompoundTag; +import cn.nukkit.nbt.tag.Tag; + +public interface PersistentDataContainer { + String STORAGE_TAG = "persistent_storage"; + + CompoundTag getStorage(); + + default CompoundTag getReadStorage() { + return this.getStorage(); + } + + void setStorage(CompoundTag storage); + + default void set(String key, PersistentDataType type, T value) { + if (value == null) { + this.remove(key); + } else { + this.getStorage().put(key, type.serialize(value)); + this.write(); + } + } + + default boolean has(String key, PersistentDataType type) { + Tag tag = this.getReadStorage().get(key); + return tag != null && type.validate(tag); + } + + default T get(String key, PersistentDataType type) { + Tag tag = this.getReadStorage().get(key); + if (tag != null && type.validate(tag)) { + return type.deserialize(tag); + } if (tag != null) { + this.remove(key); + } + return null; + } + + default void write() { + } + + default void remove(String key) { + if (this.getReadStorage().contains(key)) { + this.getStorage().remove(key); + this.write(); + } + } + + default void clearStorage() { + this.setStorage(new CompoundTag()); + } + + default boolean isEmpty() { + return this.getReadStorage().isEmpty(); + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/PersistentDataType.java b/src/main/java/cn/nukkit/level/persistence/PersistentDataType.java new file mode 100644 index 00000000000..6891bdf99ad --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/PersistentDataType.java @@ -0,0 +1,49 @@ +package cn.nukkit.level.persistence; + +import cn.nukkit.nbt.tag.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.function.Function; + +public interface PersistentDataType { + + PersistentDataType BOOLEAN = new PrimitiveDataType<>(Boolean.class, ByteTag.class, val -> new ByteTag(null, val ? 1 : 0), tag -> tag.parseValue() == 1); + PersistentDataType BYTE = new PrimitiveDataType<>(Byte.class, ByteTag.class, val -> new ByteTag(null, val), tag -> tag.parseValue().byteValue()); + PersistentDataType INT = new PrimitiveDataType<>(Integer.class, IntTag.class, val -> new IntTag(null, val), IntTag::parseValue); + PersistentDataType DOUBLE = new PrimitiveDataType<>(Double.class, DoubleTag.class, val -> new DoubleTag(null, val), DoubleTag::parseValue); + PersistentDataType FLOAT = new PrimitiveDataType<>(Float.class, FloatTag.class, val -> new FloatTag(null, val), FloatTag::parseValue); + PersistentDataType STRING = new PrimitiveDataType<>(String.class, StringTag.class, val -> new StringTag(null, val), StringTag::parseValue); + + Class getImplementation(); + + Tag serialize(T value); + + T deserialize(Tag tag); + + boolean validate(Tag tag); + + @Getter + @AllArgsConstructor + class PrimitiveDataType implements PersistentDataType { + private final Class implementation; + private final Class tagClass; + private final Function serializer; + private final Function deserializer; + + @Override + public Tag serialize(T value) { + return this.serializer.apply(value); + } + + @Override + public T deserialize(Tag tag) { + return this.deserializer.apply((I) tag); + } + + @Override + public boolean validate(Tag tag) { + return this.tagClass.isAssignableFrom(tag.getClass()); + } + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/PersistentItemDataContainer.java b/src/main/java/cn/nukkit/level/persistence/PersistentItemDataContainer.java new file mode 100644 index 00000000000..23b89815c29 --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/PersistentItemDataContainer.java @@ -0,0 +1,8 @@ +package cn.nukkit.level.persistence; + +public interface PersistentItemDataContainer extends PersistentDataContainer { + + void setConvertsToBlock(boolean value); + + boolean convertsToBlock(); +} diff --git a/src/main/java/cn/nukkit/level/persistence/impl/DelegatePersistentDataContainer.java b/src/main/java/cn/nukkit/level/persistence/impl/DelegatePersistentDataContainer.java new file mode 100644 index 00000000000..892496e5f6f --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/impl/DelegatePersistentDataContainer.java @@ -0,0 +1,34 @@ +package cn.nukkit.level.persistence.impl; + +import cn.nukkit.level.persistence.ImmutableCompoundTag; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.nbt.tag.CompoundTag; + +public abstract class DelegatePersistentDataContainer implements PersistentDataContainer { + + private PersistentDataContainer delegate; + + protected abstract PersistentDataContainer createDelegate(); + + protected final PersistentDataContainer getDelegate() { + if (this.delegate == null) { + this.delegate = this.createDelegate(); + } + return this.delegate; + } + + @Override + public CompoundTag getStorage() { + return this.getDelegate().getStorage(); + } + + @Override + public void setStorage(CompoundTag storage) { + this.getDelegate().setStorage(storage); + } + + @Override + public CompoundTag getReadStorage() { + return this.delegate == null ? ImmutableCompoundTag.EMPTY : this.getStorage(); + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerBlockWrapper.java b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerBlockWrapper.java new file mode 100644 index 00000000000..95a1d65ea8c --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerBlockWrapper.java @@ -0,0 +1,58 @@ +package cn.nukkit.level.persistence.impl; + +import cn.nukkit.blockentity.BlockEntity; +import cn.nukkit.level.persistence.ImmutableCompoundTag; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.nbt.tag.CompoundTag; + +public class PersistentDataContainerBlockWrapper implements PersistentDataContainer { + + private final BlockEntity blockEntity; + private CompoundTag storage; + + public PersistentDataContainerBlockWrapper(BlockEntity blockEntity) { + this.blockEntity = blockEntity; + } + + @Override + public CompoundTag getReadStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + return ImmutableCompoundTag.EMPTY; + } + return storage; + } + + @Override + public CompoundTag getStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + storage = new CompoundTag(); + this.setStorage(storage); + } + return storage; + } + + private CompoundTag getInternalStorage() { + if (this.storage != null) { + return this.storage; + } + + if (this.blockEntity.namedTag.contains(STORAGE_TAG)) { + return this.storage = this.blockEntity.namedTag.getCompound(STORAGE_TAG); + } + return null; + } + + @Override + public void setStorage(CompoundTag storage) { + this.blockEntity.namedTag.putCompound(STORAGE_TAG, storage); + this.storage = storage; + } + + @Override + public void clearStorage() { + this.blockEntity.namedTag.remove(STORAGE_TAG); + this.storage = null; + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerEntityWrapper.java b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerEntityWrapper.java new file mode 100644 index 00000000000..590e2e030cb --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerEntityWrapper.java @@ -0,0 +1,63 @@ +package cn.nukkit.level.persistence.impl; + +import cn.nukkit.entity.Entity; +import cn.nukkit.level.persistence.ImmutableCompoundTag; +import cn.nukkit.level.persistence.PersistentDataContainer; +import cn.nukkit.nbt.tag.CompoundTag; + +public class PersistentDataContainerEntityWrapper implements PersistentDataContainer { + + private final Entity entity; + private CompoundTag storage; + + public PersistentDataContainerEntityWrapper(Entity entity) { + this.entity = entity; + } + + @Override + public CompoundTag getReadStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + return ImmutableCompoundTag.EMPTY; + } + return storage; + } + + @Override + public CompoundTag getStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + storage = new CompoundTag(); + this.setStorage(storage); + } + return storage; + } + + private CompoundTag getInternalStorage() { + if (this.storage != null) { + return this.storage; + } + + if (this.entity.namedTag.contains(STORAGE_TAG)) { + return this.storage = this.entity.namedTag.getCompound(STORAGE_TAG); + } + return null; + } + + @Override + public void setStorage(CompoundTag storage) { + this.entity.namedTag.putCompound(STORAGE_TAG, storage); + this.storage = storage; + } + + @Override + public void write() { + this.setStorage(this.getStorage()); + } + + @Override + public void clearStorage() { + this.entity.namedTag.remove(STORAGE_TAG); + this.storage = null; + } +} diff --git a/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerItemWrapper.java b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerItemWrapper.java new file mode 100644 index 00000000000..77250f09be6 --- /dev/null +++ b/src/main/java/cn/nukkit/level/persistence/impl/PersistentDataContainerItemWrapper.java @@ -0,0 +1,84 @@ +package cn.nukkit.level.persistence.impl; + +import cn.nukkit.item.Item; +import cn.nukkit.level.persistence.ImmutableCompoundTag; +import cn.nukkit.level.persistence.PersistentItemDataContainer; +import cn.nukkit.nbt.tag.CompoundTag; + +public class PersistentDataContainerItemWrapper implements PersistentItemDataContainer { + + private final Item item; + private boolean convertsToBlock = false; + private CompoundTag storage; + + public PersistentDataContainerItemWrapper(Item item) { + this.item = item; + } + + @Override + public CompoundTag getReadStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + return ImmutableCompoundTag.EMPTY; + } + return storage; + } + + @Override + public CompoundTag getStorage() { + CompoundTag storage = this.getInternalStorage(); + if (storage == null) { + storage = new CompoundTag(); + this.setStorage(storage); + } + return storage; + } + + private CompoundTag getInternalStorage() { + if (this.storage != null) { + return this.storage; + } + + if (this.item.hasCompoundTag() && this.item.getNamedTag().contains(STORAGE_TAG)) { + return this.storage = this.item.getNamedTag().getCompound(STORAGE_TAG); + } + return null; + } + + @Override + public void setStorage(CompoundTag storage) { + CompoundTag compoundTag = this.item.hasCompoundTag() ? this.item.getNamedTag() : new CompoundTag(); + compoundTag.putCompound(STORAGE_TAG, storage); + this.item.setCompoundTag(compoundTag); + this.storage = storage; + } + + @Override + public void write() { + if (this.getReadStorage().isEmpty()) { + this.clearStorage(); + } else { + this.setStorage(this.getStorage()); + } + } + + @Override + public void setConvertsToBlock(boolean convertsToBlock) { + this.convertsToBlock = convertsToBlock; + } + + @Override + public boolean convertsToBlock() { + return this.convertsToBlock; + } + + @Override + public void clearStorage() { + if (this.item.hasCompoundTag()) { + CompoundTag compoundTag = this.item.getNamedTag(); + compoundTag.remove(STORAGE_TAG); + this.item.setCompoundTag(compoundTag); + } + this.storage = null; + } +} diff --git a/src/main/java/cn/nukkit/level/util/BitArrayVersion.java b/src/main/java/cn/nukkit/level/util/BitArrayVersion.java index 8596bc89a34..12bfbe0653c 100644 --- a/src/main/java/cn/nukkit/level/util/BitArrayVersion.java +++ b/src/main/java/cn/nukkit/level/util/BitArrayVersion.java @@ -1,6 +1,7 @@ package cn.nukkit.level.util; public enum BitArrayVersion { + V16(16, 2, null), V8(8, 4, V16), V6(6, 5, V8), // 2 bit padding @@ -8,7 +9,10 @@ public enum BitArrayVersion { V4(4, 8, V5), V3(3, 10, V4), // 2 bit padding V2(2, 16, V3), - V1(1, 32, V2); + V1(1, 32, V2), + V0(0, 0, V1); + + private static final BitArrayVersion[] VALUES = values(); final byte bits; final byte entriesPerWord; @@ -23,7 +27,7 @@ public enum BitArrayVersion { } public static BitArrayVersion get(int version, boolean read) { - for (BitArrayVersion ver : values()) { + for (BitArrayVersion ver : VALUES) { if ((!read && ver.entriesPerWord <= version) || (read && ver.bits == version)) { return ver; } @@ -31,7 +35,14 @@ public static BitArrayVersion get(int version, boolean read) { throw new IllegalArgumentException("Invalid palette version: " + version); } + public BitArray createPalette() { + return this.createPalette(4096); + } + public BitArray createPalette(int size) { + if (this == V0) { + return SingletonBitArray.INSTANCE; + } return this.createPalette(size, new int[this.getWordsForSize(size)]); } @@ -55,6 +66,8 @@ public BitArray createPalette(int size, int[] words) { if (this == V3 || this == V5 || this == V6) { // Padded palettes aren't able to use bitwise operations due to their padding. return new PaddedBitArray(this, size, words); + } else if (this == V0) { + return SingletonBitArray.INSTANCE; } else { return new Pow2BitArray(this, size, words); } diff --git a/src/main/java/cn/nukkit/level/util/PaddedBitArray.java b/src/main/java/cn/nukkit/level/util/PaddedBitArray.java index 20a9e53b8f5..b22735866a9 100644 --- a/src/main/java/cn/nukkit/level/util/PaddedBitArray.java +++ b/src/main/java/cn/nukkit/level/util/PaddedBitArray.java @@ -36,8 +36,10 @@ public class PaddedBitArray implements BitArray { @Override public void set(int index, int value) { Preconditions.checkElementIndex(index, this.size); - Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, - "Max value: %s. Received value", this.version.maxEntryValue, value); + if (value < 0 || value > this.version.maxEntryValue) { + throw new IllegalArgumentException(String.format("Max value: %s. Received value %s", this.version.maxEntryValue, value)); + } + int arrayIndex = index / this.version.entriesPerWord; int offset = (index % this.version.entriesPerWord) * this.version.bits; diff --git a/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java b/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java index 2484d8db35a..f9a5ef6d336 100644 --- a/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java +++ b/src/main/java/cn/nukkit/level/util/PalettedBlockStorage.java @@ -1,12 +1,13 @@ package cn.nukkit.level.util; +import cn.nukkit.Server; import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.utils.BinaryStream; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2IntFunction; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import java.util.function.IntConsumer; - public class PalettedBlockStorage { private static final int SIZE = 4096; @@ -19,7 +20,7 @@ public static PalettedBlockStorage createFromBlockPalette() { } public static PalettedBlockStorage createFromBlockPalette(BitArrayVersion version) { - int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(0); // Air is first + int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(0); return new PalettedBlockStorage(version, runtimeId); } @@ -31,6 +32,10 @@ public static PalettedBlockStorage createWithDefaultState(BitArrayVersion versio return new PalettedBlockStorage(version, defaultState); } + public static PalettedBlockStorage createFromBitArray(BitArray bitArray, IntList palette) { + return new PalettedBlockStorage(bitArray, palette); + } + private PalettedBlockStorage(BitArrayVersion version, int defaultState) { this.bitArray = version.createPalette(SIZE); this.palette = new IntArrayList(16); @@ -46,6 +51,10 @@ private int getPaletteHeader(BitArrayVersion version, boolean runtime) { return (version.getId() << 1) | (runtime ? 1 : 0); } + private static BitArrayVersion getVersionFromHeader(byte header) { + return BitArrayVersion.get(header >> 1, true); + } + private int getIndex(int x, int y, int z) { return (x << 8) | (z << 4) | y; } @@ -54,6 +63,11 @@ public void setBlock(int x, int y, int z, int runtimeId) { this.setBlock(this.getIndex(x, y, z), runtimeId); } + public int getBlock(int x, int y, int z) { + int index = this.getIndex(x, y, z); + return this.palette.getInt(this.bitArray.get(index)); + } + public void setBlock(int index, int runtimeId) { try { int id = this.idFor(runtimeId); @@ -63,19 +77,73 @@ public void setBlock(int index, int runtimeId) { } } + public void readFromStorage(ByteBuf buffer) { + BitArrayVersion version = getVersionFromHeader(buffer.readByte()); + this.palette.clear(); + int paletteSize = 1; + + if (version == BitArrayVersion.V0) { + this.bitArray = version.createPalette(SIZE, null); + } else { + int expectedWordCount = version.getWordsForSize(SIZE); + int[] words = new int[expectedWordCount]; + for (int i = 0; i < expectedWordCount; i++) { + words[i] = buffer.readIntLE(); + } + paletteSize = buffer.readIntLE(); + this.bitArray = version.createPalette(SIZE, words); + } + + if (!(version.getMaxEntryValue() >= paletteSize - 1)) { + throw new IllegalArgumentException(String.format("Palette (version " + version.name() + ") is too large. Max size %s. Actual size %s", version.getMaxEntryValue(), paletteSize)); + } + + for (int i = 0; i < paletteSize; i++) { + int id = buffer.readIntLE(); + this.palette.add(id); + if (id < 0) { + Server.getInstance().getLogger().warning("Negative biome ID " + id); + } + } + } + + public void writeToStorage(ByteBuf buffer) { + int paletteSize = this.palette.size(); + BitArrayVersion version = paletteSize <= 1 ? BitArrayVersion.V0 : this.bitArray.getVersion(); + buffer.writeByte(getPaletteHeader(version, false)); + + if (version != BitArrayVersion.V0) { + for (int word : this.bitArray.getWords()) { + buffer.writeIntLE(word); + } + buffer.writeIntLE(paletteSize); + } + + for (int runtimeId : this.palette) { + buffer.writeIntLE(runtimeId); + } + } + public void writeTo(BinaryStream stream) { - stream.putByte((byte) getPaletteHeader(bitArray.getVersion(), true)); + this.writeTo(stream, i -> i); + } - for (int word : bitArray.getWords()) { - stream.putLInt(word); + public void writeTo(BinaryStream stream, Int2IntFunction mapper) { + stream.putByte((byte) getPaletteHeader(this.bitArray.getVersion(), true)); + if (this.bitArray.getVersion() != BitArrayVersion.V0) { + for (int word : this.bitArray.getWords()) { + stream.putLInt(word); + } + stream.putVarInt(this.palette.size()); } - stream.putVarInt(palette.size()); - palette.forEach((IntConsumer) stream::putVarInt); + for (int runtimeId : this.palette) { + stream.putVarInt(mapper.get(runtimeId)); + } } private void onResize(BitArrayVersion version) { - BitArray newBitArray = version.createPalette(SIZE); + BitArray newBitArray = version.createPalette(); for (int i = 0; i < SIZE; i++) { newBitArray.set(i, this.bitArray.get(i)); diff --git a/src/main/java/cn/nukkit/level/util/Pow2BitArray.java b/src/main/java/cn/nukkit/level/util/Pow2BitArray.java index 6e2c1a02965..4c830953c0a 100644 --- a/src/main/java/cn/nukkit/level/util/Pow2BitArray.java +++ b/src/main/java/cn/nukkit/level/util/Pow2BitArray.java @@ -38,8 +38,10 @@ public class Pow2BitArray implements BitArray { */ public void set(int index, int value) { Preconditions.checkElementIndex(index, this.size); - Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, - "Max value: %s. Received value", this.version.maxEntryValue, value); + if (value < 0 || value > this.version.maxEntryValue) { + throw new IllegalArgumentException(String.format("Max value: %s. Received value %s", this.version.maxEntryValue, value)); + } + int bitIndex = index * this.version.bits; int arrayIndex = bitIndex >> 5; int offset = bitIndex & 31; diff --git a/src/main/java/cn/nukkit/level/util/SingletonBitArray.java b/src/main/java/cn/nukkit/level/util/SingletonBitArray.java new file mode 100644 index 00000000000..7e35bade8ef --- /dev/null +++ b/src/main/java/cn/nukkit/level/util/SingletonBitArray.java @@ -0,0 +1,39 @@ +package cn.nukkit.level.util; + +public class SingletonBitArray implements BitArray { + public static final SingletonBitArray INSTANCE = new SingletonBitArray(); + + private static final int[] EMPTY_ARRAY = new int[0]; + + public SingletonBitArray() { + } + + @Override + public void set(int index, int value) { + } + + @Override + public int get(int index) { + return 0; + } + + @Override + public int size() { + return 1; + } + + @Override + public int[] getWords() { + return EMPTY_ARRAY; + } + + @Override + public BitArrayVersion getVersion() { + return BitArrayVersion.V0; + } + + @Override + public SingletonBitArray copy() { + return new SingletonBitArray(); + } +} diff --git a/src/main/java/cn/nukkit/math/Angle.java b/src/main/java/cn/nukkit/math/Angle.java index 549284a74ab..edd19d1e375 100644 --- a/src/main/java/cn/nukkit/math/Angle.java +++ b/src/main/java/cn/nukkit/math/Angle.java @@ -148,5 +148,4 @@ private Angle(double doubleValue, boolean isDegree) { this.doubleValue = doubleValue; this.isDegree = isDegree; } - } diff --git a/src/main/java/cn/nukkit/math/BlockFace.java b/src/main/java/cn/nukkit/math/BlockFace.java index a677ce5b734..6e57e690875 100644 --- a/src/main/java/cn/nukkit/math/BlockFace.java +++ b/src/main/java/cn/nukkit/math/BlockFace.java @@ -4,9 +4,11 @@ import java.util.Iterator; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.function.Predicate; public enum BlockFace { + DOWN(0, 1, -1, "down", AxisDirection.NEGATIVE, new Vector3(0, -1, 0)), UP(1, 0, -1, "up", AxisDirection.POSITIVE, new Vector3(0, 1, 0)), NORTH(2, 3, 2, "north", AxisDirection.NEGATIVE, new Vector3(0, 0, -1)), @@ -25,7 +27,6 @@ public enum BlockFace { private static final BlockFace[] HORIZONTALS = new BlockFace[4]; static { - //Circular dependency DOWN.axis = Axis.Y; UP.axis = Axis.Y; NORTH.axis = Axis.Z; @@ -36,7 +37,7 @@ public enum BlockFace { for (BlockFace face : values()) { VALUES[face.index] = face; - if (face.getAxis().isHorizontal()) { + if (face.axis.isHorizontal()) { HORIZONTALS[face.horizontalIndex] = face; } } @@ -112,12 +113,12 @@ public static BlockFace fromHorizontalAngle(double angle) { public static BlockFace fromAxis(AxisDirection axisDirection, Axis axis) { for (BlockFace face : VALUES) { - if (face.getAxisDirection() == axisDirection && face.getAxis() == axis) { + if (face.axisDirection == axisDirection && face.axis == axis) { return face; } } - throw new RuntimeException("Unable to get face from axis: " + axisDirection + " " + axis); + throw new RuntimeException("Unable to get face from axis: " + axisDirection + ' ' + axis); } /** @@ -214,25 +215,25 @@ public int getYOffset() { /** * Returns an offset that addresses the block in front of this BlockFace * - * @return x offset + * @return z offset */ public int getZOffset() { return axis == Axis.Z ? axisDirection.getOffset() : 0; } /** - * Get the opposite BlockFace (e.g. DOWN ==> UP) - * - * @return block face + * Get the opposite BlockFace (e.g. DOWN ==> UP) + * + * @return block face */ public BlockFace getOpposite() { return fromIndex(opposite); } /** - * Rotate this BlockFace around the Y axis clockwise (NORTH => EAST => SOUTH => WEST => NORTH) - * - * @return block face + * Rotate this BlockFace around the Y axis clockwise (NORTH => EAST => SOUTH => WEST => NORTH) + * + * @return block face */ public BlockFace rotateY() { switch (this) { @@ -250,9 +251,9 @@ public BlockFace rotateY() { } /** - * Rotate this BlockFace around the Y axis counter-clockwise (NORTH => WEST => SOUTH => EAST => NORTH) - * - * @return block face + * Rotate this BlockFace around the Y axis counter-clockwise (NORTH => WEST => SOUTH => EAST => NORTH) + * + * @return block face */ public BlockFace rotateYCCW() { switch (this) { @@ -282,7 +283,6 @@ public enum Axis implements Predicate { private Plane plane; static { - //Circular dependency X.plane = Plane.HORIZONTAL; Y.plane = Plane.VERTICAL; Z.plane = Plane.HORIZONTAL; @@ -343,17 +343,20 @@ public enum Plane implements Predicate, Iterable { VERTICAL; static { - //Circular dependency HORIZONTAL.faces = new BlockFace[]{NORTH, EAST, SOUTH, WEST}; VERTICAL.faces = new BlockFace[]{UP, DOWN}; } private BlockFace[] faces; - public BlockFace random(NukkitRandom rand) { //todo Default Random? + public BlockFace random(NukkitRandom rand) { return faces[rand.nextBoundedInt(faces.length)]; } + public BlockFace random() { + return faces[ThreadLocalRandom.current().nextInt(faces.length)]; + } + public boolean test(BlockFace face) { return face != null && face.getAxis().getPlane() == this; } diff --git a/src/main/java/cn/nukkit/math/BlockVector3.java b/src/main/java/cn/nukkit/math/BlockVector3.java index 696419bcf8f..fbe20f72c38 100644 --- a/src/main/java/cn/nukkit/math/BlockVector3.java +++ b/src/main/java/cn/nukkit/math/BlockVector3.java @@ -1,6 +1,7 @@ package cn.nukkit.math; public class BlockVector3 implements Cloneable { + public int x; public int y; public int z; @@ -93,7 +94,7 @@ public BlockVector3 add(int x, int y, int z) { } public BlockVector3 add(BlockVector3 x) { - return new BlockVector3(this.x + x.getX(), this.y + x.getY(), this.z + x.getZ()); + return new BlockVector3(this.x + x.x, this.y + x.y, this.z + x.z); } public BlockVector3 subtract() { @@ -113,7 +114,7 @@ public BlockVector3 subtract(int x, int y, int z) { } public BlockVector3 subtract(BlockVector3 x) { - return this.add(-x.getX(), -x.getY(), -x.getZ()); + return this.add(-x.x, -x.y, -x.z); } public BlockVector3 multiply(int number) { @@ -129,7 +130,7 @@ public BlockVector3 getSide(BlockFace face) { } public BlockVector3 getSide(BlockFace face, int step) { - return new BlockVector3(this.getX() + face.getXOffset() * step, this.getY() + face.getYOffset() * step, this.getZ() + face.getZOffset() * step); + return new BlockVector3(this.x + face.getXOffset() * step, this.y + face.getYOffset() * step, this.z + face.getZOffset() * step); } public BlockVector3 up() { @@ -220,7 +221,7 @@ public final int hashCode() { @Override public String toString() { - return "BlockPosition(level=" + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ")"; + return "BlockVector3(level=" + ",x=" + this.x + ",y=" + this.y + ",z=" + this.z + ')'; } @Override diff --git a/src/main/java/cn/nukkit/math/MathHelper.java b/src/main/java/cn/nukkit/math/MathHelper.java index ae5f214186a..e9a6ed5380a 100644 --- a/src/main/java/cn/nukkit/math/MathHelper.java +++ b/src/main/java/cn/nukkit/math/MathHelper.java @@ -3,6 +3,7 @@ import java.util.Random; public class MathHelper { + private static final float[] a = new float[65536]; static { @@ -89,7 +90,7 @@ public static int ceil(float floatNumber) { } public static int clamp(int check, int min, int max) { - return check > max ? max : (check < min ? min : check); + return check > max ? max : (Math.max(check, min)); } public static double denormalizeClamp(double lowerBnd, double upperBnd, double slide) { @@ -99,4 +100,4 @@ public static double denormalizeClamp(double lowerBnd, double upperBnd, double s public static float denormalizeClamp(float lowerBnd, float upperBnd, float slide) { return slide < 0.0f ? lowerBnd : (slide > 1.0f ? upperBnd : lowerBnd + (upperBnd - lowerBnd) * slide); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/math/NukkitMath.java b/src/main/java/cn/nukkit/math/NukkitMath.java index 2a00f4abbd7..41ad3809021 100644 --- a/src/main/java/cn/nukkit/math/NukkitMath.java +++ b/src/main/java/cn/nukkit/math/NukkitMath.java @@ -1,7 +1,7 @@ package cn.nukkit.math; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class NukkitMath { @@ -64,5 +64,4 @@ public static double getDirection(double diffX, double diffZ) { return Math.max(diffX, diffZ); } - } diff --git a/src/main/java/cn/nukkit/math/NukkitRandom.java b/src/main/java/cn/nukkit/math/NukkitRandom.java index 82d906d9057..917f87668e5 100644 --- a/src/main/java/cn/nukkit/math/NukkitRandom.java +++ b/src/main/java/cn/nukkit/math/NukkitRandom.java @@ -5,10 +5,11 @@ import java.util.zip.CRC32; /** - * author: Angelic47 + * @author Angelic47 * Nukkit Project */ public class NukkitRandom { + protected long seed; public NukkitRandom() { @@ -75,4 +76,4 @@ public int nextRange(int start, int end) { public int nextBoundedInt(int bound) { return bound == 0 ? 0 : this.nextInt() % bound; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/math/SimpleAxisAlignedBB.java b/src/main/java/cn/nukkit/math/SimpleAxisAlignedBB.java index 3bd41cc1d18..30b44a7145c 100644 --- a/src/main/java/cn/nukkit/math/SimpleAxisAlignedBB.java +++ b/src/main/java/cn/nukkit/math/SimpleAxisAlignedBB.java @@ -1,7 +1,7 @@ package cn.nukkit.math; /** - * auth||: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class SimpleAxisAlignedBB implements AxisAlignedBB { diff --git a/src/main/java/cn/nukkit/math/Vector2.java b/src/main/java/cn/nukkit/math/Vector2.java index f9e197911ee..29dece471b8 100644 --- a/src/main/java/cn/nukkit/math/Vector2.java +++ b/src/main/java/cn/nukkit/math/Vector2.java @@ -1,10 +1,11 @@ package cn.nukkit.math; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Vector2 { + public final double x; public final double y; @@ -46,7 +47,7 @@ public Vector2 add(double x, double y) { } public Vector2 add(Vector2 x) { - return this.add(x.getX(), x.getY()); + return this.add(x.x, x.y); } public Vector2 subtract(double x) { @@ -58,7 +59,7 @@ public Vector2 subtract(double x, double y) { } public Vector2 subtract(Vector2 x) { - return this.add(-x.getX(), -x.getY()); + return this.add(-x.x, -x.y); } public Vector2 ceil() { @@ -94,7 +95,7 @@ public double distance(double x, double y) { } public double distance(Vector2 vector) { - return Math.sqrt(this.distanceSquared(vector.getX(), vector.getY())); + return Math.sqrt(this.distanceSquared(vector.x, vector.y)); } public double distanceSquared(double x) { @@ -106,7 +107,7 @@ public double distanceSquared(double x, double y) { } public double distanceSquared(Vector2 vector) { - return this.distanceSquared(vector.getX(), vector.getY()); + return this.distanceSquared(vector.x, vector.y); } public double length() { @@ -131,7 +132,6 @@ public double dot(Vector2 v) { @Override public String toString() { - return "Vector2(x=" + this.x + ",y=" + this.y + ")"; + return "Vector2(x=" + this.x + ",y=" + this.y + ')'; } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/math/Vector2f.java b/src/main/java/cn/nukkit/math/Vector2f.java index c5d17175568..8f17de8892c 100644 --- a/src/main/java/cn/nukkit/math/Vector2f.java +++ b/src/main/java/cn/nukkit/math/Vector2f.java @@ -1,10 +1,11 @@ package cn.nukkit.math; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Vector2f { + public final float x; public final float y; @@ -46,7 +47,7 @@ public Vector2f add(float x, float y) { } public Vector2f add(Vector2f x) { - return this.add(x.getX(), x.getY()); + return this.add(x.x, x.y); } public Vector2f subtract(float x) { @@ -58,7 +59,7 @@ public Vector2f subtract(float x, float y) { } public Vector2f subtract(Vector2f x) { - return this.add(-x.getX(), -x.getY()); + return this.add(-x.x, -x.y); } public Vector2f ceil() { @@ -94,7 +95,7 @@ public double distance(float x, float y) { } public double distance(Vector2f vector) { - return Math.sqrt(this.distanceSquared(vector.getX(), vector.getY())); + return Math.sqrt(this.distanceSquared(vector.x, vector.y)); } public double distanceSquared(float x) { @@ -106,7 +107,7 @@ public double distanceSquared(float x, float y) { } public double distanceSquared(Vector2f vector) { - return this.distanceSquared(vector.getX(), vector.getY()); + return this.distanceSquared(vector.x, vector.y); } public double length() { @@ -131,7 +132,6 @@ public float dot(Vector2f v) { @Override public String toString() { - return "Vector2(x=" + this.x + ",y=" + this.y + ")"; + return "Vector2f(x=" + this.x + ",y=" + this.y + ')'; } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/math/Vector3.java b/src/main/java/cn/nukkit/math/Vector3.java index dc2ed4b8683..cbbc2ce908f 100644 --- a/src/main/java/cn/nukkit/math/Vector3.java +++ b/src/main/java/cn/nukkit/math/Vector3.java @@ -1,7 +1,7 @@ package cn.nukkit.math; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Vector3 implements Cloneable { @@ -109,7 +109,7 @@ public Vector3 add(double x, double y, double z) { } public Vector3 add(Vector3 x) { - return new Vector3(this.x + x.getX(), this.y + x.getY(), this.z + x.getZ()); + return new Vector3(this.x + x.x, this.y + x.y, this.z + x.z); } public Vector3 subtract() { @@ -129,7 +129,7 @@ public Vector3 subtract(double x, double y, double z) { } public Vector3 subtract(Vector3 x) { - return this.add(-x.getX(), -x.getY(), -x.getZ()); + return this.add(-x.x, -x.y, -x.z); } public Vector3 multiply(double number) { @@ -161,7 +161,7 @@ public Vector3 getSide(BlockFace face) { } public Vector3 getSide(BlockFace face, int step) { - return new Vector3(this.getX() + face.getXOffset() * step, this.getY() + face.getYOffset() * step, this.getZ() + face.getZOffset() * step); + return new Vector3(this.x + face.getXOffset() * step, this.y + face.getYOffset() * step, this.z + face.getZOffset() * step); } // Get as a Vector3 for better performance. Do not override in Block! @@ -169,6 +169,11 @@ public Vector3 getSideVec(BlockFace face) { return new Vector3(this.getX() + face.getXOffset(), this.getY() + face.getYOffset(), this.getZ() + face.getZOffset()); } + // Get as a Vector3 for better performance. Do not override in Block! + public Vector3 getSideVec(BlockFace face, int step) { + return new Vector3(this.x + face.getXOffset() * step, this.y + face.getYOffset() * step, this.z + face.getZOffset() * step); + } + public Vector3 up() { return up(1); } @@ -378,7 +383,7 @@ public Vector3 setComponents(double x, double y, double z) { @Override public String toString() { - return "Vector3(x=" + this.x + ",y=" + this.y + ",z=" + this.z + ")"; + return "Vector3(x=" + this.x + ",y=" + this.y + ",z=" + this.z + ')'; } @Override diff --git a/src/main/java/cn/nukkit/math/Vector3f.java b/src/main/java/cn/nukkit/math/Vector3f.java index ce466ce673a..dff536eef81 100644 --- a/src/main/java/cn/nukkit/math/Vector3f.java +++ b/src/main/java/cn/nukkit/math/Vector3f.java @@ -1,6 +1,7 @@ package cn.nukkit.math; public class Vector3f implements Cloneable { + public static final int SIDE_DOWN = 0; public static final int SIDE_UP = 1; public static final int SIDE_NORTH = 2; @@ -102,7 +103,7 @@ public Vector3f add(float x, float y, float z) { } public Vector3f add(Vector3f x) { - return new Vector3f(this.x + x.getX(), this.y + x.getY(), this.z + x.getZ()); + return new Vector3f(this.x + x.x, this.y + x.y, this.z + x.z); } public Vector3f subtract() { @@ -122,7 +123,7 @@ public Vector3f subtract(float x, float y, float z) { } public Vector3f subtract(Vector3f x) { - return this.add(-x.getX(), -x.getY(), -x.getZ()); + return this.add(-x.x, -x.y, -x.z); } public Vector3f multiply(float number) { @@ -352,7 +353,7 @@ public Vector3f setComponents(float x, float y, float z) { @Override public String toString() { - return "Vector3(x=" + this.x + ",y=" + this.y + ",z=" + this.z + ")"; + return "Vector3f(x=" + this.x + ",y=" + this.y + ",z=" + this.z + ')'; } @Override diff --git a/src/main/java/cn/nukkit/math/VectorMath.java b/src/main/java/cn/nukkit/math/VectorMath.java index 9c3d5fe189b..6e8d873b977 100644 --- a/src/main/java/cn/nukkit/math/VectorMath.java +++ b/src/main/java/cn/nukkit/math/VectorMath.java @@ -1,7 +1,7 @@ package cn.nukkit.math; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @@ -10,5 +10,4 @@ public abstract class VectorMath { public static Vector2 getDirection2D(double azimuth) { return new Vector2(Math.cos(azimuth), Math.sin(azimuth)); } - } diff --git a/src/main/java/cn/nukkit/metadata/BlockMetadataStore.java b/src/main/java/cn/nukkit/metadata/BlockMetadataStore.java index 92a6ac15a2b..8d19484b6a3 100644 --- a/src/main/java/cn/nukkit/metadata/BlockMetadataStore.java +++ b/src/main/java/cn/nukkit/metadata/BlockMetadataStore.java @@ -7,10 +7,11 @@ import java.util.List; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BlockMetadataStore extends MetadataStore { + private final Level owningLevel; public BlockMetadataStore(Level owningLevel) { @@ -22,7 +23,7 @@ protected String disambiguate(Metadatable block, String metadataKey) { if (!(block instanceof Block)) { throw new IllegalArgumentException("Argument must be a Block instance"); } - return ((Block) block).x + ":" + ((Block) block).y + ":" + ((Block) block).z + ":" + metadataKey; + return ((Block) block).x + ":" + ((Block) block).y + ':' + ((Block) block).z + ':' + metadataKey; } @Override diff --git a/src/main/java/cn/nukkit/metadata/EntityMetadataStore.java b/src/main/java/cn/nukkit/metadata/EntityMetadataStore.java index c51f7d31eb8..6102a9d3d47 100644 --- a/src/main/java/cn/nukkit/metadata/EntityMetadataStore.java +++ b/src/main/java/cn/nukkit/metadata/EntityMetadataStore.java @@ -3,7 +3,7 @@ import cn.nukkit.entity.Entity; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class EntityMetadataStore extends MetadataStore { diff --git a/src/main/java/cn/nukkit/metadata/LevelMetadataStore.java b/src/main/java/cn/nukkit/metadata/LevelMetadataStore.java index d45b3cd4d6e..74569d53390 100644 --- a/src/main/java/cn/nukkit/metadata/LevelMetadataStore.java +++ b/src/main/java/cn/nukkit/metadata/LevelMetadataStore.java @@ -3,7 +3,7 @@ import cn.nukkit.level.Level; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class LevelMetadataStore extends MetadataStore { @@ -13,6 +13,6 @@ protected String disambiguate(Metadatable level, String metadataKey) { if (!(level instanceof Level)) { throw new IllegalArgumentException("Argument must be a Level instance"); } - return (((Level) level).getName() + ":" + metadataKey).toLowerCase(); + return (((Level) level).getName() + ':' + metadataKey).toLowerCase(); } } diff --git a/src/main/java/cn/nukkit/metadata/MetadataStore.java b/src/main/java/cn/nukkit/metadata/MetadataStore.java index 18680a1e735..5eb412195fe 100644 --- a/src/main/java/cn/nukkit/metadata/MetadataStore.java +++ b/src/main/java/cn/nukkit/metadata/MetadataStore.java @@ -7,7 +7,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class MetadataStore { diff --git a/src/main/java/cn/nukkit/metadata/MetadataValue.java b/src/main/java/cn/nukkit/metadata/MetadataValue.java index 58ac519c244..7bf3df4f98b 100644 --- a/src/main/java/cn/nukkit/metadata/MetadataValue.java +++ b/src/main/java/cn/nukkit/metadata/MetadataValue.java @@ -5,7 +5,7 @@ import java.lang.ref.WeakReference; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class MetadataValue { @@ -23,5 +23,4 @@ public Plugin getOwningPlugin() { public abstract Object value(); public abstract void invalidate(); - } diff --git a/src/main/java/cn/nukkit/metadata/Metadatable.java b/src/main/java/cn/nukkit/metadata/Metadatable.java index e517c1796e0..63165c97c45 100644 --- a/src/main/java/cn/nukkit/metadata/Metadatable.java +++ b/src/main/java/cn/nukkit/metadata/Metadatable.java @@ -5,7 +5,7 @@ import java.util.List; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface Metadatable { diff --git a/src/main/java/cn/nukkit/metadata/PlayerMetadataStore.java b/src/main/java/cn/nukkit/metadata/PlayerMetadataStore.java index b9e53959914..c2933ee0af3 100644 --- a/src/main/java/cn/nukkit/metadata/PlayerMetadataStore.java +++ b/src/main/java/cn/nukkit/metadata/PlayerMetadataStore.java @@ -3,7 +3,7 @@ import cn.nukkit.IPlayer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PlayerMetadataStore extends MetadataStore { @@ -13,6 +13,6 @@ protected String disambiguate(Metadatable player, String metadataKey) { if (!(player instanceof IPlayer)) { throw new IllegalArgumentException("Argument must be an IPlayer instance"); } - return (((IPlayer) player).getName() + ":" + metadataKey).toLowerCase(); + return (((IPlayer) player).getName() + ':' + metadataKey).toLowerCase(); } } diff --git a/src/main/java/cn/nukkit/metrics/Metrics.java b/src/main/java/cn/nukkit/metrics/Metrics.java index bc710e18ff8..73eab0a8cbf 100644 --- a/src/main/java/cn/nukkit/metrics/Metrics.java +++ b/src/main/java/cn/nukkit/metrics/Metrics.java @@ -20,10 +20,11 @@ /** * bStats collects some data for plugin authors. - * - * Check out https://bStats.org/ to learn more about bStats! + *

+ * Check out ... to learn more about bStats! */ public class Metrics { + public static final int B_STATS_VERSION = 1; private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); @@ -529,5 +530,4 @@ protected JSONObject getChartData() throws Exception { } } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/metrics/NukkitMetrics.java b/src/main/java/cn/nukkit/metrics/NukkitMetrics.java index 88f2b8933e0..d26d64da5ca 100644 --- a/src/main/java/cn/nukkit/metrics/NukkitMetrics.java +++ b/src/main/java/cn/nukkit/metrics/NukkitMetrics.java @@ -2,6 +2,7 @@ import cn.nukkit.Server; import cn.nukkit.utils.Config; +import cn.nukkit.utils.Utils; import java.io.BufferedWriter; import java.io.File; @@ -14,6 +15,7 @@ import java.util.regex.Pattern; public class NukkitMetrics { + private static boolean metricsStarted = false; private final Server server; @@ -31,8 +33,8 @@ public NukkitMetrics(Server server) { try { this.loadConfig(); - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException ex) { + server.getLogger().logException(ex); } if (enabled) { @@ -48,7 +50,7 @@ public NukkitMetrics(Server server) { Map valueMap = new HashMap<>(); server.getOnlinePlayers().forEach((uuid, player) -> { - String deviceOS = mapDeviceOSToString(player.getLoginChainData().getDeviceOS()); + String deviceOS = Utils.getOS(player); if (!valueMap.containsKey(deviceOS)) { valueMap.put(deviceOS, 1); } else { @@ -147,24 +149,4 @@ private void writeFile(File file, String... lines) throws IOException { } } } - - private String mapDeviceOSToString(int os) { - switch (os) { - case 1: return "Android"; - case 2: return "iOS"; - case 3: return "macOS"; - case 4: return "Fire OS"; - case 5: return "Gear VR"; - case 6: return "HoloLens"; - case 7: return "Windows 10"; - case 8: return "Windows"; - case 9: return "Dedicated"; - case 10: return "tvOS"; - case 11: return "PlayStation"; - case 12: return "Switch"; - case 13: return "Xbox"; - case 14: return "Windows Phone"; - } - return "Unknown"; - } } diff --git a/src/main/java/cn/nukkit/nbt/NBTIO.java b/src/main/java/cn/nukkit/nbt/NBTIO.java index 5ce1b2f3795..803cab1aa59 100644 --- a/src/main/java/cn/nukkit/nbt/NBTIO.java +++ b/src/main/java/cn/nukkit/nbt/NBTIO.java @@ -1,6 +1,7 @@ package cn.nukkit.nbt; import cn.nukkit.item.Item; +import cn.nukkit.item.RuntimeItems; import cn.nukkit.nbt.stream.FastByteArrayOutputStream; import cn.nukkit.nbt.stream.NBTInputStream; import cn.nukkit.nbt.stream.NBTOutputStream; @@ -43,26 +44,25 @@ public static CompoundTag putItemHelper(Item item, Integer slot) { return tag; } - public static Item getItemHelper(CompoundTag tag) { - if (!tag.contains("id") || !tag.contains("Count")) { - return Item.get(0); - } + public static CompoundTag putNetworkItemHelper(Item item) { + CompoundTag tag = new CompoundTag(null) + .putString("Name", RuntimeItems.getMapping().toRuntime(item.getId(), item.getDamage()).getIdentifier()) + .putByte("Count", item.getCount()) + .putShort("Damage", item.getDamage()); - Item item; - try { - item = Item.get(tag.getShort("id"), !tag.contains("Damage") ? 0 : tag.getShort("Damage"), tag.getByte("Count")); - } catch (Exception e) { - item = Item.fromString(tag.getString("id")); - item.setDamage(!tag.contains("Damage") ? 0 : tag.getShort("Damage")); - item.setCount(tag.getByte("Count")); + if (item.hasCompoundTag()) { + tag.putCompound("tag", item.getNamedTag()); } - Tag tagTag = tag.get("tag"); - if (tagTag instanceof CompoundTag) { - item.setNamedTag((CompoundTag) tagTag); + return tag; + } + + public static Item getItemHelper(CompoundTag tag) { + if (!tag.contains("id") || !tag.contains("Count")) { + return Item.get(0); } - return item; + return Item.getSaved(tag.getShort("id"), !tag.contains("Damage") ? 0 : tag.getShort("Damage"), tag.getByte("Count"), tag.get("tag")); } public static CompoundTag read(File file) throws IOException { @@ -71,7 +71,7 @@ public static CompoundTag read(File file) throws IOException { public static CompoundTag read(File file, ByteOrder endianness) throws IOException { if (!file.exists()) return null; - return read(new FileInputStream(file), endianness); + return read(Files.newInputStream(file.toPath()), endianness); } public static CompoundTag read(InputStream inputStream) throws IOException { @@ -92,6 +92,12 @@ public static CompoundTag read(InputStream inputStream, ByteOrder endianness, bo } } + public static Tag readNetwork(InputStream inputStream) throws IOException { + try (NBTInputStream stream = new NBTInputStream(inputStream, ByteOrder.LITTLE_ENDIAN, true)) { + return Tag.readNamedTag(stream); + } + } + public static Tag readTag(InputStream inputStream, ByteOrder endianness, boolean network) throws IOException { try (NBTInputStream stream = new NBTInputStream(inputStream, endianness, network)) { return Tag.readNamedTag(stream); @@ -185,7 +191,7 @@ public static void write(CompoundTag tag, File file) throws IOException { } public static void write(CompoundTag tag, File file, ByteOrder endianness) throws IOException { - write(tag, new FileOutputStream(file), endianness); + write(tag, Files.newOutputStream(file.toPath()), endianness); } public static void write(CompoundTag tag, OutputStream outputStream) throws IOException { diff --git a/src/main/java/cn/nukkit/nbt/stream/BufferedRandomAccessFile.java b/src/main/java/cn/nukkit/nbt/stream/BufferedRandomAccessFile.java deleted file mode 100644 index 7959c4f94c0..00000000000 --- a/src/main/java/cn/nukkit/nbt/stream/BufferedRandomAccessFile.java +++ /dev/null @@ -1,467 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package cn.nukkit.nbt.stream; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.util.Arrays; - - -/** - * A BufferedRandomAccessFile is like a - * RandomAccessFile, but it uses a private buffer so that most - * operations do not require a disk access. - *

- * - * Note: The operations on this class are unmonitored. Also, the correct - * functioning of the RandomAccessFile methods that are not - * overridden here relies on the implementation of those methods in the - * superclass. - * Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com ) - */ - -public class BufferedRandomAccessFile extends RandomAccessFile -{ - static final int LogBuffSz_ = 16; // 64K buffer - public static final int BuffSz_ = (1 << LogBuffSz_); - static final long BuffMask_ = -((long) BuffSz_); - - /* - * This implementation is based on the buffer implementation in Modula-3's - * "Rd", "Wr", "RdClass", and "WrClass" interfaces. - */ - private boolean dirty_; // true iff unflushed bytes exist - private boolean closed_; // true iff the file is closed - private long curr_; // current position in file - private long lo_, hi_; // bounds on characters in "buff" - private byte[] buff_; // local buffer - private long maxHi_; // this.lo + this.buff.length - private boolean hitEOF_; // buffer contains last file block? - private long diskPos_; // disk position - - /* - * To describe the above fields, we introduce the following abstractions for - * the file "f": - * - * len(f) the length of the file curr(f) the current position in the file - * c(f) the abstract contents of the file disk(f) the contents of f's - * backing disk file closed(f) true iff the file is closed - * - * "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a - * character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if - * "c(f)" contains unflushed writes not reflected in "disk(f)". The flush - * operation has the effect of making "disk(f)" identical to "c(f)". - * - * A file is said to be *valid* if the following conditions hold: - * - * V1. The "closed" and "curr" fields are correct: - * - * f.closed == closed(f) f.curr == curr(f) - * - * V2. The current position is either contained in the buffer, or just past - * the buffer: - * - * f.lo <= f.curr <= f.hi - * - * V3. Any (possibly) unflushed characters are stored in "f.buff": - * - * (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo]) - * - * V4. For all characters not covered by V3, c(f) and disk(f) agree: - * - * (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] == - * disk(f)[i]) - * - * V5. "f.dirty" is true iff the buffer contains bytes that should be - * flushed to the file; by V3 and V4, only part of the buffer can be dirty. - * - * f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo]) - * - * V6. this.maxHi == this.lo + this.buff.length - * - * Note that "f.buff" can be "null" in a valid file, since the range of - * characters in V3 is empty when "f.lo == f.curr". - * - * A file is said to be *ready* if the buffer contains the current position, - * i.e., when: - * - * R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi - * - * When a file is ready, reading or writing a single byte can be performed - * by reading or writing the in-memory buffer without performing a disk - * operation. - */ - - /** - * Open a new BufferedRandomAccessFile on file - * in mode mode, which should be "r" for reading only, or - * "rw" for reading and writing. - */ - public BufferedRandomAccessFile(File file, String mode) throws IOException - { - super(file, mode); - this.init(0); - } - - public BufferedRandomAccessFile(File file, String mode, int size) throws IOException - { - super(file, mode); - this.init(size); - } - - /** - * Open a new BufferedRandomAccessFile on the file named - * name in mode mode, which should be "r" for - * reading only, or "rw" for reading and writing. - */ - public BufferedRandomAccessFile(String name, String mode) throws IOException - { - super(name, mode); - this.init(0); - } - - public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException - { - super(name, mode); - this.init(size); - } - - private void init(int size) - { - this.dirty_ = this.closed_ = false; - this.lo_ = this.curr_ = this.hi_ = 0; - this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_]; - this.maxHi_ = BuffSz_; - this.hitEOF_ = false; - this.diskPos_ = 0L; - } - - @Override - public void close() throws IOException - { - this.flush(); - this.closed_ = true; - super.close(); - } - - /** - * Flush any bytes in the file's buffer that have not yet been written to - * disk. If the file was created read-only, this method is a no-op. - */ - public void flush() throws IOException - { - this.flushBuffer(); - } - - /* Flush any dirty bytes in the buffer to disk. */ - private void flushBuffer() throws IOException - { - if (this.dirty_) - { - if (this.diskPos_ != this.lo_) - super.seek(this.lo_); - int len = (int) (this.curr_ - this.lo_); - super.write(this.buff_, 0, len); - this.diskPos_ = this.curr_; - this.dirty_ = false; - } - } - - /* - * Read at most "this.buff.length" bytes into "this.buff", returning the - * number of bytes read. If the return result is less than - * "this.buff.length", then EOF was read. - */ - private int fillBuffer() throws IOException - { - int cnt = 0; - int rem = this.buff_.length; - while (rem > 0) - { - int n = super.read(this.buff_, cnt, rem); - if (n < 0) - break; - cnt += n; - rem -= n; - } - if ( (cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length)) ) - { - // make sure buffer that wasn't read is initialized with -1 - Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff); - } - this.diskPos_ += cnt; - return cnt; - } - - /* - * This method positions this.curr at position pos. - * If pos does not fall in the current buffer, it flushes the - * current buffer and loads the correct one.

- * - * On exit from this routine this.curr == this.hi iff pos - * is at or past the end-of-file, which can only happen if the file was - * opened in read-only mode. - */ - @Override - public void seek(long pos) throws IOException - { - if (pos >= this.hi_ || pos < this.lo_) - { - // seeking outside of current buffer -- flush and read - this.flushBuffer(); - this.lo_ = pos & BuffMask_; // start at BuffSz boundary - this.maxHi_ = this.lo_ + (long) this.buff_.length; - if (this.diskPos_ != this.lo_) - { - super.seek(this.lo_); - this.diskPos_ = this.lo_; - } - int n = this.fillBuffer(); - this.hi_ = this.lo_ + (long) n; - } - else - { - // seeking inside current buffer -- no read required - if (pos < this.curr_) - { - // if seeking backwards, we must flush to maintain V4 - this.flushBuffer(); - } - } - this.curr_ = pos; - } - - /* - * Does not maintain V4 (i.e. buffer differs from disk contents if previously written to) - * - Assumes no writes were made - * @param pos - * @throws IOException - */ - public void seekUnsafe(long pos) throws IOException - { - if (pos >= this.hi_ || pos < this.lo_) - { - // seeking outside of current buffer -- flush and read - this.flushBuffer(); - this.lo_ = pos & BuffMask_; // start at BuffSz boundary - this.maxHi_ = this.lo_ + (long) this.buff_.length; - if (this.diskPos_ != this.lo_) - { - super.seek(this.lo_); - this.diskPos_ = this.lo_; - } - int n = this.fillBuffer(); - this.hi_ = this.lo_ + (long) n; - } - this.curr_ = pos; - } - - @Override - public long getFilePointer() - { - return this.curr_; - } - - @Override - public long length() throws IOException - { - return Math.max(this.curr_, super.length()); - } - - @Override - public int read() throws IOException - { - if (this.curr_ >= this.hi_) - { - // test for EOF - // if (this.hi < this.maxHi) return -1; - if (this.hitEOF_) - return -1; - - // slow path -- read another buffer - this.seek(this.curr_); - if (this.curr_ == this.hi_) - return -1; - } - byte res = this.buff_[(int) (this.curr_ - this.lo_)]; - this.curr_++; - return ((int) res) & 0xFF; // convert byte -> int - } - - public byte read1() throws IOException { - if (this.curr_ >= this.hi_) - { - // test for EOF - // if (this.hi < this.maxHi) return -1; - if (this.hitEOF_) - return -1; - - // slow path -- read another buffer - this.seek(this.curr_); - if (this.curr_ == this.hi_) - return -1; - } - byte res = this.buff_[(int) (this.curr_++ - this.lo_)]; - return res; - } - - @Override - public int read(byte[] b) throws IOException - { - return this.read(b, 0, b.length); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException - { - if (this.curr_ >= this.hi_) - { - // test for EOF - // if (this.hi < this.maxHi) return -1; - if (this.hitEOF_) - return -1; - - // slow path -- read another buffer - this.seek(this.curr_); - if (this.curr_ == this.hi_) - return -1; - } - len = Math.min(len, (int) (this.hi_ - this.curr_)); - int buffOff = (int) (this.curr_ - this.lo_); - System.arraycopy(this.buff_, buffOff, b, off, len); - this.curr_ += len; - return len; - } - - public byte readCurrent() throws IOException { - if (this.curr_ >= this.hi_) - { - // test for EOF - // if (this.hi < this.maxHi) return -1; - if (this.hitEOF_) - return -1; - - // slow path -- read another buffer - this.seek(this.curr_); - if (this.curr_ == this.hi_) - return -1; - } - byte res = this.buff_[(int) (this.curr_ - this.lo_)]; - return res; - } - - public void writeCurrent(byte b) throws IOException { - if (this.curr_ >= this.hi_) - { - if (this.hitEOF_ && this.hi_ < this.maxHi_) - { - // at EOF -- bump "hi" - this.hi_++; - } - else - { - // slow path -- write current buffer; read next one - this.seek(this.curr_); - if (this.curr_ == this.hi_) - { - // appending to EOF -- bump "hi" - this.hi_++; - } - } - } - this.buff_[(int) (this.curr_ - this.lo_)] = b; - this.dirty_ = true; - } - - @Override - public void write(int b) throws IOException - { - if (this.curr_ >= this.hi_) - { - if (this.hitEOF_ && this.hi_ < this.maxHi_) - { - // at EOF -- bump "hi" - this.hi_++; - } - else - { - // slow path -- write current buffer; read next one - this.seek(this.curr_); - if (this.curr_ == this.hi_) - { - // appending to EOF -- bump "hi" - this.hi_++; - } - } - } - this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b; - this.curr_++; - this.dirty_ = true; - } - - @Override - public void write(byte[] b) throws IOException - { - this.write(b, 0, b.length); - } - - @Override - public void write(byte[] b, int off, int len) throws IOException - { - while (len > 0) - { - int n = this.writeAtMost(b, off, len); - off += n; - len -= n; - this.dirty_ = true; - } - } - - /* - * Write at most "len" bytes to "b" starting at position "off", and return - * the number of bytes written. - */ - private int writeAtMost(byte[] b, int off, int len) throws IOException - { - if (this.curr_ >= this.hi_) - { - if (this.hitEOF_ && this.hi_ < this.maxHi_) - { - // at EOF -- bump "hi" - this.hi_ = this.maxHi_; - } - else - { - // slow path -- write current buffer; read next one - this.seek(this.curr_); - if (this.curr_ == this.hi_) - { - // appending to EOF -- bump "hi" - this.hi_ = this.maxHi_; - } - } - } - len = Math.min(len, (int) (this.hi_ - this.curr_)); - int buffOff = (int) (this.curr_ - this.lo_); - System.arraycopy(b, off, this.buff_, buffOff, len); - this.curr_ += len; - return len; - } -} diff --git a/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java b/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java index 175ac8a7e09..b64fd06c666 100644 --- a/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java +++ b/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java @@ -83,24 +83,20 @@ public FastByteArrayOutputStream reset() { } public void write( final int b ) { - if ( position == length ) { - length++; - if ( position == array.length ) array = grow( array, length ); - } + if ( position >= array.length ) array = grow( array, position + 1, length ); array[ position++ ] = (byte)b; + if ( length < position ) length = position; } public static void ensureOffsetLength( final int arrayLength, final int offset, final int length ) { if ( offset < 0 ) throw new ArrayIndexOutOfBoundsException( "Offset (" + offset + ") is negative" ); if ( length < 0 ) throw new IllegalArgumentException( "Length (" + length + ") is negative" ); - if ( offset + length > arrayLength ) throw new ArrayIndexOutOfBoundsException( "Last index (" + ( offset + length ) + ") is greater than array length (" + arrayLength + ")" ); + if ( offset + length > arrayLength ) throw new ArrayIndexOutOfBoundsException( "Last index (" + ( offset + length ) + ") is greater than array length (" + arrayLength + ')'); } public static byte[] grow( final byte[] array, final int length ) { if ( length > array.length ) { - final int newLength = (int)Math.min( Math.max( ( ONEOVERPHI * array.length ) >>> 16, length ), Integer.MAX_VALUE ); - final byte[] t = - new byte[ newLength ]; + final byte[] t = new byte[(int) Math.min( Math.max( ( ONEOVERPHI * array.length ) >>> 16, length ), Integer.MAX_VALUE )]; System.arraycopy( array, 0, t, 0, array.length ); return t; } @@ -109,9 +105,7 @@ public static byte[] grow( final byte[] array, final int length ) { public static byte[] grow( final byte[] array, final int length, final int preserve ) { if ( length > array.length ) { - final int newLength = (int)Math.min( Math.max( ( ONEOVERPHI * array.length ) >>> 16, length ), Integer.MAX_VALUE ); - final byte[] t = - new byte[ newLength ]; + final byte[] t = new byte[(int) Math.min( Math.max( ( ONEOVERPHI * array.length ) >>> 16, length ), Integer.MAX_VALUE )]; System.arraycopy( array, 0, t, 0, preserve ); return t; } @@ -125,7 +119,7 @@ public void write( final byte[] b, final int off, final int len ) throws IOExcep } public void position( long newPosition ) { - if ( position > Integer.MAX_VALUE ) throw new IllegalArgumentException( "Position too large: " + newPosition ); + if ( newPosition > Integer.MAX_VALUE ) throw new IllegalArgumentException( "Position too large: " + newPosition ); position = (int)newPosition; } diff --git a/src/main/java/cn/nukkit/nbt/stream/NBTInputStream.java b/src/main/java/cn/nukkit/nbt/stream/NBTInputStream.java index 8382ecaf613..45a19616ada 100644 --- a/src/main/java/cn/nukkit/nbt/stream/NBTInputStream.java +++ b/src/main/java/cn/nukkit/nbt/stream/NBTInputStream.java @@ -10,10 +10,11 @@ import java.nio.charset.StandardCharsets; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class NBTInputStream implements DataInput, AutoCloseable { + private final DataInputStream stream; private final ByteOrder endianness; private final boolean network; @@ -149,6 +150,16 @@ public String readUTF() throws IOException { return new String(bytes, StandardCharsets.UTF_8); } + public String readUTF(int maxLen) throws IOException { + int length = network ? (int) VarInt.readUnsignedVarInt(stream) : this.readUnsignedShort(); + if (length > maxLen) { + throw new RuntimeException("Input too long!"); + } + byte[] bytes = new byte[length]; + this.stream.read(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } + public int available() throws IOException { return this.stream.available(); } diff --git a/src/main/java/cn/nukkit/nbt/stream/NBTOutputStream.java b/src/main/java/cn/nukkit/nbt/stream/NBTOutputStream.java index 276dd733854..bc8846ab0ba 100644 --- a/src/main/java/cn/nukkit/nbt/stream/NBTOutputStream.java +++ b/src/main/java/cn/nukkit/nbt/stream/NBTOutputStream.java @@ -10,10 +10,11 @@ import java.nio.charset.StandardCharsets; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class NBTOutputStream implements DataOutput, AutoCloseable { + private final DataOutputStream stream; private final ByteOrder endianness; private final boolean network; diff --git a/src/main/java/cn/nukkit/nbt/stream/PGZIPBlock.java b/src/main/java/cn/nukkit/nbt/stream/PGZIPBlock.java index 1b9a15d141d..8be3372764b 100644 --- a/src/main/java/cn/nukkit/nbt/stream/PGZIPBlock.java +++ b/src/main/java/cn/nukkit/nbt/stream/PGZIPBlock.java @@ -3,6 +3,7 @@ import java.util.concurrent.Callable; public class PGZIPBlock implements Callable { + public PGZIPBlock(final PGZIPOutputStream parent) { STATE = new PGZIPThreadLocal(parent); } @@ -11,35 +12,24 @@ public PGZIPBlock(final PGZIPOutputStream parent) { * This ThreadLocal avoids the recycling of a lot of memory, causing lumpy performance. */ protected final ThreadLocal STATE; - public static final int SIZE = 64 * 1024; - // private final int index; + public static final int SIZE = 65536; // 64 * 1024 protected final byte[] in = new byte[SIZE]; protected int in_length = 0; - /* - public Block(@Nonnegative int index) { - this.index = index; - } - */ - // Only on worker thread @Override public byte[] call() throws Exception { - // LOG.info("Processing " + this + " on " + Thread.currentThread()); PGZIPState state = STATE.get(); - // ByteArrayOutputStream buf = new ByteArrayOutputStream(in.length); // Overestimate output size required. - // DeflaterOutputStream def = newDeflaterOutputStream(buf); state.def.reset(); state.buf.reset(); state.str.write(in, 0, in_length); state.str.flush(); - // return Arrays.copyOf(in, in_length); return state.buf.toByteArray(); } @Override public String toString() { - return "Block" /* + index */ + "(" + in_length + "/" + in.length + " bytes)"; + return "Block" + "(" + in_length + "/" + in.length + " bytes)"; } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/nbt/stream/PGZIPOutputStream.java b/src/main/java/cn/nukkit/nbt/stream/PGZIPOutputStream.java index 99ee3d21ac9..15096b1741c 100644 --- a/src/main/java/cn/nukkit/nbt/stream/PGZIPOutputStream.java +++ b/src/main/java/cn/nukkit/nbt/stream/PGZIPOutputStream.java @@ -27,13 +27,9 @@ public static ExecutorService getSharedThreadPool() { return EXECUTOR; } - - // private static final Logger LOG = LoggerFactory.getLogger(PGZIPOutputStream.class); private final static int GZIP_MAGIC = 0x8b1f; - // todo: remove after block guessing is implemented - // array list that contains the block sizes - private IntList blockSizes = new IntArrayList(); + private final IntList blockSizes = new IntArrayList(); private int level = Deflater.BEST_SPEED; private int strategy = Deflater.DEFAULT_STRATEGY; @@ -56,7 +52,6 @@ protected static DeflaterOutputStream newDeflaterOutputStream(OutputStream out, return new DeflaterOutputStream(out, deflater, 512, true); } - // TODO: Share, daemonize. private final ExecutorService executor; private final int nthreads; private final CRC32 crc = new CRC32(); @@ -187,7 +182,6 @@ private void tryEmit() throws IOException, InterruptedException, ExecutionExcept private void emitUntil(int taskCountAllowed) throws IOException { try { while (emitQueue.size() > taskCountAllowed) { - // LOG.info("Waiting for taskCount=" + emitQueue.size() + " -> " + taskCountAllowed); Future future = emitQueue.remove(); // Valid because emitQueue.size() > 0 byte[] toWrite = future.get(); // Blocks until this task is done. blockSizes.add(toWrite.length); // todo: remove after block guessing is implemented @@ -206,7 +200,6 @@ private void emitUntil(int taskCountAllowed) throws IOException { // Master thread only @Override public void flush() throws IOException { - // LOG.info("Flush: " + block); if (block.in_length > 0) submit(); emitUntil(0); @@ -216,7 +209,6 @@ public void flush() throws IOException { // Master thread only @Override public void close() throws IOException { - // LOG.info("Closing: bytesWritten=" + bytesWritten); if (bytesWritten >= 0) { flush(); @@ -224,18 +216,14 @@ public void close() throws IOException { ByteBuffer buf = ByteBuffer.allocate(8); buf.order(ByteOrder.LITTLE_ENDIAN); - // LOG.info("CRC is " + crc.getValue()); buf.putInt((int) crc.getValue()); buf.putInt(bytesWritten); - out.write(buf.array()); // allocate() guarantees a backing array. - // LOG.info("trailer is " + Arrays.toString(buf.array())); + out.write(buf.array()); out.flush(); out.close(); bytesWritten = Integer.MIN_VALUE; - // } else { - // LOG.warn("Already closed."); } } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/nbt/stream/PGZIPState.java b/src/main/java/cn/nukkit/nbt/stream/PGZIPState.java index f075f49f833..f98be44bb95 100644 --- a/src/main/java/cn/nukkit/nbt/stream/PGZIPState.java +++ b/src/main/java/cn/nukkit/nbt/stream/PGZIPState.java @@ -5,6 +5,7 @@ import java.util.zip.DeflaterOutputStream; public class PGZIPState { + protected final DeflaterOutputStream str; protected final ByteArrayOutputStream buf; protected final Deflater def; @@ -14,6 +15,4 @@ public PGZIPState(PGZIPOutputStream parent) { this.buf = new ByteArrayOutputStream(PGZIPBlock.SIZE); this.str = PGZIPOutputStream.newDeflaterOutputStream(buf, def); } - - } diff --git a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java index b0e809477b5..7ab7dee8c6d 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java @@ -8,6 +8,7 @@ import java.util.Arrays; public class ByteArrayTag extends Tag { + public byte[] data; public ByteArrayTag(String name) { @@ -20,7 +21,7 @@ public ByteArrayTag(String name, byte[] data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { if (data == null) { dos.writeInt(0); return; @@ -30,7 +31,7 @@ void write(NBTOutputStream dos) throws IOException { } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { int length = dis.readInt(); data = new byte[length]; dis.readFully(data); diff --git a/src/main/java/cn/nukkit/nbt/tag/ByteTag.java b/src/main/java/cn/nukkit/nbt/tag/ByteTag.java index 8678d52fd4c..b64fea4c379 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ByteTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ByteTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class ByteTag extends NumberTag { + public int data; @Override @@ -28,12 +29,12 @@ public ByteTag(String name, int data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeByte(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readByte(); } @@ -51,9 +52,9 @@ public Integer parseValue() { public String toString() { String hex = Integer.toHexString(this.data); if (hex.length() < 2) { - hex = "0" + hex; + hex = '0' + hex; } - return "ByteTag " + this.getName() + " (data: 0x" + hex + ")"; + return "ByteTag " + this.getName() + " (data: 0x" + hex + ')'; } @Override diff --git a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java index 10def9433b0..e3fa54c1522 100644 --- a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java @@ -12,6 +12,7 @@ import java.util.StringJoiner; public class CompoundTag extends Tag implements Cloneable { + private final Map tags = new HashMap<>(); public CompoundTag() { @@ -24,7 +25,6 @@ public CompoundTag(String name) { @Override public void write(NBTOutputStream dos) throws IOException { - for (Map.Entry entry : this.tags.entrySet()) { Tag.writeNamedTag(entry.getValue(), entry.getKey(), dos); } @@ -132,70 +132,86 @@ public T removeAndGet(String name) { return (T) tags.remove(name); } - public int getByte(String name) { - if (!tags.containsKey(name)) return (byte) 0; - return ((NumberTag) tags.get(name)).getData().intValue(); + Tag t = tags.get(name); + if (t == null) return (byte) 0; + return ((NumberTag) t).getData().intValue(); } public int getShort(String name) { - if (!tags.containsKey(name)) return 0; - return ((NumberTag) tags.get(name)).getData().intValue(); + Tag t = tags.get(name); + if (t == null) return 0; + return ((NumberTag) t).getData().intValue(); } public int getInt(String name) { - if (!tags.containsKey(name)) return 0; - return ((NumberTag) tags.get(name)).getData().intValue(); + Tag t = tags.get(name); + if (t == null) return 0; + return ((NumberTag) t).getData().intValue(); } public long getLong(String name) { - if (!tags.containsKey(name)) return 0; - return ((NumberTag) tags.get(name)).getData().longValue(); + Tag t = tags.get(name); + if (t == null) return 0; + return ((NumberTag) t).getData().longValue(); } public float getFloat(String name) { - if (!tags.containsKey(name)) return (float) 0; - return ((NumberTag) tags.get(name)).getData().floatValue(); + Tag t = tags.get(name); + if (t == null) return 0f; + return ((NumberTag) t).getData().floatValue(); } public double getDouble(String name) { - if (!tags.containsKey(name)) return 0; - return ((NumberTag) tags.get(name)).getData().doubleValue(); + Tag t = tags.get(name); + if (t == null) return 0; + return ((NumberTag) t).getData().doubleValue(); } public String getString(String name) { - if (!tags.containsKey(name)) return ""; - Tag tag = tags.get(name); - if (tag instanceof NumberTag) { - return String.valueOf(((NumberTag) tag).getData()); + Tag t = tags.get(name); + if (t == null) return ""; + if (t instanceof NumberTag) { + return String.valueOf(((NumberTag) t).getData()); } - return ((StringTag) tag).data; + return ((StringTag) t).data; } public byte[] getByteArray(String name) { - if (!tags.containsKey(name)) return new byte[0]; - return ((ByteArrayTag) tags.get(name)).data; + Tag t = tags.get(name); + if (t == null) return new byte[0]; + return ((ByteArrayTag) t).data; + } + + public byte[] getByteArray(String name, int defaultSize) { + Tag t = tags.get(name); + if (t == null) return new byte[defaultSize]; + return ((ByteArrayTag) t).data; } public int[] getIntArray(String name) { - if (!tags.containsKey(name)) return new int[0]; - return ((IntArrayTag) tags.get(name)).data; + Tag t = tags.get(name); + if (t == null) return new int[0]; + return ((IntArrayTag) t).data; } public CompoundTag getCompound(String name) { - if (!tags.containsKey(name)) return new CompoundTag(name); - return (CompoundTag) tags.get(name); + Tag t = tags.get(name); + if (t == null) return new CompoundTag(name); + return (CompoundTag) t; } public ListTag getList(String name) { - if (!tags.containsKey(name)) return new ListTag<>(name); - return (ListTag) tags.get(name); + Tag t = tags.get(name); + if (t == null) return new ListTag<>(name); + return (ListTag) t; } @SuppressWarnings("unchecked") public ListTag getList(String name, Class type) { - if (tags.containsKey(name)) { - return (ListTag) tags.get(name); + Tag t = tags.get(name); + if (t != null) { + return (ListTag) t; } return new ListTag<>(name); } @@ -219,6 +235,12 @@ public boolean getBoolean(String name) { return getByte(name) != 0; } + public boolean getBoolean(String name, boolean def) { + Tag t = tags.get(name); + if (t == null) return def; + return (((NumberTag) t).getData().intValue()) != 0; + } + public String toString() { StringJoiner joiner = new StringJoiner(",\n\t"); tags.forEach((key, tag) -> joiner.add('\'' + key + "' : " + tag.toString().replace("\n", "\n\t"))); @@ -227,13 +249,13 @@ public String toString() { public void print(String prefix, PrintStream out) { super.print(prefix, out); - out.println(prefix + "{"); + out.println(prefix + '{'); String orgPrefix = prefix; prefix += " "; for (Tag tag : tags.values()) { tag.print(prefix, out); } - out.println(orgPrefix + "}"); + out.println(orgPrefix + '}'); } public boolean isEmpty() { @@ -242,8 +264,8 @@ public boolean isEmpty() { public CompoundTag copy() { CompoundTag tag = new CompoundTag(getName()); - for (String key : tags.keySet()) { - tag.put(key, tags.get(key).copy()); + for (Entry entry : tags.entrySet()) { + tag.put(entry.getKey(), entry.getValue().copy()); } return tag; } @@ -273,4 +295,4 @@ public CompoundTag clone() { this.getTags().forEach((key, value) -> nbt.put(key, value.copy())); return nbt; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java b/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java index f594beaade4..f9c592e6dd0 100644 --- a/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class DoubleTag extends NumberTag { + public double data; @Override @@ -28,12 +29,12 @@ public DoubleTag(String name, double data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeDouble(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readDouble(); } @@ -49,7 +50,7 @@ public byte getId() { @Override public String toString() { - return "DoubleTag " + this.getName() + " (data: " + data + ")"; + return "DoubleTag " + this.getName() + " (data: " + data + ')'; } @Override @@ -65,5 +66,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/EndTag.java b/src/main/java/cn/nukkit/nbt/tag/EndTag.java index 8f4851a2dc9..dde6093a682 100644 --- a/src/main/java/cn/nukkit/nbt/tag/EndTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/EndTag.java @@ -12,11 +12,11 @@ public EndTag() { } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { } @Override diff --git a/src/main/java/cn/nukkit/nbt/tag/FloatTag.java b/src/main/java/cn/nukkit/nbt/tag/FloatTag.java index 795b04f1cad..8c628fa322b 100644 --- a/src/main/java/cn/nukkit/nbt/tag/FloatTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/FloatTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class FloatTag extends NumberTag { + public float data; @Override @@ -28,12 +29,12 @@ public FloatTag(String name, float data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeFloat(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readFloat(); } @@ -49,7 +50,7 @@ public byte getId() { @Override public String toString() { - return "FloatTag " + this.getName() + " (data: " + data + ")"; + return "FloatTag " + this.getName() + " (data: " + data + ')'; } @Override @@ -65,5 +66,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java b/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java index 23b99b90747..37303d7200c 100644 --- a/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java @@ -7,6 +7,7 @@ import java.util.Arrays; public class IntArrayTag extends Tag { + public int[] data; public IntArrayTag(String name) { @@ -19,7 +20,7 @@ public IntArrayTag(String name, int[] data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeInt(data.length); for (int aData : data) { dos.writeInt(aData); @@ -27,7 +28,7 @@ void write(NBTOutputStream dos) throws IOException { } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { int length = dis.readInt(); data = new int[length]; for (int i = 0; i < length; i++) { diff --git a/src/main/java/cn/nukkit/nbt/tag/IntTag.java b/src/main/java/cn/nukkit/nbt/tag/IntTag.java index a4d21d20f64..84a4edbfff7 100644 --- a/src/main/java/cn/nukkit/nbt/tag/IntTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/IntTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class IntTag extends NumberTag { + public int data; @Override @@ -28,12 +29,12 @@ public IntTag(String name, int data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeInt(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readInt(); } @@ -49,7 +50,7 @@ public byte getId() { @Override public String toString() { - return "IntTag " + this.getName() + "(data: " + data + ")"; + return "IntTag " + this.getName() + "(data: " + data + ')'; } @Override @@ -65,5 +66,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/ListTag.java b/src/main/java/cn/nukkit/nbt/tag/ListTag.java index 3f91bcbf8ae..d756843bbf5 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ListTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ListTag.java @@ -25,8 +25,8 @@ public ListTag(String name) { } @Override - void write(NBTOutputStream dos) throws IOException { - if (list.size() > 0) type = list.get(0).getId(); + public void write(NBTOutputStream dos) throws IOException { + if (!list.isEmpty()) type = list.get(0).getId(); else type = 1; dos.writeByte(type); @@ -36,7 +36,7 @@ void write(NBTOutputStream dos) throws IOException { @Override @SuppressWarnings("unchecked") - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { type = dis.readByte(); int size = dis.readInt(); @@ -64,11 +64,11 @@ public String toString() { public void print(String prefix, PrintStream out) { super.print(prefix, out); - out.println(prefix + "{"); + out.println(prefix + '{'); String orgPrefix = prefix; prefix += " "; for (T aList : list) aList.print(prefix, out); - out.println(orgPrefix + "}"); + out.println(orgPrefix + '}'); } public ListTag add(T tag) { @@ -152,5 +152,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/LongTag.java b/src/main/java/cn/nukkit/nbt/tag/LongTag.java index a37df491729..de08af0b7b7 100644 --- a/src/main/java/cn/nukkit/nbt/tag/LongTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/LongTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class LongTag extends NumberTag { + public long data; @Override @@ -28,12 +29,12 @@ public LongTag(String name, long data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeLong(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readLong(); } @@ -49,7 +50,7 @@ public byte getId() { @Override public String toString() { - return "LongTag " + this.getName() + " (data:" + data + ")"; + return "LongTag " + this.getName() + " (data:" + data + ')'; } @Override @@ -65,5 +66,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/NumberTag.java b/src/main/java/cn/nukkit/nbt/tag/NumberTag.java index 0e01650a4a9..6e91d2aea45 100644 --- a/src/main/java/cn/nukkit/nbt/tag/NumberTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/NumberTag.java @@ -1,10 +1,11 @@ package cn.nukkit.nbt.tag; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class NumberTag extends Tag { + protected NumberTag(String name) { super(name); } diff --git a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java index e6465316d63..9b9cc9b532f 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class ShortTag extends NumberTag { + public int data; @Override @@ -28,12 +29,12 @@ public ShortTag(String name, int data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeShort(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readShort(); } @@ -49,7 +50,7 @@ public byte getId() { @Override public String toString() { - return "" + data; + return "ShortTag " + this.getName() + "(data: " + data + ')'; } @Override @@ -65,5 +66,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/StringTag.java b/src/main/java/cn/nukkit/nbt/tag/StringTag.java index 95094cc7685..3c3d022b42d 100644 --- a/src/main/java/cn/nukkit/nbt/tag/StringTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/StringTag.java @@ -6,6 +6,7 @@ import java.io.IOException; public class StringTag extends Tag { + public String data; public StringTag(String name) { @@ -19,12 +20,12 @@ public StringTag(String name, String data) { } @Override - void write(NBTOutputStream dos) throws IOException { + public void write(NBTOutputStream dos) throws IOException { dos.writeUTF(data); } @Override - void load(NBTInputStream dis) throws IOException { + public void load(NBTInputStream dis) throws IOException { data = dis.readUTF(); } @@ -40,7 +41,7 @@ public byte getId() { @Override public String toString() { - return "StringTag " + this.getName() + " (data: " + data + ")"; + return "StringTag " + this.getName() + " (data: " + data + ')'; } @Override @@ -56,5 +57,4 @@ public boolean equals(Object obj) { } return false; } - } diff --git a/src/main/java/cn/nukkit/nbt/tag/Tag.java b/src/main/java/cn/nukkit/nbt/tag/Tag.java index 3b66e063b2b..7969bc74c55 100644 --- a/src/main/java/cn/nukkit/nbt/tag/Tag.java +++ b/src/main/java/cn/nukkit/nbt/tag/Tag.java @@ -7,6 +7,7 @@ import java.io.PrintStream; public abstract class Tag { + public static final byte TAG_End = 0; public static final byte TAG_Byte = 1; public static final byte TAG_Short = 2; @@ -56,7 +57,7 @@ public void print(String prefix, PrintStream out) { out.print(prefix); out.print(getTagName(getId())); - if (name.length() > 0) { + if (!name.isEmpty()) { out.print("(\"" + name + "\")"); } out.print(": "); @@ -81,7 +82,7 @@ public static Tag readNamedTag(NBTInputStream dis) throws IOException { byte type = dis.readByte(); if (type == 0) return new EndTag(); - String name = dis.readUTF(); + String name = dis.readUTF(1024); Tag tag = newTag(type, name); diff --git a/src/main/java/cn/nukkit/network/AdvancedSourceInterface.java b/src/main/java/cn/nukkit/network/AdvancedSourceInterface.java index 6a982696c63..240c2ea3dcb 100644 --- a/src/main/java/cn/nukkit/network/AdvancedSourceInterface.java +++ b/src/main/java/cn/nukkit/network/AdvancedSourceInterface.java @@ -6,7 +6,7 @@ import java.net.InetSocketAddress; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface AdvancedSourceInterface extends SourceInterface { diff --git a/src/main/java/cn/nukkit/network/BatchingHelper.java b/src/main/java/cn/nukkit/network/BatchingHelper.java new file mode 100644 index 00000000000..a4de2512848 --- /dev/null +++ b/src/main/java/cn/nukkit/network/BatchingHelper.java @@ -0,0 +1,37 @@ +package cn.nukkit.network; + +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.network.protocol.DataPacket; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class BatchingHelper { + + private final ExecutorService threadedExecutor; + + public BatchingHelper() { + ThreadFactoryBuilder builder = new ThreadFactoryBuilder(); + builder.setNameFormat("Batching Executor"); + builder.setUncaughtExceptionHandler((thread, ex) -> Server.getInstance().getLogger().error("Exception in " + thread.getName(), ex)); + this.threadedExecutor = Executors.newSingleThreadExecutor(builder.build()); + } + + public void batchPackets(Player[] players, DataPacket[] packets) { + if (players.length > 0 && packets.length > 0) { + this.threadedExecutor.execute(() -> { + for (Player player : players) { + for (DataPacket packet : packets) { + player.getNetworkSession().sendPacket(packet); + } + } + }); + } + } + + public void shutdown() { + this.threadedExecutor.shutdownNow(); + } +} diff --git a/src/main/java/cn/nukkit/network/CompressBatchedPacket.java b/src/main/java/cn/nukkit/network/CompressBatchedPacket.java deleted file mode 100644 index bcf4f135e8f..00000000000 --- a/src/main/java/cn/nukkit/network/CompressBatchedPacket.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.nukkit.network; - -import cn.nukkit.Server; -import cn.nukkit.scheduler.AsyncTask; - -import java.net.InetSocketAddress; -import java.util.List; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class CompressBatchedPacket extends AsyncTask { - - public int level = 7; - public byte[] data; - public byte[] finalData; - public int channel = 0; - public List targets; - - public CompressBatchedPacket(byte[] data, List targets) { - this(data, targets, 7); - } - - public CompressBatchedPacket(byte[] data, List targets, int level) { - this(data, targets, level, 0); - } - - public CompressBatchedPacket(byte[] data, List targets, int level, int channel) { - this.data = data; - this.targets = targets; - this.level = level; - this.channel = channel; - } - - @Override - public void onRun() { - try { - this.finalData = Network.deflateRaw(data, level); - this.data = null; - } catch (Exception e) { - //ignore - } - } - - @Override - public void onCompletion(Server server) { - server.broadcastPacketsCallback(this.finalData, this.targets); - } -} diff --git a/src/main/java/cn/nukkit/network/CompressBatchedTask.java b/src/main/java/cn/nukkit/network/CompressBatchedTask.java deleted file mode 100644 index bd64ccd0579..00000000000 --- a/src/main/java/cn/nukkit/network/CompressBatchedTask.java +++ /dev/null @@ -1,50 +0,0 @@ -package cn.nukkit.network; - -import cn.nukkit.Server; -import cn.nukkit.scheduler.AsyncTask; - -import java.net.InetSocketAddress; -import java.util.List; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public class CompressBatchedTask extends AsyncTask { - - public int level = 7; - public byte[][] data; - public byte[] finalData; - public int channel = 0; - public List targets; - - public CompressBatchedTask(byte[][] data, List targets) { - this(data, targets, 7); - } - - public CompressBatchedTask(byte[][] data, List targets, int level) { - this(data, targets, level, 0); - } - - public CompressBatchedTask(byte[][] data, List targets, int level, int channel) { - this.data = data; - this.targets = targets; - this.level = level; - this.channel = channel; - } - - @Override - public void onRun() { - try { - this.finalData = Network.deflateRaw(this.data, this.level); - this.data = null; - } catch (Exception e) { - //ignore - } - } - - @Override - public void onCompletion(Server server) { - server.broadcastPacketsCallback(this.finalData, this.targets); - } -} diff --git a/src/main/java/cn/nukkit/network/CompressionProvider.java b/src/main/java/cn/nukkit/network/CompressionProvider.java index a416d7c33de..5aff8b99885 100644 --- a/src/main/java/cn/nukkit/network/CompressionProvider.java +++ b/src/main/java/cn/nukkit/network/CompressionProvider.java @@ -2,6 +2,8 @@ import cn.nukkit.network.protocol.types.PacketCompressionAlgorithm; import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.SnappyCompression; +import cn.nukkit.utils.Zlib; public interface CompressionProvider { @@ -16,6 +18,11 @@ public byte[] decompress(byte[] compressed) throws Exception { return compressed; } + @Override + public byte[] decompress(byte[] compressed, int maxSize) throws Exception { + return compressed; + } + @Override public byte getPrefix() { return (byte) 0xff; @@ -25,12 +32,34 @@ public byte getPrefix() { CompressionProvider ZLIB = new CompressionProvider() { @Override public byte[] compress(BinaryStream packet, int level) throws Exception { - return Network.deflateRaw(packet.getBuffer(), level); + return Zlib.deflatePre16Packet(packet.getBuffer(), level); } @Override public byte[] decompress(byte[] compressed) throws Exception { - return Network.inflateRaw(compressed); + return Zlib.inflate(compressed, 3145728); + } + + @Override + public byte[] decompress(byte[] compressed, int maxSize) throws Exception { + return Zlib.inflate(compressed, maxSize); + } + }; + + CompressionProvider ZLIB_RAW = new CompressionProvider() { + @Override + public byte[] compress(BinaryStream packet, int level) throws Exception { + return Zlib.deflateRaw(packet.getBuffer(), level); + } + + @Override + public byte[] decompress(byte[] compressed) throws Exception { + return Zlib.inflateRaw(compressed, 3145728); + } + + @Override + public byte[] decompress(byte[] compressed, int maxSize) throws Exception { + return Zlib.inflateRaw(compressed, maxSize); } @Override @@ -39,15 +68,43 @@ public byte getPrefix() { } }; + CompressionProvider SNAPPY = new CompressionProvider() { + @Override + public byte[] compress(BinaryStream packet, int level) throws Exception { + return SnappyCompression.compress(packet.getBuffer()); + } + + @Override + public byte[] decompress(byte[] compressed) throws Exception { + return SnappyCompression.decompress(compressed, 3145728); + } + + @Override + public byte[] decompress(byte[] compressed, int maxSize) throws Exception { + return SnappyCompression.decompress(compressed, maxSize); + } + + @Override + public byte getPrefix() { + return (byte) 0x01; + } + }; + byte[] compress(BinaryStream packet, int level) throws Exception; byte[] decompress(byte[] compressed) throws Exception; - static CompressionProvider from(PacketCompressionAlgorithm algorithm) { + default byte[] decompress(byte[] compressed, int maxSize) throws Exception { + return this.decompress(compressed); + } + + static CompressionProvider from(PacketCompressionAlgorithm algorithm, int raknetVersion) { if (algorithm == null) { return NONE; } else if (algorithm == PacketCompressionAlgorithm.ZLIB) { - return ZLIB; + return raknetVersion < 10 ? ZLIB : ZLIB_RAW; + } else if (algorithm == PacketCompressionAlgorithm.SNAPPY) { + return SNAPPY; } throw new UnsupportedOperationException(); } @@ -59,10 +116,12 @@ default byte getPrefix() { static CompressionProvider byPrefix(byte prefix) { switch (prefix) { case 0x00: - return ZLIB; + return ZLIB_RAW; + case 0x01: + return SNAPPY; case (byte) 0xff: return NONE; } - throw new IllegalArgumentException("Unsupported compression type: " + prefix); + throw new IllegalArgumentException("Unknown compression type: " + prefix); } } diff --git a/src/main/java/cn/nukkit/network/Network.java b/src/main/java/cn/nukkit/network/Network.java index 117d8bb0f51..6aa8ab4c40a 100644 --- a/src/main/java/cn/nukkit/network/Network.java +++ b/src/main/java/cn/nukkit/network/Network.java @@ -1,42 +1,32 @@ package cn.nukkit.network; import cn.nukkit.Nukkit; +import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.nbt.stream.FastByteArrayOutputStream; import cn.nukkit.network.protocol.*; import cn.nukkit.utils.BinaryStream; -import cn.nukkit.utils.ThreadCache; import cn.nukkit.utils.Utils; import cn.nukkit.utils.VarInt; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; import lombok.extern.log4j.Log4j2; +import javax.annotation.Nullable; import java.io.ByteArrayInputStream; -import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ProtocolException; import java.util.Collection; import java.util.HashSet; import java.util.Set; -import java.util.zip.DataFormatException; -import java.util.zip.Deflater; -import java.util.zip.Inflater; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @Log4j2 +@SuppressWarnings("unchecked") public class Network { - private static final ThreadLocal INFLATER_RAW = ThreadLocal.withInitial(() -> new Inflater(true)); - private static final ThreadLocal DEFLATER_RAW = ThreadLocal.withInitial(() -> new Deflater(7, true)); - private static final ThreadLocal BUFFER = ThreadLocal.withInitial(() -> new byte[2 * 1024 * 1024]); - - public static final byte CHANNEL_NONE = 0; public static final byte CHANNEL_PRIORITY = 1; //Priority channel, only to be used when it matters public static final byte CHANNEL_WORLD_CHUNKS = 2; //Chunk sending @@ -66,74 +56,6 @@ public Network(Server server) { this.server = server; } - public static byte[] inflateRaw(byte[] data) throws IOException, DataFormatException { - Inflater inflater = INFLATER_RAW.get(); - try { - inflater.setInput(data); - inflater.finished(); - - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buf = BUFFER.get(); - while (!inflater.finished()) { - int i = inflater.inflate(buf); - if (i == 0) { - throw new IOException("Could not decompress the data. Needs input: " + inflater.needsInput() + ", Needs Dictionary: " + inflater.needsDictionary()); - } - bos.write(buf, 0, i); - } - return bos.toByteArray(); - } finally { - inflater.reset(); - } - } - - public static byte[] deflateRaw(byte[] data, int level) throws IOException { - Deflater deflater = DEFLATER_RAW.get(); - try { - deflater.setLevel(data.length < Server.getInstance().networkCompressionThreshold ? 0 : level); - deflater.setInput(data); - deflater.finish(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buffer = BUFFER.get(); - while (!deflater.finished()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - - return bos.toByteArray(); - } finally { - deflater.reset(); - } - } - - public static byte[] deflateRaw(byte[][] datas, int level) throws IOException { - Deflater deflater = DEFLATER_RAW.get(); - try { - deflater.setLevel(level); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buffer = BUFFER.get(); - - for (byte[] data : datas) { - deflater.setInput(data); - while (!deflater.needsInput()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - } - deflater.finish(); - while (!deflater.finished()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - return bos.toByteArray(); - } finally { - deflater.reset(); - } - } - public void addStatistics(double upload, double download) { this.upload += upload; this.download += download; @@ -161,13 +83,9 @@ public void processInterfaces() { try { interfaz.process(); } catch (Exception e) { - if (Nukkit.DEBUG > 1) { - this.server.getLogger().logException(e); - } - interfaz.emergencyShutdown(); this.unregisterInterface(interfaz); - log.fatal(this.server.getLanguage().translateString("nukkit.server.networkError", new String[]{interfaz.getClass().getName(), Utils.getExceptionMessage(e)})); + log.fatal(this.server.getLanguage().translateString("nukkit.server.networkError", new String[]{interfaz.getClass().getName(), Utils.getExceptionMessage(e)}), e); } } } @@ -219,58 +137,59 @@ public Server getServer() { return server; } - public void processBatch(byte[] payload, Collection packets, CompressionProvider compression) { - byte[] data; - try { - data = compression.decompress(payload); - } catch (Exception e) { - log.debug("Exception while inflating batch packet", e); - return; - } + public void processBatch(byte[] payload, Collection packets, CompressionProvider compression, @Nullable Player player) throws Exception { + //byte[] data; + //try { + + // Allow first batch to be bigger so large skin in login packet won't get the player kicked + byte[] data = compression.decompress(payload, player != null && player.getSkin() == null ? 6291456 : 3145728); + + //} catch (Exception e) { + // log.error("Exception while inflating batch packet", e); + // return; + //} BinaryStream stream = new BinaryStream(data); - try { - int count = 0; - while (!stream.feof()) { - count++; - if (count >= 1000) { - throw new ProtocolException("Illegal batch with " + count + " packets"); - } - byte[] buf = stream.getByteArray(); - - ByteArrayInputStream bais = new ByteArrayInputStream(buf); - int header = (int) VarInt.readUnsignedVarInt(bais); - - // | Client ID | Sender ID | Packet ID | - // | 2 bits | 2 bits | 10 bits | - int packetId = header & 0x3ff; - - DataPacket pk = this.getPacket(packetId); - - if (pk != null) { - pk.setBuffer(buf, buf.length - bais.available()); - try { - pk.decode(); - } catch (Exception e) { - if (log.isTraceEnabled()) { - log.trace("Dumping Packet\n{}", ByteBufUtil.prettyHexDump(Unpooled.wrappedBuffer(buf))); - } - log.error("Unable to decode packet", e); - throw new IllegalStateException("Unable to decode " + pk.getClass().getSimpleName()); - } - - packets.add(pk); - } else { - log.debug("Received unknown packet with ID: {}", Integer.toHexString(packetId)); + int count = 0; + while (!stream.feof()) { + count++; + if (count > 1300) { + throw new ProtocolException("Too big batch packet (count > 1300)"); + } + + byte[] buf = stream.getByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(buf); + + int packetId = ((int) VarInt.readUnsignedVarInt(bais) & 0x3ff); + + DataPacket pk = this.getPacket(packetId); + if (pk == null) { + if (Nukkit.DEBUG > 1) { + log.debug("Received unknown packet with ID: 0x{}", Integer.toHexString(packetId)); } + continue; } - } catch (Exception e) { - if (log.isDebugEnabled()) { - log.debug("Error whilst decoding batch packet", e); + + pk.setBuffer(buf, buf.length - bais.available()); + + try { + pk.decode(); + + if (Nukkit.DEBUG > 1 && pk.offset < pk.getRawBuffer().length) { + log.debug(pk.getClass().getSimpleName() + " still has " + (pk.getRawBuffer().length - pk.offset) + " bytes to read!"); + } + } catch (Exception e) { + throw new IllegalStateException("Unable to decode " + pk.getClass().getSimpleName(), e); } + + packets.add(pk); } } + public DataPacket getPacket(byte id) { + return this.getPacket(id & 0xff); + } + public DataPacket getPacket(int id) { Class clazz = this.packetPool[id]; if (clazz != null) { @@ -316,7 +235,6 @@ private void registerPackets() { 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.ANVIL_DAMAGE_PACKET, AnvilDamagePacket.class); this.registerPacket(ProtocolInfo.AVAILABLE_COMMANDS_PACKET, AvailableCommandsPacket.class); this.registerPacket(ProtocolInfo.BATCH_PACKET, BatchPacket.class); this.registerPacket(ProtocolInfo.BLOCK_ENTITY_DATA_PACKET, BlockEntityDataPacket.class); @@ -332,20 +250,17 @@ private void registerPackets() { this.registerPacket(ProtocolInfo.CONTAINER_OPEN_PACKET, ContainerOpenPacket.class); this.registerPacket(ProtocolInfo.CONTAINER_SET_DATA_PACKET, ContainerSetDataPacket.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.ENTITY_EVENT_PACKET, EntityEventPacket.class); - this.registerPacket(ProtocolInfo.ENTITY_FALL_PACKET, EntityFallPacket.class); this.registerPacket(ProtocolInfo.FULL_CHUNK_DATA_PACKET, LevelChunkPacket.class); this.registerPacket(ProtocolInfo.GAME_RULES_CHANGED_PACKET, GameRulesChangedPacket.class); - this.registerPacket(ProtocolInfo.HURT_ARMOR_PACKET, HurtArmorPacket.class); this.registerPacket(ProtocolInfo.INTERACT_PACKET, InteractPacket.class); this.registerPacket(ProtocolInfo.INVENTORY_CONTENT_PACKET, InventoryContentPacket.class); this.registerPacket(ProtocolInfo.INVENTORY_SLOT_PACKET, InventorySlotPacket.class); this.registerPacket(ProtocolInfo.INVENTORY_TRANSACTION_PACKET, InventoryTransactionPacket.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_V1, LevelSoundEventPacketV1.class); + //this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V1, LevelSoundEventPacketV1.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); @@ -376,56 +291,43 @@ private void registerPackets() { 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_TITLE_PACKET, SetTitlePacket.class); this.registerPacket(ProtocolInfo.SET_TIME_PACKET, SetTimePacket.class); this.registerPacket(ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET, ServerSettingsRequestPacket.class); this.registerPacket(ProtocolInfo.SERVER_SETTINGS_RESPONSE_PACKET, ServerSettingsResponsePacket.class); - this.registerPacket(ProtocolInfo.SHOW_CREDITS_PACKET, ShowCreditsPacket.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_ATTRIBUTES_PACKET, UpdateAttributesPacket.class); this.registerPacket(ProtocolInfo.UPDATE_BLOCK_PACKET, UpdateBlockPacket.class); this.registerPacket(ProtocolInfo.UPDATE_TRADE_PACKET, UpdateTradePacket.class); - this.registerPacket(ProtocolInfo.MOVE_ENTITY_DELTA_PACKET, MoveEntityDeltaPacket.class); this.registerPacket(ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET, SetLocalPlayerAsInitializedPacket.class); - this.registerPacket(ProtocolInfo.NETWORK_STACK_LATENCY_PACKET, NetworkStackLatencyPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_SOFT_ENUM_PACKET, UpdateSoftEnumPacket.class); this.registerPacket(ProtocolInfo.NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET, NetworkChunkPublisherUpdatePacket.class); this.registerPacket(ProtocolInfo.AVAILABLE_ENTITY_IDENTIFIERS_PACKET, AvailableEntityIdentifiersPacket.class); - this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2, LevelSoundEventPacket.class); - this.registerPacket(ProtocolInfo.SCRIPT_CUSTOM_EVENT_PACKET, ScriptCustomEventPacket.class); + //this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2, LevelSoundEventPacket.class); this.registerPacket(ProtocolInfo.SPAWN_PARTICLE_EFFECT_PACKET, SpawnParticleEffectPacket.class); this.registerPacket(ProtocolInfo.BIOME_DEFINITION_LIST_PACKET, BiomeDefinitionListPacket.class); - this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET, LevelSoundEventPacket.class); - this.registerPacket(ProtocolInfo.LEVEL_EVENT_GENERIC_PACKET, LevelEventGenericPacket.class); + //this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET, LevelSoundEventPacket.class); this.registerPacket(ProtocolInfo.LECTERN_UPDATE_PACKET, LecternUpdatePacket.class); - this.registerPacket(ProtocolInfo.VIDEO_STREAM_CONNECT_PACKET, VideoStreamConnectPacket.class); - this.registerPacket(ProtocolInfo.CLIENT_CACHE_STATUS_PACKET, ClientCacheStatusPacket.class); - this.registerPacket(ProtocolInfo.MAP_CREATE_LOCKED_COPY_PACKET, MapCreateLockedCopyPacket.class); - this.registerPacket(ProtocolInfo.EMOTE_PACKET, EmotePacket.class); - this.registerPacket(ProtocolInfo.ON_SCREEN_TEXTURE_ANIMATION_PACKET, OnScreenTextureAnimationPacket.class); - this.registerPacket(ProtocolInfo.COMPLETED_USING_ITEM_PACKET, CompletedUsingItemPacket.class); this.registerPacket(ProtocolInfo.NETWORK_SETTINGS_PACKET, NetworkSettingsPacket.class); - this.registerPacket(ProtocolInfo.CODE_BUILDER_PACKET, CodeBuilderPacket.class); this.registerPacket(ProtocolInfo.PLAYER_AUTH_INPUT_PACKET, PlayerAuthInputPacket.class); this.registerPacket(ProtocolInfo.CREATIVE_CONTENT_PACKET, CreativeContentPacket.class); - this.registerPacket(ProtocolInfo.DEBUG_INFO_PACKET, DebugInfoPacket.class); - this.registerPacket(ProtocolInfo.EMOTE_LIST_PACKET, EmoteListPacket.class); this.registerPacket(ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET, PacketViolationWarningPacket.class); - this.registerPacket(ProtocolInfo.PLAYER_ARMOR_DAMAGE_PACKET, PlayerArmorDamagePacket.class); - this.registerPacket(ProtocolInfo.PLAYER_ENCHANT_OPTIONS_PACKET, PlayerEnchantOptionsPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_PLAYER_GAME_TYPE_PACKET, UpdatePlayerGameTypePacket.class); this.registerPacket(ProtocolInfo.UPDATE_ABILITIES_PACKET, UpdateAbilitiesPacket.class); this.registerPacket(ProtocolInfo.REQUEST_ABILITY_PACKET, RequestAbilityPacket.class); this.registerPacket(ProtocolInfo.UPDATE_ADVENTURE_SETTINGS_PACKET, UpdateAdventureSettingsPacket.class); + this.registerPacket(ProtocolInfo.EMOTE_PACKET, EmotePacket.class); this.registerPacket(ProtocolInfo.FILTER_TEXT_PACKET, FilterTextPacket.class); this.registerPacket(ProtocolInfo.TOAST_REQUEST_PACKET, ToastRequestPacket.class); + this.registerPacket(ProtocolInfo.DEATH_INFO_PACKET, DeathInfoPacket.class); this.registerPacket(ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET, RequestNetworkSettingsPacket.class); + this.registerPacket(ProtocolInfo.SERVER_TO_CLIENT_HANDSHAKE_PACKET, ServerToClientHandshakePacket.class); this.registerPacket(ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET, ClientToServerHandshakePacket.class); + this.registerPacket(ProtocolInfo.REQUEST_PERMISSIONS_PACKET, RequestPermissionsPacket.class); + this.registerPacket(ProtocolInfo.SET_DEFAULT_GAME_TYPE_PACKET, SetDefaultGameTypePacket.class); + this.registerPacket(ProtocolInfo.SETTINGS_COMMAND_PACKET, SettingsCommandPacket.class); } } diff --git a/src/main/java/cn/nukkit/network/RakNetInterface.java b/src/main/java/cn/nukkit/network/RakNetInterface.java index d8a6015b96e..9c6711b77a6 100644 --- a/src/main/java/cn/nukkit/network/RakNetInterface.java +++ b/src/main/java/cn/nukkit/network/RakNetInterface.java @@ -25,27 +25,25 @@ import java.util.concurrent.TimeUnit; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @Log4j2 public class RakNetInterface implements RakNetServerListener, AdvancedSourceInterface { private final Server server; - private Network network; private final RakNetServer raknet; + private Network network; + + private byte[] advertisement; private final Map sessions = new HashMap<>(); private final Queue sessionCreationQueue = PlatformDependent.newMpscQueue(); - - private byte[] advertisement; - public RakNetInterface(Server server) { this.server = server; - InetSocketAddress bindAddress = new InetSocketAddress(Strings.isNullOrEmpty(this.server.getIp()) ? "0.0.0.0" : this.server.getIp(), this.server.getPort()); - this.raknet = new RakNetServer(bindAddress, Runtime.getRuntime().availableProcessors()); + this.raknet = new RakNetServer(new InetSocketAddress(Strings.isNullOrEmpty(this.server.getIp()) ? "0.0.0.0" : this.server.getIp(), this.server.getPort()), Runtime.getRuntime().availableProcessors()); this.raknet.setProtocolVersion(11); this.raknet.bind().join(); this.raknet.setListener(this); @@ -69,11 +67,11 @@ public boolean process() { Constructor constructor = event.getPlayerClass().getConstructor(SourceInterface.class, Long.class, InetSocketAddress.class); Player player = constructor.newInstance(this, event.getClientId(), event.getSocketAddress()); - this.server.addPlayer(address, player); session.setPlayer(player); + this.server.addPlayer(address, player); } catch (Exception e) { - Server.getInstance().getLogger().error("Failed to create player", e); - session.disconnect("Internal error"); + Server.getInstance().getLogger().error("Failed to create Player", e); + session.disconnect("Internal Server Error"); this.sessions.remove(address); } } @@ -151,7 +149,7 @@ public void sendRawPacket(InetSocketAddress socketAddress, ByteBuf payload) { @Override public void setName(String name) { QueryRegenerateEvent info = this.server.getQueryInformation(); - String[] names = name.split("!@#"); //Split double names within the program + String[] names = name.split("!@#"); // Split double names within the program String motd = Utils.rtrim(names[0].replace(";", "\\;"), '\\'); String subMotd = names.length > 1 ? Utils.rtrim(names[1].replace(";", "\\;"), '\\') : ""; StringJoiner joiner = new StringJoiner(";") @@ -223,4 +221,4 @@ public void onUnhandledDatagram(ChannelHandlerContext ctx, DatagramPacket datagr public Network getNetwork() { return this.network; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/SourceInterface.java b/src/main/java/cn/nukkit/network/SourceInterface.java index 3b29a763841..ac5cea7da53 100644 --- a/src/main/java/cn/nukkit/network/SourceInterface.java +++ b/src/main/java/cn/nukkit/network/SourceInterface.java @@ -8,24 +8,27 @@ /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface SourceInterface { @Deprecated default Integer putPacket(Player player, DataPacket packet) { - throw new UnsupportedOperationException("This method is deprecated"); + player.getNetworkSession().sendPacket(packet); + return null; } @Deprecated default Integer putPacket(Player player, DataPacket packet, boolean needACK) { - throw new UnsupportedOperationException("This method is deprecated"); + player.getNetworkSession().sendPacket(packet); + return null; } @Deprecated default Integer putPacket(Player player, DataPacket packet, boolean needACK, boolean immediate) { - throw new UnsupportedOperationException("This method is deprecated"); + player.getNetworkSession().sendPacket(packet); + return null; } NetworkPlayerSession getSession(InetSocketAddress address); diff --git a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java index a52ca45877c..f95d3f4f385 100644 --- a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java +++ b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java @@ -156,7 +156,7 @@ public static boolean verifyChain(JSONArray chain) throws JOSEException, ParseEx return !iterator.hasNext(); } - if (lastKey.equals(EncryptionUtils.getMojangPublicKey())) { + if (lastKey.equals(EncryptionUtils.MOJANG_PUBLIC_KEY)) { validChain = true; } diff --git a/src/main/java/cn/nukkit/network/protocol/AddBehaviorTreePacket.java b/src/main/java/cn/nukkit/network/protocol/AddBehaviorTreePacket.java index 59eb42d690b..e2005c16e83 100644 --- a/src/main/java/cn/nukkit/network/protocol/AddBehaviorTreePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AddBehaviorTreePacket.java @@ -5,21 +5,23 @@ @ToString public class AddBehaviorTreePacket extends DataPacket { - public String unknown; + public static final byte NETWORK_ID = ProtocolInfo.ADD_BEHAVIOR_TREE_PACKET; + + public String behaviorTreeJson; @Override public byte pid() { - return ProtocolInfo.ADD_BEHAVIOR_TREE_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.behaviorTreeJson = this.getString(); } @Override public void encode() { this.reset(); - this.putString(unknown); + this.putString(behaviorTreeJson); } } diff --git a/src/main/java/cn/nukkit/network/protocol/AddEntityPacket.java b/src/main/java/cn/nukkit/network/protocol/AddEntityPacket.java index 2a416621075..bc81c0c99b1 100644 --- a/src/main/java/cn/nukkit/network/protocol/AddEntityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AddEntityPacket.java @@ -1,6 +1,8 @@ package cn.nukkit.network.protocol; import cn.nukkit.entity.Attribute; +import cn.nukkit.entity.custom.EntityDefinition; +import cn.nukkit.entity.custom.EntityManager; import cn.nukkit.entity.data.EntityMetadata; import cn.nukkit.entity.item.*; import cn.nukkit.entity.mob.*; @@ -13,14 +15,15 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class AddEntityPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ADD_ENTITY_PACKET; - public static ImmutableMap LEGACY_IDS = ImmutableMap.builder() + public static final ImmutableMap LEGACY_IDS = ImmutableMap.builder() .put(51, "minecraft:npc") .put(63, "minecraft:player") .put(EntityWitherSkeleton.NETWORK_ID, "minecraft:wither_skeleton") @@ -60,9 +63,9 @@ public class AddEntityPacket extends DataPacket { .put(EntityChicken.NETWORK_ID, "minecraft:chicken") .put(107, "minecraft:balloon") .put(EntityLlama.NETWORK_ID, "minecraft:llama") - .put(20, "minecraft:iron_golem") + .put(EntityIronGolem.NETWORK_ID, "minecraft:iron_golem") .put(EntityRabbit.NETWORK_ID, "minecraft:rabbit") - .put(21, "minecraft:snow_golem") + .put(EntitySnowGolem.NETWORK_ID, "minecraft:snow_golem") .put(EntityBat.NETWORK_ID, "minecraft:bat") .put(EntityOcelot.NETWORK_ID, "minecraft:ocelot") .put(EntityHorse.NETWORK_ID, "minecraft:horse") @@ -83,7 +86,7 @@ public class AddEntityPacket extends DataPacket { .put(EntityMinecartTNT.NETWORK_ID, "minecraft:tnt_minecart") .put(EntityMinecartChest.NETWORK_ID, "minecraft:chest_minecart") .put(100, "minecraft:command_block_minecart") - .put(61, "minecraft:armor_stand") + .put(EntityArmorStand.NETWORK_ID, "minecraft:armor_stand") .put(EntityItem.NETWORK_ID, "minecraft:item") .put(EntityPrimedTNT.NETWORK_ID, "minecraft:tnt") .put(EntityFallingBlock.NETWORK_ID, "minecraft:falling_block") @@ -109,8 +112,8 @@ public class AddEntityPacket extends DataPacket { .put(EntityLightning.NETWORK_ID, "minecraft:lightning_bolt") .put(94, "minecraft:small_fireball") .put(102, "minecraft:llama_spit") - .put(95, "minecraft:area_effect_cloud") - .put(101, "minecraft:lingering_potion") + .put(EntityAreaEffectCloud.NETWORK_ID, "minecraft:area_effect_cloud") + .put(EntityPotionLingering.NETWORK_ID, "minecraft:lingering_potion") .put(EntityFirework.NETWORK_ID, "minecraft:fireworks_rocket") .put(103, "minecraft:evocation_fang") .put(104, "minecraft:evocation_illager") @@ -138,7 +141,8 @@ public class AddEntityPacket extends DataPacket { .put(EntityFrog.NETWORK_ID, "minecraft:frog") .put(EntityTadpole.NETWORK_ID, "minecraft:tadpole") .put(EntityAllay.NETWORK_ID, "minecraft:allay") - .put(138, "minecraft:camel") + .put(EntityChestBoat.NETWORK_ID, "minecraft:chest_boat") + .put(EntityCamel.NETWORK_ID, "minecraft:camel") .build(); @Override @@ -166,7 +170,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -174,16 +178,13 @@ public void encode() { this.reset(); this.putEntityUniqueId(this.entityUniqueId); this.putEntityRuntimeId(this.entityRuntimeId); - if (id == null) { - id = LEGACY_IDS.get(type); - } - this.putString(this.id); + this.putString(this.getEntityIdentifier()); this.putVector3f(this.x, this.y, this.z); this.putVector3f(this.speedX, this.speedY, this.speedZ); this.putLFloat(this.pitch); this.putLFloat(this.yaw); this.putLFloat(this.headYaw); - this.putLFloat(this.bodyYaw == -1 ? this.yaw : this.bodyYaw); + this.putLFloat(this.bodyYaw == -1 ? this.yaw : this.bodyYaw); // For backwards compatibility this.putAttributeList(this.attributes); this.put(Binary.writeMetadata(this.metadata)); this.putUnsignedVarInt(0); // Entity properties int @@ -193,4 +194,25 @@ public void encode() { putEntityLink(link); } } + + private String getEntityIdentifier() { + if (this.id != null) { + return this.id; + } + + String identifier = LEGACY_IDS.get(type); + + if (identifier == null) { + // Maybe a custom entity + EntityDefinition definition = EntityManager.get().getDefinition(this.type); + if (definition != null) { + identifier = definition.getIdentifier(); + } + } + + if (identifier == null) { + throw new IllegalStateException("Unknown entity with network id " + this.type); + } + return identifier; + } } diff --git a/src/main/java/cn/nukkit/network/protocol/AddItemEntityPacket.java b/src/main/java/cn/nukkit/network/protocol/AddItemEntityPacket.java index b53ec71a9fd..8860508d329 100644 --- a/src/main/java/cn/nukkit/network/protocol/AddItemEntityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AddItemEntityPacket.java @@ -6,11 +6,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class AddItemEntityPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ADD_ITEM_ENTITY_PACKET; @Override @@ -32,7 +33,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/AddPaintingPacket.java b/src/main/java/cn/nukkit/network/protocol/AddPaintingPacket.java index 0101c62b3c5..59a494c0d68 100644 --- a/src/main/java/cn/nukkit/network/protocol/AddPaintingPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AddPaintingPacket.java @@ -20,7 +20,7 @@ public class AddPaintingPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -28,7 +28,6 @@ public void encode() { this.reset(); this.putEntityUniqueId(this.entityUniqueId); this.putEntityRuntimeId(this.entityRuntimeId); - this.putVector3f(this.x, this.y, this.z); this.putVarInt(this.direction); this.putString(this.title); @@ -38,5 +37,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/AddPlayerPacket.java b/src/main/java/cn/nukkit/network/protocol/AddPlayerPacket.java index e11053b5e23..6b94c09bb44 100644 --- a/src/main/java/cn/nukkit/network/protocol/AddPlayerPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AddPlayerPacket.java @@ -9,11 +9,12 @@ import java.util.UUID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class AddPlayerPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ADD_PLAYER_PACKET; @Override @@ -34,16 +35,16 @@ public byte pid() { public float speedZ; public float pitch; public float yaw; + public float headYaw = -1; public Item item; public int gameType = Server.getInstance().getGamemode(); public EntityMetadata metadata = new EntityMetadata(); - //public EntityLink links = new EntityLink[0]; public String deviceId = ""; public int buildPlatform = -1; @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -51,29 +52,28 @@ public void encode() { this.reset(); this.putUUID(this.uuid); this.putString(this.username); - // this.putEntityUniqueId(this.entityUniqueId); this.putEntityRuntimeId(this.entityRuntimeId); this.putString(this.platformChatId); this.putVector3f(this.x, this.y, this.z); this.putVector3f(this.speedX, this.speedY, this.speedZ); this.putLFloat(this.pitch); - this.putLFloat(this.yaw); //TODO headrot this.putLFloat(this.yaw); + this.putLFloat(this.headYaw == -1 ? this.yaw : this.headYaw); this.putSlot(this.item); this.putVarInt(this.gameType); this.put(Binary.writeMetadata(this.metadata)); this.putUnsignedVarInt(0); // Entity properties int this.putUnsignedVarInt(0); // Entity properties float this.putLLong(entityUniqueId); - this.putUnsignedVarInt(0); // playerPermission - this.putUnsignedVarInt(0); // commandPermission - this.putUnsignedVarInt(1); // abilitiesLayer size - this.putLShort(1); // BASE layer type - this.putLInt(262143); // abilitiesSet - all abilities - this.putLInt(63); // abilityValues - survival abilities - this.putLFloat(0.1f); // flySpeed - this.putLFloat(0.05f); // walkSpeed - this.putUnsignedVarInt(0); //TODO: Entity links + this.putUnsignedVarInt(0); // getPlayerPermission().ordinal() + this.putUnsignedVarInt(0); // getCommandPermission().ordinal() + this.putUnsignedVarInt(1); // getAbilityLayers().size() + this.putLShort(1); // getLayerType().ordinal() == BASE + this.putLInt(262143); // getAbilitiesSet() + this.putLInt(63); // getAbilityValues() + this.putLFloat(0.1f); // getFlySpeed() + this.putLFloat(0.05f); // getWalkSpeed() + this.putUnsignedVarInt(0); // Entity links this.putString(deviceId); this.putLInt(buildPlatform); } diff --git a/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java b/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java index 347275f7ac0..5ec76e5c5eb 100644 --- a/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java @@ -7,7 +7,6 @@ * @author Nukkit Project Team */ @ToString -@Deprecated public class AdventureSettingsPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.ADVENTURE_SETTINGS_PACKET; @@ -35,7 +34,6 @@ public class AdventureSettingsPacket extends DataPacket { public static final int WORLD_BUILDER = 0x100; public static final int FLYING = 0x200; public static final int MUTED = 0x400; - public static final int MINE = 0x01 | BITFLAG_SECOND_SET; public static final int DOORS_AND_SWITCHES = 0x02 | BITFLAG_SECOND_SET; public static final int OPEN_CONTAINERS = 0x04 | BITFLAG_SECOND_SET; @@ -47,17 +45,15 @@ public class AdventureSettingsPacket extends DataPacket { public static final int DEFAULT_LEVEL_PERMISSIONS = 0x200 | BITFLAG_SECOND_SET; public long flags = 0; - - public long commandPermission = PERMISSION_NORMAL; - public long flags2 = 0; + public long customFlags; + public long commandPermission = PERMISSION_NORMAL; public long playerPermission = Player.PERMISSION_MEMBER; - public long customFlags; //... - public long entityUniqueId; //This is a little-endian long, NOT a var-long. (WTF Mojang) + @Override public void decode() { this.flags = getUnsignedVarInt(); this.commandPermission = getUnsignedVarInt(); @@ -67,6 +63,7 @@ public void decode() { this.entityUniqueId = getLLong(); } + @Override public void encode() { this.reset(); this.putUnsignedVarInt(this.flags); diff --git a/src/main/java/cn/nukkit/network/protocol/AnimateEntityPacket.java b/src/main/java/cn/nukkit/network/protocol/AnimateEntityPacket.java index 03e0ecf849d..6d467452771 100644 --- a/src/main/java/cn/nukkit/network/protocol/AnimateEntityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AnimateEntityPacket.java @@ -51,6 +51,7 @@ public class AnimateEntityPacket extends DataPacket { @Override public void decode() { + this.decodeUnsupported(); } @Override @@ -60,7 +61,9 @@ public void encode() { this.putString(this.animation); this.putString(this.nextState); this.putString(this.stopExpression); + this.putLInt(this.stopExpressionVersion); + this.putString(this.controller); this.putLFloat(this.blendOutTime); diff --git a/src/main/java/cn/nukkit/network/protocol/AnimatePacket.java b/src/main/java/cn/nukkit/network/protocol/AnimatePacket.java index 1cc3b18c404..09898e7cc3b 100644 --- a/src/main/java/cn/nukkit/network/protocol/AnimatePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AnimatePacket.java @@ -12,7 +12,6 @@ public class AnimatePacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.ANIMATE_PACKET; - public long eid; public Action action; public float rowingTime; diff --git a/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java b/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java index f03d8b886f0..bd65246bc89 100644 --- a/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AnvilDamagePacket.java @@ -6,6 +6,8 @@ @ToString public class AnvilDamagePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ANVIL_DAMAGE_PACKET; + public int damage; public int x; public int y; @@ -13,7 +15,7 @@ public class AnvilDamagePacket extends DataPacket { @Override public byte pid() { - return ProtocolInfo.ANVIL_DAMAGE_PACKET; + return NETWORK_ID; } @Override @@ -27,6 +29,6 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java b/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java index 4be1509d86f..22b829215cf 100644 --- a/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AvailableCommandsPacket.java @@ -8,7 +8,7 @@ import java.util.function.ObjIntConsumer; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString @@ -55,7 +55,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -131,7 +131,7 @@ public void encode() { this.putUnsignedVarInt(0); //subCommandData - putUnsignedVarInt(commands.size()); + this.putUnsignedVarInt(commands.size()); commands.forEach((name, cmdData) -> { CommandData data = cmdData.versions.get(0); @@ -170,12 +170,13 @@ public void encode() { putLInt(type); putBoolean(parameter.optional); - putByte(parameter.options); // TODO: 19/03/2019 Bit flags. Only first bit is used for GameRules. + putByte(parameter.options); } } }); this.putUnsignedVarInt(softEnums.size()); + softEnums.forEach((name, values) -> { this.putString(name); this.putUnsignedVarInt(values.size()); diff --git a/src/main/java/cn/nukkit/network/protocol/AvailableEntityIdentifiersPacket.java b/src/main/java/cn/nukkit/network/protocol/AvailableEntityIdentifiersPacket.java index 97b22f9b641..e075a86008d 100644 --- a/src/main/java/cn/nukkit/network/protocol/AvailableEntityIdentifiersPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AvailableEntityIdentifiersPacket.java @@ -1,32 +1,27 @@ package cn.nukkit.network.protocol; import cn.nukkit.Nukkit; +import cn.nukkit.entity.custom.EntityManager; import com.google.common.io.ByteStreams; -import lombok.ToString; import java.io.InputStream; -@ToString(exclude = {"tag"}) public class AvailableEntityIdentifiersPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.AVAILABLE_ENTITY_IDENTIFIERS_PACKET; - private static final byte[] TAG; + public static final byte[] TAG; static { try { InputStream inputStream = Nukkit.class.getClassLoader().getResourceAsStream("entity_identifiers.dat"); - if (inputStream == null) { - throw new AssertionError("Could not find entity_identifiers.dat"); - } - //noinspection UnstableApiUsage + if (inputStream == null) throw new AssertionError("Could not find entity_identifiers.dat"); TAG = ByteStreams.toByteArray(inputStream); } catch (Exception e) { throw new AssertionError("Error whilst loading entity_identifiers.dat", e); } } - public byte[] tag = TAG; - @Override public byte pid() { return NETWORK_ID; @@ -34,12 +29,12 @@ public byte pid() { @Override public void decode() { - this.tag = this.get(); + this.decodeUnsupported(); } @Override public void encode() { this.reset(); - this.put(this.tag); + this.put(EntityManager.get().getNetworkTagCached()); } } diff --git a/src/main/java/cn/nukkit/network/protocol/BatchPacket.java b/src/main/java/cn/nukkit/network/protocol/BatchPacket.java index 5e67c79ef79..fa739e6f371 100644 --- a/src/main/java/cn/nukkit/network/protocol/BatchPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BatchPacket.java @@ -1,10 +1,11 @@ package cn.nukkit.network.protocol; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BatchPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.BATCH_PACKET; public byte[] payload; @@ -21,7 +22,7 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } public void trim() { diff --git a/src/main/java/cn/nukkit/network/protocol/BiomeDefinitionListPacket.java b/src/main/java/cn/nukkit/network/protocol/BiomeDefinitionListPacket.java index 49727aa1750..d9bac2642b6 100644 --- a/src/main/java/cn/nukkit/network/protocol/BiomeDefinitionListPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BiomeDefinitionListPacket.java @@ -4,29 +4,21 @@ import com.google.common.io.ByteStreams; import lombok.ToString; -import java.io.InputStream; - -@ToString(exclude = "tag") +@ToString() public class BiomeDefinitionListPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.BIOME_DEFINITION_LIST_PACKET; - private static final byte[] TAG; + private static final byte[] TAG; // 554 static { try { - InputStream inputStream = Nukkit.class.getClassLoader().getResourceAsStream("biome_definitions.dat"); - if (inputStream == null) { - throw new AssertionError("Could not find biome_definitions.dat"); - } - //noinspection UnstableApiUsage - TAG = ByteStreams.toByteArray(inputStream); + TAG = ByteStreams.toByteArray(Nukkit.class.getClassLoader().getResourceAsStream("biome_definitions.dat")); } catch (Exception e) { throw new AssertionError("Error whilst loading biome_definitions.dat", e); } } - public byte[] tag = TAG; - @Override public byte pid() { return NETWORK_ID; @@ -34,11 +26,12 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override public void encode() { this.reset(); - this.put(tag); + this.put(TAG); } } diff --git a/src/main/java/cn/nukkit/network/protocol/BlockEntityDataPacket.java b/src/main/java/cn/nukkit/network/protocol/BlockEntityDataPacket.java index ea21643a338..ecfe75151ca 100644 --- a/src/main/java/cn/nukkit/network/protocol/BlockEntityDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BlockEntityDataPacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString(exclude = "namedTag") public class BlockEntityDataPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.BLOCK_ENTITY_DATA_PACKET; public int x; diff --git a/src/main/java/cn/nukkit/network/protocol/BlockEventPacket.java b/src/main/java/cn/nukkit/network/protocol/BlockEventPacket.java index 566603238b1..841cd323dcb 100644 --- a/src/main/java/cn/nukkit/network/protocol/BlockEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BlockEventPacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class BlockEventPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.BLOCK_EVENT_PACKET; @Override @@ -23,7 +24,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/BlockPickRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/BlockPickRequestPacket.java index 52f81f782c2..cd672f85e4f 100644 --- a/src/main/java/cn/nukkit/network/protocol/BlockPickRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BlockPickRequestPacket.java @@ -31,6 +31,6 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/BookEditPacket.java b/src/main/java/cn/nukkit/network/protocol/BookEditPacket.java index b9ec95c7048..d5cc0b1b26f 100644 --- a/src/main/java/cn/nukkit/network/protocol/BookEditPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BookEditPacket.java @@ -53,7 +53,7 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } public enum Action { diff --git a/src/main/java/cn/nukkit/network/protocol/BossEventPacket.java b/src/main/java/cn/nukkit/network/protocol/BossEventPacket.java index 0610691c25c..e8ee6b2133d 100644 --- a/src/main/java/cn/nukkit/network/protocol/BossEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BossEventPacket.java @@ -10,23 +10,25 @@ public class BossEventPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.BOSS_EVENT_PACKET; - /* S2C: Shows the bossbar to the player. */ + /** Shows the bossbar to the player. */ public static final int TYPE_SHOW = 0; - /* C2S: Registers a player to a boss fight. */ + /** Registers a player to a boss fight. */ public static final int TYPE_REGISTER_PLAYER = 1; + /** Not sure on this. */ public static final int TYPE_UPDATE = 1; - /* S2C: Removes the bossbar from the client. */ + /** Removes the bossbar from the client. */ public static final int TYPE_HIDE = 2; - /* C2S: Unregisters a player from a boss fight. */ + /** Unregisters a player from a boss fight. */ public static final int TYPE_UNREGISTER_PLAYER = 3; - /* S2C: Sets the bar percentage. */ + /** Sets the bar percentage. */ public static final int TYPE_HEALTH_PERCENT = 4; - /* S2C: Sets title of the bar. */ + /** Sets title of the bar. */ public static final int TYPE_TITLE = 5; - /* S2C: Not sure on this. Includes color and overlay fields, plus an unknown short. TODO: check this */ + /** Not sure on this. Includes color and overlay fields, plus an unknown short. */ public static final int TYPE_UPDATE_PROPERTIES = 6; - /* S2C: Sets color and overlay of the bar. */ + /** S2C: Sets color and overlay of the bar. **/ public static final int TYPE_TEXTURE = 7; + /** Unknown. Since 1.18.10 **/ public static final int TYPE_QUERY = 8; public long bossEid; diff --git a/src/main/java/cn/nukkit/network/protocol/CameraPacket.java b/src/main/java/cn/nukkit/network/protocol/CameraPacket.java index deb610056e7..f1b74a28d0d 100644 --- a/src/main/java/cn/nukkit/network/protocol/CameraPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CameraPacket.java @@ -5,18 +5,19 @@ @ToString public class CameraPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CAMERA_PACKET; + public long cameraUniqueId; public long playerUniqueId; @Override public byte pid() { - return ProtocolInfo.CAMERA_PACKET; + return NETWORK_ID; } @Override public void decode() { - this.cameraUniqueId = this.getVarLong(); - this.playerUniqueId = this.getVarLong(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ChangeDimensionPacket.java b/src/main/java/cn/nukkit/network/protocol/ChangeDimensionPacket.java index ea92e19cb06..734435bbe37 100644 --- a/src/main/java/cn/nukkit/network/protocol/ChangeDimensionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ChangeDimensionPacket.java @@ -21,7 +21,7 @@ public class ChangeDimensionPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java b/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java index f17e058b561..13a4b4956ba 100644 --- a/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java @@ -3,7 +3,7 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString @@ -15,7 +15,7 @@ public class ChunkRadiusUpdatedPacket extends DataPacket { @Override public void decode() { - this.radius = this.getVarInt(); + this.decodeUnsupported(); } @Override @@ -28,5 +28,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/ClientCacheStatusPacket.java b/src/main/java/cn/nukkit/network/protocol/ClientCacheStatusPacket.java index 45cdc00aa31..49c9ef520fa 100644 --- a/src/main/java/cn/nukkit/network/protocol/ClientCacheStatusPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ClientCacheStatusPacket.java @@ -1,6 +1,10 @@ package cn.nukkit.network.protocol; +import lombok.ToString; + +@ToString public class ClientCacheStatusPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CLIENT_CACHE_STATUS_PACKET; public boolean supported; diff --git a/src/main/java/cn/nukkit/network/protocol/ClientToServerHandshakePacket.java b/src/main/java/cn/nukkit/network/protocol/ClientToServerHandshakePacket.java index 3e8689fad9e..f5907b67868 100644 --- a/src/main/java/cn/nukkit/network/protocol/ClientToServerHandshakePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ClientToServerHandshakePacket.java @@ -5,18 +5,20 @@ @ToString public class ClientToServerHandshakePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET; + @Override public byte pid() { - return ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET; + return NETWORK_ID; } @Override public void decode() { - //no content + // No payload } @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java b/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java index 730f1fde8cd..60be9eeadbf 100644 --- a/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java @@ -15,7 +15,7 @@ public class ClientboundMapItemDataPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.CLIENTBOUND_MAP_ITEM_DATA_PACKET; - public int[] eids = new int[0]; + public long[] eids = new long[0]; public long mapId; public int update; @@ -45,7 +45,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -68,11 +68,12 @@ public void encode() { this.putUnsignedVarInt(update); this.putByte(this.dimensionId); this.putBoolean(this.isLocked); - this.putBlockVector3(this.origin); + + this.putSignedBlockPosition(origin); if ((update & ENTITIES_UPDATE) != 0) { this.putUnsignedVarInt(eids.length); - for (int eid : eids) { + for (long eid : eids) { this.putEntityUniqueId(eid); } } @@ -101,7 +102,7 @@ public void encode() { this.putByte(decorator.offsetX); this.putByte(decorator.offsetZ); this.putString(decorator.label); - this.putUnsignedVarInt(decorator.color.getRGB()); + this.putUnsignedVarInt(decorator.color.getRGB()); //toABGR? } } @@ -123,7 +124,7 @@ public void encode() { image.flush(); } else if (colors.length > 0) { for (int color : colors) { - this.putUnsignedVarInt(color); + this.putUnsignedVarInt(color); //toABGR? } } } diff --git a/src/main/java/cn/nukkit/network/protocol/CodeBuilderPacket.java b/src/main/java/cn/nukkit/network/protocol/CodeBuilderPacket.java index a7289d26430..d01c477a3f5 100644 --- a/src/main/java/cn/nukkit/network/protocol/CodeBuilderPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CodeBuilderPacket.java @@ -4,6 +4,7 @@ @ToString public class CodeBuilderPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CODE_BUILDER_PACKET; public boolean isOpening; @@ -16,8 +17,7 @@ public byte pid() { @Override public void decode() { - this.url = this.getString(); - this.isOpening = this.getBoolean(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/CommandBlockUpdatePacket.java b/src/main/java/cn/nukkit/network/protocol/CommandBlockUpdatePacket.java index 4d5e579eb38..faca28de079 100644 --- a/src/main/java/cn/nukkit/network/protocol/CommandBlockUpdatePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CommandBlockUpdatePacket.java @@ -6,6 +6,8 @@ @ToString public class CommandBlockUpdatePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.COMMAND_BLOCK_UPDATE_PACKET; + public boolean isBlock; public int x; public int y; @@ -21,7 +23,7 @@ public class CommandBlockUpdatePacket extends DataPacket { @Override public byte pid() { - return ProtocolInfo.COMMAND_BLOCK_UPDATE_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/CommandRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/CommandRequestPacket.java index 4dc10ad1a23..f887a0b5c28 100644 --- a/src/main/java/cn/nukkit/network/protocol/CommandRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CommandRequestPacket.java @@ -6,7 +6,7 @@ import java.util.UUID; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString @@ -28,6 +28,7 @@ public class CommandRequestPacket extends DataPacket { public String command; public CommandOriginData data; + public boolean internal; @Override public byte pid() { @@ -46,10 +47,12 @@ public void decode() { varLong = this.getVarLong(); } this.data = new CommandOriginData(type, uuid, requestId, varLong); + this.internal = this.getBoolean(); + this.getVarInt(); // version } @Override public void encode() { + this.encodeUnsupported(); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/CompletedUsingItemPacket.java b/src/main/java/cn/nukkit/network/protocol/CompletedUsingItemPacket.java index 8f34df797d2..433817dcaf6 100644 --- a/src/main/java/cn/nukkit/network/protocol/CompletedUsingItemPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CompletedUsingItemPacket.java @@ -27,7 +27,6 @@ public class CompletedUsingItemPacket extends DataPacket { public int itemId; public int action; - @Override public byte pid() { return NETWORK_ID; @@ -35,6 +34,7 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java b/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java index 6d8d96c0a80..fbe09f244b3 100644 --- a/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ContainerClosePacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class ContainerClosePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CONTAINER_CLOSE_PACKET; @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ContainerOpenPacket.java b/src/main/java/cn/nukkit/network/protocol/ContainerOpenPacket.java index 557dc71f156..0f2f3002ffc 100644 --- a/src/main/java/cn/nukkit/network/protocol/ContainerOpenPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ContainerOpenPacket.java @@ -1,14 +1,14 @@ package cn.nukkit.network.protocol; -import cn.nukkit.math.BlockVector3; import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class ContainerOpenPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CONTAINER_OPEN_PACKET; @Override @@ -25,13 +25,7 @@ public byte pid() { @Override public void decode() { - this.windowId = this.getByte(); - this.type = this.getByte(); - BlockVector3 v = this.getBlockVector3(); - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.entityId = this.getEntityUniqueId(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ContainerSetDataPacket.java b/src/main/java/cn/nukkit/network/protocol/ContainerSetDataPacket.java index 087fbcbf5b6..9a811166844 100644 --- a/src/main/java/cn/nukkit/network/protocol/ContainerSetDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ContainerSetDataPacket.java @@ -3,17 +3,18 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class ContainerSetDataPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CONTAINER_SET_DATA_PACKET; public static final int PROPERTY_FURNACE_TICK_COUNT = 0; public static final int PROPERTY_FURNACE_LIT_TIME = 1; public static final int PROPERTY_FURNACE_LIT_DURATION = 2; - //TODO: check property 3 + public static final int PROPERTY_UNKNOWN = 3; public static final int PROPERTY_FURNACE_FUEL_AUX = 4; public static final int PROPERTY_BREWING_STAND_BREW_TIME = 0; @@ -31,7 +32,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java b/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java index 0a324b1f59b..9873c3339fb 100644 --- a/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CraftingDataPacket.java @@ -23,11 +23,12 @@ public class CraftingDataPacket extends DataPacket { public static final String CRAFTING_TAG_CAMPFIRE = "campfire"; public static final String CRAFTING_TAG_BLAST_FURNACE = "blast_furnace"; public static final String CRAFTING_TAG_SMOKER = "smoker"; + public static final String CRAFTING_TAG_SMITHING_TABLE = "smithing_table"; private List entries = new ArrayList<>(); private final List brewingEntries = new ArrayList<>(); private final List containerEntries = new ArrayList<>(); - public boolean cleanRecipes; + public boolean cleanRecipes = true; public void addShapelessRecipe(ShapelessRecipe... recipe) { Collections.addAll(entries, recipe); @@ -41,14 +42,14 @@ public void addFurnaceRecipe(FurnaceRecipe... recipe) { Collections.addAll(entries, recipe); } - public void addMultiRecipe(MultiRecipe... recipe) { - Collections.addAll(entries, recipe); - } - public void addBrewingRecipe(BrewingRecipe... recipe) { Collections.addAll(brewingEntries, recipe); } + public void addMultiRecipe(MultiRecipe... recipe) { + Collections.addAll(entries, recipe); + } + public void addContainerRecipe(ContainerRecipe... recipe) { Collections.addAll(containerEntries, recipe); } @@ -61,7 +62,7 @@ public DataPacket clean() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -69,10 +70,13 @@ public void encode() { this.reset(); this.putUnsignedVarInt(entries.size()); - int recipeNetworkId = 1; - for (Recipe recipe : entries) { - this.putVarInt(recipe.getType().ordinal()); + RecipeType networkType = recipe.getType(); + if (networkType == RecipeType.SMITHING_TRANSFORM) { + networkType = RecipeType.REPAIR; + } + this.putVarInt(networkType.ordinal()); + switch (recipe.getType()) { case SHAPELESS: ShapelessRecipe shapeless = (ShapelessRecipe) recipe; @@ -87,7 +91,7 @@ public void encode() { this.putUUID(shapeless.getId()); this.putString(CRAFTING_TAG_CRAFTING_TABLE); this.putVarInt(shapeless.getPriority()); - this.putUnsignedVarInt(recipeNetworkId++); + this.putUnsignedVarInt(shapeless.getNetworkId()); break; case SHAPED: ShapedRecipe shaped = (ShapedRecipe) recipe; @@ -110,7 +114,7 @@ public void encode() { this.putUUID(shaped.getId()); this.putString(CRAFTING_TAG_CRAFTING_TABLE); this.putVarInt(shaped.getPriority()); - this.putUnsignedVarInt(recipeNetworkId++); + this.putUnsignedVarInt(shaped.getNetworkId()); break; case FURNACE: case FURNACE_DATA: @@ -125,7 +129,29 @@ public void encode() { break; case MULTI: this.putUUID(((MultiRecipe) recipe).getId()); - this.putUnsignedVarInt(recipeNetworkId++); + this.putUnsignedVarInt(((MultiRecipe) recipe).getNetworkId()); + break; + case SHULKER_BOX: + break; + case SHAPELESS_CHEMISTRY: + break; + case SHAPED_CHEMISTRY: + break; + case REPAIR: + break; + case CAMPFIRE: + break; + case CAMPFIRE_DATA: + break; + case SMITHING_TRANSFORM: + SmithingRecipe smithing = (SmithingRecipe) recipe; + this.putString(smithing.getRecipeId()); + this.putRecipeIngredient(Item.get(Item.AIR)); //template + this.putRecipeIngredient(smithing.getEquipment()); + this.putRecipeIngredient(smithing.getIngredient()); + this.putSlot(smithing.getResult(), true); + this.putString(CRAFTING_TAG_SMITHING_TABLE); + this.putUnsignedVarInt(smithing.getNetworkId()); break; } } @@ -156,5 +182,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java b/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java index 54b351fe739..8dfa5457f3f 100644 --- a/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java @@ -1,7 +1,7 @@ package cn.nukkit.network.protocol; + import cn.nukkit.item.Item; -import cn.nukkit.utils.BinaryStream; import lombok.ToString; import java.util.UUID; @@ -34,18 +34,26 @@ public void decode() { this.type = (int) this.getUnsignedVarInt(); this.id = this.getUUID(); - this.input = this.getArray(Item.class, BinaryStream::getSlot); - this.output = this.getArray(Item.class, BinaryStream::getSlot); + int inputSize = (int) this.getUnsignedVarInt(); + this.input = new Item[Math.min(inputSize, 128)]; + for (int i = 0; i < this.input.length; ++i) { + this.input[i] = this.getSlot(); + } + + int outputSize = (int) this.getUnsignedVarInt(); + this.output = new Item[Math.min(outputSize, 128)]; + for (int i = 0; i < this.output.length; ++i) { + this.output[i] = this.getSlot(); + } } @Override public void encode() { - + this.encodeUnsupported(); } @Override public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java b/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java index efc9847b8a2..a2649a36157 100644 --- a/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CreativeContentPacket.java @@ -5,9 +5,10 @@ @ToString public class CreativeContentPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.CREATIVE_CONTENT_PACKET; - public Item[] entries = new Item[0]; + public Item[] entries; @Override public byte pid() { @@ -16,16 +17,17 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { this.reset(); this.putUnsignedVarInt(entries.length); - for (int i = 0; i < entries.length; i++) { - this.putUnsignedVarInt(i + 1); - this.putSlot(entries[i], true); + int i = 1; //HACK around since 0 is not indexed by client + for (Item entry : entries) { + this.putUnsignedVarInt(i++); + this.putSlot(entry, true); } } } diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index 3bebf44a984..c45145917f5 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -1,19 +1,25 @@ package cn.nukkit.network.protocol; +import cn.nukkit.Nukkit; import cn.nukkit.Server; import cn.nukkit.network.Network; import cn.nukkit.utils.BinaryStream; +import cn.nukkit.utils.SnappyCompression; +import cn.nukkit.utils.Zlib; import com.nukkitx.network.raknet.RakNetReliability; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class DataPacket extends BinaryStream implements Cloneable { public volatile boolean isEncoded = false; - private int channel = 0; + @Deprecated + private int channel = Network.CHANNEL_NONE; + + @Deprecated public RakNetReliability reliability = RakNetReliability.RELIABLE_ORDERED; public abstract byte pid(); @@ -22,31 +28,24 @@ public abstract class DataPacket extends BinaryStream implements Cloneable { public abstract void encode(); - public final void tryEncode() { - if (!this.isEncoded) { - this.isEncoded = true; - this.encode(); - } - } - @Override public DataPacket reset() { super.reset(); - byte packetId = this.pid(); if (packetId < 0 && packetId >= -56) { // Hack: (byte) 200+ --> (int) 300+ this.putUnsignedVarInt(packetId + 356); } else { this.putUnsignedVarInt(packetId & 0xff); } - return this; } + @Deprecated public void setChannel(int channel) { this.channel = channel; } + @Deprecated public int getChannel() { return channel; } @@ -62,7 +61,7 @@ public DataPacket clean() { public DataPacket clone() { try { DataPacket packet = (DataPacket) super.clone(); - packet.setBuffer(this.getBuffer()); // prevent reflecting same buffer instance + packet.setBuffer(this.count < 0 ? null : this.getBuffer()); // prevent reflecting same buffer instance packet.offset = this.offset; packet.count = this.count; return packet; @@ -72,20 +71,45 @@ public DataPacket clone() { } public BatchPacket compress() { - return compress(Server.getInstance().networkCompressionLevel); + return this.compress(Server.getInstance().networkCompressionLevel); } public BatchPacket compress(int level) { - BinaryStream stream = new BinaryStream(); byte[] buf = this.getBuffer(); + BinaryStream stream = new BinaryStream(new byte[5 + buf.length]).reset(); stream.putUnsignedVarInt(buf.length); stream.put(buf); try { + byte[] bytes = stream.getBuffer(); BatchPacket batched = new BatchPacket(); - batched.payload = Network.deflateRaw(stream.getBuffer(), level); + if (Server.getInstance().useSnappy) { + batched.payload = SnappyCompression.compress(bytes); + } else { + batched.payload = Zlib.deflateRaw(bytes, level); + } return batched; } catch (Exception e) { throw new RuntimeException(e); } } + + public final void tryEncode() { + if (!this.isEncoded) { + this.isEncoded = true; + this.encode(); + } + } + + void decodeUnsupported() { + if (Nukkit.DEBUG > 1) { + Server.getInstance().getLogger().debug("Warning: decode() not implemented for " + this.getClass().getName()); + } + } + + void encodeUnsupported() { + if (Nukkit.DEBUG > 1) { + Server.getInstance().getLogger().debug("Warning: encode() not implemented for " + this.getClass().getName()); + Thread.dumpStack(); + } + } } diff --git a/src/main/java/cn/nukkit/network/protocol/DeathInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/DeathInfoPacket.java index 092347e01dd..74e1ba3111f 100644 --- a/src/main/java/cn/nukkit/network/protocol/DeathInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DeathInfoPacket.java @@ -14,6 +14,7 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/DebugInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/DebugInfoPacket.java index a5bb7aecdf4..9bcb6d535df 100644 --- a/src/main/java/cn/nukkit/network/protocol/DebugInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DebugInfoPacket.java @@ -1,9 +1,7 @@ package cn.nukkit.network.protocol; -import lombok.ToString; - -@ToString public class DebugInfoPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.DEBUG_INFO_PACKET; public long entityId; diff --git a/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java new file mode 100644 index 00000000000..9975fb2621c --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java @@ -0,0 +1,43 @@ +package cn.nukkit.network.protocol; + +import cn.nukkit.network.protocol.types.DimensionDefinition; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.ToString; + +import java.util.List; + +@ToString +public class DimensionDataPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.DIMENSION_DATA_PACKET; + + private static final List DEFAULT_DEFINITIONS = new ObjectArrayList() { + { + add(new DimensionDefinition("minecraft:overworld", 319, -64, 1)); + } + }; + + public List definitions = DEFAULT_DEFINITIONS; + + @Override + public byte pid() { + return NETWORK_ID; + } + + @Override + public void decode() { + this.decodeUnsupported(); + } + + @Override + public void encode() { + this.reset(); + this.putUnsignedVarInt(definitions.size()); + for (DimensionDefinition definition : definitions) { + this.putString(definition.getId()); + this.putVarInt(definition.getMaximumHeight()); + this.putVarInt(definition.getMinimumHeight()); + this.putVarInt(definition.getGeneratorType()); + } + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/DisconnectPacket.java b/src/main/java/cn/nukkit/network/protocol/DisconnectPacket.java index 33eed39497b..adaeff3d361 100644 --- a/src/main/java/cn/nukkit/network/protocol/DisconnectPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DisconnectPacket.java @@ -2,11 +2,9 @@ import lombok.ToString; -/** - * Created by on 15-10-12. - */ @ToString public class DisconnectPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.DISCONNECT_PACKET; public boolean hideDisconnectionScreen = false; @@ -33,6 +31,4 @@ public void encode() { this.putString(this.message); } } - - } diff --git a/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java b/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java index ba49ea4c280..0c0884bab72 100644 --- a/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EmoteListPacket.java @@ -8,6 +8,7 @@ @ToString public class EmoteListPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.EMOTE_LIST_PACKET; public long runtimeId; @@ -22,6 +23,9 @@ public byte pid() { public void decode() { this.runtimeId = this.getEntityRuntimeId(); int size = (int) this.getUnsignedVarInt(); + if (size > 1000) { + throw new RuntimeException("Too big EmoteListPacket: " + size); + } for (int i = 0; i < size; i++) { UUID id = this.getUUID(); pieceIds.add(id); diff --git a/src/main/java/cn/nukkit/network/protocol/EmotePacket.java b/src/main/java/cn/nukkit/network/protocol/EmotePacket.java index 7a563e7d8fb..55b7fc6e9c0 100644 --- a/src/main/java/cn/nukkit/network/protocol/EmotePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EmotePacket.java @@ -4,10 +4,12 @@ @ToString public class EmotePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.EMOTE_PACKET; + public long runtimeId; - public String xuid; - public String platformId; + public String xuid = ""; + public String platformId = ""; public String emoteID; public byte flags; diff --git a/src/main/java/cn/nukkit/network/protocol/EntityEventPacket.java b/src/main/java/cn/nukkit/network/protocol/EntityEventPacket.java index 6fccf347d67..cbde32c5b6a 100644 --- a/src/main/java/cn/nukkit/network/protocol/EntityEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EntityEventPacket.java @@ -3,12 +3,13 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class EntityEventPacket extends DataPacket { - public static final int NETWORK_ID = ProtocolInfo.ENTITY_EVENT_PACKET; + + public static final byte NETWORK_ID = ProtocolInfo.ENTITY_EVENT_PACKET; public static final int JUMP = 1; public static final int HURT_ANIMATION = 2; @@ -49,9 +50,7 @@ public class EntityEventPacket extends DataPacket { public static final int ENDER_DRAGON_DEATH = 37; public static final int DUST_PARTICLES = 38; public static final int ARROW_SHAKE = 39; - public static final int EATING_ITEM = 57; - public static final int BABY_ANIMAL_FEED = 60; public static final int DEATH_SMOKE_CLOUD = 61; public static final int COMPLETE_TRADE = 62; @@ -79,7 +78,7 @@ public byte pid() { public long eid; public int event; - public int data; + public int data = 0; @Override public void decode() { diff --git a/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java b/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java index 611002c32a9..0c415501069 100644 --- a/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java @@ -4,22 +4,23 @@ @ToString public class EntityFallPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ENTITY_FALL_PACKET; public long eid; public float fallDistance; - public boolean unknown; + public boolean isInVoid; @Override public void decode() { this.eid = this.getEntityRuntimeId(); this.fallDistance = this.getLFloat(); - this.unknown = this.getBoolean(); + this.isInVoid = this.getBoolean(); } @Override public void encode() { - + this.encodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/EntityPickRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/EntityPickRequestPacket.java index 952e9f12a59..8e7c8ce22ab 100644 --- a/src/main/java/cn/nukkit/network/protocol/EntityPickRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EntityPickRequestPacket.java @@ -14,11 +14,11 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { - //TODO + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/EventPacket.java b/src/main/java/cn/nukkit/network/protocol/EventPacket.java index 1da0f376a8c..07ebcc89b5a 100644 --- a/src/main/java/cn/nukkit/network/protocol/EventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EventPacket.java @@ -5,25 +5,53 @@ @ToString public class EventPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.EVENT_PACKET; + public long eid; - public int unknown1; - public byte unknown2; + public int eventData; + public byte eventType; + + public static final int TYPE_ACHIEVEMENT_AWARDED = 0; + public static final int TYPE_ENTITY_INTERACT = 1; + public static final int TYPE_PORTAL_BUILT = 2; + public static final int TYPE_PORTAL_USED = 3; + public static final int TYPE_MOB_KILLED = 4; + public static final int TYPE_CAULDRON_USED = 5; + public static final int TYPE_PLAYER_DEATH = 6; + public static final int TYPE_BOSS_KILLED = 7; + public static final int TYPE_AGENT_COMMAND = 8; + public static final int TYPE_AGENT_CREATED = 9; + public static final int TYPE_PATTERN_REMOVED = 10; + public static final int TYPE_COMMANED_EXECUTED = 11; + public static final int TYPE_FISH_BUCKETED = 12; + public static final int TYPE_MOB_BORN = 13; + public static final int TYPE_PET_DIED = 14; + public static final int TYPE_CAULDRON_BLOCK_USED = 15; + public static final int TYPE_COMPOSTER_BLOCK_USED = 16; + public static final int TYPE_BELL_BLOCK_USED = 17; + public static final int TYPE_ACTOR_DEFINITION = 18; + public static final int TYPE_RAID_UPDATE = 19; + public static final int TYPE_PLAYER_MOVEMENT_ANOMALY = 20; + public static final int TYPE_PLAYER_MOVEMENT_CORRECTED = 21; + public static final int TYPE_HONEY_HARVESTED = 22; + public static final int TYPE_TARGET_BLOCK_HIT = 23; + public static final int TYPE_PIGLIN_BARTER = 24; @Override public byte pid() { - return ProtocolInfo.EVENT_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { this.reset(); this.putVarLong(this.eid); - this.putVarInt(this.unknown1); - this.putByte(this.unknown2); + this.putVarInt(this.eventData); + this.putByte(this.eventType); } } diff --git a/src/main/java/cn/nukkit/network/protocol/GUIDataPickItemPacket.java b/src/main/java/cn/nukkit/network/protocol/GUIDataPickItemPacket.java index 5458b3167f3..2a77b7ab1ef 100644 --- a/src/main/java/cn/nukkit/network/protocol/GUIDataPickItemPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/GUIDataPickItemPacket.java @@ -5,11 +5,13 @@ @ToString public class GUIDataPickItemPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.GUI_DATA_PICK_ITEM_PACKET; + public int hotbarSlot; @Override public byte pid() { - return ProtocolInfo.GUI_DATA_PICK_ITEM_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/GameRulesChangedPacket.java b/src/main/java/cn/nukkit/network/protocol/GameRulesChangedPacket.java index bb4216829a6..5c5c2995361 100644 --- a/src/main/java/cn/nukkit/network/protocol/GameRulesChangedPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/GameRulesChangedPacket.java @@ -1,14 +1,18 @@ package cn.nukkit.network.protocol; +import cn.nukkit.level.GameRule; import cn.nukkit.level.GameRules; import lombok.ToString; +import java.util.Map; + /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class GameRulesChangedPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.GAME_RULES_CHANGED_PACKET; @Override @@ -17,14 +21,20 @@ public byte pid() { } public GameRules gameRules; + public Map gameRulesMap; @Override public void decode() { + this.decodeUnsupported(); } @Override public void encode() { this.reset(); - putGameRules(gameRules); + if (gameRulesMap == null) { // For compatibility + putGameRules(gameRules); + } else { + putGameRulesMap(gameRulesMap); + } } } diff --git a/src/main/java/cn/nukkit/network/protocol/HurtArmorPacket.java b/src/main/java/cn/nukkit/network/protocol/HurtArmorPacket.java index 0622bfecf49..4387c1ea6e0 100644 --- a/src/main/java/cn/nukkit/network/protocol/HurtArmorPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/HurtArmorPacket.java @@ -16,7 +16,7 @@ public class HurtArmorPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -31,5 +31,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/InitiateWebSocketConnectionPacket.java b/src/main/java/cn/nukkit/network/protocol/InitiateWebSocketConnectionPacket.java index 242a46a7c96..2d39fc4610b 100644 --- a/src/main/java/cn/nukkit/network/protocol/InitiateWebSocketConnectionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InitiateWebSocketConnectionPacket.java @@ -5,18 +5,20 @@ @ToString public class InitiateWebSocketConnectionPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.INITIATE_WEB_SOCKET_CONNECTION_PACKET; + @Override public byte pid() { - return ProtocolInfo.INITIATE_WEB_SOCKET_CONNECTION_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { - //TODO + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/InteractPacket.java b/src/main/java/cn/nukkit/network/protocol/InteractPacket.java index 5587ece9e5e..b56f32d12f8 100644 --- a/src/main/java/cn/nukkit/network/protocol/InteractPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InteractPacket.java @@ -2,9 +2,6 @@ import lombok.ToString; -/** - * Created on 15-10-15. - */ @ToString public class InteractPacket extends DataPacket { @@ -12,16 +9,24 @@ public class InteractPacket extends DataPacket { public static final int ACTION_VEHICLE_EXIT = 3; public static final int ACTION_MOUSEOVER = 4; - + public static final int ACTION_OPEN_NPC = 5; public static final int ACTION_OPEN_INVENTORY = 6; public int action; public long target; + float x; + float y; + float z; @Override public void decode() { this.action = this.getByte(); this.target = this.getEntityRuntimeId(); + if (this.action == ACTION_MOUSEOVER || this.action == ACTION_VEHICLE_EXIT) { + this.x = this.getFloat(); + this.y = this.getFloat(); + this.z = this.getFloat(); + } } @Override @@ -35,5 +40,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java index 7947e92f3f6..b43db62e48c 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class InventoryContentPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.INVENTORY_CONTENT_PACKET; @Override @@ -24,6 +25,7 @@ public byte pid() { public static final int SPECIAL_FIXED_INVENTORY = 0x7b; public int inventoryId; + public int networkId; public Item[] slots = new Item[0]; @Override @@ -34,7 +36,7 @@ public DataPacket clean() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java index 9fbde849b28..2dcc59732d9 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class InventorySlotPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.INVENTORY_SLOT_PACKET; @Override @@ -22,9 +23,7 @@ public byte pid() { @Override public void decode() { - this.inventoryId = (int) this.getUnsignedVarInt(); - this.slot = (int) this.getUnsignedVarInt(); - this.item = this.getSlot(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java index f95d63292cc..76237696ea6 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java @@ -4,15 +4,15 @@ import cn.nukkit.inventory.transaction.data.TransactionData; import cn.nukkit.inventory.transaction.data.UseItemData; import cn.nukkit.inventory.transaction.data.UseItemOnEntityData; +import cn.nukkit.math.Vector3; import cn.nukkit.network.protocol.types.NetworkInventoryAction; import lombok.ToString; -import java.util.ArrayDeque; -import java.util.Collection; - @ToString public class InventoryTransactionPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.INVENTORY_TRANSACTION_PACKET; + public static final int TYPE_NORMAL = 0; public static final int TYPE_MISMATCH = 1; public static final int TYPE_USE_ITEM = 2; @@ -39,6 +39,7 @@ public class InventoryTransactionPacket extends DataPacket { public int transactionType; public NetworkInventoryAction[] actions; public TransactionData transactionData; + public boolean hasNetworkIds = false; public int legacyRequestId; /** @@ -51,16 +52,19 @@ public class InventoryTransactionPacket extends DataPacket { @Override public byte pid() { - return ProtocolInfo.INVENTORY_TRANSACTION_PACKET; + return NETWORK_ID; } @Override public void encode() { this.reset(); + this.putVarInt(this.legacyRequestId); - //TODO legacySlot array + this.putUnsignedVarInt(this.transactionType); + this.putBoolean(this.hasNetworkIds); this.putUnsignedVarInt(this.actions.length); + for (NetworkInventoryAction action : this.actions) { action.write(this); } @@ -109,22 +113,23 @@ public void decode() { this.legacyRequestId = this.getVarInt(); if (legacyRequestId < -1 && (legacyRequestId & 1) == 0) { int length = (int) this.getUnsignedVarInt(); + if (length > 4096) { + throw new RuntimeException("Too many inventory transactions in one packet"); + } + for (int i = 0; i < length; i++) { this.getByte(); int bufLen = (int) this.getUnsignedVarInt(); this.get(bufLen); } - } this.transactionType = (int) this.getUnsignedVarInt(); - int length = (int) this.getUnsignedVarInt(); - Collection actions = new ArrayDeque<>(); - for (int i = 0; i < length; i++) { - actions.add(new NetworkInventoryAction().read(this)); + this.actions = new NetworkInventoryAction[Math.min((int) this.getUnsignedVarInt(), 4096)]; + for (int i = 0; i < this.actions.length; i++) { + this.actions[i] = new NetworkInventoryAction().read(this); } - this.actions = actions.toArray(new NetworkInventoryAction[0]); switch (this.transactionType) { case TYPE_NORMAL: @@ -139,7 +144,7 @@ public void decode() { itemData.face = this.getBlockFace(); itemData.hotbarSlot = this.getVarInt(); itemData.itemInHand = this.getSlot(); - itemData.playerPos = this.getVector3f().asVector3(); + itemData.playerPos = this.getVector3fAsVector3(); itemData.clickPos = this.getVector3f(); itemData.blockRuntimeId = (int) this.getUnsignedVarInt(); @@ -152,8 +157,8 @@ public void decode() { useItemOnEntityData.actionType = (int) this.getUnsignedVarInt(); useItemOnEntityData.hotbarSlot = this.getVarInt(); useItemOnEntityData.itemInHand = this.getSlot(); - useItemOnEntityData.playerPos = this.getVector3f().asVector3(); - useItemOnEntityData.clickPos = this.getVector3f().asVector3(); + useItemOnEntityData.playerPos = this.getVector3fAsVector3(); + useItemOnEntityData.clickPos = this.getVector3fAsVector3(); this.transactionData = useItemOnEntityData; break; @@ -162,8 +167,8 @@ public void decode() { releaseItemData.actionType = (int) getUnsignedVarInt(); releaseItemData.hotbarSlot = getVarInt(); - releaseItemData.itemInHand = getSlot(); - releaseItemData.headRot = this.getVector3f().asVector3(); + releaseItemData.itemInHand = this.getSlot(); + releaseItemData.headRot = this.getVector3fAsVector3(); this.transactionData = releaseItemData; break; @@ -171,4 +176,8 @@ public void decode() { throw new RuntimeException("Unknown transaction type " + this.transactionType); } } + + private Vector3 getVector3fAsVector3() { + return new Vector3(this.getLFloat(4), this.getLFloat(4), this.getLFloat(4)); + } } diff --git a/src/main/java/cn/nukkit/network/protocol/ItemComponentPacket.java b/src/main/java/cn/nukkit/network/protocol/ItemComponentPacket.java new file mode 100644 index 00000000000..1cb6d7b572b --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/ItemComponentPacket.java @@ -0,0 +1,25 @@ +package cn.nukkit.network.protocol; + +import lombok.ToString; + +@ToString +public class ItemComponentPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.ITEM_COMPONENT_PACKET; + + @Override + public byte pid() { + return NETWORK_ID; + } + + @Override + public void decode() { + this.decodeUnsupported(); + } + + @Override + public void encode() { + this.reset(); + this.putUnsignedVarInt(0); // Send an empty array for now + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java b/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java index c6d75c543d2..1a14bee948f 100644 --- a/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java @@ -25,7 +25,7 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ItemStackRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/ItemStackRequestPacket.java deleted file mode 100644 index 998e6bfc5f0..00000000000 --- a/src/main/java/cn/nukkit/network/protocol/ItemStackRequestPacket.java +++ /dev/null @@ -1,103 +0,0 @@ -package cn.nukkit.network.protocol; - -import cn.nukkit.item.Item; -import lombok.Value; - -import java.util.ArrayList; -import java.util.List; -import java.util.StringJoiner; - -public class ItemStackRequestPacket extends DataPacket { - public final List requests = new ArrayList<>(); - - @Override - public byte pid() { - return ProtocolInfo.ITEM_STACK_REQUEST_PACKET; - } - - @Override - public void decode() { - - } - - @Override - public void encode() { - - } - - @Value - public static class Request { - private final int requestId; - private final List actions; - } - - @Value - public static class ItemStackAction { - private final byte type; - private final boolean bool0; - private final byte byte0; - private final int varInt0; - private final int varInt1; - private final byte baseByte0; - private final byte baseByte1; - private final byte baseByte2; - private final int baseVarInt0; - private final byte flagsByte0; - private final byte flagsByte1; - private final int flagsVarInt0; - private final List items; - - @Override - public String toString() { - StringJoiner joiner = new StringJoiner(", "); - joiner.add("type=" + type); - - switch (type) { - case 0: - case 1: - case 2: - joiner.add("baseByte0=" + baseByte0) - .add("baseByte1=" + baseByte1) - .add("baseByte2=" + baseByte2) - .add("baseVarInt0=" + baseVarInt0) - .add("flagsByte0=" + flagsByte0) - .add("flagsByte1=" + flagsByte1) - .add("flagsVarInt0=" + flagsVarInt0); - break; - case 3: - joiner.add("bool0=" + bool0) - .add("baseByte0=" + baseByte0) - .add("baseByte1=" + baseByte1) - .add("baseByte2=" + baseByte2) - .add("baseVarInt0=" + baseVarInt0); - break; - case 4: - case 5: - joiner.add("baseByte0=" + baseByte0) - .add("baseByte1=" + baseByte1) - .add("baseByte2=" + baseByte2) - .add("baseVarInt0=" + baseVarInt0); - break; - case 6: - joiner.add("byte0=" + byte0); - break; - case 8: - joiner.add("varInt0=" + varInt0) - .add("varInt1=" + varInt1); - break; - case 10: - case 11: - case 12: - case 13: - case 14: - case 15: - joiner.add("varInt0=" + varInt0); - break; - case 17: - joiner.add("items=" + items); - break; - } - return "ItemStackAction(" + joiner.toString() + ")"; - } - } -} diff --git a/src/main/java/cn/nukkit/network/protocol/LecternUpdatePacket.java b/src/main/java/cn/nukkit/network/protocol/LecternUpdatePacket.java index c2819021295..a61aae31c7d 100644 --- a/src/main/java/cn/nukkit/network/protocol/LecternUpdatePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LecternUpdatePacket.java @@ -11,6 +11,7 @@ public class LecternUpdatePacket extends DataPacket { public int page; public int totalPages; public BlockVector3 blockPosition; + public boolean dropBook; @Override public byte pid() { @@ -26,5 +27,6 @@ public void decode() { @Override public void encode() { + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/LevelChunkPacket.java b/src/main/java/cn/nukkit/network/protocol/LevelChunkPacket.java index 7b4119ba94d..20fa7b4b447 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelChunkPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelChunkPacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString(exclude = "data") public class LevelChunkPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.FULL_CHUNK_DATA_PACKET; @Override @@ -27,7 +28,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -44,11 +45,9 @@ public void encode() { this.putUnsignedVarInt(-2); this.putUnsignedVarInt(this.subChunkLimit); } - this.putBoolean(cacheEnabled); if (this.cacheEnabled) { this.putUnsignedVarInt(blobIds.length); - for (long blobId : blobIds) { this.putLLong(blobId); } diff --git a/src/main/java/cn/nukkit/network/protocol/LevelEventGenericPacket.java b/src/main/java/cn/nukkit/network/protocol/LevelEventGenericPacket.java index 557ae35e63f..b91b4968b09 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelEventGenericPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelEventGenericPacket.java @@ -2,11 +2,14 @@ import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import lombok.ToString; import java.io.IOException; import java.nio.ByteOrder; +@ToString public class LevelEventGenericPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.LEVEL_EVENT_GENERIC_PACKET; public int eventId; @@ -19,6 +22,7 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/LevelEventPacket.java b/src/main/java/cn/nukkit/network/protocol/LevelEventPacket.java index eb99ca5c00b..8146b91e396 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelEventPacket.java @@ -1,14 +1,14 @@ package cn.nukkit.network.protocol; -import cn.nukkit.math.Vector3f; import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class LevelEventPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.LEVEL_EVENT_PACKET; public static final int EVENT_SOUND_CLICK = 1000; @@ -152,12 +152,7 @@ public byte pid() { @Override public void decode() { - this.evid = this.getVarInt(); - Vector3f v = this.getVector3f(); - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.data = this.getVarInt(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacket.java b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacket.java index aacc612af67..8d4672f8161 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacket.java @@ -5,6 +5,7 @@ @ToString public class LevelSoundEventPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.LEVEL_SOUND_EVENT_PACKET; public static final int SOUND_ITEM_USE_ON = 0; @@ -120,7 +121,7 @@ public class LevelSoundEventPacket extends DataPacket { public static final int SOUND_RECORD_WARD = 110; public static final int SOUND_RECORD_11 = 111; public static final int SOUND_RECORD_WAIT = 112; - public static final int SOUND_STOP_RECORD = 113; //Not really a sound + public static final int SOUND_STOP_RECORD = 113; public static final int SOUND_GUARDIAN_FLOP = 114; public static final int SOUND_ELDERGUARDIAN_CURSE = 115; public static final int SOUND_MOB_WARNING = 116; @@ -430,7 +431,6 @@ public class LevelSoundEventPacket extends DataPacket { public static final int SOUND_GOAT_BASS_7 = 420; public static final int SOUND_GOAT_BASS_8 = 421; public static final int SOUND_GOAT_BASS_9 = 422; - public static final int SOUND_IMITATE_WARDEN = 426; public static final int SOUND_LISTENING_ANGRY = 427; public static final int SOUND_ITEM_GIVEN = 428; @@ -448,7 +448,6 @@ public class LevelSoundEventPacket extends DataPacket { public static final int SOUND_CONVERT_TO_FROG = 440; public static final int SOUND_RECORD_PLAYING = 441; public static final int SOUND_ENCHANTING_TABLE_USE = 442; - public static final int SOUND_PRESSURE_PLATE_CLICK_OFF = 448; public static final int SOUND_PRESSURE_PLATE_CLICK_ON = 449; public static final int SOUND_BUTTON_CLICK_OFF = 450; @@ -480,6 +479,13 @@ public class LevelSoundEventPacket extends DataPacket { public static final int SOUND_AMBIENT_UNDERWATER_EXIT = 476; public static final int SOUND_BOTTLE_FILL = 477; public static final int SOUND_BOTTLE_EMPTY = 478; + public static final int SOUND_CRAFTER_CRAFT = 479; + public static final int SOUND_CRAFTER_FAILED = 480; + public static final int SOUND_CRAFTER_DISABLE_SLOT = 481; + public static final int SOUND_DECORATED_POT_INSERT = 482; + public static final int SOUND_DECORATED_POT_INSERT_FAILED = 483; + public static final int SOUND_COPPER_BULB_ON = 490; + public static final int SOUND_COPPER_BULB_OFF = 491; public int sound; public float x; @@ -518,4 +524,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV1.java b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV1.java index e3729f51989..6c56245c67d 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV1.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV1.java @@ -6,14 +6,15 @@ @ToString public class LevelSoundEventPacketV1 extends LevelSoundEventPacket { + public static final byte NETWORK_ID = ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V1; public int sound; public float x; public float y; public float z; - public int extraData = -1; //TODO: Check name - public int pitch = 1; //TODO: Check name + public int extraData = -1; + public int pitch = 1; public boolean isBabyMob; public boolean isGlobal; diff --git a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV2.java b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV2.java index 7e3bc64a1b6..30955fea20d 100644 --- a/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV2.java +++ b/src/main/java/cn/nukkit/network/protocol/LevelSoundEventPacketV2.java @@ -5,6 +5,7 @@ @ToString public class LevelSoundEventPacketV2 extends LevelSoundEventPacket { + public static final byte NETWORK_ID = ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2; public int sound; diff --git a/src/main/java/cn/nukkit/network/protocol/LoginPacket.java b/src/main/java/cn/nukkit/network/protocol/LoginPacket.java index 5a5fba24a22..ad293a30435 100644 --- a/src/main/java/cn/nukkit/network/protocol/LoginPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/LoginPacket.java @@ -1,5 +1,6 @@ package cn.nukkit.network.protocol; +import cn.nukkit.Server; import cn.nukkit.entity.data.Skin; import cn.nukkit.utils.*; import com.google.gson.Gson; @@ -11,21 +12,19 @@ import java.nio.charset.StandardCharsets; import java.util.*; - -/** - * Created by on 15-10-13. - */ @ToString public class LoginPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.LOGIN_PACKET; public String username; - public int protocol; + private int protocol_; public UUID clientUUID; public long clientId; public Skin skin; + private static final Gson GSON = new Gson(); + @Override public byte pid() { return NETWORK_ID; @@ -33,39 +32,42 @@ public byte pid() { @Override public void decode() { - this.protocol = this.getInt(); - if (protocol == 0) { + this.protocol_ = this.getInt(); + if (this.protocol_ == 0) { setOffset(getOffset() + 2); - this.protocol = getInt(); + this.protocol_ = getInt(); } - if (!ProtocolInfo.SUPPORTED_PROTOCOLS.contains(protocol)) { - // decoding the chain could cause issues on newer or older versions. - return; + if (ProtocolInfo.SUPPORTED_PROTOCOLS.contains(this.protocol_)) { // Avoid errors with unsupported versions + this.setBuffer(this.getByteArray(), 0); + decodeChainData(); + decodeSkinData(); } - - this.setBuffer(this.getByteArray(), 0); - decodeChainData(); - decodeSkinData(); } @Override public void encode() { - + this.encodeUnsupported(); } public int getProtocol() { - return protocol; + return protocol_; } private void decodeChainData() { - Map> map = new Gson().fromJson(new String(this.get(getLInt()), StandardCharsets.UTF_8), - new TypeToken>>() { - }.getType()); + int size = this.getLInt(); + if (size > 3145728) { + throw new IllegalArgumentException("The chain data is too big: " + size); + } + + String data = new String(this.get(size), StandardCharsets.UTF_8); + + Map> map = GSON.fromJson(data, new MapTypeToken().getType()); if (map.isEmpty() || !map.containsKey("chain") || map.get("chain").isEmpty()) return; - List chains = map.get("chain"); - for (String c : chains) { - JsonObject chainMap = decodeToken(c); + + for (String c : map.get("chain")) { + JsonObject chainMap = ClientChainData.decodeToken(c); if (chainMap == null) continue; + if (chainMap.has("extraData")) { JsonObject extra = chainMap.get("extraData").getAsJsonObject(); if (extra.has("displayName")) this.username = extra.get("displayName").getAsString(); @@ -75,84 +77,108 @@ private void decodeChainData() { } private void decodeSkinData() { - JsonObject skinToken = decodeToken(new String(this.get(this.getLInt()))); - if (skinToken.has("ClientRandomId")) this.clientId = skinToken.get("ClientRandomId").getAsLong(); + int size = this.getLInt(); + if (size > 10485760) { + Server.getInstance().getLogger().warning(username + ": The skin data is too big: " + size); + return; // Get disconnected due to "invalid skin" + } + + JsonObject skinToken = ClientChainData.decodeToken(new String(this.get(size), StandardCharsets.UTF_8)); + if (skinToken == null) throw new RuntimeException("Invalid null skin token"); + + if (skinToken.has("ClientRandomId")) { + this.clientId = skinToken.get("ClientRandomId").getAsLong(); + } skin = new Skin(); skin.setTrusted(false); // Don't trust player skins if (skinToken.has("SkinId")) { skin.setSkinId(skinToken.get("SkinId").getAsString()); + } else { + skin.setSkinId(UUID.randomUUID().toString()); } skin.setFullSkinId(skin.getSkinId()); - if (skinToken.has("PlayFabId")) { - skin.setPlayFabId(skinToken.get("PlayFabId").getAsString()); - } + if (protocol_ < 388) { + if (skinToken.has("SkinData")) { + skin.setSkinData(Base64.getDecoder().decode(skinToken.get("SkinData").getAsString())); + } - if (skinToken.has("CapeId")) { - skin.setCapeId(skinToken.get("CapeId").getAsString()); - } + if (skinToken.has("CapeData")) { + skin.setCapeData(Base64.getDecoder().decode(skinToken.get("CapeData").getAsString())); + } - skin.setSkinData(getImage(skinToken, "Skin")); - skin.setCapeData(getImage(skinToken, "Cape")); + if (skinToken.has("SkinGeometryName")) { + skin.setGeometryName(skinToken.get("SkinGeometryName").getAsString()); + } - if (skinToken.has("PremiumSkin")) { - skin.setPremium(skinToken.get("PremiumSkin").getAsBoolean()); - } + if (skinToken.has("SkinGeometry")) { + skin.setGeometryData(new String(Base64.getDecoder().decode(skinToken.get("SkinGeometry").getAsString()), StandardCharsets.UTF_8)); + } + } else { + if (skinToken.has("PlayFabId")) { + skin.setPlayFabId(skinToken.get("PlayFabId").getAsString()); + } - if (skinToken.has("PersonaSkin")) { - skin.setPersona(skinToken.get("PersonaSkin").getAsBoolean()); - } + if (skinToken.has("CapeId")) { + skin.setCapeId(skinToken.get("CapeId").getAsString()); + } - if (skinToken.has("CapeOnClassicSkin")) { - skin.setCapeOnClassic(skinToken.get("CapeOnClassicSkin").getAsBoolean()); - } + skin.setSkinData(getImage(skinToken, "Skin")); + skin.setCapeData(getImage(skinToken, "Cape")); - if (skinToken.has("SkinResourcePatch")) { - skin.setSkinResourcePatch(new String(Base64.getDecoder().decode(skinToken.get("SkinResourcePatch").getAsString()), StandardCharsets.UTF_8)); - } + if (skinToken.has("PremiumSkin")) { + skin.setPremium(skinToken.get("PremiumSkin").getAsBoolean()); + } - if (skinToken.has("SkinGeometryData")) { - skin.setGeometryData(new String(Base64.getDecoder().decode(skinToken.get("SkinGeometryData").getAsString()), StandardCharsets.UTF_8)); - } + if (skinToken.has("PersonaSkin")) { + skin.setPersona(skinToken.get("PersonaSkin").getAsBoolean()); + } - if (skinToken.has("SkinAnimationData")) { - skin.setAnimationData(new String(Base64.getDecoder().decode(skinToken.get("SkinAnimationData").getAsString()), StandardCharsets.UTF_8)); - } + if (skinToken.has("CapeOnClassicSkin")) { + skin.setCapeOnClassic(skinToken.get("CapeOnClassicSkin").getAsBoolean()); + } - if (skinToken.has("AnimatedImageData")) { - for (JsonElement element : skinToken.get("AnimatedImageData").getAsJsonArray()) { - skin.getAnimations().add(getAnimation(element.getAsJsonObject())); + if (skinToken.has("SkinResourcePatch")) { + skin.setSkinResourcePatch(new String(Base64.getDecoder().decode(skinToken.get("SkinResourcePatch").getAsString()), StandardCharsets.UTF_8)); } - } - if (skinToken.has("SkinColor")) { - skin.setSkinColor(skinToken.get("SkinColor").getAsString()); - } + if (skinToken.has("SkinGeometryData")) { + skin.setGeometryData(new String(Base64.getDecoder().decode(skinToken.get("SkinGeometryData").getAsString()), StandardCharsets.UTF_8)); + } - if (skinToken.has("ArmSize")) { - skin.setArmSize(skinToken.get("ArmSize").getAsString()); - } + if (skinToken.has("SkinAnimationData")) { + skin.setAnimationData(new String(Base64.getDecoder().decode(skinToken.get("SkinAnimationData").getAsString()), StandardCharsets.UTF_8)); + } - if (skinToken.has("PersonaPieces")) { - for (JsonElement object : skinToken.get("PersonaPieces").getAsJsonArray()) { - skin.getPersonaPieces().add(getPersonaPiece(object.getAsJsonObject())); + if (skinToken.has("AnimatedImageData")) { + for (JsonElement element : skinToken.get("AnimatedImageData").getAsJsonArray()) { + skin.getAnimations().add(getAnimation(element.getAsJsonObject())); + } } - } - if (skinToken.has("PieceTintColors")) { - for (JsonElement object : skinToken.get("PieceTintColors").getAsJsonArray()) { - skin.getTintColors().add(getTint(object.getAsJsonObject())); + if (skinToken.has("SkinColor")) { + skin.setSkinColor(skinToken.get("SkinColor").getAsString()); } - } - } - private JsonObject decodeToken(String token) { - String[] base = token.split("\\."); - if (base.length < 2) return null; - return new Gson().fromJson(new String(Base64.getDecoder().decode(base[1]), StandardCharsets.UTF_8), JsonObject.class); + if (skinToken.has("ArmSize")) { + skin.setArmSize(skinToken.get("ArmSize").getAsString()); + } + + if (skinToken.has("PersonaPieces")) { + for (JsonElement object : skinToken.get("PersonaPieces").getAsJsonArray()) { + skin.getPersonaPieces().add(getPersonaPiece(object.getAsJsonObject())); + } + } + + if (skinToken.has("PieceTintColors")) { + for (JsonElement object : skinToken.get("PieceTintColors").getAsJsonArray()) { + skin.getTintColors().add(getTint(object.getAsJsonObject())); + } + } + } } private static SkinAnimation getAnimation(JsonObject element) { @@ -196,4 +222,7 @@ public static PersonaPieceTint getTint(JsonObject object) { } return new PersonaPieceTint(pieceType, colors); } -} + + private static class MapTypeToken extends TypeToken>> { + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/protocol/MapCreateLockedCopyPacket.java b/src/main/java/cn/nukkit/network/protocol/MapCreateLockedCopyPacket.java index 792e0997215..91cc5fbfbe9 100644 --- a/src/main/java/cn/nukkit/network/protocol/MapCreateLockedCopyPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MapCreateLockedCopyPacket.java @@ -1,13 +1,18 @@ package cn.nukkit.network.protocol; +import lombok.ToString; + +@ToString public class MapCreateLockedCopyPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MAP_CREATE_LOCKED_COPY_PACKET; + public long originalMapId; public long newMapId; @Override public byte pid() { - return ProtocolInfo.MAP_CREATE_LOCKED_COPY_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/MapInfoRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/MapInfoRequestPacket.java index e1e42c5b27b..f8a26077865 100644 --- a/src/main/java/cn/nukkit/network/protocol/MapInfoRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MapInfoRequestPacket.java @@ -7,11 +7,14 @@ */ @ToString public class MapInfoRequestPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.MAP_INFO_REQUEST_PACKET; + public long mapId; @Override public byte pid() { - return ProtocolInfo.MAP_INFO_REQUEST_PACKET; + return NETWORK_ID; } @Override @@ -21,6 +24,6 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/MobArmorEquipmentPacket.java b/src/main/java/cn/nukkit/network/protocol/MobArmorEquipmentPacket.java index 141e87c9f97..a15ee725a02 100644 --- a/src/main/java/cn/nukkit/network/protocol/MobArmorEquipmentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MobArmorEquipmentPacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class MobArmorEquipmentPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MOB_ARMOR_EQUIPMENT_PACKET; @Override diff --git a/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java b/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java index 186870dead8..9951924d937 100644 --- a/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java @@ -3,7 +3,7 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString @@ -30,7 +30,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/MobEquipmentPacket.java b/src/main/java/cn/nukkit/network/protocol/MobEquipmentPacket.java index 826ec1ee572..361ac146a08 100644 --- a/src/main/java/cn/nukkit/network/protocol/MobEquipmentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MobEquipmentPacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class MobEquipmentPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MOB_EQUIPMENT_PACKET; @Override @@ -24,7 +25,7 @@ public byte pid() { @Override public void decode() { - this.eid = this.getEntityRuntimeId(); //EntityRuntimeID + this.eid = this.getEntityRuntimeId(); this.item = this.getSlot(); this.inventorySlot = this.getByte(); this.hotbarSlot = this.getByte(); @@ -34,7 +35,7 @@ public void decode() { @Override public void encode() { this.reset(); - this.putEntityRuntimeId(this.eid); //EntityRuntimeID + this.putEntityRuntimeId(this.eid); this.putSlot(this.item); this.putByte((byte) this.inventorySlot); this.putByte((byte) this.hotbarSlot); diff --git a/src/main/java/cn/nukkit/network/protocol/ModalFormRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/ModalFormRequestPacket.java index 72da652e6a9..5592e608b86 100644 --- a/src/main/java/cn/nukkit/network/protocol/ModalFormRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ModalFormRequestPacket.java @@ -5,17 +5,19 @@ @ToString public class ModalFormRequestPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MODAL_FORM_REQUEST_PACKET; + public int formId; public String data; @Override public byte pid() { - return ProtocolInfo.MODAL_FORM_REQUEST_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ModalFormResponsePacket.java b/src/main/java/cn/nukkit/network/protocol/ModalFormResponsePacket.java index eae893fdc0f..5bf8c657425 100644 --- a/src/main/java/cn/nukkit/network/protocol/ModalFormResponsePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ModalFormResponsePacket.java @@ -5,13 +5,15 @@ @ToString public class ModalFormResponsePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MODAL_FORM_RESPONSE_PACKET; + public int formId; public String data = "null"; public int cancelReason; @Override public byte pid() { - return ProtocolInfo.MODAL_FORM_RESPONSE_PACKET; + return NETWORK_ID; } @Override @@ -27,6 +29,6 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/MoveEntityAbsolutePacket.java b/src/main/java/cn/nukkit/network/protocol/MoveEntityAbsolutePacket.java index 9ce410cf137..e0c25b10395 100644 --- a/src/main/java/cn/nukkit/network/protocol/MoveEntityAbsolutePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MoveEntityAbsolutePacket.java @@ -4,11 +4,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class MoveEntityAbsolutePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MOVE_ENTITY_ABSOLUTE_PACKET; public long eid; diff --git a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java index 4e728b64a42..0370b18c558 100644 --- a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java @@ -4,26 +4,24 @@ @ToString public class MoveEntityDeltaPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.MOVE_ENTITY_DELTA_PACKET; - public static final int FLAG_HAS_X = 0B1; - public static final int FLAG_HAS_Y = 0B10; - public static final int FLAG_HAS_Z = 0B100; - public static final int FLAG_HAS_PITCH = 0B1000; - public static final int FLAG_HAS_YAW = 0B10000; - public static final int FLAG_HAS_HEAD_YAW = 0B100000; - public static final int FLAG_ON_GROUND = 0B1000000; - public static final int FLAG_TELEPORTING = 0B10000000; - public static final int FLAG_FORCE_MOVE_LOCAL_ENTITY = 0B100000000; + public static final int FLAG_HAS_X = 0b1; + public static final int FLAG_HAS_Y = 0b10; + public static final int FLAG_HAS_Z = 0b100; + public static final int FLAG_HAS_YAW = 0b1000; + public static final int FLAG_HAS_HEAD_YAW = 0b10000; + public static final int FLAG_HAS_PITCH = 0b100000; - public long runtimeEntityId; + public long eid; public int flags = 0; public float x = 0; public float y = 0; public float z = 0; - public float pitch = 0; - public float yaw = 0; - public float headYaw = 0; + public double yawDelta = 0; + public double headYawDelta = 0; + public double pitchDelta = 0; @Override public byte pid() { @@ -32,70 +30,52 @@ public byte pid() { @Override public void decode() { - this.runtimeEntityId = this.getEntityRuntimeId(); + this.getEntityRuntimeId(); this.flags = this.getByte(); - if ((this.flags & FLAG_HAS_X) != 0) { - this.x = this.getCoordinate(); - } - if ((this.flags & FLAG_HAS_Y) != 0) { - this.y = this.getCoordinate(); - } - if ((this.flags & FLAG_HAS_Z) != 0) { - this.z = this.getCoordinate(); - } - if ((this.flags & FLAG_HAS_PITCH) != 0) { - this.pitch = this.getRotation(); - } - if ((this.flags & FLAG_HAS_YAW) != 0) { - this.yaw = this.getRotation(); - } - if ((this.flags & FLAG_HAS_HEAD_YAW) != 0) { - this.headYaw = this.getRotation(); - } + this.x = getCoordinate(FLAG_HAS_X); + this.y = getCoordinate(FLAG_HAS_Y); + this.z = getCoordinate(FLAG_HAS_Z); + this.yawDelta = getRotation(FLAG_HAS_YAW); + this.headYawDelta = getRotation(FLAG_HAS_HEAD_YAW); + this.pitchDelta = getRotation(FLAG_HAS_PITCH); } @Override public void encode() { this.reset(); - this.putEntityRuntimeId(this.runtimeEntityId); - this.putLShort(this.flags); - if ((this.flags & FLAG_HAS_X) != 0) { - this.putCoordinate(this.x); - } - if ((this.flags & FLAG_HAS_Y) != 0) { - this.putCoordinate(this.y); - } - if ((this.flags & FLAG_HAS_Z) != 0) { - this.putCoordinate(this.z); - } - if ((this.flags & FLAG_HAS_PITCH) != 0) { - this.putRotation(this.pitch); - } - if ((this.flags & FLAG_HAS_YAW) != 0) { - this.putRotation(this.yaw); - } - if ((this.flags & FLAG_HAS_HEAD_YAW) != 0) { - this.putRotation(this.headYaw); - } - } - - private float getCoordinate() { - return this.getLFloat(); + this.putEntityRuntimeId(this.eid); + this.putByte((byte) flags); + putCoordinate(FLAG_HAS_X, this.x); + putCoordinate(FLAG_HAS_Y, this.y); + putCoordinate(FLAG_HAS_Z, this.z); + putRotation(FLAG_HAS_YAW, this.yawDelta); + putRotation(FLAG_HAS_HEAD_YAW, this.headYawDelta); + putRotation(FLAG_HAS_PITCH, this.pitchDelta); } - private void putCoordinate(float value) { - this.putLFloat(value); + private float getCoordinate(int flag) { + if ((flags & flag) != 0) { + return this.getLFloat(); + } + return 0; } - private float getRotation() { - return this.getByte() * (360F / 256F); + private double getRotation(int flag) { + if ((flags & flag) != 0) { + return this.getByte() * 1.40625; + } + return 0d; } - private void putRotation(float value) { - this.putByte((byte) (value / (360F / 256F))); + private void putCoordinate(int flag, float value) { + if ((flags & flag) != 0) { + this.putLFloat(value); + } } - public boolean hasFlag(int flag) { - return (this.flags & flag) != 0; + private void putRotation(int flag, double value) { + if ((flags & flag) != 0) { + this.putByte((byte) (value / 1.40625)); + } } } diff --git a/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java b/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java index e8d4a50c9fc..62432bc7237 100644 --- a/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MovePlayerPacket.java @@ -3,9 +3,6 @@ import cn.nukkit.math.Vector3f; import lombok.ToString; -/** - * Created on 15-10-14. - */ @ToString public class MovePlayerPacket extends DataPacket { @@ -14,7 +11,7 @@ public class MovePlayerPacket extends DataPacket { public static final int MODE_NORMAL = 0; public static final int MODE_RESET = 1; public static final int MODE_TELEPORT = 2; - public static final int MODE_PITCH = 3; //facepalm Mojang + public static final int MODE_PITCH = 3; public long eid; public float x; @@ -26,8 +23,8 @@ public class MovePlayerPacket extends DataPacket { public int mode = MODE_NORMAL; public boolean onGround; public long ridingEid; - public int int1 = 0; - public int int2 = 0; + public int teleportCause = 0; + public int teleportItem = 0; public long frame; @Override @@ -44,8 +41,8 @@ public void decode() { this.onGround = this.getBoolean(); this.ridingEid = this.getEntityRuntimeId(); if (this.mode == MODE_TELEPORT) { - this.int1 = this.getLInt(); - this.int2 = this.getLInt(); + this.teleportCause = this.getLInt(); + this.teleportItem = this.getLInt(); } this.frame = this.getUnsignedVarLong(); } @@ -62,8 +59,8 @@ public void encode() { this.putBoolean(this.onGround); this.putEntityRuntimeId(this.ridingEid); if (this.mode == MODE_TELEPORT) { - this.putLInt(this.int1); - this.putLInt(this.int2); + this.putLInt(this.teleportCause); + this.putLInt(this.teleportItem); } this.putUnsignedVarLong(this.frame); } @@ -72,5 +69,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java index 21b498a526f..d4914fb5717 100644 --- a/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java @@ -5,30 +5,27 @@ @ToString public class NPCRequestPacket extends DataPacket { - public long entityRuntimeId; + public static final byte NETWORK_ID = ProtocolInfo.NPC_REQUEST_PACKET; + public long entityRuntimeId; public RequestType requestType; - public String commandString; - public int actionType; - public String sceneName; public enum RequestType { - SET_ACTIONS, EXECUTE_ACTION, EXECUTE_CLOSING_COMMANDS, SET_NAME, SET_SKIN, - SET_INTERACTION_TEXT - + SET_INTERACTION_TEXT, + EXECUTE_OPENING_COMMANDS } @Override public byte pid() { - return ProtocolInfo.NPC_REQUEST_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/NetworkChunkPublisherUpdatePacket.java b/src/main/java/cn/nukkit/network/protocol/NetworkChunkPublisherUpdatePacket.java index 2e0fbf75f8d..51547a101ba 100644 --- a/src/main/java/cn/nukkit/network/protocol/NetworkChunkPublisherUpdatePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/NetworkChunkPublisherUpdatePacket.java @@ -6,18 +6,19 @@ @ToString public class NetworkChunkPublisherUpdatePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET; + public BlockVector3 position; public int radius; @Override public byte pid() { - return ProtocolInfo.NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET; + return NETWORK_ID; } @Override public void decode() { - this.position = this.getSignedBlockPosition(); - this.radius = (int) this.getUnsignedVarInt(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/NetworkSettingsPacket.java b/src/main/java/cn/nukkit/network/protocol/NetworkSettingsPacket.java index 448315de4fe..ab7342c495b 100644 --- a/src/main/java/cn/nukkit/network/protocol/NetworkSettingsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/NetworkSettingsPacket.java @@ -6,6 +6,8 @@ @ToString public class NetworkSettingsPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.NETWORK_SETTINGS_PACKET; + public int compressionThreshold; public PacketCompressionAlgorithm compressionAlgorithm; public boolean clientThrottleEnabled; @@ -14,7 +16,7 @@ public class NetworkSettingsPacket extends DataPacket { @Override public byte pid() { - return ProtocolInfo.NETWORK_SETTINGS_PACKET; + return NETWORK_ID; } @Override @@ -29,6 +31,6 @@ public void encode() { @Override public void decode() { - throw new UnsupportedOperationException(); + this.decodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/NetworkStackLatencyPacket.java b/src/main/java/cn/nukkit/network/protocol/NetworkStackLatencyPacket.java index 2491f0f0caf..5d09cf62dcf 100644 --- a/src/main/java/cn/nukkit/network/protocol/NetworkStackLatencyPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/NetworkStackLatencyPacket.java @@ -5,23 +5,26 @@ @ToString public class NetworkStackLatencyPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.NETWORK_STACK_LATENCY_PACKET; + public long timestamp; - public boolean unknownBool; + public boolean needResponse; @Override public byte pid() { - return ProtocolInfo.NETWORK_STACK_LATENCY_PACKET; + return NETWORK_ID; } @Override public void decode() { timestamp = this.getLLong(); + needResponse = this.getBoolean(); } @Override public void encode() { this.reset(); this.putLLong(timestamp); - this.putBoolean(unknownBool); + this.putBoolean(needResponse); } } diff --git a/src/main/java/cn/nukkit/network/protocol/NpcDialoguePacket.java b/src/main/java/cn/nukkit/network/protocol/NpcDialoguePacket.java new file mode 100644 index 00000000000..b44f9b1fa30 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/NpcDialoguePacket.java @@ -0,0 +1,40 @@ +package cn.nukkit.network.protocol; + +import lombok.ToString; + +@ToString +public class NpcDialoguePacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.NPC_DIALOGUE_PACKET; + + public static final int ACTION_OPEN = 0; + public static final int ACTION_CLOSE = 1; + + public long entityId; + public int actionType; + public String dialogue; + public String sceneName; + public String npcName; + public String actionJson; + + @Override + public byte pid() { + return NETWORK_ID; + } + + @Override + public void encode() { + this.reset(); + this.putLLong(this.entityId); + this.putVarInt(this.actionType); + this.putString(this.dialogue); + this.putString(this.sceneName); + this.putString(this.npcName); + this.putString(this.actionJson); + } + + @Override + public void decode() { + // Noop + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/OnScreenTextureAnimationPacket.java b/src/main/java/cn/nukkit/network/protocol/OnScreenTextureAnimationPacket.java index fccbc9b3abf..a5559382a9d 100644 --- a/src/main/java/cn/nukkit/network/protocol/OnScreenTextureAnimationPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/OnScreenTextureAnimationPacket.java @@ -1,17 +1,22 @@ package cn.nukkit.network.protocol; +import lombok.ToString; + +@ToString public class OnScreenTextureAnimationPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.ON_SCREEN_TEXTURE_ANIMATION_PACKET; + public int effectId; @Override public byte pid() { - return ProtocolInfo.ON_SCREEN_TEXTURE_ANIMATION_PACKET; + return NETWORK_ID; } @Override public void decode() { - this.effectId = this.getLInt(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/OpenSignPacket.java b/src/main/java/cn/nukkit/network/protocol/OpenSignPacket.java index 71676db504c..1baf17507dc 100644 --- a/src/main/java/cn/nukkit/network/protocol/OpenSignPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/OpenSignPacket.java @@ -14,7 +14,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/PacketViolationWarningPacket.java b/src/main/java/cn/nukkit/network/protocol/PacketViolationWarningPacket.java index 3899fed58cb..d9cec0fb12c 100644 --- a/src/main/java/cn/nukkit/network/protocol/PacketViolationWarningPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PacketViolationWarningPacket.java @@ -2,8 +2,11 @@ import lombok.ToString; +import java.nio.charset.StandardCharsets; + @ToString public class PacketViolationWarningPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET; public PacketViolationType type; @@ -21,16 +24,23 @@ public void decode() { this.type = PacketViolationType.values()[this.getVarInt() + 1]; this.severity = PacketViolationSeverity.values()[this.getVarInt()]; this.packetId = this.getVarInt(); - this.context = this.getString(); + + // BinaryStream::getString() + int contextLength = (int) this.getUnsignedVarInt(); + String context; + + if (contextLength > 1024) { + context = "Context too long: " + contextLength; + } else { + context = new String(this.get(contextLength), StandardCharsets.UTF_8); + } + + this.context = context; } @Override public void encode() { - this.reset(); - this.putVarInt(this.type.ordinal() - 1); - this.putVarInt(this.severity.ordinal()); - this.putVarInt(this.packetId); - this.putString(this.context); + this.encodeUnsupported(); } public enum PacketViolationType { diff --git a/src/main/java/cn/nukkit/network/protocol/PlaySoundPacket.java b/src/main/java/cn/nukkit/network/protocol/PlaySoundPacket.java index 6ed137a5752..e685b3029cf 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlaySoundPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlaySoundPacket.java @@ -21,14 +21,14 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { this.reset(); this.putString(this.name); - this.putBlockVector3(this.x * 8, this.y * 8, this.z * 8); + this.putBlockVector3(this.x << 3, this.y << 3, this.z << 3); this.putLFloat(this.volume); this.putLFloat(this.pitch); } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayStatusPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayStatusPacket.java index 0a58e25b2ca..0230a0eb745 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayStatusPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayStatusPacket.java @@ -2,9 +2,6 @@ import lombok.ToString; -/** - * Created on 15-10-13. - */ @ToString public class PlayStatusPacket extends DataPacket { @@ -15,22 +12,52 @@ public byte pid() { return NETWORK_ID; } + /** + * Sent to confirm login success and move onto resource pack sequence + */ public static final int LOGIN_SUCCESS = 0; + /** + * Displays outdated client disconnection screen + */ public static final int LOGIN_FAILED_CLIENT = 1; + /** + * Displays outdated server disconnection screen + */ public static final int LOGIN_FAILED_SERVER = 2; + /** + * Spawns player into the world + */ public static final int PLAYER_SPAWN = 3; + /** + * Unknown + */ public static final int LOGIN_FAILED_INVALID_TENANT = 4; + /** + * Sent when an Education Edition client joins a Bedrock Edition server + */ public static final int LOGIN_FAILED_VANILLA_EDU = 5; + /** + * Sent when a Bedrock Edition client joins an EducationEdition server + */ public static final int LOGIN_FAILED_EDU_VANILLA = 6; + /** + * Sent to a split screen player when the server is full + */ public static final int LOGIN_FAILED_SERVER_FULL = 7; + /** + * Unknown + */ public static final int LOGIN_FAILED_EDITOR_TO_VANILLA_MISMATCH = 8; + /** + * Unknown + */ public static final int LOGIN_FAILED_VANILLA_TO_EDITOR_MISMATCH = 9; public int status; @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -38,5 +65,4 @@ public void encode() { this.reset(); this.putInt(this.status); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerActionPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerActionPacket.java index ffdacc5fc4e..01918a17cd8 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerActionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerActionPacket.java @@ -30,6 +30,7 @@ public class PlayerActionPacket extends DataPacket { public static final int ACTION_STOP_GLIDE = 16; public static final int ACTION_BUILD_DENIED = 17; public static final int ACTION_CONTINUE_BREAK = 18; + public static final int ACTION_CHANGE_SKIN = 19; public static final int ACTION_SET_ENCHANTMENT_SEED = 20; public static final int ACTION_START_SWIMMING = 21; public static final int ACTION_STOP_SWIMMING = 22; @@ -67,7 +68,11 @@ public void encode() { this.putEntityRuntimeId(this.entityId); this.putVarInt(this.action); this.putBlockVector3(this.x, this.y, this.z); - this.putBlockVector3(this.resultPosition); + if (this.resultPosition == null) { + this.putBlockVector3(new BlockVector3()); + } else { + this.putBlockVector3(this.resultPosition); + } this.putVarInt(this.face); } @@ -75,5 +80,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerArmorDamagePacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerArmorDamagePacket.java index 65017387323..5b351112a17 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerArmorDamagePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerArmorDamagePacket.java @@ -7,6 +7,7 @@ @ToString public class PlayerArmorDamagePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_ARMOR_DAMAGE_PACKET; public final Set flags = EnumSet.noneOf(PlayerArmorDamageFlag.class); @@ -19,13 +20,7 @@ public byte pid() { @Override public void decode() { - int flagsval = this.getByte(); - for (int i = 0; i < 4; i++) { - if ((flagsval & (1 << i)) != 0) { - this.flags.add(PlayerArmorDamageFlag.values()[i]); - this.damage[i] = this.getVarInt(); - } - } + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java index 15222445150..26101e51797 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java @@ -15,6 +15,7 @@ @ToString @Getter public class PlayerAuthInputPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_AUTH_INPUT_PACKET; private float yaw; @@ -22,18 +23,18 @@ public class PlayerAuthInputPacket extends DataPacket { private float headYaw; private Vector3f position; private Vector2 motion; - private Set inputData = EnumSet.noneOf(AuthInputAction.class); + private final Set inputData = EnumSet.noneOf(AuthInputAction.class); private InputMode inputMode; private ClientPlayMode playMode; private AuthInteractionModel interactionModel; private Vector3f vrGazeDirection; private long tick; private Vector3f delta; - // private ItemStackRequest itemStackRequest; - private Map blockActionData = new EnumMap<>(PlayerActionType.class); private Vector2 analogMoveVector; - private long predictedVehicle; - private Vector2f vehicleRotation; + //private ItemStackRequest itemStackRequest; + private final Map blockActionData = new EnumMap<>(PlayerActionType.class); + private long predictedVehicle; // 1.20.60+ + private Vector2f vehicleRotation; // 1.20.70+ @Override public byte pid() { @@ -57,6 +58,7 @@ public void decode() { this.inputMode = InputMode.fromOrdinal((int) this.getUnsignedVarInt()); this.playMode = ClientPlayMode.fromOrdinal((int) this.getUnsignedVarInt()); + this.interactionModel = AuthInteractionModel.fromOrdinal((int) this.getUnsignedVarInt()); if (this.playMode == ClientPlayMode.REALITY) { @@ -66,13 +68,14 @@ public void decode() { this.tick = this.getUnsignedVarLong(); this.delta = this.getVector3f(); - if (this.inputData.contains(AuthInputAction.PERFORM_ITEM_STACK_REQUEST)) { + //if (this.inputData.contains(AuthInputAction.PERFORM_ITEM_STACK_REQUEST)) { // TODO: this.itemStackRequest = readItemStackRequest(buf, protocolVersion); // We are safe to leave this for later, since it is only sent with ServerAuthInventories - } + //} if (this.inputData.contains(AuthInputAction.PERFORM_BLOCK_ACTIONS)) { int arraySize = this.getVarInt(); + if (arraySize > 512) throw new IllegalArgumentException("PlayerAuthInputPacket PERFORM_BLOCK_ACTIONS is too long: " + arraySize); for (int i = 0; i < arraySize; i++) { PlayerActionType type = PlayerActionType.from(this.getVarInt()); switch (type) { @@ -99,6 +102,6 @@ public void decode() { @Override public void encode() { - // Noop + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerEnchantOptionsPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerEnchantOptionsPacket.java index a8eecd819c9..74fd2fd93e5 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerEnchantOptionsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerEnchantOptionsPacket.java @@ -1,6 +1,5 @@ package cn.nukkit.network.protocol; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.ToString; import lombok.Value; @@ -9,6 +8,7 @@ @ToString public class PlayerEnchantOptionsPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_ENCHANT_OPTIONS_PACKET; public final List options = new ArrayList<>(); @@ -20,36 +20,7 @@ public byte pid() { @Override public void decode() { - int size = (int) this.getUnsignedVarInt(); - for (int i = 0; i < size; i++) { - int minLevel = this.getVarInt(); - int slot = this.getInt(); - - int eSize = (int) this.getUnsignedVarInt(); - List list1 = new ObjectArrayList<>(); - for (int j = 0; j < eSize; j++) { - EnchantData data = new EnchantData(this.getByte(), this.getByte()); - list1.add(data); - } - - eSize = (int) this.getUnsignedVarInt(); - List list2 = new ObjectArrayList<>(); - for (int j = 0; j < eSize; j++) { - EnchantData data = new EnchantData(this.getByte(), this.getByte()); - list2.add(data); - } - - eSize = (int) this.getUnsignedVarInt(); - List list3 = new ObjectArrayList<>(); - for (int j = 0; j < eSize; j++) { - EnchantData data = new EnchantData(this.getByte(), this.getByte()); - list3.add(data); - } - String enchantName = this.getString(); - int eNetId = (int) this.getUnsignedVarInt(); - this.options.add(new EnchantOptionData(minLevel, slot, list1, list2, list3, enchantName, eNetId)); - } - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerHotbarPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerHotbarPacket.java index c96bd8e6dd9..a1a1df5d3b6 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerHotbarPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerHotbarPacket.java @@ -6,14 +6,15 @@ @ToString public class PlayerHotbarPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_HOTBAR_PACKET; + public int selectedHotbarSlot; public int windowId = ContainerIds.INVENTORY; - public boolean selectHotbarSlot = true; @Override public byte pid() { - return ProtocolInfo.PLAYER_HOTBAR_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerInputPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerInputPacket.java index d6adb559d0a..bfcb8b3d627 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerInputPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerInputPacket.java @@ -26,12 +26,11 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } @Override public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java index cea7d5c2f59..a9e09dfa742 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerListPacket.java @@ -21,7 +21,7 @@ public class PlayerListPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -29,25 +29,28 @@ public void encode() { this.reset(); this.putByte(this.type); this.putUnsignedVarInt(this.entries.length); - for (Entry entry : this.entries) { - this.putUUID(entry.uuid); - if (type == TYPE_ADD) { - this.putVarLong(entry.entityId); - this.putString(entry.name); - this.putString(entry.xboxUserId); - this.putString(entry.platformChatId); - this.putLInt(entry.buildPlatform); - this.putSkin(entry.skin); - this.putBoolean(entry.isTeacher); - this.putBoolean(entry.isHost); - this.putBoolean(entry.isSubClient); - } - } - - if (type == TYPE_ADD) { - for (Entry entry : this.entries) { - this.putBoolean(entry.skin.isTrusted()); - } + switch (type) { + case TYPE_ADD: + for (Entry entry : this.entries) { + this.putUUID(entry.uuid); + this.putVarLong(entry.entityId); + this.putString(entry.name); + this.putString(entry.xboxUserId); + this.putString(entry.platformChatId); + this.putLInt(entry.buildPlatform); + this.putSkin(entry.skin); + this.putBoolean(entry.isTeacher); + this.putBoolean(entry.isHost); + this.putBoolean(entry.isSubClient); + } + for (Entry entry : this.entries) { // WTF Mojang + this.putBoolean(entry.skin.isTrusted()); + } + break; + case TYPE_REMOVE: + for (Entry entry : this.entries) { + this.putUUID(entry.uuid); + } } } @@ -62,10 +65,10 @@ public static class Entry { public final UUID uuid; public long entityId = 0; public String name = ""; - public String xboxUserId = ""; //TODO - public String platformChatId = ""; //TODO - public int buildPlatform = -1; public Skin skin; + public String xboxUserId = ""; + public String platformChatId = ""; + public int buildPlatform = -1; public boolean isTeacher; public boolean isHost; public boolean isSubClient; @@ -86,5 +89,4 @@ public Entry(UUID uuid, long entityId, String name, Skin skin, String xboxUserId this.xboxUserId = xboxUserId == null ? "" : xboxUserId; } } - } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerSkinPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerSkinPacket.java index b93833b5a84..9b3232457b8 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerSkinPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerSkinPacket.java @@ -8,14 +8,17 @@ @ToString public class PlayerSkinPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_SKIN_PACKET; + public UUID uuid; public Skin skin; public String newSkinName; public String oldSkinName; + public boolean premium; @Override public byte pid() { - return ProtocolInfo.PLAYER_SKIN_PACKET; + return NETWORK_ID; } @Override @@ -39,4 +42,4 @@ public void encode() { putString(oldSkinName); putBoolean(skin.isTrusted()); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerStartItemCooldownPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerStartItemCooldownPacket.java new file mode 100644 index 00000000000..04b47655889 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/PlayerStartItemCooldownPacket.java @@ -0,0 +1,29 @@ +package cn.nukkit.network.protocol; + +import lombok.ToString; + +@ToString +public class PlayerStartItemCooldownPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.PLAYER_START_ITEM_COOLDOWN_PACKET; + + public String itemCategory; + public int cooldownDuration; + + @Override + public byte pid() { + return NETWORK_ID; + } + + @Override + public void decode() { + this.decodeUnsupported(); + } + + @Override + public void encode() { + this.reset(); + this.putString(this.itemCategory); + this.putVarInt(this.cooldownDuration); + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java index 895695127c1..b291cbb0edf 100644 --- a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java +++ b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java @@ -5,13 +5,13 @@ import java.util.List; /** - * author: MagicDroidX & iNevet + * @author MagicDroidX & iNevet * Nukkit Project */ public interface ProtocolInfo { /** - * Actual Minecraft: PE protocol version + * Actual Minecraft protocol version */ @SuppressWarnings("UnnecessaryBoxing") int CURRENT_PROTOCOL = Integer.valueOf("662"); // DO NOT REMOVE BOXING @@ -126,12 +126,16 @@ public interface ProtocolInfo { byte SERVER_SETTINGS_RESPONSE_PACKET = 0x67; byte SHOW_PROFILE_PACKET = 0x68; byte SET_DEFAULT_GAME_TYPE_PACKET = 0x69; + byte REMOVE_OBJECTIVE_PACKET = 0x6a; + byte SET_DISPLAY_OBJECTIVE_PACKET = 0x6b; + byte SET_SCORE_PACKET = 0x6c; + byte LAB_TABLE_PACKET = 0x6d; + byte UPDATE_BLOCK_SYNCED_PACKET = 0x6e; byte MOVE_ENTITY_DELTA_PACKET = 0x6f; byte SET_SCOREBOARD_IDENTITY_PACKET = 0x70; byte SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET = 0x71; byte UPDATE_SOFT_ENUM_PACKET = 0x72; byte NETWORK_STACK_LATENCY_PACKET = 0x73; - byte SCRIPT_CUSTOM_EVENT_PACKET = 0x75; byte SPAWN_PARTICLE_EFFECT_PACKET = 0x76; byte AVAILABLE_ENTITY_IDENTIFIERS_PACKET = 0x77; @@ -142,8 +146,8 @@ public interface ProtocolInfo { byte LEVEL_EVENT_GENERIC_PACKET = 0x7c; byte LECTERN_UPDATE_PACKET = 0x7d; byte VIDEO_STREAM_CONNECT_PACKET = 0x7e; - //byte ADD_ENTITY_PACKET = 0x7f; - //byte REMOVE_ENTITY_PACKET = 0x80; + byte ADD_ENTITY_PACKET_2 = 0x7f; + byte REMOVE_ENTITY_PACKET_2 = (byte) 0x80; byte CLIENT_CACHE_STATUS_PACKET = (byte) 0x81; byte ON_SCREEN_TEXTURE_ANIMATION_PACKET = (byte) 0x82; byte MAP_CREATE_LOCKED_COPY_PACKET = (byte) 0x83; @@ -214,6 +218,6 @@ public interface ProtocolInfo { byte UNLOCKED_RECIPES_PACKET = (byte) 0xc7; // MC packet IDs continue from 300 (0x12c) // Hack: 100 is added to the IDs below on encode - // TODO: New pid() function (int) while trying not to break too many plugins - byte __INTERNAL__OPEN_SIGN_PACKET = (byte) 203; // 303 + // TODO: New pid() function (int) that calls the old one by default + byte __INTERNAL__OPEN_SIGN_PACKET = (byte) 203; } diff --git a/src/main/java/cn/nukkit/network/protocol/RemoveEntityPacket.java b/src/main/java/cn/nukkit/network/protocol/RemoveEntityPacket.java index 4c2faa91cac..0f74c3246e8 100644 --- a/src/main/java/cn/nukkit/network/protocol/RemoveEntityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RemoveEntityPacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class RemoveEntityPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.REMOVE_ENTITY_PACKET; public long eid; @@ -19,7 +20,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/RequestAbilityPacket.java b/src/main/java/cn/nukkit/network/protocol/RequestAbilityPacket.java index 19ae4f9b81b..bbafa9e4003 100644 --- a/src/main/java/cn/nukkit/network/protocol/RequestAbilityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RequestAbilityPacket.java @@ -9,6 +9,9 @@ @Getter @Setter public class RequestAbilityPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.REQUEST_ABILITY_PACKET; + protected static final PlayerAbility[] ABILITIES = UpdateAbilitiesPacket.VALID_FLAGS; protected static final AbilityType[] ABILITY_TYPES = AbilityType.values(); @@ -27,12 +30,12 @@ public void decode() { @Override public void encode() { - throw new UnsupportedOperationException(); + this.encodeUnsupported(); } @Override public byte pid() { - return ProtocolInfo.REQUEST_ABILITY_PACKET; + return NETWORK_ID; } public enum AbilityType { diff --git a/src/main/java/cn/nukkit/network/protocol/RequestChunkRadiusPacket.java b/src/main/java/cn/nukkit/network/protocol/RequestChunkRadiusPacket.java index f547e0dd6b7..9a9ec0358f0 100644 --- a/src/main/java/cn/nukkit/network/protocol/RequestChunkRadiusPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RequestChunkRadiusPacket.java @@ -3,7 +3,7 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString @@ -22,12 +22,11 @@ public void decode() { @Override public void encode() { - + this.encodeUnsupported(); } @Override public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/RequestNetworkSettingsPacket.java b/src/main/java/cn/nukkit/network/protocol/RequestNetworkSettingsPacket.java index 0c09ad2489a..6faea0a6b4d 100644 --- a/src/main/java/cn/nukkit/network/protocol/RequestNetworkSettingsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RequestNetworkSettingsPacket.java @@ -5,16 +5,18 @@ @ToString public class RequestNetworkSettingsPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET; + public int protocolVersion; @Override public byte pid() { - return ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET; + return NETWORK_ID; } @Override public void encode() { - throw new UnsupportedOperationException(); + this.encodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/RequestPermissionsPacket.java b/src/main/java/cn/nukkit/network/protocol/RequestPermissionsPacket.java new file mode 100644 index 00000000000..c963589b9f3 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/RequestPermissionsPacket.java @@ -0,0 +1,21 @@ +package cn.nukkit.network.protocol; + +public class RequestPermissionsPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.REQUEST_PERMISSIONS_PACKET; + + @Override + public void decode() { + // TODO + } + + @Override + public void encode() { + this.encodeUnsupported(); + } + + @Override + public byte pid() { + return NETWORK_ID; + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackChunkDataPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackChunkDataPacket.java index 72232810409..a18c9cda865 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackChunkDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackChunkDataPacket.java @@ -16,10 +16,7 @@ public class ResourcePackChunkDataPacket extends DataPacket { @Override public void decode() { - this.packId = UUID.fromString(this.getString()); - this.chunkIndex = this.getLInt(); - this.progress = this.getLLong(); - this.data = this.getByteArray(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackClientResponsePacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackClientResponsePacket.java index c89ed011ac9..dcccc3182f1 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackClientResponsePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackClientResponsePacket.java @@ -44,6 +44,7 @@ public byte pid() { @ToString public static class Entry { + public final UUID uuid; public final String version; diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java index 7a601abcd95..fe97f41b11d 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java @@ -1,46 +1,47 @@ package cn.nukkit.network.protocol; +import cn.nukkit.network.protocol.types.ExperimentData; import cn.nukkit.resourcepacks.ResourcePack; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.ToString; +import java.util.List; + @ToString public class ResourcePackStackPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.RESOURCE_PACK_STACK_PACKET; public boolean mustAccept = false; + @SuppressWarnings("unused") + public String gameVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK; // For plugin compatibility public ResourcePack[] behaviourPackStack = new ResourcePack[0]; public ResourcePack[] resourcePackStack = new ResourcePack[0]; - public boolean isExperimental = false; - public String gameVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK; + public final List experiments = new ObjectArrayList<>(); @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { this.reset(); this.putBoolean(this.mustAccept); - this.putUnsignedVarInt(this.behaviourPackStack.length); for (ResourcePack entry : this.behaviourPackStack) { this.putString(entry.getPackId().toString()); this.putString(entry.getPackVersion()); - this.putString(""); //TODO: subpack name + this.putString(""); } - this.putUnsignedVarInt(this.resourcePackStack.length); for (ResourcePack entry : this.resourcePackStack) { this.putString(entry.getPackId().toString()); this.putString(entry.getPackVersion()); - this.putString(""); //TODO: subpack name + this.putString(""); } - - this.putString(this.gameVersion); - this.putLInt(0); // Experiments length - this.putBoolean(false); // Were experiments previously toggled + this.putString(ProtocolInfo.MINECRAFT_VERSION_NETWORK); + this.putExperiments(this.experiments); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java index 9eb40c30268..fbaed8fcae4 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java @@ -22,7 +22,7 @@ public class ResourcePacksInfoPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -32,6 +32,7 @@ public void encode() { this.putBoolean(this.hasAddonPacks); this.putBoolean(this.scripting); this.putBoolean(this.forceServerPacks); + this.encodeBehaviourPacks(this.behaviourPackEntries); this.encodeResourcePacks(this.resourcePackEntries); diff --git a/src/main/java/cn/nukkit/network/protocol/RespawnPacket.java b/src/main/java/cn/nukkit/network/protocol/RespawnPacket.java index 9cdc223ba94..fe6907e67b5 100644 --- a/src/main/java/cn/nukkit/network/protocol/RespawnPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RespawnPacket.java @@ -43,5 +43,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/RiderJumpPacket.java b/src/main/java/cn/nukkit/network/protocol/RiderJumpPacket.java index dff26bc18c6..d810349d489 100644 --- a/src/main/java/cn/nukkit/network/protocol/RiderJumpPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/RiderJumpPacket.java @@ -7,7 +7,7 @@ public class RiderJumpPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.RIDER_JUMP_PACKET; - public int unknown; + public int jumpStrength; @Override public byte pid() { @@ -16,12 +16,12 @@ public byte pid() { @Override public void decode() { - this.unknown = this.getVarInt(); + this.jumpStrength = this.getVarInt(); } @Override public void encode() { this.reset(); - this.putVarInt(this.unknown); + this.putVarInt(this.jumpStrength); } } diff --git a/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java b/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java index f03ca85bebb..59875d19d94 100644 --- a/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java @@ -4,13 +4,15 @@ @ToString public class ScriptCustomEventPacket extends DataPacket { - + + public static final byte NETWORK_ID = ProtocolInfo.SCRIPT_CUSTOM_EVENT_PACKET; + public String eventName; public byte[] eventData; @Override public byte pid() { - return ProtocolInfo.SCRIPT_CUSTOM_EVENT_PACKET; + return NETWORK_ID; } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ServerSettingsRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/ServerSettingsRequestPacket.java index 9e83cd1475f..8c9f8788263 100644 --- a/src/main/java/cn/nukkit/network/protocol/ServerSettingsRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ServerSettingsRequestPacket.java @@ -5,18 +5,20 @@ @ToString public class ServerSettingsRequestPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET; + @Override public byte pid() { - return ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET; + return NETWORK_ID; } @Override public void decode() { - + // Nothing to decode } @Override public void encode() { - + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/ServerSettingsResponsePacket.java b/src/main/java/cn/nukkit/network/protocol/ServerSettingsResponsePacket.java index 640b592c400..b2bd5aad165 100644 --- a/src/main/java/cn/nukkit/network/protocol/ServerSettingsResponsePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ServerSettingsResponsePacket.java @@ -5,17 +5,19 @@ @ToString public class ServerSettingsResponsePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SERVER_SETTINGS_RESPONSE_PACKET; + public int formId; public String data; @Override public byte pid() { - return ProtocolInfo.SERVER_SETTINGS_RESPONSE_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ServerToClientHandshakePacket.java b/src/main/java/cn/nukkit/network/protocol/ServerToClientHandshakePacket.java index edd3abbc4a8..d3f9801abd2 100644 --- a/src/main/java/cn/nukkit/network/protocol/ServerToClientHandshakePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ServerToClientHandshakePacket.java @@ -5,16 +5,18 @@ @ToString public class ServerToClientHandshakePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SERVER_TO_CLIENT_HANDSHAKE_PACKET; + @Override public byte pid() { - return ProtocolInfo.SERVER_TO_CLIENT_HANDSHAKE_PACKET; + return NETWORK_ID; } public String jwt; @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetCommandsEnabledPacket.java b/src/main/java/cn/nukkit/network/protocol/SetCommandsEnabledPacket.java index 98465c28836..d79c85368b4 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetCommandsEnabledPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetCommandsEnabledPacket.java @@ -16,7 +16,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetDefaultGameTypePacket.java b/src/main/java/cn/nukkit/network/protocol/SetDefaultGameTypePacket.java new file mode 100644 index 00000000000..60889d2efe9 --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/SetDefaultGameTypePacket.java @@ -0,0 +1,24 @@ +package cn.nukkit.network.protocol; + +public class SetDefaultGameTypePacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.SET_DEFAULT_GAME_TYPE_PACKET; + + public int gamemode; + + @Override + public void decode() { + this.gamemode = this.getVarInt(); + } + + @Override + public void encode() { + this.reset(); + this.putVarInt(this.gamemode); + } + + @Override + public byte pid() { + return NETWORK_ID; + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/SetDifficultyPacket.java b/src/main/java/cn/nukkit/network/protocol/SetDifficultyPacket.java index ce1ac54b082..40eb48d2d31 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetDifficultyPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetDifficultyPacket.java @@ -27,5 +27,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java b/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java index 318a9656cb8..67d5c4dc173 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetEntityDataPacket.java @@ -5,11 +5,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class SetEntityDataPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_ENTITY_DATA_PACKET; @Override @@ -23,7 +24,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetEntityLinkPacket.java b/src/main/java/cn/nukkit/network/protocol/SetEntityLinkPacket.java index dd255db60dc..07787636df8 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetEntityLinkPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetEntityLinkPacket.java @@ -2,9 +2,6 @@ import lombok.ToString; -/** - * Created on 15-10-22. - */ @ToString public class SetEntityLinkPacket extends DataPacket { @@ -14,15 +11,19 @@ public class SetEntityLinkPacket extends DataPacket { public static final byte TYPE_RIDE = 1; public static final byte TYPE_PASSENGER = 2; - public long vehicleUniqueId; //from - public long riderUniqueId; //to + public long vehicleUniqueId; + public long riderUniqueId; public byte type; - public byte immediate; + public byte immediate; // boolean public boolean riderInitiated = false; @Override public void decode() { - + this.vehicleUniqueId = this.getEntityUniqueId(); + this.riderUniqueId = this.getEntityUniqueId(); + this.type = (byte) this.getByte(); + this.immediate = (byte) this.getByte(); + this.riderInitiated = this.getBoolean(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetEntityMotionPacket.java b/src/main/java/cn/nukkit/network/protocol/SetEntityMotionPacket.java index c09d3be420d..ecdf74f7513 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetEntityMotionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetEntityMotionPacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class SetEntityMotionPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_ENTITY_MOTION_PACKET; public long eid; @@ -23,7 +24,11 @@ public byte pid() { @Override public void decode() { - + this.eid = this.getEntityRuntimeId(); + this.motionX = this.getLFloat(); + this.motionY = this.getLFloat(); + this.motionZ = this.getLFloat(); + this.tick = this.getUnsignedVarLong(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetHealthPacket.java b/src/main/java/cn/nukkit/network/protocol/SetHealthPacket.java index 2972fede444..2b5ba7329ec 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetHealthPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetHealthPacket.java @@ -16,7 +16,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetLastHurtByPacket.java b/src/main/java/cn/nukkit/network/protocol/SetLastHurtByPacket.java index 8d55cf10cbc..c42f0cf1eba 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetLastHurtByPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetLastHurtByPacket.java @@ -5,18 +5,20 @@ @ToString public class SetLastHurtByPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_LAST_HURT_BY_PACKET; + @Override public byte pid() { - return ProtocolInfo.SET_LAST_HURT_BY_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { - //TODO + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java b/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java index 472e186941a..6e8ce62897b 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java @@ -4,6 +4,7 @@ @ToString public class SetLocalPlayerAsInitializedPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET; public long eid; @@ -15,7 +16,7 @@ public byte pid() { @Override public void decode() { - eid = this.getUnsignedVarLong(); + this.eid = this.getUnsignedVarLong(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SetPlayerGameTypePacket.java b/src/main/java/cn/nukkit/network/protocol/SetPlayerGameTypePacket.java index 1ca96859525..9c363bbf5c3 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetPlayerGameTypePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetPlayerGameTypePacket.java @@ -3,12 +3,13 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class SetPlayerGameTypePacket extends DataPacket { - public final static byte NETWORK_ID = ProtocolInfo.SET_PLAYER_GAME_TYPE_PACKET; + + public static final byte NETWORK_ID = ProtocolInfo.SET_PLAYER_GAME_TYPE_PACKET; @Override public byte pid() { diff --git a/src/main/java/cn/nukkit/network/protocol/SetSpawnPositionPacket.java b/src/main/java/cn/nukkit/network/protocol/SetSpawnPositionPacket.java index e24015fd743..368ea6c2ded 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetSpawnPositionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetSpawnPositionPacket.java @@ -21,7 +21,7 @@ public class SetSpawnPositionPacket extends DataPacket { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -29,7 +29,7 @@ public void encode() { this.reset(); this.putVarInt(this.spawnType); this.putBlockVector3(this.x, this.y, this.z); - this.putVarInt(dimension); + this.putVarInt(this.dimension); this.putBlockVector3(this.x, this.y, this.z); } @@ -37,5 +37,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/SetTimePacket.java b/src/main/java/cn/nukkit/network/protocol/SetTimePacket.java index 76d5542b085..db31c5d61ef 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetTimePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetTimePacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class SetTimePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_TIME_PACKET; public int time; @@ -19,7 +20,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -27,5 +28,4 @@ public void encode() { this.reset(); this.putVarInt(this.time); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/SetTitlePacket.java b/src/main/java/cn/nukkit/network/protocol/SetTitlePacket.java index 01da1578d7e..ed3828b8939 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetTitlePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetTitlePacket.java @@ -7,6 +7,7 @@ */ @ToString public class SetTitlePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SET_TITLE_PACKET; public static final int TYPE_CLEAR = 0; @@ -31,13 +32,7 @@ public byte pid() { @Override public void decode() { - this.type = this.getVarInt(); - this.text = this.getString(); - this.fadeInTime = this.getVarInt(); - this.stayTime = this.getVarInt(); - this.fadeOutTime = this.getVarInt(); - this.xuid = this.getString(); - this.platformOnlineId = this.getString(); + this.decodeUnsupported(); } @Override @@ -48,7 +43,7 @@ public void encode() { this.putVarInt(fadeInTime); this.putVarInt(stayTime); this.putVarInt(fadeOutTime); - this.putString(this.xuid); - this.putString(this.platformOnlineId); + this.putString(xuid); + this.putString(platformOnlineId); } } diff --git a/src/main/java/cn/nukkit/network/protocol/SettingsCommandPacket.java b/src/main/java/cn/nukkit/network/protocol/SettingsCommandPacket.java new file mode 100644 index 00000000000..df4d909d9ce --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/SettingsCommandPacket.java @@ -0,0 +1,25 @@ +package cn.nukkit.network.protocol; + +public class SettingsCommandPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.SETTINGS_COMMAND_PACKET; + + public String command; + public boolean suppressOutput; + + @Override + public void decode() { + this.command = this.getString(); + this.suppressOutput = this.getBoolean(); + } + + @Override + public void encode() { + this.encodeUnsupported(); + } + + @Override + public byte pid() { + return NETWORK_ID; + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/ShowProfilePacket.java b/src/main/java/cn/nukkit/network/protocol/ShowProfilePacket.java index d7a6784a8fc..1cfdd5571de 100644 --- a/src/main/java/cn/nukkit/network/protocol/ShowProfilePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ShowProfilePacket.java @@ -3,11 +3,12 @@ import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class ShowProfilePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SHOW_PROFILE_PACKET; public String xuid; @@ -19,7 +20,7 @@ public byte pid() { @Override public void decode() { - this.xuid = this.getString(); + this.decodeUnsupported(); } @Override @@ -27,5 +28,4 @@ public void encode() { this.reset(); this.putString(this.xuid); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/SimpleEventPacket.java b/src/main/java/cn/nukkit/network/protocol/SimpleEventPacket.java index 972f1578216..68e44bf0895 100644 --- a/src/main/java/cn/nukkit/network/protocol/SimpleEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SimpleEventPacket.java @@ -5,21 +5,27 @@ @ToString public class SimpleEventPacket extends DataPacket { - public short unknown; + public static final byte NETWORK_ID = ProtocolInfo.SIMPLE_EVENT_PACKET; + + public static final int TYPE_ENABLE_COMMANDS = 1; + public static final int TYPE_DISABLE_COMMANDS = 2; + public static final int TYPE_UNLOCK_WORLD_TEMPLATE_SETTINGS = 3; + + public short eventType; @Override public byte pid() { - return ProtocolInfo.SIMPLE_EVENT_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.eventType = (short) this.getShort(); } @Override public void encode() { this.reset(); - this.putShort(this.unknown); + this.putShort(this.eventType); } } diff --git a/src/main/java/cn/nukkit/network/protocol/SpawnExperienceOrbPacket.java b/src/main/java/cn/nukkit/network/protocol/SpawnExperienceOrbPacket.java index 5dc0a4317c2..e0c543e762a 100644 --- a/src/main/java/cn/nukkit/network/protocol/SpawnExperienceOrbPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SpawnExperienceOrbPacket.java @@ -14,7 +14,10 @@ public class SpawnExperienceOrbPacket extends DataPacket { @Override public void decode() { - + this.x = this.getLFloat(); + this.y = this.getLFloat(); + this.z = this.getLFloat(); + this.amount = (int) this.getUnsignedVarInt(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/SpawnParticleEffectPacket.java b/src/main/java/cn/nukkit/network/protocol/SpawnParticleEffectPacket.java index 3f20056224e..1c34b2e44cb 100644 --- a/src/main/java/cn/nukkit/network/protocol/SpawnParticleEffectPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SpawnParticleEffectPacket.java @@ -3,14 +3,18 @@ import cn.nukkit.math.Vector3f; import lombok.ToString; +import java.util.Optional; + @ToString public class SpawnParticleEffectPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SPAWN_PARTICLE_EFFECT_PACKET; public int dimensionId; public long uniqueEntityId = -1; public Vector3f position; public String identifier; + public Optional molangVariablesJson = Optional.empty(); @Override public byte pid() { @@ -19,6 +23,7 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override @@ -28,6 +33,7 @@ public void encode() { this.putEntityUniqueId(uniqueEntityId); this.putVector3f(this.position); this.putString(this.identifier); - this.putBoolean(false); + this.putBoolean(this.molangVariablesJson.isPresent()); + this.molangVariablesJson.ifPresent(this::putString); } } diff --git a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java index 64c7d6c59df..1659a5d3302 100644 --- a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java @@ -1,19 +1,21 @@ package cn.nukkit.network.protocol; +import cn.nukkit.customblock.CustomBlockDefinition; +import cn.nukkit.customblock.CustomBlockManager; import cn.nukkit.item.RuntimeItems; import cn.nukkit.level.GameRules; +import cn.nukkit.network.protocol.types.ExperimentData; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.ToString; -import lombok.extern.log4j.Log4j2; import java.io.IOException; import java.util.UUID; -/** - * Created on 15-10-13. - */ -@Log4j2 +import java.util.Collection; +import java.util.List; + @ToString public class StartGamePacket extends DataPacket { @@ -48,36 +50,36 @@ public byte pid() { public int spawnZ; public boolean hasAchievementsDisabled = true; public boolean worldEditor; - public int dayCycleStopTime = -1; //-1 = not stopped, any positive value = stopped at that time + public int dayCycleStopTime = -1; // -1 = not stopped, any positive value = stopped public int eduEditionOffer = 0; - public boolean hasEduFeaturesEnabled = false; + public boolean hasEduFeaturesEnabled; public float rainLevel; public float lightningLevel; - public boolean hasConfirmedPlatformLockedContent = false; + public boolean hasConfirmedPlatformLockedContent; public boolean multiplayerGame = true; public boolean broadcastToLAN = true; public int xblBroadcastIntent = GAME_PUBLISH_SETTING_PUBLIC; public int platformBroadcastIntent = GAME_PUBLISH_SETTING_PUBLIC; public boolean commandsEnabled; - public boolean isTexturePacksRequired = false; + public boolean isTexturePacksRequired; public GameRules gameRules; - public boolean bonusChest = false; - public boolean hasStartWithMapEnabled = false; + public boolean bonusChest; + public boolean hasStartWithMapEnabled; public int permissionLevel = 1; public int serverChunkTickRange = 4; - public boolean hasLockedBehaviorPack = false; - public boolean hasLockedResourcePack = false; - public boolean isFromLockedWorldTemplate = false; - public boolean isUsingMsaGamertagsOnly = false; - public boolean isFromWorldTemplate = false; - public boolean isWorldTemplateOptionLocked = false; - public boolean isOnlySpawningV1Villagers = false; + public boolean hasLockedBehaviorPack; + public boolean hasLockedResourcePack; + public boolean isFromLockedWorldTemplate; + public boolean isUsingMsaGamertagsOnly; + public boolean isFromWorldTemplate; + public boolean isWorldTemplateOptionLocked; + public boolean isOnlySpawningV1Villagers; public String vanillaVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK; - public String levelId = ""; //base64 string, usually the same as world folder name in vanilla + public String levelId = ""; // base64 string, usually the same as world folder name in vanilla public String worldName; public String premiumWorldTemplateId = ""; - public boolean isTrial = false; - public boolean isMovementServerAuthoritative; + public boolean isTrial; + public boolean isMovementServerAuthoritative = true; public boolean isInventoryServerAuthoritative; public long currentTick; public int enchantmentSeed; @@ -88,10 +90,12 @@ public byte pid() { public byte chatRestrictionLevel; public boolean disablePlayerInteractions; public boolean emoteChatMuted; + public Collection blockDefinitions = CustomBlockManager.get().getBlockDefinitions(); + public final List experiments = new ObjectArrayList<>(); @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -130,8 +134,7 @@ public void encode() { this.putBoolean(this.commandsEnabled); this.putBoolean(this.isTexturePacksRequired); this.putGameRules(this.gameRules); - this.putLInt(0); // Experiment count - this.putBoolean(false); // Were experiments previously toggled + this.putExperiments(this.experiments); this.putBoolean(this.bonusChest); this.putBoolean(this.hasStartWithMapEnabled); this.putVarInt(this.permissionLevel); @@ -146,12 +149,13 @@ public void encode() { this.putBoolean(this.isDisablingPersonas); this.putBoolean(this.isDisablingCustomSkins); this.putBoolean(this.emoteChatMuted); - this.putString(this.vanillaVersion); + this.putString(ProtocolInfo.MINECRAFT_VERSION_NETWORK); this.putLInt(16); // Limited world width this.putLInt(16); // Limited world height this.putBoolean(false); // Nether type - this.putString(""); // EduSharedUriResource buttonName - this.putString(""); // EduSharedUriResource linkUri + // EduSharedUriResource + this.putString(""); // buttonName + this.putString(""); // linkUri this.putBoolean(false); // Experimental Gameplay this.putByte(this.chatRestrictionLevel); this.putBoolean(this.disablePlayerInteractions); @@ -165,11 +169,19 @@ public void encode() { this.putBoolean(true); // isServerAuthoritativeBlockBreaking this.putLLong(this.currentTick); this.putVarInt(this.enchantmentSeed); - this.putUnsignedVarInt(0); // Custom blocks + if (this.blockDefinitions != null && !this.blockDefinitions.isEmpty()) { + this.putUnsignedVarInt(this.blockDefinitions.size()); + for (CustomBlockDefinition definition : this.blockDefinitions) { + this.putString(definition.getIdentifier()); + this.putNbtTag(definition.getNetworkData()); + } + } else { + this.putUnsignedVarInt(0); // No custom blocks + } this.put(RuntimeItems.getMapping().getItemPalette()); this.putString(this.multiplayerCorrelationId); - this.putBoolean(this.isInventoryServerAuthoritative); - this.putString(""); // Server Engine + this.putBoolean(false); // isInventoryServerAuthoritative + this.putString(""); // serverEngine try { this.put(NBTIO.writeNetwork(new CompoundTag(""))); // playerPropertyData } catch (IOException e) { @@ -179,6 +191,6 @@ public void encode() { this.putUUID(new UUID(0, 0)); // worldTemplateId this.putBoolean(this.clientSideGenerationEnabled); this.putBoolean(false); // blockIdsAreHashed - this.putBoolean(false); // serverAuthSounds + this.putBoolean(true); // isServerAuthSounds } } diff --git a/src/main/java/cn/nukkit/network/protocol/StopSoundPacket.java b/src/main/java/cn/nukkit/network/protocol/StopSoundPacket.java index 3474b3ac885..fef0a74e8ae 100644 --- a/src/main/java/cn/nukkit/network/protocol/StopSoundPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StopSoundPacket.java @@ -17,7 +17,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/StructureBlockUpdatePacket.java b/src/main/java/cn/nukkit/network/protocol/StructureBlockUpdatePacket.java index 11bfde67133..44f47b98423 100644 --- a/src/main/java/cn/nukkit/network/protocol/StructureBlockUpdatePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StructureBlockUpdatePacket.java @@ -5,18 +5,20 @@ @ToString public class StructureBlockUpdatePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.STRUCTURE_BLOCK_UPDATE_PACKET; + @Override public byte pid() { - return ProtocolInfo.STRUCTURE_BLOCK_UPDATE_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { - //TODO + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/SubClientLoginPacket.java b/src/main/java/cn/nukkit/network/protocol/SubClientLoginPacket.java index c8e94b2a43c..578c18c693b 100644 --- a/src/main/java/cn/nukkit/network/protocol/SubClientLoginPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SubClientLoginPacket.java @@ -5,18 +5,20 @@ @ToString public class SubClientLoginPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.SUB_CLIENT_LOGIN_PACKET; + @Override public byte pid() { - return ProtocolInfo.SUB_CLIENT_LOGIN_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override public void encode() { - //TODO + this.encodeUnsupported(); } } diff --git a/src/main/java/cn/nukkit/network/protocol/TakeItemEntityPacket.java b/src/main/java/cn/nukkit/network/protocol/TakeItemEntityPacket.java index a5eec2a4652..608a8d75083 100644 --- a/src/main/java/cn/nukkit/network/protocol/TakeItemEntityPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/TakeItemEntityPacket.java @@ -2,9 +2,6 @@ import lombok.ToString; -/** - * Created on 15-10-14. - */ @ToString public class TakeItemEntityPacket extends DataPacket { @@ -15,8 +12,7 @@ public class TakeItemEntityPacket extends DataPacket { @Override public void decode() { - this.target = this.getEntityRuntimeId(); - this.entityId = this.getEntityRuntimeId(); + this.decodeUnsupported(); } @Override @@ -30,5 +26,4 @@ public void encode() { public byte pid() { return NETWORK_ID; } - } diff --git a/src/main/java/cn/nukkit/network/protocol/TextPacket.java b/src/main/java/cn/nukkit/network/protocol/TextPacket.java index b53083a8581..c108635af4b 100644 --- a/src/main/java/cn/nukkit/network/protocol/TextPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/TextPacket.java @@ -1,11 +1,7 @@ package cn.nukkit.network.protocol; -import cn.nukkit.utils.BinaryStream; import lombok.ToString; -/** - * Created on 15-10-13. - */ @ToString public class TextPacket extends DataPacket { @@ -57,7 +53,11 @@ public void decode() { case TYPE_POPUP: case TYPE_JUKEBOX_POPUP: this.message = this.getString(); - this.parameters = this.getArray(String.class, BinaryStream::getString); + int paramCount = (int) this.getUnsignedVarInt(); + this.parameters = new String[Math.min(paramCount, 128)]; + for (int i = 0; i < this.parameters.length; i++) { + this.parameters[i] = this.getString(); + } } this.xboxUserId = this.getString(); this.platformChatId = this.getString(); diff --git a/src/main/java/cn/nukkit/network/protocol/ToastRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/ToastRequestPacket.java index 81ae08556d6..834171b7433 100644 --- a/src/main/java/cn/nukkit/network/protocol/ToastRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ToastRequestPacket.java @@ -5,18 +5,19 @@ @ToString public class ToastRequestPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.TOAST_REQUEST_PACKET; + public String title = ""; public String content = ""; @Override public byte pid() { - return ProtocolInfo.TOAST_REQUEST_PACKET; + return NETWORK_ID; } @Override public void decode() { - this.title = this.getString(); - this.content = this.getString(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/TransferPacket.java b/src/main/java/cn/nukkit/network/protocol/TransferPacket.java index d791ca2bc15..dabde70e67f 100644 --- a/src/main/java/cn/nukkit/network/protocol/TransferPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/TransferPacket.java @@ -4,15 +4,15 @@ @ToString public class TransferPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.TRANSFER_PACKET; - public String address; // Server address - public int port = 19132; // Server port + public String address; + public int port = 19132; @Override public void decode() { - this.address = this.getString(); - this.port = (short) this.getLShort(); + this.decodeUnsupported(); } @Override @@ -24,6 +24,6 @@ public void encode() { @Override public byte pid() { - return ProtocolInfo.TRANSFER_PACKET; + return NETWORK_ID; } } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java index 28743aaa1a4..c2f35ad37b1 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java @@ -17,6 +17,8 @@ @Setter public class UpdateAbilitiesPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_ABILITIES_PACKET; + protected static final PlayerAbility[] VALID_FLAGS = { PlayerAbility.BUILD, PlayerAbility.MINE, @@ -38,6 +40,7 @@ public class UpdateAbilitiesPacket extends DataPacket { PlayerAbility.NO_CLIP, PlayerAbility.PRIVILEGED_BUILDER }; + private static final EnumMap FLAGS_TO_BITS = new EnumMap<>(PlayerAbility.class); static { @@ -53,7 +56,7 @@ public class UpdateAbilitiesPacket extends DataPacket { @Override public void decode() { - throw new UnsupportedOperationException(); + this.decodeUnsupported(); } @Override @@ -83,7 +86,7 @@ private static int getAbilitiesNumber(Set abilities) { @Override public byte pid() { - return ProtocolInfo.UPDATE_ABILITIES_PACKET; + return NETWORK_ID; } public enum PlayerPermission { diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateAdventureSettingsPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateAdventureSettingsPacket.java index 634f57b1cf5..872ff0c4c50 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateAdventureSettingsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateAdventureSettingsPacket.java @@ -8,6 +8,9 @@ @Getter @Setter public class UpdateAdventureSettingsPacket extends DataPacket { + + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_ADVENTURE_SETTINGS_PACKET; + private boolean noPvM; private boolean noMvP; private boolean immutableWorld; @@ -16,7 +19,7 @@ public class UpdateAdventureSettingsPacket extends DataPacket { @Override public void decode() { - throw new UnsupportedOperationException(); + this.decodeUnsupported(); } @Override @@ -31,6 +34,6 @@ public void encode() { @Override public byte pid() { - return ProtocolInfo.UPDATE_ADVENTURE_SETTINGS_PACKET; + return NETWORK_ID; } } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java index f3027ae9b64..c91d59165bf 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateAttributesPacket.java @@ -20,10 +20,12 @@ public byte pid() { return NETWORK_ID; } + @Override public void decode() { - + this.decodeUnsupported(); } + @Override public void encode() { this.reset(); @@ -39,10 +41,11 @@ public void encode() { this.putLFloat(entry.getValue()); this.putLFloat(entry.getDefaultValue()); this.putString(entry.getName()); + this.putUnsignedVarInt(0); // Modifiers } } + this.putUnsignedVarInt(this.frame); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateBlockPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateBlockPacket.java index 94748131473..a15dbf06ef6 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateBlockPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateBlockPacket.java @@ -1,14 +1,14 @@ package cn.nukkit.network.protocol; - import lombok.ToString; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ @ToString public class UpdateBlockPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_BLOCK_PACKET; public static final int FLAG_NONE = 0b0000; @@ -34,7 +34,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateEquipmentPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateEquipmentPacket.java index 6280ea29aa0..6a6747394d8 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateEquipmentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateEquipmentPacket.java @@ -5,21 +5,23 @@ @ToString(exclude = "namedtag") public class UpdateEquipmentPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_EQUIPMENT_PACKET; + public int windowId; public int windowType; - public int unknown; //TODO: find out what this is (vanilla always sends 0) + public int unknown; public long eid; public byte[] namedtag; @Override public byte pid() { - return ProtocolInfo.UPDATE_EQUIPMENT_PACKET; + return NETWORK_ID; } @Override public void decode() { - + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/UpdatePlayerGameTypePacket.java b/src/main/java/cn/nukkit/network/protocol/UpdatePlayerGameTypePacket.java index a85dca66949..85480fba231 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdatePlayerGameTypePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdatePlayerGameTypePacket.java @@ -4,6 +4,7 @@ @ToString public class UpdatePlayerGameTypePacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_PLAYER_GAME_TYPE_PACKET; public GameType gameType; @@ -16,8 +17,7 @@ public byte pid() { @Override public void decode() { - this.gameType = GameType.from(this.getVarInt()); - this.entityId = this.getVarLong(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateSoftEnumPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateSoftEnumPacket.java index c2629cbd547..3e9fd8842f7 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateSoftEnumPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateSoftEnumPacket.java @@ -5,17 +5,20 @@ @ToString public class UpdateSoftEnumPacket extends DataPacket { + public static final byte NETWORK_ID = ProtocolInfo.UPDATE_SOFT_ENUM_PACKET; + public final String[] values = new String[0]; public String name = ""; public Type type = Type.SET; @Override public byte pid() { - return ProtocolInfo.UPDATE_SOFT_ENUM_PACKET; + return NETWORK_ID; } @Override public void decode() { + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java index b93bd97d6d0..10b24244120 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateTradePacket.java @@ -8,8 +8,10 @@ public class UpdateTradePacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.UPDATE_TRADE_PACKET; public byte windowId; - public byte windowType = 15; //trading id - public int unknownVarInt1; // hardcoded to 0 + public byte windowType = 15; + public int unknownVarInt1; + public int unknownVarInt2; + public int unknownVarInt3; public int tradeTier; public long trader; public long player; @@ -25,7 +27,7 @@ public byte pid() { @Override public void decode() { - + this.decodeUnsupported(); } @Override @@ -42,5 +44,4 @@ public void encode() { this.putBoolean(isWilling); this.put(this.offers); } - } diff --git a/src/main/java/cn/nukkit/network/protocol/VideoStreamConnectPacket.java b/src/main/java/cn/nukkit/network/protocol/VideoStreamConnectPacket.java index 5f1b67c5f3a..9a63f45ac58 100644 --- a/src/main/java/cn/nukkit/network/protocol/VideoStreamConnectPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/VideoStreamConnectPacket.java @@ -21,10 +21,12 @@ public byte pid() { @Override public void decode() { + this.decodeUnsupported(); } @Override public void encode() { + this.reset(); this.putString(address); this.putLFloat(screenshotFrequency); this.putByte(action); diff --git a/src/main/java/cn/nukkit/network/protocol/types/AbilityLayer.java b/src/main/java/cn/nukkit/network/protocol/types/AbilityLayer.java index b1b43e75ab8..49b472e88ca 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/AbilityLayer.java +++ b/src/main/java/cn/nukkit/network/protocol/types/AbilityLayer.java @@ -7,6 +7,7 @@ @Data public class AbilityLayer { + private Type layerType; private final Set abilitiesSet = EnumSet.noneOf(PlayerAbility.class); private final Set abilityValues = EnumSet.noneOf(PlayerAbility.class); diff --git a/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java b/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java index 4e9303bac54..e9e6b0101d9 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java @@ -1,6 +1,7 @@ package cn.nukkit.network.protocol.types; public enum AuthInputAction { + ASCEND, DESCEND, NORTH_JUMP, diff --git a/src/main/java/cn/nukkit/network/protocol/types/AuthInteractionModel.java b/src/main/java/cn/nukkit/network/protocol/types/AuthInteractionModel.java index 0ed43728ce6..3ce41575fba 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/AuthInteractionModel.java +++ b/src/main/java/cn/nukkit/network/protocol/types/AuthInteractionModel.java @@ -1,6 +1,7 @@ package cn.nukkit.network.protocol.types; public enum AuthInteractionModel { + TOUCH, CROSSHAIR, CLASSIC; diff --git a/src/main/java/cn/nukkit/network/protocol/types/ClientPlayMode.java b/src/main/java/cn/nukkit/network/protocol/types/ClientPlayMode.java index f68c998bac7..0766890d791 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/ClientPlayMode.java +++ b/src/main/java/cn/nukkit/network/protocol/types/ClientPlayMode.java @@ -1,6 +1,7 @@ package cn.nukkit.network.protocol.types; public enum ClientPlayMode { + NORMAL, TEASER, SCREEN, diff --git a/src/main/java/cn/nukkit/network/protocol/types/CommandOriginData.java b/src/main/java/cn/nukkit/network/protocol/types/CommandOriginData.java index fa0a73ea810..e00ab1edfae 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/CommandOriginData.java +++ b/src/main/java/cn/nukkit/network/protocol/types/CommandOriginData.java @@ -1,6 +1,5 @@ package cn.nukkit.network.protocol.types; - import lombok.ToString; import java.util.OptionalLong; @@ -12,6 +11,7 @@ */ @ToString public final class CommandOriginData { + public final Origin type; public final UUID uuid; public final String requestId; diff --git a/src/main/java/cn/nukkit/network/protocol/types/DimensionDefinition.java b/src/main/java/cn/nukkit/network/protocol/types/DimensionDefinition.java new file mode 100644 index 00000000000..29256fd517d --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/types/DimensionDefinition.java @@ -0,0 +1,12 @@ +package cn.nukkit.network.protocol.types; + +import lombok.Value; + +@Value +public class DimensionDefinition { + + String id; + int maximumHeight; + int minimumHeight; + int generatorType; +} diff --git a/src/main/java/cn/nukkit/network/protocol/types/EntityLink.java b/src/main/java/cn/nukkit/network/protocol/types/EntityLink.java index 88c41565058..f37424aa07c 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/EntityLink.java +++ b/src/main/java/cn/nukkit/network/protocol/types/EntityLink.java @@ -19,4 +19,4 @@ public EntityLink(long fromEntityUniquieId, long toEntityUniquieId, byte type, b this.immediate = immediate; this.riderInitiated = riderInitiated; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/protocol/types/ExperimentData.java b/src/main/java/cn/nukkit/network/protocol/types/ExperimentData.java new file mode 100644 index 00000000000..bfb2053d0dd --- /dev/null +++ b/src/main/java/cn/nukkit/network/protocol/types/ExperimentData.java @@ -0,0 +1,9 @@ +package cn.nukkit.network.protocol.types; + +import lombok.Data; + +@Data +public class ExperimentData { + private final String name; + private final boolean enabled; +} diff --git a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java index 49943e718e4..78f9c6cd03d 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java @@ -1,14 +1,14 @@ package cn.nukkit.network.protocol.types; import cn.nukkit.Player; -import cn.nukkit.inventory.AnvilInventory; -import cn.nukkit.inventory.BeaconInventory; -import cn.nukkit.inventory.EnchantInventory; -import cn.nukkit.inventory.Inventory; -import cn.nukkit.inventory.PlayerUIComponent; +import cn.nukkit.block.BlockID; +import cn.nukkit.inventory.*; import cn.nukkit.inventory.transaction.action.*; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.network.protocol.InventoryTransactionPacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import lombok.ToString; import java.util.Optional; @@ -108,6 +108,7 @@ public NetworkInventoryAction read(InventoryTransactionPacket packet) { this.inventorySlot = (int) packet.getUnsignedVarInt(); this.oldItem = packet.getSlot(); this.newItem = packet.getSlot(); + return this; } @@ -116,8 +117,6 @@ public void write(InventoryTransactionPacket packet) { switch (this.sourceType) { case SOURCE_CONTAINER: - case SOURCE_CRAFT_SLOT: - case SOURCE_TODO: packet.putVarInt(this.windowId); break; case SOURCE_WORLD: @@ -125,6 +124,10 @@ public void write(InventoryTransactionPacket packet) { break; case SOURCE_CREATIVE: break; + case SOURCE_CRAFT_SLOT: + case SOURCE_TODO: + packet.putVarInt(this.windowId); + break; } packet.putUnsignedVarInt(this.inventorySlot); @@ -132,26 +135,36 @@ public void write(InventoryTransactionPacket packet) { packet.putSlot(this.newItem); } + private static final IntSet validSmithingEquipment = new IntOpenHashSet(new int[]{Item.AIR, ItemID.DIAMOND_SWORD, ItemID.DIAMOND_SHOVEL, ItemID.DIAMOND_PICKAXE, ItemID.DIAMOND_AXE, ItemID.DIAMOND_HOE, ItemID.DIAMOND_HELMET, ItemID.DIAMOND_CHESTPLATE, ItemID.DIAMOND_LEGGINGS, ItemID.DIAMOND_BOOTS}); + public InventoryAction createInventoryAction(Player player) { switch (this.sourceType) { case SOURCE_CONTAINER: if (this.windowId == ContainerIds.ARMOR) { - //TODO: HACK! this.inventorySlot += 36; this.windowId = ContainerIds.INVENTORY; + if (this.newItem == null || + (this.inventorySlot == 36 && !(this.newItem.canBePutInHelmetSlot() || this.newItem.getId() == (255 - BlockID.CARVED_PUMPKIN)) && !(this.oldItem.canBePutInHelmetSlot() || this.oldItem.getId() == (255 - BlockID.CARVED_PUMPKIN))) || + (this.inventorySlot == 37 && !this.newItem.isChestplate() && !this.oldItem.isChestplate()) || + (this.inventorySlot == 38 && !this.newItem.isLeggings() && !this.oldItem.isLeggings()) || + (this.inventorySlot == 39 && !this.newItem.isBoots()) && !this.oldItem.isBoots()) { + player.getServer().getLogger().warning(player.getName() + " tried to set an invalid armor item"); + return null; + } } + // ID 124 with slot 14/15 is enchant inventory if (this.windowId == ContainerIds.UI) { switch (this.inventorySlot) { case PlayerUIComponent.CREATED_ITEM_OUTPUT_UI_SLOT: - if (player.getWindowById(Player.ANVIL_WINDOW_ID) != null) { + if (player.getWindowById(Player.ANVIL_WINDOW_ID) instanceof AnvilInventory) { this.windowId = Player.ANVIL_WINDOW_ID; this.inventorySlot = 2; } break; case EnchantInventory.ENCHANT_INPUT_ITEM_UI_SLOT: - if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); + if (!(player.getWindowById(Player.ENCHANT_WINDOW_ID) instanceof EnchantInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have enchant window open"); return null; } this.windowId = Player.ENCHANT_WINDOW_ID; @@ -159,29 +172,53 @@ public InventoryAction createInventoryAction(Player player) { // TODO, check if unenchanted item and send EnchantOptionsPacket break; case EnchantInventory.ENCHANT_REAGENT_UI_SLOT: - if (player.getWindowById(Player.ENCHANT_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have enchant window open"); + if (!(player.getWindowById(Player.ENCHANT_WINDOW_ID) instanceof EnchantInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have enchant window open"); return null; } this.windowId = Player.ENCHANT_WINDOW_ID; this.inventorySlot = 1; break; case AnvilInventory.ANVIL_INPUT_UI_SLOT: - if (player.getWindowById(Player.ANVIL_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have anvil window open"); + if (!(player.getWindowById(Player.ANVIL_WINDOW_ID) instanceof AnvilInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have anvil window open"); return null; } this.windowId = Player.ANVIL_WINDOW_ID; this.inventorySlot = 0; break; case AnvilInventory.ANVIL_MATERIAL_UI_SLOT: - if (player.getWindowById(Player.ANVIL_WINDOW_ID) == null) { - player.getServer().getLogger().error("Player " + player.getName() + " does not have anvil window open"); + if (!(player.getWindowById(Player.ANVIL_WINDOW_ID) instanceof AnvilInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have anvil window open"); return null; } this.windowId = Player.ANVIL_WINDOW_ID; this.inventorySlot = 1; break; + case SmithingInventory.SMITHING_EQUIPMENT_UI_SLOT: + if (!(player.getWindowById(Player.SMITHING_WINDOW_ID) instanceof SmithingInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have smithing table window open"); + return null; + } + if (!(this.oldItem == null || validSmithingEquipment.contains(this.oldItem.getId())) || !(this.newItem == null || validSmithingEquipment.contains(this.oldItem.getId()))) { + player.getServer().getLogger().debug(player.getName() + " had invalid smithing equipment"); + return null; + } + this.windowId = Player.SMITHING_WINDOW_ID; + this.inventorySlot = 0; + break; + case SmithingInventory.SMITHING_INGREDIENT_UI_SLOT: + if (!(player.getWindowById(Player.SMITHING_WINDOW_ID) instanceof SmithingInventory)) { + player.getServer().getLogger().debug(player.getName() + " does not have smithing table window open"); + return null; + } + if (!(this.oldItem == null || this.oldItem.getId() == 0 || this.oldItem.getId() == ItemID.NETHERITE_INGOT) || !(this.newItem == null || this.newItem.getId() == 0 || this.newItem.getId() == ItemID.NETHERITE_INGOT)) { + player.getServer().getLogger().debug(player.getName() + " had invalid smithing ingredient"); + return null; + } + this.windowId = Player.SMITHING_WINDOW_ID; + this.inventorySlot = 1; + break; } } @@ -190,7 +227,7 @@ public InventoryAction createInventoryAction(Player player) { return new SlotChangeAction(window, this.inventorySlot, this.oldItem, this.newItem); } - player.getServer().getLogger().debug("Player " + player.getName() + " has no open container with window ID " + this.windowId); + player.getServer().getLogger().debug(player.getName() + " has no open container with window ID " + this.windowId); return null; case SOURCE_WORLD: if (this.inventorySlot != InventoryTransactionPacket.ACTION_MAGIC_SLOT_DROP_ITEM) { @@ -217,7 +254,6 @@ public InventoryAction createInventoryAction(Player player) { return new CreativeInventoryAction(this.oldItem, this.newItem, type); case SOURCE_CRAFT_SLOT: case SOURCE_TODO: - //These types need special handling. switch (this.windowId) { case SOURCE_TYPE_CRAFTING_ADD_INGREDIENT: case SOURCE_TYPE_CRAFTING_REMOVE_INGREDIENT: @@ -232,17 +268,37 @@ public InventoryAction createInventoryAction(Player player) { case SOURCE_TYPE_CRAFTING_RESULT: return new CraftingTakeResultAction(this.oldItem, this.newItem); case SOURCE_TYPE_CRAFTING_USE_INGREDIENT: + Inventory inv = player.getWindowById(Player.LOOM_WINDOW_ID); + if (inv instanceof LoomInventory) { + LoomInventory loomInventory = (LoomInventory) inv; + return new LoomItemAction(this.oldItem, this.newItem, loomInventory); + } return new CraftingTransferMaterialAction(this.oldItem, this.newItem, this.inventorySlot); } - if (this.windowId >= SOURCE_TYPE_ANVIL_OUTPUT && this.windowId <= SOURCE_TYPE_ANVIL_INPUT) { //anvil actions + if (this.windowId >= SOURCE_TYPE_ANVIL_OUTPUT && this.windowId <= SOURCE_TYPE_ANVIL_INPUT) { Inventory inv = player.getWindowById(Player.ANVIL_WINDOW_ID); if (!(inv instanceof AnvilInventory)) { - player.getServer().getLogger().debug("Player " + player.getName() + " has no open anvil inventory"); + // Hack: Fix beacon payment // TODO: Better fix + if ((inv = player.getWindowById(Player.BEACON_WINDOW_ID)) instanceof BeaconInventory) { + inv.setItem(0, Item.get(Item.AIR)); + return null; + } + + if (player.getWindowById(Player.SMITHING_WINDOW_ID) instanceof SmithingInventory) { + switch (this.windowId) { + case SOURCE_TYPE_ANVIL_INPUT: + case SOURCE_TYPE_ANVIL_MATERIAL: + case SOURCE_TYPE_ANVIL_OUTPUT: + case SOURCE_TYPE_ANVIL_RESULT: + return new SmithingItemAction(this.oldItem, this.newItem, this.inventorySlot); + } + } + + player.getServer().getLogger().debug(player.getName() + " has no open anvil inventory"); return null; } - AnvilInventory anvil = (AnvilInventory) inv; switch (this.windowId) { case SOURCE_TYPE_ANVIL_INPUT: @@ -251,14 +307,14 @@ public InventoryAction createInventoryAction(Player player) { return new RepairItemAction(this.oldItem, this.newItem, this.windowId); } - return new SlotChangeAction(anvil, this.inventorySlot, this.oldItem, this.newItem); + return new SlotChangeAction(inv, this.inventorySlot, this.oldItem, this.newItem); } if (this.windowId >= SOURCE_TYPE_ENCHANT_OUTPUT && this.windowId <= SOURCE_TYPE_ENCHANT_INPUT) { Inventory inv = player.getWindowById(Player.ENCHANT_WINDOW_ID); if (!(inv instanceof EnchantInventory)) { - player.getServer().getLogger().debug("Player " + player.getName() + " has no open enchant inventory"); + player.getServer().getLogger().debug(player.getName() + " has no open enchant inventory"); return null; } EnchantInventory enchant = (EnchantInventory) inv; @@ -279,7 +335,7 @@ public InventoryAction createInventoryAction(Player player) { Inventory inv = player.getWindowById(Player.BEACON_WINDOW_ID); if (!(inv instanceof BeaconInventory)) { - player.getServer().getLogger().debug("Player " + player.getName() + " has no open beacon inventory"); + player.getServer().getLogger().debug(player.getName() + " has no open beacon inventory"); return null; } BeaconInventory beacon = (BeaconInventory) inv; @@ -288,8 +344,7 @@ public InventoryAction createInventoryAction(Player player) { return new SlotChangeAction(beacon, this.inventorySlot, this.oldItem, this.newItem); } - //TODO: more stuff - player.getServer().getLogger().debug("Player " + player.getName() + " has no open container with window ID " + this.windowId); + player.getServer().getLogger().debug(player.getName() + " has no open container with window ID " + this.windowId); return null; default: player.getServer().getLogger().debug("Unknown inventory source type " + this.sourceType); diff --git a/src/main/java/cn/nukkit/network/protocol/types/PacketCompressionAlgorithm.java b/src/main/java/cn/nukkit/network/protocol/types/PacketCompressionAlgorithm.java index c7eb3c67265..1f9b0fe99c3 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/PacketCompressionAlgorithm.java +++ b/src/main/java/cn/nukkit/network/protocol/types/PacketCompressionAlgorithm.java @@ -1,6 +1,7 @@ package cn.nukkit.network.protocol.types; public enum PacketCompressionAlgorithm { + ZLIB, SNAPPY } diff --git a/src/main/java/cn/nukkit/network/protocol/types/PlayerAbility.java b/src/main/java/cn/nukkit/network/protocol/types/PlayerAbility.java index 3b5b5c95201..0736937aa16 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/PlayerAbility.java +++ b/src/main/java/cn/nukkit/network/protocol/types/PlayerAbility.java @@ -4,6 +4,7 @@ import java.util.List; public enum PlayerAbility { + BUILD, MINE, DOORS_AND_SWITCHES, diff --git a/src/main/java/cn/nukkit/network/protocol/types/PlayerBlockActionData.java b/src/main/java/cn/nukkit/network/protocol/types/PlayerBlockActionData.java index 8e46210db9f..414cc949d4b 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/PlayerBlockActionData.java +++ b/src/main/java/cn/nukkit/network/protocol/types/PlayerBlockActionData.java @@ -7,6 +7,7 @@ @Data @AllArgsConstructor public class PlayerBlockActionData { + private PlayerActionType action; private BlockVector3 position; private int facing; diff --git a/src/main/java/cn/nukkit/network/query/QueryHandler.java b/src/main/java/cn/nukkit/network/query/QueryHandler.java index d9b45fb9c06..54ef0a36932 100644 --- a/src/main/java/cn/nukkit/network/query/QueryHandler.java +++ b/src/main/java/cn/nukkit/network/query/QueryHandler.java @@ -2,6 +2,7 @@ import cn.nukkit.Server; import cn.nukkit.event.server.QueryRegenerateEvent; +import cn.nukkit.utils.Utils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; @@ -12,11 +13,9 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class QueryHandler { @@ -33,6 +32,7 @@ public class QueryHandler { public QueryHandler() { this.server = Server.getInstance(); + this.server.getLogger().info(this.server.getLanguage().translateString("nukkit.server.query.start")); String ip = this.server.getIp(); String addr = (!ip.isEmpty()) ? ip : "0.0.0.0"; @@ -47,8 +47,8 @@ public QueryHandler() { public void regenerateInfo() { QueryRegenerateEvent ev = this.server.getQueryInformation(); - this.longData = ev.getLongQuery(this.longData); - this.shortData = ev.getShortQuery(this.shortData); + this.longData = ev.getLongQuery(); + this.shortData = ev.getShortQuery(); this.timeout = System.currentTimeMillis() + ev.getTimeout(); } @@ -56,7 +56,7 @@ public void regenerateToken() { this.lastToken = this.token; byte[] token = new byte[16]; for (int i = 0; i < 16; i++) { - token[i] = (byte) new Random().nextInt(255); + token[i] = (byte) Utils.random.nextInt(255); } this.token = token; } @@ -65,6 +65,7 @@ public static byte[] getTokenString(String token, InetAddress address) { return getTokenString(token.getBytes(StandardCharsets.UTF_8), address); } + public static byte[] getTokenString(byte[] token, InetAddress address) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); @@ -72,7 +73,7 @@ public static byte[] getTokenString(byte[] token, InetAddress address) { digest.update(token); return Arrays.copyOf(digest.digest(), 4); } catch (NoSuchAlgorithmException e) { - return ByteBuffer.allocate(4).putInt(ThreadLocalRandom.current().nextInt()).array(); + return ByteBuffer.allocate(4).putInt(Utils.random.nextInt()).array(); } } @@ -102,7 +103,8 @@ public void handle(InetSocketAddress address, ByteBuf packet) { if (this.timeout < System.currentTimeMillis()) { this.regenerateInfo(); } - reply = ByteBufAllocator.DEFAULT.ioBuffer(64); + + reply = ByteBufAllocator.DEFAULT.directBuffer(64); reply.writeByte(STATISTICS); reply.writeInt(sessionId); if (packet.readableBytes() == 8) { diff --git a/src/main/java/cn/nukkit/network/rcon/RCON.java b/src/main/java/cn/nukkit/network/rcon/RCON.java index 47438a8f595..f9dd325cced 100644 --- a/src/main/java/cn/nukkit/network/rcon/RCON.java +++ b/src/main/java/cn/nukkit/network/rcon/RCON.java @@ -10,18 +10,19 @@ /** * Implementation of Source RCON protocol. * https://developer.valvesoftware.com/wiki/Source_RCON_Protocol - *

+ * * Wrapper for RCONServer. Handles data. * * @author Tee7even */ public class RCON { + private final Server server; private final RCONServer serverThread; public RCON(Server server, String password, String address, int port) { if (password.isEmpty()) { - throw new IllegalArgumentException("nukkit.server.rcon.emptyPasswordError"); + throw new IllegalArgumentException(server.getLanguage().translateString("nukkit.server.rcon.emptyPasswordError")); } this.server = server; @@ -30,10 +31,11 @@ public RCON(Server server, String password, String address, int port) { this.serverThread = new RCONServer(address, port, password); this.serverThread.start(); } catch (IOException e) { - throw new IllegalArgumentException("nukkit.server.rcon.startupError", e); + throw new IllegalArgumentException(server.getLanguage().translateString("nukkit.server.rcon.startupError"), e); } - this.server.getLogger().info(this.server.getLanguage().translateString("nukkit.server.rcon.running", new String[]{address, String.valueOf(port)})); + server.getLogger().info(server.getLanguage().translateString("nukkit.server.rcon.running", new String[]{address, String.valueOf(port)})); + server.getLogger().warning("RCON is not secure! Please consider using other remote control solutions or at least make sure RCON is running behind a firewall."); } public void check() { @@ -54,6 +56,7 @@ public void check() { } this.serverThread.respond(command.getSender(), command.getId(), TextFormat.clean(sender.getMessages())); + sender.clearMessages(); } } @@ -63,8 +66,6 @@ public void close() { serverThread.close(); serverThread.wait(5000); } - } catch (InterruptedException exception) { - // - } + } catch (InterruptedException ignored) {} } } diff --git a/src/main/java/cn/nukkit/network/rcon/RCONCommand.java b/src/main/java/cn/nukkit/network/rcon/RCONCommand.java index a992cd35c24..b85eb5cbe62 100644 --- a/src/main/java/cn/nukkit/network/rcon/RCONCommand.java +++ b/src/main/java/cn/nukkit/network/rcon/RCONCommand.java @@ -8,6 +8,7 @@ * @author Tee7even */ public class RCONCommand { + private final SocketChannel sender; private final int id; private final String command; diff --git a/src/main/java/cn/nukkit/network/rcon/RCONPacket.java b/src/main/java/cn/nukkit/network/rcon/RCONPacket.java index afa057c2753..43cbca63b13 100644 --- a/src/main/java/cn/nukkit/network/rcon/RCONPacket.java +++ b/src/main/java/cn/nukkit/network/rcon/RCONPacket.java @@ -1,6 +1,7 @@ package cn.nukkit.network.rcon; import java.io.IOException; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -10,6 +11,7 @@ * @author Tee7even */ public class RCONPacket { + private final int id; private final int type; private final byte[] payload; @@ -23,6 +25,10 @@ public RCONPacket(int id, int type, byte[] payload) { public RCONPacket(ByteBuffer buffer) throws IOException { int size = buffer.getInt(); + if (size > 524288) { + throw new RuntimeException("Too big RCON packet: " + size); + } + this.id = buffer.getInt(); this.type = buffer.getInt(); this.payload = new byte[size - 10]; @@ -43,7 +49,8 @@ public ByteBuffer toBuffer() { buffer.put((byte) 0); buffer.put((byte) 0); - buffer.flip(); + //noinspection RedundantCast + ((Buffer) buffer).flip(); // do not remove the cast return buffer; } diff --git a/src/main/java/cn/nukkit/network/rcon/RCONServer.java b/src/main/java/cn/nukkit/network/rcon/RCONServer.java index 330db5abe84..946f4f1404e 100644 --- a/src/main/java/cn/nukkit/network/rcon/RCONServer.java +++ b/src/main/java/cn/nukkit/network/rcon/RCONServer.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.nio.Buffer; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -12,7 +13,7 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.*; /** @@ -21,6 +22,7 @@ * @author Tee7even */ public class RCONServer extends Thread { + private static final int SERVERDATA_AUTH = 3; private static final int SERVERDATA_AUTH_RESPONSE = 2; private static final int SERVERDATA_EXECCOMMAND = 2; @@ -28,10 +30,10 @@ public class RCONServer extends Thread { private volatile boolean running; - private ServerSocketChannel serverChannel; - private Selector selector; + private final ServerSocketChannel serverChannel; + private final Selector selector; - private String password; + private final String password; private final Set rconSessions = new HashSet<>(); private final List receiveQueue = new ArrayList<>(); @@ -101,8 +103,7 @@ public void run() { this.write(key); } } - } catch (BufferUnderflowException exception) { - //Corrupted packet, ignore + } catch (BufferUnderflowException ignored) { } catch (Exception exception) { Server.getInstance().getLogger().logException(exception); } @@ -145,7 +146,8 @@ private void read(SelectionKey key) throws IOException { return; } - buffer.flip(); + //noinspection RedundantCast + ((Buffer) buffer).flip(); // do not remove the cast this.handle(channel, new RCONPacket(buffer)); } @@ -154,12 +156,14 @@ private void handle(SocketChannel channel, RCONPacket packet) { case SERVERDATA_AUTH: byte[] payload = new byte[1]; - if (new String(packet.getPayload(), Charset.forName("UTF-8")).equals(this.password)) { + if (new String(packet.getPayload(), StandardCharsets.UTF_8).equals(this.password)) { this.rconSessions.add(channel); this.send(channel, new RCONPacket(packet.getId(), SERVERDATA_AUTH_RESPONSE, payload)); + try { Server.getInstance().getLogger().info("[RCON] " + channel.getRemoteAddress().toString() + " connected"); } catch (Exception ignored) {} return; } + try { Server.getInstance().getLogger().info("[RCON] Authentication failed for " + channel.getRemoteAddress().toString()); } catch (Exception ignored) {} this.send(channel, new RCONPacket(-1, SERVERDATA_AUTH_RESPONSE, payload)); break; case SERVERDATA_EXECCOMMAND: @@ -167,7 +171,7 @@ private void handle(SocketChannel channel, RCONPacket packet) { return; } - String command = new String(packet.getPayload(), Charset.forName("UTF-8")).trim(); + String command = new String(packet.getPayload(), StandardCharsets.UTF_8).trim(); synchronized (this.receiveQueue) { this.receiveQueue.add(new RCONCommand(channel, packet.getId(), command)); } @@ -213,4 +217,4 @@ private void send(SocketChannel channel, RCONPacket packet) { this.selector.wakeup(); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java index a091c76854c..50abfd475cd 100644 --- a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java @@ -58,7 +58,7 @@ public class RakNetPlayerSession implements NetworkPlayerSession, RakNetSessionL public RakNetPlayerSession(RakNetInterface server, RakNetServerSession session) { this.server = server; this.session = session; - this.tickFuture = session.getEventLoop().scheduleAtFixedRate(this::networkTick, 0, 50, TimeUnit.MILLISECONDS); + this.tickFuture = session.getEventLoop().scheduleAtFixedRate(this::networkTick, 0, 20, TimeUnit.MILLISECONDS); } @Override @@ -66,6 +66,15 @@ public void onEncapsulated(EncapsulatedPacket packet) { ByteBuf buffer = packet.getBuffer(); short packetId = buffer.readUnsignedByte(); if (packetId == 0xfe) { + int len = buffer.readableBytes(); + if (len > 12582912) { + Server.getInstance().getLogger().error("Received too big packet: " + len); + if (this.player != null) { + this.player.close("Too big packet"); + } + return; + } + byte[] packetBuffer; CompressionProvider compressionIn = CompressionProvider.NONE; @@ -95,7 +104,7 @@ public void onEncapsulated(EncapsulatedPacket packet) { buffer.readBytes(packetBuffer); try { - this.server.getNetwork().processBatch(packetBuffer, this.inbound, compressionIn); + this.server.getNetwork().processBatch(packetBuffer, this.inbound, compressionIn, this.player); } catch (Exception e) { this.disconnect("Sent malformed packet"); log.error("[{}] Unable to process batch packet", (this.player == null ? this.session.getAddress() : this.player.getName()), e); @@ -178,8 +187,8 @@ private void networkTick() { DataPacket packet; while ((packet = this.outbound.poll()) != null) { if (packet instanceof DisconnectPacket) { - BinaryStream batched = new BinaryStream(); byte[] buf = packet.getBuffer(); + BinaryStream batched = new BinaryStream(new byte[5 + buf.length]).reset(); batched.putUnsignedVarInt(buf.length); batched.put(buf); @@ -329,4 +338,4 @@ private byte[] generateTrailer(ByteBuf buf) { hash.reset(); } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/permission/BanEntry.java b/src/main/java/cn/nukkit/permission/BanEntry.java index b7bd159f446..1d767c62f3f 100644 --- a/src/main/java/cn/nukkit/permission/BanEntry.java +++ b/src/main/java/cn/nukkit/permission/BanEntry.java @@ -6,23 +6,21 @@ import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BanEntry { + public static final String format = "yyyy-MM-dd HH:mm:ss Z"; private final String name; - private Date creationDate = null; + private Date creationDate; private String source = "(Unknown)"; private Date expirationDate = null; - private String reason = "Banned by an operator."; + private String reason = "Banned"; public BanEntry(String name) { this.name = name.toLowerCase(); @@ -72,11 +70,11 @@ public void setReason(String reason) { public LinkedHashMap getMap() { LinkedHashMap map = new LinkedHashMap<>(); - map.put("name", getName()); - map.put("creationDate", new SimpleDateFormat(format).format(getCreationDate())); - map.put("source", this.getSource()); - map.put("expireDate", getExpirationDate() != null ? new SimpleDateFormat(format).format(getExpirationDate()) : "Forever"); - map.put("reason", this.getReason()); + map.put("name", name); + map.put("creationDate", new SimpleDateFormat(format).format(creationDate)); + map.put("source", this.source); + map.put("expireDate", expirationDate != null ? new SimpleDateFormat(format).format(expirationDate) : "Forever"); + map.put("reason", this.reason); return map; } @@ -98,8 +96,7 @@ public String getString() { } public static BanEntry fromString(String str) { - Map map = new Gson().fromJson(str, new TypeToken>() { - }.getType()); + Map map = new Gson().fromJson(str, new TreeMapTypeToken().getType()); BanEntry banEntry = new BanEntry(map.get("name")); try { banEntry.setCreationDate(new SimpleDateFormat(format).parse(map.get("creationDate"))); @@ -112,4 +109,6 @@ public static BanEntry fromString(String str) { return banEntry; } + private static class TreeMapTypeToken extends TypeToken> { + } } diff --git a/src/main/java/cn/nukkit/permission/BanList.java b/src/main/java/cn/nukkit/permission/BanList.java index 636a78c0875..27b7e4a96d1 100644 --- a/src/main/java/cn/nukkit/permission/BanList.java +++ b/src/main/java/cn/nukkit/permission/BanList.java @@ -13,7 +13,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class BanList { @@ -42,7 +42,7 @@ public LinkedHashMap getEntires() { } public boolean isBanned(String name) { - if (!this.isEnable() || name == null) { + if (!this.enable || name == null) { return false; } else { this.removeExpired(); @@ -106,17 +106,15 @@ public void load() { this.save(); } else { - LinkedList> list = new Gson().fromJson(Utils.readFile(this.file), new TypeToken>>() { - }.getType()); + LinkedList> list = new Gson().fromJson(Utils.readFile(this.file), new LinkedListTypeToken().getType()); for (TreeMap map : list) { BanEntry entry = BanEntry.fromMap(map); this.list.put(entry.getName(), entry); } } } catch (IOException e) { - MainLogger.getLogger().error("Could not load ban list: ", e); + MainLogger.getLogger().error("Could not load ban list", e); } - } public void save() { @@ -134,7 +132,10 @@ public void save() { } Utils.writeFile(this.file, new ByteArrayInputStream(new GsonBuilder().setPrettyPrinting().create().toJson(list).getBytes(StandardCharsets.UTF_8))); } catch (IOException e) { - MainLogger.getLogger().error("Could not save ban list ", e); + MainLogger.getLogger().error("Could not save ban list", e); } } + + private static class LinkedListTypeToken extends TypeToken>> { + } } diff --git a/src/main/java/cn/nukkit/permission/DefaultPermissions.java b/src/main/java/cn/nukkit/permission/DefaultPermissions.java index 195b843c9bb..ed10034286b 100644 --- a/src/main/java/cn/nukkit/permission/DefaultPermissions.java +++ b/src/main/java/cn/nukkit/permission/DefaultPermissions.java @@ -3,10 +3,11 @@ import cn.nukkit.Server; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public abstract class DefaultPermissions { + public static final String ROOT = "nukkit"; public static Permission registerPermission(Permission perm) { @@ -25,103 +26,103 @@ public static Permission registerPermission(Permission perm, Permission parent) public static void registerCorePermissions() { Permission parent = registerPermission(new Permission(ROOT, "Allows using all Nukkit commands and utilities")); - Permission broadcasts = registerPermission(new Permission(ROOT + ".broadcast", "Allows the user to receive all broadcast messages"), parent); + Permission broadcasts = registerPermission(new Permission("nukkit.broadcast", "Allows the user to receive all broadcast messages"), parent); - registerPermission(new Permission(ROOT + ".broadcast.admin", "Allows the user to receive administrative broadcasts", Permission.DEFAULT_OP), broadcasts); - registerPermission(new Permission(ROOT + ".broadcast.user", "Allows the user to receive user broadcasts", Permission.DEFAULT_TRUE), broadcasts); + registerPermission(new Permission("nukkit.broadcast.admin", "Allows the user to receive administrative broadcasts", Permission.DEFAULT_OP), broadcasts); + registerPermission(new Permission("nukkit.broadcast.user", "Allows the user to receive user broadcasts", Permission.DEFAULT_TRUE), broadcasts); broadcasts.recalculatePermissibles(); - Permission commands = registerPermission(new Permission(ROOT + ".command", "Allows using all Nukkit commands"), parent); + Permission commands = registerPermission(new Permission("nukkit.command", "Allows using all Nukkit commands"), parent); - Permission whitelist = registerPermission(new Permission(ROOT + ".command.whitelist", "Allows the user to modify the server whitelist", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.whitelist.add", "Allows the user to add a player to the server whitelist"), whitelist); - registerPermission(new Permission(ROOT + ".command.whitelist.remove", "Allows the user to remove a player to the server whitelist"), whitelist); - registerPermission(new Permission(ROOT + ".command.whitelist.reload", "Allows the user to reload the server whitelist"), whitelist); - registerPermission(new Permission(ROOT + ".command.whitelist.enable", "Allows the user to enable the server whitelist"), whitelist); - registerPermission(new Permission(ROOT + ".command.whitelist.disable", "Allows the user to disable the server whitelist"), whitelist); - registerPermission(new Permission(ROOT + ".command.whitelist.list", "Allows the user to list all the players on the server whitelist"), whitelist); + Permission whitelist = registerPermission(new Permission("nukkit.command.whitelist", "Allows the user to modify the server whitelist", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.whitelist.add", "Allows the user to add a player to the server whitelist"), whitelist); + registerPermission(new Permission("nukkit.command.whitelist.remove", "Allows the user to remove a player to the server whitelist"), whitelist); + registerPermission(new Permission("nukkit.command.whitelist.reload", "Allows the user to reload the server whitelist"), whitelist); + registerPermission(new Permission("nukkit.command.whitelist.enable", "Allows the user to enable the server whitelist"), whitelist); + registerPermission(new Permission("nukkit.command.whitelist.disable", "Allows the user to disable the server whitelist"), whitelist); + registerPermission(new Permission("nukkit.command.whitelist.list", "Allows the user to list all the players on the server whitelist"), whitelist); whitelist.recalculatePermissibles(); - Permission ban = registerPermission(new Permission(ROOT + ".command.ban", "Allows the user to ban people", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.ban.player", "Allows the user to ban players"), ban); - registerPermission(new Permission(ROOT + ".command.ban.ip", "Allows the user to ban IP addresses"), ban); - registerPermission(new Permission(ROOT + ".command.ban.list", "Allows the user to list all the banned ips or players"), ban); + Permission ban = registerPermission(new Permission("nukkit.command.ban", "Allows the user to ban people", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.ban.player", "Allows the user to ban players"), ban); + registerPermission(new Permission("nukkit.command.ban.ip", "Allows the user to ban IP addresses"), ban); + registerPermission(new Permission("nukkit.command.ban.list", "Allows the user to list all the banned ips or players"), ban); ban.recalculatePermissibles(); - Permission unban = registerPermission(new Permission(ROOT + ".command.unban", "Allows the user to unban people", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.unban.player", "Allows the user to unban players"), unban); - registerPermission(new Permission(ROOT + ".command.unban.ip", "Allows the user to unban IP addresses"), unban); + Permission unban = registerPermission(new Permission("nukkit.command.unban", "Allows the user to unban people", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.unban.player", "Allows the user to unban players"), unban); + registerPermission(new Permission("nukkit.command.unban.ip", "Allows the user to unban IP addresses"), unban); unban.recalculatePermissibles(); - Permission op = registerPermission(new Permission(ROOT + ".command.op", "Allows the user to change operators", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.op.give", "Allows the user to give a player operator status"), op); - registerPermission(new Permission(ROOT + ".command.op.take", "Allows the user to take a players operator status"), op); + Permission op = registerPermission(new Permission("nukkit.command.op", "Allows the user to change operators", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.op.give", "Allows the user to give a player operator status"), op); + registerPermission(new Permission("nukkit.command.op.take", "Allows the user to take a players operator status"), op); op.recalculatePermissibles(); - Permission save = registerPermission(new Permission(ROOT + ".command.save", "Allows the user to save the worlds", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.save.enable", "Allows the user to enable automatic saving"), save); - registerPermission(new Permission(ROOT + ".command.save.disable", "Allows the user to disable automatic saving"), save); - registerPermission(new Permission(ROOT + ".command.save.perform", "Allows the user to perform a manual save"), save); + Permission save = registerPermission(new Permission("nukkit.command.save", "Allows the user to save the worlds", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.save.enable", "Allows the user to enable automatic saving"), save); + registerPermission(new Permission("nukkit.command.save.disable", "Allows the user to disable automatic saving"), save); + registerPermission(new Permission("nukkit.command.save.perform", "Allows the user to perform a manual save"), save); save.recalculatePermissibles(); - Permission time = registerPermission(new Permission(ROOT + ".command.time", "Allows the user to alter the time", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.time.add", "Allows the user to fast-forward time"), time); - registerPermission(new Permission(ROOT + ".command.time.set", "Allows the user to change the time"), time); - registerPermission(new Permission(ROOT + ".command.time.start", "Allows the user to restart the time"), time); - registerPermission(new Permission(ROOT + ".command.time.stop", "Allows the user to stop the time"), time); - registerPermission(new Permission(ROOT + ".command.time.query", "Allows the user query the time"), time); + Permission time = registerPermission(new Permission("nukkit.command.time", "Allows the user to alter the time", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.time.add", "Allows the user to fast-forward time"), time); + registerPermission(new Permission("nukkit.command.time.set", "Allows the user to change the time"), time); + registerPermission(new Permission("nukkit.command.time.start", "Allows the user to restart the time"), time); + registerPermission(new Permission("nukkit.command.time.stop", "Allows the user to stop the time"), time); + registerPermission(new Permission("nukkit.command.time.query", "Allows the user query the time"), time); time.recalculatePermissibles(); - Permission kill = registerPermission(new Permission(ROOT + ".command.kill", "Allows the user to kill players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.kill.self", "Allows the user to commit suicide", Permission.DEFAULT_TRUE), kill); - registerPermission(new Permission(ROOT + ".command.kill.other", "Allows the user to kill other players"), kill); + Permission kill = registerPermission(new Permission("nukkit.command.kill", "Allows the user to kill players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.kill.self", "Allows the user to commit suicide", Permission.DEFAULT_TRUE), kill); + registerPermission(new Permission("nukkit.command.kill.other", "Allows the user to kill other players"), kill); kill.recalculatePermissibles(); - Permission gamemode = registerPermission(new Permission(ROOT + ".command.gamemode", "Allows the user to change the gamemode of players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.gamemode.survival", "Allows the user to change the gamemode to survival", Permission.DEFAULT_OP), gamemode); - registerPermission(new Permission(ROOT + ".command.gamemode.creative", "Allows the user to change the gamemode to creative", Permission.DEFAULT_OP), gamemode); - registerPermission(new Permission(ROOT + ".command.gamemode.adventure", "Allows the user to change the gamemode to adventure", Permission.DEFAULT_OP), gamemode); - registerPermission(new Permission(ROOT + ".command.gamemode.spectator", "Allows the user to change the gamemode to spectator", Permission.DEFAULT_OP), gamemode); - registerPermission(new Permission(ROOT + ".command.gamemode.other", "Allows the user to change the gamemode of other players", Permission.DEFAULT_OP), gamemode); + Permission gamemode = registerPermission(new Permission("nukkit.command.gamemode", "Allows the user to change the gamemode of players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.gamemode.survival", "Allows the user to change the gamemode to survival", Permission.DEFAULT_OP), gamemode); + registerPermission(new Permission("nukkit.command.gamemode.creative", "Allows the user to change the gamemode to creative", Permission.DEFAULT_OP), gamemode); + registerPermission(new Permission("nukkit.command.gamemode.adventure", "Allows the user to change the gamemode to adventure", Permission.DEFAULT_OP), gamemode); + registerPermission(new Permission("nukkit.command.gamemode.spectator", "Allows the user to change the gamemode to spectator", Permission.DEFAULT_OP), gamemode); + registerPermission(new Permission("nukkit.command.gamemode.other", "Allows the user to change the gamemode of other players", Permission.DEFAULT_OP), gamemode); gamemode.recalculatePermissibles(); - registerPermission(new Permission(ROOT + ".command.me", "Allows the user to perform a chat action", Permission.DEFAULT_TRUE), commands); - registerPermission(new Permission(ROOT + ".command.tell", "Allows the user to privately message another player", Permission.DEFAULT_TRUE), commands); - registerPermission(new Permission(ROOT + ".command.say", "Allows the user to talk as the console", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.give", "Allows the user to give items to players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.clear", "Allows the user to clear items from players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.effect", "Allows the user to give/take potion effects", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.particle", "Allows the user to create particle effects", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.teleport", "Allows the user to teleport players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.kick", "Allows the user to kick players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.stop", "Allows the user to stop the server", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.list", "Allows the user to list all online players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.help", "Allows the user to view the help menu", Permission.DEFAULT_TRUE), commands); - registerPermission(new Permission(ROOT + ".command.plugins", "Allows the user to view the list of plugins", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.reload", "Allows the user to reload the server settings", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.version", "Allows the user to view the version of the server", Permission.DEFAULT_TRUE), commands); - registerPermission(new Permission(ROOT + ".command.defaultgamemode", "Allows the user to change the default gamemode", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.seed", "Allows the user to view the seed of the world", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.status", "Allows the user to view the server performance", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.gc", "Allows the user to fire garbage collection tasks", Permission.DEFAULT_OP), commands); - //registerPermission(new Permission(ROOT + ".command.dumpmemory", "Allows the user to dump memory contents", Permission.DEFAULT_OP), commands); // this command is not implemented - registerPermission(new Permission(ROOT + ".command.gamerule", "Sets or queries a game rule value", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.timings", "Allows the user to records timings for all plugin events", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.title", "Allows the user to send titles to players", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.spawnpoint", "Allows the user to change player's spawnpoint", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.setworldspawn", "Allows the user to change the world spawn", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.weather", "Allows the user to change the weather", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.xp", "Allows the user to give experience", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.enchant", "Allows the user to enchant items", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.difficulty", "Allows the user to change the difficulty", Permission.DEFAULT_OP), commands); - registerPermission(new Permission(ROOT + ".command.debug.perform", "Allows the user to use debugpaste command", Permission.DEFAULT_OP), commands); - - registerPermission(new Permission(ROOT + ".textcolor", "Allows the user to write colored text", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.me", "Allows the user to perform a chat action", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.tell", "Allows the user to privately message another player", Permission.DEFAULT_TRUE), commands); + registerPermission(new Permission("nukkit.command.say", "Allows the user to talk as the console", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.give", "Allows the user to give items to players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.clear", "Allows the user to clear items from players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.effect", "Allows the user to give/take potion effects", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.particle", "Allows the user to create particle effects", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.teleport", "Allows the user to teleport players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.kick", "Allows the user to kick players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.stop", "Allows the user to stop the server", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.list", "Allows the user to list all online players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.help", "Allows the user to view the help menu", Permission.DEFAULT_TRUE), commands); + registerPermission(new Permission("nukkit.command.plugins", "Allows the user to view the list of plugins", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.reload", "Allows the user to reload the server settings", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.version", "Allows the user to view the version of the server", Permission.DEFAULT_TRUE), commands); + registerPermission(new Permission("nukkit.command.version.plugins", "Allows the user to view the version of the plugins", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.defaultgamemode", "Allows the user to change the default gamemode", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.status", "Allows the user to view the server performance", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.gc", "Allows the user to fire garbage collection tasks", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.title", "Allows the user to send titles to players", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.spawnpoint", "Allows the user to change player's spawnpoint", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.setworldspawn", "Allows the user to change the world spawn", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.weather", "Allows the user to change the weather", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.xp", "Allows the user to give experience", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.summon", "Allows the user to summon entities", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.seed", "Allows the user to see world's seed", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.playsound", "Allows the user to play sounds", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.gamerule", "Allows the user to change game rules", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.enchant", "Allows the user to enchant items", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.difficulty", "Allows the user to change difficulty", Permission.DEFAULT_OP), commands); + registerPermission(new Permission("nukkit.command.world.convert", "Allows the user to convert worlds to LevelDB format", Permission.DEFAULT_FALSE), commands); // Console only + + registerPermission(new Permission("nukkit.textcolor", "Allows the user to write colored text", Permission.DEFAULT_OP), commands); commands.recalculatePermissibles(); parent.recalculatePermissibles(); } - } diff --git a/src/main/java/cn/nukkit/permission/Permissible.java b/src/main/java/cn/nukkit/permission/Permissible.java index fd393e4ba94..795c8d3c647 100644 --- a/src/main/java/cn/nukkit/permission/Permissible.java +++ b/src/main/java/cn/nukkit/permission/Permissible.java @@ -5,7 +5,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface Permissible extends ServerOperator { @@ -29,5 +29,4 @@ public interface Permissible extends ServerOperator { void recalculatePermissions(); Map getEffectivePermissions(); - } diff --git a/src/main/java/cn/nukkit/permission/PermissibleBase.java b/src/main/java/cn/nukkit/permission/PermissibleBase.java index d521a475310..778b8f09eb5 100644 --- a/src/main/java/cn/nukkit/permission/PermissibleBase.java +++ b/src/main/java/cn/nukkit/permission/PermissibleBase.java @@ -4,26 +4,24 @@ import cn.nukkit.plugin.Plugin; import cn.nukkit.utils.PluginException; import cn.nukkit.utils.ServerException; -import co.aikar.timings.Timings; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PermissibleBase implements Permissible { - ServerOperator opable = null; + private final ServerOperator opable; private Permissible parent = null; - private final Set attachments = new HashSet<>(); + private final Set attachments = ConcurrentHashMap.newKeySet(); - private final Map permissions = new HashMap<>(); + private final Map permissions = new ConcurrentHashMap<>(); public PermissibleBase(ServerOperator opable) { this.opable = opable; @@ -58,8 +56,9 @@ public boolean isPermissionSet(Permission permission) { @Override public boolean hasPermission(String name) { - if (this.isPermissionSet(name)) { - return this.permissions.get(name).getValue(); + PermissionAttachmentInfo isPermissionSet = this.permissions.get(name); + if (isPermissionSet != null) { + return isPermissionSet.getValue(); } Permission perm = Server.getInstance().getPluginManager().getPermission(name); @@ -67,9 +66,11 @@ public boolean hasPermission(String name) { if (perm != null) { String permission = perm.getDefault(); - return Permission.DEFAULT_TRUE.equals(permission) || (this.isOp() && Permission.DEFAULT_OP.equals(permission)) || (!this.isOp() && Permission.DEFAULT_NOT_OP.equals(permission)); + boolean op; + return Permission.DEFAULT_TRUE.equals(permission) || ((op = this.isOp()) && Permission.DEFAULT_OP.equals(permission)) || (!op && Permission.DEFAULT_NOT_OP.equals(permission)); } else { - return Permission.DEFAULT_TRUE.equals(Permission.DEFAULT_PERMISSION) || (this.isOp() && Permission.DEFAULT_OP.equals(Permission.DEFAULT_PERMISSION)) || (!this.isOp() && Permission.DEFAULT_NOT_OP.equals(Permission.DEFAULT_PERMISSION)); + return this.isOp(); + //return Permission.DEFAULT_TRUE.equals(Permission.DEFAULT_PERMISSION) || ((op = this.isOp()) && Permission.DEFAULT_OP.equals(Permission.DEFAULT_PERMISSION)) || (!op && Permission.DEFAULT_NOT_OP.equals(Permission.DEFAULT_PERMISSION)); } } @@ -118,8 +119,6 @@ public void removeAttachment(PermissionAttachment attachment) { @Override public void recalculatePermissions() { - Timings.permissibleCalculationTimer.startTiming(); - this.clearPermissions(); Map defaults = Server.getInstance().getPluginManager().getDefaultPermissions(this.isOp()); Server.getInstance().getPluginManager().subscribeToDefaultPerms(this.isOp(), this.parent != null ? this.parent : this); @@ -134,7 +133,6 @@ public void recalculatePermissions() { for (PermissionAttachment attachment : this.attachments) { this.calculateChildPermissions(attachment.getPermissions(), false, attachment); } - Timings.permissibleCalculationTimer.stopTiming(); } public void clearPermissions() { @@ -142,7 +140,6 @@ public void clearPermissions() { 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); diff --git a/src/main/java/cn/nukkit/permission/Permission.java b/src/main/java/cn/nukkit/permission/Permission.java index 00039105b87..e3cbaf346ec 100644 --- a/src/main/java/cn/nukkit/permission/Permission.java +++ b/src/main/java/cn/nukkit/permission/Permission.java @@ -5,7 +5,7 @@ import java.util.*; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Permission { @@ -47,7 +47,7 @@ public static String getByName(String value) { private String description; - private Map children = new HashMap<>(); + private final Map children; private String defaultValue; @@ -114,7 +114,7 @@ public void recalculatePermissibles() { } public void addParent(Permission permission, boolean value) { - this.getChildren().put(this.getName(), value); + this.children.put(this.name, value); permission.recalculatePermissibles(); } @@ -173,9 +173,7 @@ public static Permission loadPermission(String name, Map data, S Object v = entry.getValue(); if (v instanceof Map) { Permission permission = loadPermission(k, (Map) v, defaultValue, output); - if (permission != null) { - output.add(permission); - } + output.add(permission); } children.put(k, true); } @@ -190,5 +188,4 @@ public static Permission loadPermission(String name, Map data, S return new Permission(name, desc, defaultValue, children); } - } diff --git a/src/main/java/cn/nukkit/permission/PermissionAttachment.java b/src/main/java/cn/nukkit/permission/PermissionAttachment.java index 1103b658f57..bfe5a70caa4 100644 --- a/src/main/java/cn/nukkit/permission/PermissionAttachment.java +++ b/src/main/java/cn/nukkit/permission/PermissionAttachment.java @@ -8,7 +8,7 @@ import java.util.Map; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PermissionAttachment { @@ -17,9 +17,9 @@ public class PermissionAttachment { private final Map permissions = new HashMap<>(); - private Permissible permissible; + private final Permissible permissible; - private Plugin plugin; + private final Plugin plugin; public PermissionAttachment(Plugin plugin, Permissible permissible) { if (!plugin.isEnabled()) { @@ -95,5 +95,4 @@ public void unsetPermission(String name, boolean value) { public void remove() { this.permissible.removeAttachment(this); } - } diff --git a/src/main/java/cn/nukkit/permission/PermissionAttachmentInfo.java b/src/main/java/cn/nukkit/permission/PermissionAttachmentInfo.java index 0e952fea9ea..10d02619d58 100644 --- a/src/main/java/cn/nukkit/permission/PermissionAttachmentInfo.java +++ b/src/main/java/cn/nukkit/permission/PermissionAttachmentInfo.java @@ -1,16 +1,16 @@ package cn.nukkit.permission; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PermissionAttachmentInfo { - private Permissible permissible; + private final Permissible permissible; - private String permission; + private final String permission; - private PermissionAttachment attachment; + private final PermissionAttachment attachment; private boolean value; @@ -40,4 +40,13 @@ public PermissionAttachment getAttachment() { public boolean getValue() { return value; } + + /** + * Set value. + * + * @param value value + */ + public void setValue(boolean value) { + this.value = value; + } } diff --git a/src/main/java/cn/nukkit/permission/PermissionRemovedExecutor.java b/src/main/java/cn/nukkit/permission/PermissionRemovedExecutor.java index 35008233126..8d6affbe412 100644 --- a/src/main/java/cn/nukkit/permission/PermissionRemovedExecutor.java +++ b/src/main/java/cn/nukkit/permission/PermissionRemovedExecutor.java @@ -1,7 +1,7 @@ package cn.nukkit.permission; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public interface PermissionRemovedExecutor { diff --git a/src/main/java/cn/nukkit/permission/ServerOperator.java b/src/main/java/cn/nukkit/permission/ServerOperator.java index 1051458a49a..9d00039b02b 100644 --- a/src/main/java/cn/nukkit/permission/ServerOperator.java +++ b/src/main/java/cn/nukkit/permission/ServerOperator.java @@ -7,7 +7,6 @@ * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.permission.Permissible - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface ServerOperator { @@ -16,7 +15,6 @@ public interface ServerOperator { * Returns if this object is an operator. * * @return 这个对象是不是服务器管理员。
if this object is an operator. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isOp(); @@ -26,7 +24,6 @@ public interface ServerOperator { * * @param value {@code true}为授予管理员,{@code false}为取消管理员。
* {@code true} for giving this operator or {@code false} for cancelling. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void setOp(boolean value); } diff --git a/src/main/java/cn/nukkit/plugin/EventExecutor.java b/src/main/java/cn/nukkit/plugin/EventExecutor.java index f9c4ee692a5..297e0e4a2d6 100644 --- a/src/main/java/cn/nukkit/plugin/EventExecutor.java +++ b/src/main/java/cn/nukkit/plugin/EventExecutor.java @@ -5,7 +5,7 @@ import cn.nukkit.utils.EventException; /** - * author: iNevet + * @author iNevet * Nukkit Project */ public interface EventExecutor { diff --git a/src/main/java/cn/nukkit/plugin/JavaPluginLoader.java b/src/main/java/cn/nukkit/plugin/JavaPluginLoader.java index 8c1b7501567..302b369908e 100644 --- a/src/main/java/cn/nukkit/plugin/JavaPluginLoader.java +++ b/src/main/java/cn/nukkit/plugin/JavaPluginLoader.java @@ -58,11 +58,10 @@ public Plugin loadPlugin(File file) throws Exception { return plugin; } catch (ClassCastException e) { - throw new PluginException("Error whilst initializing main class `" + description.getMain() + "'", e); + throw new PluginException("Error whilst initializing main class `" + description.getMain() + '\'', e); } catch (InstantiationException | IllegalAccessException e) { Server.getInstance().getLogger().logException(e); } - } catch (ClassNotFoundException e) { throw new PluginException("Couldn't load plugin " + description.getName() + ": main class not found"); } @@ -143,8 +142,7 @@ Class getClassByName(final String name) { try { cachedClass = loader.findClass(name, false); - } catch (ClassNotFoundException e) { - //ignore + } catch (ClassNotFoundException ignored) { } if (cachedClass != null) { return cachedClass; @@ -159,8 +157,4 @@ void setClass(final String name, final Class clazz) { classes.put(name, clazz); } } - - private void removeClass(String name) { - Class clazz = classes.remove(name); - } } diff --git a/src/main/java/cn/nukkit/plugin/LambdaEventExecutor.java b/src/main/java/cn/nukkit/plugin/LambdaEventExecutor.java new file mode 100644 index 00000000000..70c05f6611c --- /dev/null +++ b/src/main/java/cn/nukkit/plugin/LambdaEventExecutor.java @@ -0,0 +1,29 @@ +package cn.nukkit.plugin; + +import cn.nukkit.event.Event; +import cn.nukkit.event.Listener; +import cn.nukkit.utils.EventException; + +import java.util.function.BiConsumer; + +public class LambdaEventExecutor implements EventExecutor { + + private final Class clazz; + private final BiConsumer callback; + + public LambdaEventExecutor(Class clazz, BiConsumer callback) { + this.clazz = clazz; + this.callback = callback; + } + + @Override + public void execute(Listener listener, Event event) throws EventException { + if (clazz.isAssignableFrom(event.getClass())) { + try { + this.callback.accept(listener, event); + } catch (Throwable t) { + throw new EventException(t); + } + } + } +} diff --git a/src/main/java/cn/nukkit/plugin/Library.java b/src/main/java/cn/nukkit/plugin/Library.java index 02cd7a356dd..b5f8eff9566 100644 --- a/src/main/java/cn/nukkit/plugin/Library.java +++ b/src/main/java/cn/nukkit/plugin/Library.java @@ -1,8 +1,5 @@ package cn.nukkit.plugin; -/** - * Created on 15-12-13. - */ public interface Library { String getGroupId(); @@ -10,5 +7,4 @@ public interface Library { String getArtifactId(); String getVersion(); - } diff --git a/src/main/java/cn/nukkit/plugin/LibraryLoadException.java b/src/main/java/cn/nukkit/plugin/LibraryLoadException.java index f274d277d95..509dc852f8a 100644 --- a/src/main/java/cn/nukkit/plugin/LibraryLoadException.java +++ b/src/main/java/cn/nukkit/plugin/LibraryLoadException.java @@ -1,12 +1,9 @@ package cn.nukkit.plugin; -/** - * Created on 15-12-13. - */ +@SuppressWarnings("serial") public class LibraryLoadException extends RuntimeException { public LibraryLoadException(Library library) { super("Load library " + library.getArtifactId() + " failed!"); } - } diff --git a/src/main/java/cn/nukkit/plugin/LibraryLoader.java b/src/main/java/cn/nukkit/plugin/LibraryLoader.java index 3ea82670da1..7bb98c0f79c 100644 --- a/src/main/java/cn/nukkit/plugin/LibraryLoader.java +++ b/src/main/java/cn/nukkit/plugin/LibraryLoader.java @@ -10,9 +10,6 @@ import java.nio.file.Files; import java.util.logging.Logger; -/** - * Created on 15-12-13. - */ public class LibraryLoader { private static final File BASE_FOLDER = new File("./libraries"); @@ -21,7 +18,7 @@ public class LibraryLoader { static { if (BASE_FOLDER.mkdir()) { - LOGGER.info("Created libraries folder."); + LOGGER.info("[LibraryLoader] Created libraries folder"); } } @@ -84,5 +81,4 @@ public static void load(Library library) { public static File getBaseFolder() { return BASE_FOLDER; } - } diff --git a/src/main/java/cn/nukkit/plugin/MethodEventExecutor.java b/src/main/java/cn/nukkit/plugin/MethodEventExecutor.java index 79bac4bf19e..5efd61626a8 100644 --- a/src/main/java/cn/nukkit/plugin/MethodEventExecutor.java +++ b/src/main/java/cn/nukkit/plugin/MethodEventExecutor.java @@ -8,7 +8,7 @@ import java.lang.reflect.Method; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class MethodEventExecutor implements EventExecutor { diff --git a/src/main/java/cn/nukkit/plugin/Plugin.java b/src/main/java/cn/nukkit/plugin/Plugin.java index f53c1e3d751..b19cd5b2c3d 100644 --- a/src/main/java/cn/nukkit/plugin/Plugin.java +++ b/src/main/java/cn/nukkit/plugin/Plugin.java @@ -10,41 +10,38 @@ /** * 所有Nukkit插件必须实现的接口。
* An interface what must be implemented by all Nukkit plugins. - * - *

对于插件作者,我们建议让插件主类继承{@link cn.nukkit.plugin.PluginBase}类,而不是实现这个接口。
+ * + * 对于插件作者,我们建议让插件主类继承{@link cn.nukkit.plugin.PluginBase}类,而不是实现这个接口。
* For plugin developers: it's recommended to use {@link cn.nukkit.plugin.PluginBase} for an actual plugin - * instead of implement this interface.

+ * instead of implement this interface. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.plugin.PluginBase * @see cn.nukkit.plugin.PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface Plugin extends CommandExecutor { /** * 在一个Nukkit插件被加载时调用的方法。这个方法会在{@link Plugin#onEnable()}之前调用。
* Called when a Nukkit plugin is loaded, before {@link Plugin#onEnable()} . + * + * 应该填写加载插件时需要作出的动作。例如:初始化数组、初始化数据库连接。
+ * Use this to init a Nukkit plugin, such as init arrays or init database connections. * - *

应该填写加载插件时需要作出的动作。例如:初始化数组、初始化数据库连接。
- * Use this to init a Nukkit plugin, such as init arrays or init database connections.

- * - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void onLoad(); /** * 在一个Nukkit插件被启用时调用的方法。
* Called when a Nukkit plugin is enabled. - * - *

应该填写插件启用时需要作出的动作。例如:读取配置文件、读取资源、连接数据库。
- * Use this to open config files, open resources, connect databases.

- * - *

注意到可能存在的插件管理器插件,这个方法在插件多次重启时可能被调用多次。
+ * + * 应该填写插件启用时需要作出的动作。例如:读取配置文件、读取资源、连接数据库。
+ * Use this to open config files, open resources, connect databases. + * + * 注意到可能存在的插件管理器插件,这个方法在插件多次重启时可能被调用多次。
* Notes that there may be plugin manager plugins, - * this method can be called many times when a plugin is restarted many times.

+ * this method can be called many times when a plugin is restarted many times. * - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void onEnable(); @@ -53,23 +50,21 @@ public interface Plugin extends CommandExecutor { * Whether this Nukkit plugin is enabled. * * @return 这个插件是否已经启用。
Whether this plugin is enabled. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isEnabled(); /** * 在一个Nukkit插件被停用时调用的方法。
* Called when a Nukkit plugin is disabled. - * - *

应该填写插件停用时需要作出的动作。例如:关闭数据库,断开资源。
+ * + * 应该填写插件停用时需要作出的动作。例如:关闭数据库,断开资源。
* Use this to free open things and finish actions, - * such as disconnecting databases and close resources.

- * - *

注意到可能存在的插件管理器插件,这个方法在插件多次重启时可能被调用多次。
+ * such as disconnecting databases and close resources. + * + * 注意到可能存在的插件管理器插件,这个方法在插件多次重启时可能被调用多次。
* Notes that there may be plugin manager plugins, - * this method can be called many times when a plugin is restarted many times.

+ * this method can be called many times when a plugin is restarted many times. * - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void onDisable(); @@ -78,100 +73,94 @@ public interface Plugin extends CommandExecutor { * Whether this Nukkit plugin is disabled. * * @return 这个插件是否已经停用。
Whether this plugin is disabled. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean isDisabled(); /** * 返回这个Nukkit插件的数据文件夹。
* The data folder of this Nukkit plugin. - * - *

一般情况下,数据文件夹名字与插件名字相同,而且都放在nukkit安装目录下的plugins文件夹里。
+ * + * 一般情况下,数据文件夹名字与插件名字相同,而且都放在nukkit安装目录下的plugins文件夹里。
* Under normal circumstances, the data folder has the same name with the plugin, - * and is placed in the 'plugins' folder inside the nukkit installation directory.

+ * and is placed in the 'plugins' folder inside the nukkit installation directory. * * @return 这个插件的数据文件夹。
The data folder of this plugin. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ File getDataFolder(); /** * 返回描述这个Nukkit插件的{@link PluginDescription}对象。
* The description this Nukkit plugin as a {@link PluginDescription} object. - * - *

对于jar格式的Nukkit插件,插件的描述在plugin.yml文件内定义。
- * For jar-packed Nukkit plugins, the description is defined in the 'plugin.yml' file.

+ * + * 对于jar格式的Nukkit插件,插件的描述在plugin.yml文件内定义。
+ * For jar-packed Nukkit plugins, the description is defined in the 'plugin.yml' file. * * @return 这个插件的描述。
A description of this plugin. * @see cn.nukkit.plugin.PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ PluginDescription getDescription(); /** * 读取这个插件特定的资源文件,并返回为{@code InputStream}对象。
* Reads a resource of this plugin, and returns as an {@code InputStream} object. - * - *

对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹(一般为resources文件夹)寻找资源文件。
+ * + * 对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹(一般为resources文件夹)寻找资源文件。
* For jar-packed Nukkit plugins, Nukkit will look for your resource file in the resources folder, - * which is normally named 'resources' and placed in plugin jar file.

- * - *

当你需要把一个文件的所有内容读取为字符串,可以使用{@link cn.nukkit.utils.Utils#readFile}函数, + * which is normally named 'resources' and placed in plugin jar file. + * + * 当你需要把一个文件的所有内容读取为字符串,可以使用 函数, * 来从{@code InputStream}读取所有内容为字符串。例如:
- * When you need to read the whole file content as a String, you can use {@link cn.nukkit.utils.Utils#readFile} - * to read from a {@code InputStream} and get whole content as a String. For example:

- *

String string = Utils.readFile(this.getResource("string.txt"));

+ * When you need to read the whole file content as a String, you can use + * to read from a {@code InputStream} and get whole content as a String. For example: + * {@code String string = Utils.readFile(this.getResource("string.txt"));} * * @param filename 要读取的资源文件名字。
The name of the resource file to read. * @return 读取的资源文件的 {@code InputStream}对象。若错误会返回{@code null}
* The resource as an {@code InputStream} object, or {@code null} when an error occurred. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ InputStream getResource(String filename); /** * 保存这个Nukkit插件的资源。
* Saves the resource of this plugin. - * - *

对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹寻找资源文件,然后保存到数据文件夹。
+ * + * 对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹寻找资源文件,然后保存到数据文件夹。
* For jar-packed Nukkit plugins, Nukkit will look for your resource file in the resources folder, - * which is normally named 'resources' and placed in plugin jar file, and copy it into data folder.

- * - *

这个函数通常用来在插件被加载(load)时,保存默认的资源文件。这样插件在启用(enable)时不会错误读取空的资源文件, + * which is normally named 'resources' and placed in plugin jar file, and copy it into data folder. + * + * 这个函数通常用来在插件被加载(load)时,保存默认的资源文件。这样插件在启用(enable)时不会错误读取空的资源文件, * 用户也无需从开发者处手动下载资源文件后再使用插件。
* This is usually used to save the default plugin resource when the plugin is LOADED .If this is used, * it won't happen to load an empty resource when plugin is ENABLED, and plugin users are not required to get - * default resources from the developer and place it manually.

- * - *

如果需要替换已存在的资源文件,建议使用{@link cn.nukkit.plugin.Plugin#saveResource(String, boolean)}
+ * default resources from the developer and place it manually. + * + * 如果需要替换已存在的资源文件,建议使用{@link cn.nukkit.plugin.Plugin#saveResource(String, boolean)}
* If you need to REPLACE an existing resource file, it's recommended - * to use {@link cn.nukkit.plugin.Plugin#saveResource(String, boolean)}.

+ * to use {@link cn.nukkit.plugin.Plugin#saveResource(String, boolean)}. * * @param filename 要保存的资源文件名字。
The name of the resource file to save. * @return 保存是否成功。
true if the saving action is successful. * @see cn.nukkit.plugin.Plugin#saveDefaultConfig * @see cn.nukkit.plugin.Plugin#saveResource(String, boolean) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean saveResource(String filename); /** * 保存或替换这个Nukkit插件的资源。
* Saves or replaces the resource of this plugin. - * - *

对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹寻找资源文件,然后保存到数据文件夹。
+ * + * 对于jar格式的Nukkit插件,Nukkit会在jar包内的资源文件夹寻找资源文件,然后保存到数据文件夹。
* For jar-packed Nukkit plugins, Nukkit will look for your resource file in the resources folder, - * which is normally named 'resources' and placed in plugin jar file, and copy it into data folder.

- * - *

如果需要保存默认的资源文件,建议使用{@link cn.nukkit.plugin.Plugin#saveResource(String)}
+ * which is normally named 'resources' and placed in plugin jar file, and copy it into data folder. + * + * 如果需要保存默认的资源文件,建议使用{@link cn.nukkit.plugin.Plugin#saveResource(String)}
* If you need to SAVE DEFAULT resource file, it's recommended - * to use {@link cn.nukkit.plugin.Plugin#saveResource(String)}.

+ * to use {@link cn.nukkit.plugin.Plugin#saveResource(String)}. * * @param filename 要保存的资源文件名字。
The name of the resource file to save. * @param replace 是否替换目标文件。
if true, Nukkit will replace the target resource file. * @return 保存是否成功。
true if the saving action is successful. * @see cn.nukkit.plugin.Plugin#saveResource(String) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ boolean saveResource(String filename, boolean replace); @@ -181,13 +170,12 @@ public interface Plugin extends CommandExecutor { /** * 返回这个Nukkit插件配置文件的{@link cn.nukkit.utils.Config}对象。
* The config file this Nukkit plugin as a {@link cn.nukkit.utils.Config} object. - * - *

一般地,插件的配置保存在数据文件夹下的config.yml文件。
- * Normally, the plugin config is saved in the 'config.yml' file in its data folder.

+ * + * 一般地,插件的配置保存在数据文件夹下的config.yml文件。
+ * Normally, the plugin config is saved in the 'config.yml' file in its data folder. * * @return 插件的配置文件。
The configuration of this plugin. * @see cn.nukkit.plugin.Plugin#getDataFolder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Config getConfig(); @@ -196,42 +184,39 @@ public interface Plugin extends CommandExecutor { * Saves the plugin config. * * @see cn.nukkit.plugin.Plugin#getDataFolder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void saveConfig(); /** * 保存这个Nukkit插件的默认配置文件。
* Saves the DEFAULT plugin config. - * - *

执行这个函数时,Nukkit会在资源文件夹内寻找开发者配置好的默认配置文件config.yml,然后保存在数据文件夹。 + * + * 执行这个函数时,Nukkit会在资源文件夹内寻找开发者配置好的默认配置文件config.yml,然后保存在数据文件夹。 * 如果数据文件夹已经有一个config.yml文件,Nukkit不会替换这个文件。
* When this is used, Nukkit will look for the default 'config.yml' file which is configured by plugin developer - * and save it to the data folder. If a config.yml file exists in the data folder, Nukkit won't replace it.

- * - *

这个函数通常用来在插件被加载(load)时,保存默认的配置文件。这样插件在启用(enable)时不会错误读取空的配置文件, + * and save it to the data folder. If a config.yml file exists in the data folder, Nukkit won't replace it. + * + * 这个函数通常用来在插件被加载(load)时,保存默认的配置文件。这样插件在启用(enable)时不会错误读取空的配置文件, * 用户也无需从开发者处手动下载配置文件保存后再使用插件。
* This is usually used to save the default plugin config when the plugin is LOADED .If this is used, * it won't happen to load an empty config when plugin is ENABLED, and plugin users are not required to get - * default config from the developer and place it manually.

+ * default config from the developer and place it manually. * * @see cn.nukkit.plugin.Plugin#getDataFolder * @see cn.nukkit.plugin.Plugin#saveResource - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void saveDefaultConfig(); /** * 重新读取这个Nukkit插件的默认配置文件。
* Reloads the plugin config. - * - *

执行这个函数时,Nukkit会从数据文件夹中的config.yml文件重新加载配置。 + * + * 执行这个函数时,Nukkit会从数据文件夹中的config.yml文件重新加载配置。 * 这样用户在调整插件配置后,无需重启就可以马上使用新的配置。
* By using this, Nukkit will reload the config from 'config.yml' file, then it isn't necessary to restart - * for plugin user who changes the config and needs to use new config at once.

+ * for plugin user who changes the config and needs to use new config at once. * * @see cn.nukkit.plugin.Plugin#getDataFolder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void reloadConfig(); @@ -240,31 +225,28 @@ public interface Plugin extends CommandExecutor { * Gets the server which is running this plugin, and returns as a {@link cn.nukkit.Server} object. * * @see cn.nukkit.Server - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Server getServer(); /** * 返回这个插件的名字。
* Returns the name of this plugin. - * - *

Nukkit会从已经读取的插件描述中获取插件的名字。
- * Nukkit will read plugin name from plugin description.

+ * + * Nukkit会从已经读取的插件描述中获取插件的名字。
+ * Nukkit will read plugin name from plugin description. * * @see cn.nukkit.plugin.Plugin#getDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ String getName(); /** * 返回这个插件的日志记录器为{@link cn.nukkit.plugin.PluginLogger}对象。
* Returns the logger of this plugin as a {@link cn.nukkit.plugin.PluginLogger} object. - * - *

使用日志记录器,你可以在控制台和日志文件输出信息。
- * You can use a plugin logger to output messages to the console and log file.

+ * + * 使用日志记录器,你可以在控制台和日志文件输出信息。
+ * You can use a plugin logger to output messages to the console and log file. * * @see cn.nukkit.plugin.PluginLogger - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ PluginLogger getLogger(); @@ -273,8 +255,6 @@ public interface Plugin extends CommandExecutor { * Returns the loader of this plugin as a {@link cn.nukkit.plugin.PluginLoader} object. * * @see cn.nukkit.plugin.PluginLoader - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ PluginLoader getPluginLoader(); - } diff --git a/src/main/java/cn/nukkit/plugin/PluginBase.java b/src/main/java/cn/nukkit/plugin/PluginBase.java index bcd042f2e09..a5476b735ae 100644 --- a/src/main/java/cn/nukkit/plugin/PluginBase.java +++ b/src/main/java/cn/nukkit/plugin/PluginBase.java @@ -22,7 +22,6 @@ * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see cn.nukkit.plugin.PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ abstract public class PluginBase implements Plugin { @@ -62,11 +61,10 @@ public final boolean isEnabled() { /** * 加载这个插件。
* Enables this plugin. - *

- *

如果你需要卸载这个插件,建议使用{@link #setEnabled(boolean)}
- * If you need to disable this plugin, it's recommended to use {@link #setEnabled(boolean)}

+ * + * 如果你需要卸载这个插件,建议使用{@link #setEnabled(boolean)}
+ * If you need to disable this plugin, it's recommended to use {@link #setEnabled(boolean)} * - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final void setEnabled() { this.setEnabled(true); @@ -75,12 +73,11 @@ public final void setEnabled() { /** * 加载或卸载这个插件。
* Enables or disables this plugin. - *

- *

插件管理器插件常常使用这个方法。
- * It's normally used by a plugin manager plugin to manage plugins.

+ * + * 插件管理器插件常常使用这个方法。
+ * It's normally used by a plugin manager plugin to manage plugins. * * @param value {@code true}为加载,{@code false}为卸载。
{@code true} for enable, {@code false} for disable. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final void setEnabled(boolean value) { if (isEnabled != value) { @@ -108,9 +105,9 @@ public final PluginDescription getDescription() { /** * 初始化这个插件。
* Initialize the plugin. - *

- *

这个方法会在加载(load)之前被插件加载器调用,初始化关于插件的一些事项,不能被重写。
- * Called by plugin loader before load, and initialize the plugin. Can't be overridden.

+ * + * 这个方法会在加载(load)之前被插件加载器调用,初始化关于插件的一些事项,不能被重写。
+ * Called by plugin loader before load, and initialize the plugin. Can't be overridden. * * @param loader 加载这个插件的插件加载器的{@code PluginLoader}对象。
* The plugin loader ,which loads this plugin, as a {@code PluginLoader} object. @@ -122,7 +119,6 @@ public final PluginDescription getDescription() { * The data folder of this plugin. * @param file 这个插件的文件{@code File}对象。对于jar格式的插件,就是jar文件本身。
* The {@code File} object of this plugin itself. For jar-packed plugins, it is the jar file itself. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final void init(PluginLoader loader, Server server, PluginDescription description, File dataFolder, File file) { if (!initialized) { @@ -146,7 +142,6 @@ public PluginLogger getLogger() { * Returns if this plugin is initialized. * * @return 这个插件是否已初始化。
if this plugin is initialized. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final boolean isInitialized() { return initialized; @@ -156,9 +151,9 @@ public final boolean isInitialized() { * TODO: FINISH JAVADOC */ public PluginIdentifiableCommand getCommand(String name) { - PluginIdentifiableCommand command = this.getServer().getPluginCommand(name); + PluginIdentifiableCommand command = this.server.getPluginCommand(name); if (command == null || !command.getPlugin().equals(this)) { - command = this.getServer().getPluginCommand(this.description.getName().toLowerCase() + ":" + name); + command = this.server.getPluginCommand(this.description.getName().toLowerCase() + ':' + name); } if (command != null && command.getPlugin().equals(this)) { @@ -191,7 +186,7 @@ public boolean saveResource(String filename, boolean replace) { @Override public boolean saveResource(String filename, String outputName, boolean replace) { Preconditions.checkArgument(filename != null && outputName != null, "Filename can not be null!"); - Preconditions.checkArgument(filename.trim().length() != 0 && outputName.trim().length() != 0, "Filename can not be empty!"); + Preconditions.checkArgument(!filename.trim().isEmpty() && !outputName.trim().isEmpty(), "Filename can not be empty!"); File out = new File(dataFolder, outputName); if (!out.exists() || replace) { @@ -223,7 +218,7 @@ public Config getConfig() { @Override public void saveConfig() { if (!this.getConfig().save()) { - this.getLogger().critical("Could not save config to " + this.configFile.toString()); + this.logger.critical("Could not save config to " + this.configFile.toString()); } } @@ -263,14 +258,13 @@ public String getName() { /** * 返回这个插件完整的名字。
* Returns the full name of this plugin. - *

- *

一个插件完整的名字由{@code 名字+" v"+版本号}组成。比如:
- * A full name of a plugin is composed by {@code name+" v"+version}.for example:

- *

{@code HelloWorld v1.0.0}

+ * + * 一个插件完整的名字由{@code 名字+" v"+版本号}组成。比如:
+ * A full name of a plugin is composed by {@code name+" v"+version}.for example: + * {@code HelloWorld v1.0.0} * * @return 这个插件完整的名字。
The full name of this plugin. * @see cn.nukkit.plugin.PluginDescription#getFullName - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final String getFullName() { return this.description.getFullName(); @@ -281,9 +275,8 @@ public final String getFullName() { * Returns the {@code File} object of this plugin itself. For jar-packed plugins, it is the jar file itself. * * @return 这个插件的文件 {@code File}对象。
The {@code File} object of this plugin itself. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ - protected File getFile() { + public File getFile() { return file; } diff --git a/src/main/java/cn/nukkit/plugin/PluginClassLoader.java b/src/main/java/cn/nukkit/plugin/PluginClassLoader.java index 29d5208098b..1904bf19e89 100644 --- a/src/main/java/cn/nukkit/plugin/PluginClassLoader.java +++ b/src/main/java/cn/nukkit/plugin/PluginClassLoader.java @@ -6,15 +6,14 @@ import java.net.URLClassLoader; import java.util.HashMap; import java.util.Map; -import java.util.Set; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PluginClassLoader extends URLClassLoader { - private JavaPluginLoader loader; + private final JavaPluginLoader loader; private final Map classes = new HashMap<>(); @@ -53,9 +52,4 @@ protected Class findClass(String name, boolean checkGlobal) throws ClassNotFo return result; } - - Set getClasses() { - return classes.keySet(); - } - } diff --git a/src/main/java/cn/nukkit/plugin/PluginDescription.java b/src/main/java/cn/nukkit/plugin/PluginDescription.java index e62bc82c608..e86daf1b6e6 100644 --- a/src/main/java/cn/nukkit/plugin/PluginDescription.java +++ b/src/main/java/cn/nukkit/plugin/PluginDescription.java @@ -7,19 +7,10 @@ import java.util.*; -/* TODO Add these to Javadoc: - *
  • softdepend
    - *
    - *
  • - *
  • loadbefore
    - *
    - *
  • - */ - /** * 描述一个Nukkit插件的类。
    * Describes a Nukkit plugin. - * + * * 在jar格式的插件中,插件的描述内容可以在plugin.yml中定义。比如这个:
    * The description of a jar-packed plugin can be defined in the 'plugin.yml' file. For example: *
    @@ -28,7 +19,7 @@
      * version: "1.0.0"
      * api: ["1.0.0"]
      * load: POSTWORLD
    - * author: 粉鞋大妈
    + * @author 粉鞋大妈
      * description: A simple Hello World plugin for Nukkit
      * website: http://www.cnblogs.com/xtypr
      * permissions:
    @@ -48,12 +39,11 @@
      * When using plugin.yml file to define your plugin, it's REQUIRED to fill these items:
      * {@code name},{@code main},{@code version} and {@code api}.You are supposed to fill these items to make sure
      * your plugin can be normally loaded by Nukkit.
    - * - *

    接下来对所有的字段做一些说明,加粗的字段表示必需,斜体表示可选:(来自 + * + * 接下来对所有的字段做一些说明,加粗的字段表示必需,斜体表示可选:(来自 * 粉鞋大妈的博客文章
    * Here are some instructions for there items, bold means required, italic means optional: (From * a blog article of @粉鞋大妈) - *

    *
      *
    • name
      * 字符串,表示这个插件的名字,名字是区分不同插件的标准之一。 @@ -105,7 +95,6 @@ * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see Plugin * @see PluginLoadOrder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public class PluginDescription { @@ -138,12 +127,14 @@ public PluginDescription(String yamlString) { private void loadMap(Map plugin) throws PluginException { this.name = ((String) plugin.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (this.name.equals("")) { - throw new PluginException("Invalid PluginDescription name"); + if (this.name.isEmpty()) { + throw new PluginException("plugin.yml must contain 'name'"); } + this.name = this.name.replace(" ", "_"); this.version = String.valueOf(plugin.get("version")); this.main = (String) plugin.get("main"); + Object api = plugin.get("api"); if (api instanceof List) { this.api = (List) api; @@ -152,8 +143,9 @@ private void loadMap(Map plugin) throws PluginException { list.add((String) api); this.api = list; } + if (this.main.startsWith("cn.nukkit.")) { - throw new PluginException("Invalid PluginDescription main, cannot start within the cn.nukkit. package"); + throw new PluginException(this.name + " has an invalid 'main' in plugin.yml (plugins can't be in cn.nukkit): " + this.main); } if (plugin.containsKey("commands") && plugin.get("commands") instanceof Map) { @@ -189,7 +181,7 @@ private void loadMap(Map plugin) throws PluginException { try { this.order = PluginLoadOrder.valueOf(order); } catch (Exception e) { - throw new PluginException("Invalid PluginDescription load"); + throw new PluginException(this.name + " has an invalid 'load' in plugin.yml: " + order); } } @@ -209,14 +201,12 @@ private void loadMap(Map plugin) throws PluginException { /** * 返回这个插件完整的名字。
      * Returns the full name of this plugin. - * - *

      一个插件完整的名字由{@code 名字+" v"+版本号}组成。比如:
      - * A full name of a plugin is composed by {@code name+" v"+version}.for example:

      - *

      {@code HelloWorld v1.0.0}

      + * + * 一个插件完整的名字由{@code 名字+" v"+版本号}组成。比如:
      + * A full name of a plugin is composed by {@code name+" v"+version}.for example: + * {@code HelloWorld v1.0.0} * * @return 这个插件完整的名字。
      The full name of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getFullName() { return this.name + " v" + this.version; @@ -227,8 +217,6 @@ public String getFullName() { * Returns all Nukkit API versions this plugin supports. * * @return 这个插件支持的Nukkit API版本列表。
      A list of all Nukkit API versions String this plugin supports. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public List getCompatibleAPIs() { return api; @@ -239,8 +227,6 @@ public List getCompatibleAPIs() { * Returns all the authors of this plugin. * * @return 这个插件的作者列表。
      A list of all authors of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public List getAuthors() { return authors; @@ -249,16 +235,14 @@ public List getAuthors() { /** * 返回这个插件的信息前缀。
      * Returns the message title of this plugin. - * - *

      插件的信息前缀在记录器记录信息时,会作为信息头衔使用。如果没有定义记录器,会使用插件的名字作为信息头衔。
      + * + * 插件的信息前缀在记录器记录信息时,会作为信息头衔使用。如果没有定义记录器,会使用插件的名字作为信息头衔。
      * When a PluginLogger logs, the message title is used as the prefix of message. If prefix is undefined, - * the plugin name will be used instead.

      + * the plugin name will be used instead. * * @return 这个插件的作信息前缀。如果没定义,返回{@code null}。
      * The message title of this plugin, or{@code null} if undefined. * @see PluginLogger - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getPrefix() { return prefix; @@ -269,8 +253,6 @@ public String getPrefix() { * Returns all the defined commands of this plugin. * * @return 这个插件定义的命令列表。
      A map of all defined commands of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public Map getCommands() { return commands; @@ -279,7 +261,7 @@ public Map getCommands() { /** * 返回这个插件所依赖的插件名字。
      * The names of the plugins what is depended by this plugin. - * + * * Nukkit插件的依赖有这些注意事项:
      Here are some note for Nukkit plugin depending: *
        *
      • 一个插件不能依赖自己(否则会报错)。
        A plugin can not depend on itself (or there will be an exception).
      • @@ -290,16 +272,14 @@ public Map getCommands() { * When the required dependency plugin does not exists, Nukkit won't load this plugin, but will tell the * user that this dependency is required. *
      - * - *

      举个例子,如果A插件依赖于B插件,在没有安装B插件而安装A插件的情况下,Nukkit会阻止A插件的加载。 + * + * 举个例子,如果A插件依赖于B插件,在没有安装B插件而安装A插件的情况下,Nukkit会阻止A插件的加载。 * 只有在安装B插件前安装了它所依赖的A插件,Nukkit才会允许加载B插件。
      * For example, there is a Plugin A which relies on Plugin B. If you installed A without installing B, * Nukkit won't load A because its dependency B is lost. Only when B is installed, A will be loaded - * by Nukkit.

      + * by Nukkit. * * @return 插件名字列表的 {@code List}对象。
      A {@code List} object carries the plugin names. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public List getDepend() { return depend; @@ -310,8 +290,6 @@ public List getDepend() { * Returns the description text of this plugin. * * @return 这个插件的描述文字。
      The description text of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getDescription() { return description; @@ -327,15 +305,13 @@ public List getLoadBefore() { /** * 返回这个插件的主类名。
      * Returns the main class name of this plugin. - * - *

      一个插件的加载都是从主类开始的。主类的名字在插件的配置文件中定义后可以通过这个函数返回。一个返回值例子:
      + * + * 一个插件的加载都是从主类开始的。主类的名字在插件的配置文件中定义后可以通过这个函数返回。一个返回值例子:
      * The load action of a Nukkit plugin begins from main class. The name of main class should be defined * in the plugin configuration, and it can be returned by this function. An example for return value:
      - * {@code "com.example.ExamplePlugin"}

      + * {@code "com.example.ExamplePlugin"} * * @return 这个插件的主类名。
      The main class name of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getMain() { return main; @@ -346,8 +322,6 @@ public String getMain() { * Returns the name of this plugin. * * @return 这个插件的名字。
      The name of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getName() { return name; @@ -358,9 +332,7 @@ public String getName() { * Returns the order the plugin loads, or when the plugin is loaded. * * @return 这个插件加载的顺序。
      The order the plugin loads. - * @see PluginDescription * @see PluginLoadOrder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public PluginLoadOrder getOrder() { return order; @@ -371,8 +343,6 @@ public PluginLoadOrder getOrder() { * Returns all the defined permissions of this plugin. * * @return 这个插件定义的权限列表。
      A map of all defined permissions of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public List getPermissions() { return permissions; @@ -390,8 +360,6 @@ public List getSoftDepend() { * Returns the version string of this plugin. * * @return 这个插件的版本号。
      The version string od this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getVersion() { return version; @@ -402,8 +370,6 @@ public String getVersion() { * Returns the website of this plugin. * * @return 这个插件的网站。
      The website of this plugin. - * @see PluginDescription - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public String getWebsite() { return website; diff --git a/src/main/java/cn/nukkit/plugin/PluginLoadOrder.java b/src/main/java/cn/nukkit/plugin/PluginLoadOrder.java index 341730ed834..9e5f79cc24e 100644 --- a/src/main/java/cn/nukkit/plugin/PluginLoadOrder.java +++ b/src/main/java/cn/nukkit/plugin/PluginLoadOrder.java @@ -3,16 +3,15 @@ /** * 描述一个Nukkit插件加载顺序的类。
      * Describes a Nukkit plugin load order. - *

      - *

      Nukkit插件的加载顺序有两个:{@link cn.nukkit.plugin.PluginLoadOrder#STARTUP} + * + * Nukkit插件的加载顺序有两个:{@link cn.nukkit.plugin.PluginLoadOrder#STARTUP} * 和 {@link cn.nukkit.plugin.PluginLoadOrder#POSTWORLD}。
      * The load order of a Nukkit plugin can be {@link cn.nukkit.plugin.PluginLoadOrder#STARTUP} - * or {@link cn.nukkit.plugin.PluginLoadOrder#POSTWORLD}.

      + * or {@link cn.nukkit.plugin.PluginLoadOrder#POSTWORLD}. * * @author MagicDroidX(code) @ Nukkit Project * @author iNevet(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public enum PluginLoadOrder { /** @@ -20,7 +19,6 @@ public enum PluginLoadOrder { * Indicates that the plugin will be loaded at startup. * * @see cn.nukkit.plugin.PluginLoadOrder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ STARTUP, /** @@ -28,7 +26,6 @@ public enum PluginLoadOrder { * Indicates that the plugin will be loaded after the first/default world was created. * * @see cn.nukkit.plugin.PluginLoadOrder - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ POSTWORLD } diff --git a/src/main/java/cn/nukkit/plugin/PluginLoader.java b/src/main/java/cn/nukkit/plugin/PluginLoader.java index 4d36c55fbbf..6b7b8d1fc97 100644 --- a/src/main/java/cn/nukkit/plugin/PluginLoader.java +++ b/src/main/java/cn/nukkit/plugin/PluginLoader.java @@ -10,108 +10,100 @@ * @author iNevet(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project * @see JavaPluginLoader - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public interface PluginLoader { /** * 通过文件名字的字符串,来加载和初始化一个插件。
      * Loads and initializes a plugin by its file name. - * - *

      这个方法应该设置好插件的相关属性。比如,插件所在的服务器对象,插件的加载器对象,插件的描述对象,插件的数据文件夹。
      + * + * 这个方法应该设置好插件的相关属性。比如,插件所在的服务器对象,插件的加载器对象,插件的描述对象,插件的数据文件夹。
      * Properties for loaded plugin should be set in this method. Such as, the {@code Server} object for which this * plugin is running in, the {@code PluginLoader} object for its loader, and the {@code File} object for its - * data folder.

      - * - *

      如果插件加载失败,这个方法应该返回{@code null},或者抛出异常。
      + * data folder. + * + * 如果插件加载失败,这个方法应该返回{@code null},或者抛出异常。
      * If the plugin loader does not load this plugin successfully, a {@code null} should be returned, - * or an exception should be thrown.

      + * or an exception should be thrown. * * @param filename 这个插件的文件名字字符串。
      A string of its file name. * @return 加载完毕的插件的 {@code Plugin}对象。
      The loaded plugin as a {@code Plugin} object. * @throws java.lang.Exception 插件加载失败所抛出的异常。
      Thrown when an error occurred. * @see #loadPlugin(File) * @see cn.nukkit.plugin.PluginBase#init(PluginLoader, cn.nukkit.Server, PluginDescription, File, File) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Plugin loadPlugin(String filename) throws Exception; /** * 通过插件的 {@code File}对象,来加载和初始化一个插件。
      * Loads and initializes a plugin by a {@code File} object describes the file. - * - *

      这个方法应该设置好插件的相关属性。比如,插件所在的服务器对象,插件的加载器对象,插件的描述对象,插件的数据文件夹。
      + * + * 这个方法应该设置好插件的相关属性。比如,插件所在的服务器对象,插件的加载器对象,插件的描述对象,插件的数据文件夹。
      * Properties for loaded plugin should be set in this method. Such as, the {@code Server} object for which this * plugin is running in, the {@code PluginLoader} object for its loader, and the {@code File} object for its - * data folder.

      - * - *

      如果插件加载失败,这个方法应该返回{@code null},或者抛出异常。
      + * data folder. + * + * 如果插件加载失败,这个方法应该返回{@code null},或者抛出异常。
      * If the plugin loader does not load this plugin successfully, a {@code null} should be returned, - * or an exception should be thrown.

      + * or an exception should be thrown. * * @param file 这个插件的文件的 {@code File}对象。
      A {@code File} object for this plugin. * @return 加载完毕的插件的 {@code Plugin}对象。
      The loaded plugin as a {@code Plugin} object. * @throws java.lang.Exception 插件加载失败所抛出的异常。
      Thrown when an error occurred. * @see #loadPlugin(String) * @see cn.nukkit.plugin.PluginBase#init(PluginLoader, cn.nukkit.Server, PluginDescription, File, File) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Plugin loadPlugin(File file) throws Exception; /** * 通过插件文件名的字符串,来获得描述这个插件的 {@code PluginDescription}对象。
      * Gets a {@code PluginDescription} object describes the plugin by its file name. - * - *

      如果插件的描述对象获取失败,这个方法应该返回{@code null}。
      - * If the plugin loader does not get its description successfully, a {@code null} should be returned.

      + * + * 如果插件的描述对象获取失败,这个方法应该返回{@code null}。
      + * If the plugin loader does not get its description successfully, a {@code null} should be returned. * * @param filename 这个插件的文件名字。
      A string of its file name. * @return 描述这个插件的 {@code PluginDescription}对象。
      * A {@code PluginDescription} object describes the plugin. * @see #getPluginDescription(File) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ PluginDescription getPluginDescription(String filename); /** * 通过插件的 {@code File}对象,来获得描述这个插件的 {@code PluginDescription}对象。
      * Gets a {@code PluginDescription} object describes the plugin by a {@code File} object describes the plugin file. - * - *

      如果插件的描述对象获取失败,这个方法应该返回{@code null}。
      - * If the plugin loader does not get its description successfully, a {@code null} should be returned.

      + * + * 如果插件的描述对象获取失败,这个方法应该返回{@code null}。
      + * If the plugin loader does not get its description successfully, a {@code null} should be returned. * * @param file 这个插件的文件的 {@code File}对象。
      A {@code File} object for this plugin. * @return 描述这个插件的 {@code PluginDescription}对象。
      * A {@code PluginDescription} object describes the plugin. * @see #getPluginDescription(String) - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ PluginDescription getPluginDescription(File file); /** * 返回这个插件加载器支持的文件类型。
      * Returns the file types this plugin loader supports. - * - *

      在Nukkit读取所有插件时,插件管理器会查找所有已经安装的插件加载器,通过识别这个插件是否满足下面的条件, + * + * 在Nukkit读取所有插件时,插件管理器会查找所有已经安装的插件加载器,通过识别这个插件是否满足下面的条件, * 来选择对应的插件加载器。
      * When Nukkit is trying to load all its plugins, the plugin manager will look for all installed plugin loader, - * and choose the correct one by checking if this plugin matches the filters given below.

      - * - *

      举个例子,识别这个文件是否以jar为扩展名,它的正则表达式是:
      + * and choose the correct one by checking if this plugin matches the filters given below. + * + * 举个例子,识别这个文件是否以jar为扩展名,它的正则表达式是:
      * For example, to check if this file is has a "jar" extension, the regular expression should be:
      * {@code ^.+\\.jar$}
      * 所以只读取jar扩展名的插件加载器,这个函数应该写成:
      * So, for a jar-extension-only file plugin loader, this method should be: - *

      *
       {@code           @Override}
            *      public Pattern[] getPluginFilters() {
            *          return new Pattern[]{Pattern.compile("^.+\\.jar$")};
      -     *      }
      -     * 
      + * }
    * * @return 表达这个插件加载器支持的文件类型的正则表达式数组。
    * An array of regular expressions, that describes what kind of file this plugin loader supports. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ Pattern[] getPluginFilters(); @@ -121,7 +113,6 @@ public interface PluginLoader { * * @param plugin 要被启用的插件。
    The plugin to enable. * @see #disablePlugin - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void enablePlugin(Plugin plugin); @@ -131,8 +122,6 @@ public interface PluginLoader { * * @param plugin 要被停用的插件。
    The plugin to disable. * @see #enablePlugin - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ void disablePlugin(Plugin plugin); - } diff --git a/src/main/java/cn/nukkit/plugin/PluginLogger.java b/src/main/java/cn/nukkit/plugin/PluginLogger.java index 9b7f50229c1..95064713144 100644 --- a/src/main/java/cn/nukkit/plugin/PluginLogger.java +++ b/src/main/java/cn/nukkit/plugin/PluginLogger.java @@ -5,7 +5,7 @@ import cn.nukkit.utils.Logger; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class PluginLogger implements Logger { @@ -14,7 +14,7 @@ public class PluginLogger implements Logger { public PluginLogger(Plugin context) { String prefix = context.getDescription().getPrefix(); - this.pluginName = prefix != null ? "[" + prefix + "] " : "[" + context.getDescription().getName() + "] "; + this.pluginName = prefix != null ? '[' + prefix + "] " : '[' + context.getDescription().getName() + "] "; } @Override @@ -106,5 +106,4 @@ public void debug(String message, Throwable t) { public void log(LogLevel level, String message, Throwable t) { Server.getInstance().getLogger().log(level, this.pluginName + message, t); } - } diff --git a/src/main/java/cn/nukkit/plugin/PluginManager.java b/src/main/java/cn/nukkit/plugin/PluginManager.java index 0ee96b95d06..b172782c25c 100644 --- a/src/main/java/cn/nukkit/plugin/PluginManager.java +++ b/src/main/java/cn/nukkit/plugin/PluginManager.java @@ -1,5 +1,6 @@ package cn.nukkit.plugin; +import cn.nukkit.Nukkit; import cn.nukkit.Server; import cn.nukkit.command.PluginCommand; import cn.nukkit.command.SimpleCommandMap; @@ -9,19 +10,25 @@ import cn.nukkit.utils.MainLogger; import cn.nukkit.utils.PluginException; import cn.nukkit.utils.Utils; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; +import com.google.common.reflect.TypeToken; +import org.lanternpowered.lmbda.LambdaFactory; +import org.lanternpowered.lmbda.LambdaType; import java.io.File; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; import java.util.regex.Pattern; /** * @author MagicDroidX */ public class PluginManager { + private static final MethodHandles.Lookup CALLER = MethodHandles.lookup(); + private static final LambdaType> EVENT_EXECUTOR_TYPE = LambdaType.of(new TypeToken>(){}.getType()); private final Server server; @@ -29,17 +36,17 @@ public class PluginManager { protected final Map plugins = new LinkedHashMap<>(); - protected final Map permissions = new HashMap<>(); + protected final Map permissions = new ConcurrentHashMap<>(); - protected final Map defaultPerms = new HashMap<>(); + protected final Map defaultPerms = new ConcurrentHashMap<>(); - protected final Map defaultPermsOp = new HashMap<>(); + protected final Map defaultPermsOp = new ConcurrentHashMap<>(); - protected final Map> permSubs = new HashMap<>(); + protected final Map> permSubs = new ConcurrentHashMap<>(); - protected final Set defSubs = Collections.newSetFromMap(new WeakHashMap<>()); + protected final Set defSubs = ConcurrentHashMap.newKeySet(); - protected final Set defSubsOp = Collections.newSetFromMap(new WeakHashMap<>()); + protected final Set defSubsOp = ConcurrentHashMap.newKeySet(); protected final Map fileAssociations = new HashMap<>(); @@ -49,10 +56,7 @@ public PluginManager(Server server, SimpleCommandMap commandMap) { } public Plugin getPlugin(String name) { - if (this.plugins.containsKey(name)) { - return this.plugins.get(name); - } - return null; + return this.plugins.get(name); } public boolean registerInterface(Class loaderClass) { @@ -223,8 +227,7 @@ public Map loadPlugins(File dictionary, List newLoaders, } } } catch (Exception e) { - this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin" + - ".fileError", file.getName(), dictionary.toString(), Utils + this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin.fileError", file.getName(), dictionary.toString(), Utils .getExceptionMessage(e))); MainLogger logger = this.server.getLogger(); if (logger != null) { @@ -243,8 +246,7 @@ public Map loadPlugins(File dictionary, List newLoaders, if (loadedPlugins.containsKey(dependency) || this.getPlugin(dependency) != null) { dependencies.get(name).remove(dependency); } else if (!plugins.containsKey(dependency)) { - this.server.getLogger().critical(this.server.getLanguage().translateString("nukkit" + - ".plugin.loadError", new String[]{name, "%nukkit.plugin.unknownDependency"}) + ": " + dependency); + this.server.getLogger().critical(this.server.getLanguage().translateString("nukkit.plugin.loadError", new String[]{name, "%nukkit.plugin.unknownDependency"}) + ' ' + dependency); break; } } @@ -255,8 +257,11 @@ public Map loadPlugins(File dictionary, List newLoaders, } if (softDependencies.containsKey(name)) { - softDependencies.get(name).removeIf(dependency -> - loadedPlugins.containsKey(dependency) || this.getPlugin(dependency) != null); + for (String dependency : new ArrayList<>(softDependencies.get(name))) { + if (loadedPlugins.containsKey(dependency) || this.getPlugin(dependency) != null) { + softDependencies.get(name).remove(dependency); + } + } if (softDependencies.get(name).isEmpty()) { softDependencies.remove(name); @@ -307,10 +312,7 @@ public Map loadPlugins(File dictionary, List newLoaders, } public Permission getPermission(String name) { - if (this.permissions.containsKey(name)) { - return this.permissions.get(name); - } - return null; + return this.permissions.get(name); } public boolean addPermission(Permission permission) { @@ -349,7 +351,6 @@ public void recalculatePermissionDefaults(Permission permission) { } private void calculatePermissionDefault(Permission permission) { - Timings.permissionDefaultTimer.startTiming(); if (permission.getDefault().equals(Permission.DEFAULT_OP) || permission.getDefault().equals(Permission.DEFAULT_TRUE)) { this.defaultPermsOp.put(permission.getName(), permission); this.dirtyPermissibles(true); @@ -359,7 +360,6 @@ private void calculatePermissionDefault(Permission permission) { this.defaultPerms.put(permission.getName(), permission); this.dirtyPermissibles(false); } - Timings.permissionDefaultTimer.startTiming(); } private void dirtyPermissibles(boolean op) { @@ -370,7 +370,7 @@ private void dirtyPermissibles(boolean op) { public void subscribeToPermission(String permission, Permissible permissible) { if (!this.permSubs.containsKey(permission)) { - this.permSubs.put(permission, Collections.newSetFromMap(new WeakHashMap<>())); + this.permSubs.put(permission, ConcurrentHashMap.newKeySet()); } this.permSubs.get(permission).add(permissible); } @@ -378,7 +378,7 @@ public void subscribeToPermission(String permission, Permissible permissible) { public void unsubscribeFromPermission(String permission, Permissible permissible) { if (this.permSubs.containsKey(permission)) { this.permSubs.get(permission).remove(permissible); - if (this.permSubs.get(permission).size() == 0) { + if (this.permSubs.get(permission).isEmpty()) { this.permSubs.remove(permission); } } @@ -444,6 +444,7 @@ public void enablePlugin(Plugin plugin) { } } + @SuppressWarnings("unchecked") protected List parseYamlCommands(Plugin plugin) { List pluginCmds = new ArrayList<>(); @@ -497,7 +498,7 @@ protected List parseYamlCommands(Plugin plugin) { } public void disablePlugins() { - ListIterator plugins = new ArrayList<>(this.getPlugins().values()).listIterator(this.getPlugins().size()); + ListIterator plugins = new ArrayList<>(this.plugins.values()).listIterator(this.plugins.size()); while (plugins.hasPrevious()) { this.disablePlugin(plugins.previous()); @@ -534,7 +535,12 @@ public void clearPlugins() { public void callEvent(Event event) { try { - for (RegisteredListener registration : getEventListeners(event.getClass()).getRegisteredListeners()) { + RegisteredListener[] listeners = this.getEventListeners(event.getClass()).getRegisteredListeners(); + if (listeners == null) { + return; + } + + for (RegisteredListener registration : listeners) { if (!registration.getPlugin().isEnabled()) { continue; } @@ -556,7 +562,6 @@ public void registerEvents(Listener listener, Plugin plugin) { throw new PluginException("Plugin attempted to register " + listener.getClass().getName() + " while not enabled"); } - Map, Set> ret = new HashMap<>(); Set methods; try { Method[] publicMethods = listener.getClass().getMethods(); @@ -585,16 +590,21 @@ public void registerEvents(Listener listener, Plugin plugin) { final Class eventClass = checkClass.asSubclass(Event.class); method.setAccessible(true); - for (Class clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { - // This loop checks for extending deprecated events - if (clazz.getAnnotation(Deprecated.class) != null) { - if (Boolean.parseBoolean(String.valueOf(this.server.getConfig("settings.deprecated-verbose", true)))) { + if (Nukkit.DEBUG > 1) { + for (Class clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { + if (clazz.getAnnotation(Deprecated.class) != null) { this.server.getLogger().warning(this.server.getLanguage().translateString("nukkit.plugin.deprecatedEvent", plugin.getName(), clazz.getName(), listener.getClass().getName() + "." + method.getName() + "()")); + break; } - break; } } - this.registerEvent(eventClass, listener, eh.priority(), new MethodEventExecutor(method), plugin, eh.ignoreCancelled()); + + try { + BiConsumer consumer = LambdaFactory.create(EVENT_EXECUTOR_TYPE, CALLER.unreflect(method)); + this.registerEvent(eventClass, listener, eh.priority(), new LambdaEventExecutor(eventClass, consumer), plugin, eh.ignoreCancelled()); + } catch (IllegalAccessException e) { + plugin.getLogger().error(plugin.getDescription().getFullName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass()); + } } } @@ -608,23 +618,34 @@ public void registerEvent(Class event, Listener listener, Event } try { - Timing timing = Timings.getPluginEventTiming(event, listener, executor, plugin); - this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled, timing)); + this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); } catch (IllegalAccessException e) { Server.getInstance().getLogger().logException(e); } } private HandlerList getEventListeners(Class type) throws IllegalAccessException { + HandlerList handlerList = HandlerList.getCachedHandlerList(type); + if (handlerList != null) { + return handlerList; + } + try { Method method = getRegistrationClass(type).getDeclaredMethod("getHandlers"); method.setAccessible(true); - return (HandlerList) method.invoke(null); + handlerList = (HandlerList) method.invoke(null); } catch (NullPointerException e) { - throw new IllegalArgumentException("getHandlers method in " + type.getName() + " was not static!"); + if (Nukkit.DEBUG > 1) { + Server.getInstance().getLogger().debug("Static getHandlers method in " + type.getName() + " was not found. Creating HandlerList dynamically."); + } } catch (Exception e) { throw new IllegalAccessException(Utils.getExceptionMessage(e)); } + + if (handlerList == null) { // do not require user to create static HandlerList anymore + HandlerList.putCachedHandlerList(type, handlerList = new HandlerList()); + } + return handlerList; } private Class getRegistrationClass(Class clazz) throws IllegalAccessException { @@ -633,7 +654,7 @@ private Class getRegistrationClass(Class clazz return clazz; } catch (NoSuchMethodException e) { if (clazz.getSuperclass() != null - && !clazz.getSuperclass().equals(Event.class) + && clazz.getSuperclass() != Event.class && Event.class.isAssignableFrom(clazz.getSuperclass())) { return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); } else { diff --git a/src/main/java/cn/nukkit/plugin/RegisteredListener.java b/src/main/java/cn/nukkit/plugin/RegisteredListener.java index 0744db1f04c..9c6a6f0e025 100644 --- a/src/main/java/cn/nukkit/plugin/RegisteredListener.java +++ b/src/main/java/cn/nukkit/plugin/RegisteredListener.java @@ -5,10 +5,9 @@ import cn.nukkit.event.EventPriority; import cn.nukkit.event.Listener; import cn.nukkit.utils.EventException; -import co.aikar.timings.Timing; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class RegisteredListener { @@ -23,15 +22,12 @@ public class RegisteredListener { private final boolean ignoreCancelled; - private final Timing timing; - - public RegisteredListener(Listener listener, EventExecutor executor, EventPriority priority, Plugin plugin, boolean ignoreCancelled, Timing timing) { + public RegisteredListener(Listener listener, EventExecutor executor, EventPriority priority, Plugin plugin, boolean ignoreCancelled) { this.listener = listener; this.priority = priority; this.plugin = plugin; this.executor = executor; this.ignoreCancelled = ignoreCancelled; - this.timing = timing; } public Listener getListener() { @@ -48,13 +44,11 @@ public EventPriority getPriority() { public void callEvent(Event event) throws EventException { if (event instanceof Cancellable) { - if (event.isCancelled() && isIgnoringCancelled()) { + if (event.isCancelled() && ignoreCancelled) { return; } } - this.timing.startTiming(); executor.execute(listener, event); - this.timing.stopTiming(); } public boolean isIgnoringCancelled() { diff --git a/src/main/java/cn/nukkit/plugin/service/NKServiceManager.java b/src/main/java/cn/nukkit/plugin/service/NKServiceManager.java index 95bfa43a082..5f4ffa94160 100644 --- a/src/main/java/cn/nukkit/plugin/service/NKServiceManager.java +++ b/src/main/java/cn/nukkit/plugin/service/NKServiceManager.java @@ -1,6 +1,5 @@ package cn.nukkit.plugin.service; - import cn.nukkit.Server; import cn.nukkit.plugin.Plugin; import com.google.common.base.Preconditions; @@ -8,18 +7,15 @@ import java.util.*; -/** - * Created on 16-11-20. - */ public class NKServiceManager implements ServiceManager { private final Map, List>> handle = new HashMap<>(); @Override public boolean register(Class service, T provider, Plugin plugin, ServicePriority priority) { - Preconditions.checkNotNull(provider); - Preconditions.checkNotNull(priority); - Preconditions.checkNotNull(service); + Preconditions.checkNotNull(provider, "provider"); + Preconditions.checkNotNull(priority, "priority"); + Preconditions.checkNotNull(service, "service"); // build-in service provider needn't plugin param if (plugin == null && provider.getClass().getClassLoader() != Server.class.getClassLoader()) { @@ -63,7 +59,6 @@ public List> cancel(Plugin plugin) { builder.add(registered); } } - } } @@ -71,6 +66,7 @@ public List> cancel(Plugin plugin) { } @Override + @SuppressWarnings("unchecked") public RegisteredServiceProvider cancel(Class service, T provider) { RegisteredServiceProvider result = null; @@ -85,13 +81,13 @@ public RegisteredServiceProvider cancel(Class service, T provider) { result = next; } } - } return result; } @Override + @SuppressWarnings("unchecked") public RegisteredServiceProvider getProvider(Class service) { synchronized (handle) { List> list = handle.get(service); @@ -126,8 +122,7 @@ public List> getRegistrations(Class service) synchronized (handle) { List> registered = handle.get(service); if (registered == null) { - ImmutableList> empty = ImmutableList.of(); - return empty; + return ImmutableList.of(); } for (RegisteredServiceProvider provider : registered) { builder.add((RegisteredServiceProvider)provider); diff --git a/src/main/java/cn/nukkit/plugin/service/RegisteredServiceProvider.java b/src/main/java/cn/nukkit/plugin/service/RegisteredServiceProvider.java index abecb212298..a43575f864d 100644 --- a/src/main/java/cn/nukkit/plugin/service/RegisteredServiceProvider.java +++ b/src/main/java/cn/nukkit/plugin/service/RegisteredServiceProvider.java @@ -1,17 +1,13 @@ package cn.nukkit.plugin.service; - import cn.nukkit.plugin.Plugin; -/** - * Created on 16-11-20. - */ public class RegisteredServiceProvider implements Comparable> { - private Plugin plugin; - private ServicePriority priority; - private Class service; - private T provider; + private final Plugin plugin; + private final ServicePriority priority; + private final Class service; + private final T provider; RegisteredServiceProvider(Class service, T provider, ServicePriority priority, Plugin plugin) { this.plugin = plugin; @@ -70,5 +66,4 @@ public int hashCode() { public int compareTo(RegisteredServiceProvider other) { return other.priority.ordinal() - priority.ordinal(); } - } diff --git a/src/main/java/cn/nukkit/plugin/service/ServiceManager.java b/src/main/java/cn/nukkit/plugin/service/ServiceManager.java index b58eb8cfcc8..2478c907421 100644 --- a/src/main/java/cn/nukkit/plugin/service/ServiceManager.java +++ b/src/main/java/cn/nukkit/plugin/service/ServiceManager.java @@ -4,9 +4,6 @@ import java.util.List; -/** - * Created on 16-11-20. - */ public interface ServiceManager { /** diff --git a/src/main/java/cn/nukkit/plugin/service/ServicePriority.java b/src/main/java/cn/nukkit/plugin/service/ServicePriority.java index 241f13acef1..d4b9db365fa 100644 --- a/src/main/java/cn/nukkit/plugin/service/ServicePriority.java +++ b/src/main/java/cn/nukkit/plugin/service/ServicePriority.java @@ -1,10 +1,6 @@ package cn.nukkit.plugin.service; -/** - * Created on 16-11-20. - */ public enum ServicePriority { LOWEST, LOWER, NORMAL, HIGHER, HIGHEST, - } diff --git a/src/main/java/cn/nukkit/potion/Effect.java b/src/main/java/cn/nukkit/potion/Effect.java index 44945bc9202..b793ffd515b 100644 --- a/src/main/java/cn/nukkit/potion/Effect.java +++ b/src/main/java/cn/nukkit/potion/Effect.java @@ -1,7 +1,9 @@ package cn.nukkit.potion; import cn.nukkit.Player; +import cn.nukkit.entity.Attribute; import cn.nukkit.entity.Entity; +import cn.nukkit.entity.EntityBoss; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.EntityRegainHealthEvent; @@ -9,7 +11,7 @@ import cn.nukkit.utils.ServerException; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Effect implements Cloneable { @@ -17,7 +19,7 @@ public class Effect implements Cloneable { public static final int SPEED = 1; public static final int SLOWNESS = 2; public static final int HASTE = 3; - public static final int SWIFTNESS = 3; + public static final int SWIFTNESS = 3; // incorrect? public static final int FATIGUE = 4; public static final int MINING_FATIGUE = 4; public static final int STRENGTH = 5; @@ -59,7 +61,7 @@ public static void init() { effects[Effect.SPEED] = new Effect(Effect.SPEED, "%potion.moveSpeed", 124, 175, 198); effects[Effect.SLOWNESS] = new Effect(Effect.SLOWNESS, "%potion.moveSlowdown", 90, 108, 129, true); - effects[Effect.SWIFTNESS] = new Effect(Effect.SWIFTNESS, "%potion.digSpeed", 217, 192, 67); + effects[Effect.HASTE] = new Effect(Effect.HASTE, "%potion.digSpeed", 217, 192, 67); effects[Effect.FATIGUE] = new Effect(Effect.FATIGUE, "%potion.digSlowDown", 74, 66, 23, true); effects[Effect.STRENGTH] = new Effect(Effect.STRENGTH, "%potion.damageBoost", 147, 36, 35); effects[Effect.HEALING] = new InstantEffect(Effect.HEALING, "%potion.heal", 248, 36, 35); @@ -71,16 +73,13 @@ public static void init() { effects[Effect.FIRE_RESISTANCE] = new Effect(Effect.FIRE_RESISTANCE, "%potion.fireResistance", 228, 154, 58); effects[Effect.WATER_BREATHING] = new Effect(Effect.WATER_BREATHING, "%potion.waterBreathing", 46, 82, 153); effects[Effect.INVISIBILITY] = new Effect(Effect.INVISIBILITY, "%potion.invisibility", 127, 131, 146); - effects[Effect.BLINDNESS] = new Effect(Effect.BLINDNESS, "%potion.blindness", 191, 192, 192); effects[Effect.NIGHT_VISION] = new Effect(Effect.NIGHT_VISION, "%potion.nightVision", 0, 0, 139); effects[Effect.HUNGER] = new Effect(Effect.HUNGER, "%potion.hunger", 46, 139, 87); - effects[Effect.WEAKNESS] = new Effect(Effect.WEAKNESS, "%potion.weakness", 72, 77, 72, true); effects[Effect.POISON] = new Effect(Effect.POISON, "%potion.poison", 78, 147, 49, true); effects[Effect.WITHER] = new Effect(Effect.WITHER, "%potion.wither", 53, 42, 39, true); effects[Effect.HEALTH_BOOST] = new Effect(Effect.HEALTH_BOOST, "%potion.healthBoost", 248, 125, 35); - effects[Effect.ABSORPTION] = new Effect(Effect.ABSORPTION, "%potion.absorption", 36, 107, 251); effects[Effect.SATURATION] = new Effect(Effect.SATURATION, "%potion.saturation", 255, 0, 255); effects[Effect.LEVITATION] = new Effect(Effect.LEVITATION, "%potion.levitation", 206, 255, 255, true); @@ -188,18 +187,18 @@ public boolean isBad() { public boolean canTick() { int interval; switch (this.id) { - case Effect.POISON: //POISON + case Effect.POISON: case Effect.FATAL_POISON: if ((interval = (25 >> this.amplifier)) > 0) { return (this.duration % interval) == 0; } return true; - case Effect.WITHER: //WITHER + case Effect.WITHER: if ((interval = (50 >> this.amplifier)) > 0) { return (this.duration % interval) == 0; } return true; - case Effect.REGENERATION: //REGENERATION + case Effect.REGENERATION: if ((interval = (40 >> this.amplifier)) > 0) { return (this.duration % interval) == 0; } @@ -209,18 +208,19 @@ public boolean canTick() { } public void applyEffect(Entity entity) { + if (entity instanceof EntityBoss) return; // Boss mobs are immune to poison, wither and regeneration switch (this.id) { - case Effect.POISON: //POISON + case Effect.POISON: case Effect.FATAL_POISON: if (entity.getHealth() > 1 || this.id == FATAL_POISON) { entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, 1)); } break; - case Effect.WITHER: //WITHER + case Effect.WITHER: entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, 1)); break; - case Effect.REGENERATION: //REGENERATION - if (entity.getHealth() < entity.getMaxHealth()) { + case Effect.REGENERATION: + if (entity.getHealth() < entity.getRealMaxHealth()) { entity.heal(new EntityRegainHealthEvent(entity, 1, EntityRegainHealthEvent.CAUSE_MAGIC)); } break; @@ -236,10 +236,10 @@ public void setColor(int r, int g, int b) { } public void add(Entity entity) { - Effect oldEffect = entity.getEffect(getId()); - if (oldEffect != null && (Math.abs(this.getAmplifier()) < Math.abs(oldEffect.getAmplifier()) || - Math.abs(this.getAmplifier()) == Math.abs(oldEffect.getAmplifier()) - && this.getDuration() < oldEffect.getDuration())) { + Effect oldEffect = entity.getEffect(id); + if (oldEffect != null && (Math.abs(this.amplifier) < Math.abs(oldEffect.amplifier) || + Math.abs(this.amplifier) == Math.abs(oldEffect.amplifier) + && this.duration < oldEffect.duration)) { return; } if (entity instanceof Player) { @@ -247,10 +247,10 @@ public void add(Entity entity) { MobEffectPacket pk = new MobEffectPacket(); pk.eid = entity.getId(); - pk.effectId = this.getId(); - pk.amplifier = this.getAmplifier(); - pk.particles = this.isVisible(); - pk.duration = this.getDuration(); + pk.effectId = this.id; + pk.amplifier = this.amplifier; + pk.particles = this.show; + pk.duration = this.duration; if (oldEffect != null) { pk.eventId = MobEffectPacket.EVENT_MODIFY; } else { @@ -260,17 +260,19 @@ public void add(Entity entity) { player.dataPacket(pk); if (this.id == Effect.SPEED) { - if (oldEffect != null) { + /*if (oldEffect != null) { player.setMovementSpeed(player.getMovementSpeed() / (1 + 0.2f * (oldEffect.amplifier + 1)), false); } - player.setMovementSpeed(player.getMovementSpeed() * (1 + 0.2f * (this.amplifier + 1))); + player.setMovementSpeed(player.getMovementSpeed() * (1 + 0.2f * (this.amplifier + 1)));*/ + player.setMovementSpeed(Player.DEFAULT_SPEED * (1 + 0.2f * (this.amplifier + 1))); //HACK: Fix beacon exploit } if (this.id == Effect.SLOWNESS) { - if (oldEffect != null) { + /*if (oldEffect != null) { player.setMovementSpeed(player.getMovementSpeed() / (1 - 0.15f * (oldEffect.amplifier + 1)), false); } - player.setMovementSpeed(player.getMovementSpeed() * (1 - 0.15f * (this.amplifier + 1))); + player.setMovementSpeed(player.getMovementSpeed() * (1 - 0.15f * (this.amplifier + 1)));*/ + player.setMovementSpeed(Player.DEFAULT_SPEED * (1 - 0.15f * (this.amplifier + 1))); } } @@ -280,7 +282,7 @@ public void add(Entity entity) { } if (this.id == Effect.ABSORPTION) { - int add = (this.amplifier + 1) * 4; + int add = (this.amplifier + 1) << 2; if (add > entity.getAbsorption()) entity.setAbsorption(add); } } @@ -289,16 +291,22 @@ public void remove(Entity entity) { if (entity instanceof Player) { MobEffectPacket pk = new MobEffectPacket(); pk.eid = entity.getId(); - pk.effectId = this.getId(); + pk.effectId = this.id; pk.eventId = MobEffectPacket.EVENT_REMOVE; ((Player) entity).dataPacket(pk); if (this.id == Effect.SPEED) { - ((Player) entity).setMovementSpeed(((Player) entity).getMovementSpeed() / (1 + 0.2f * (this.amplifier + 1))); + //((Player) entity).setMovementSpeed(((Player) entity).getMovementSpeed() / (1 + 0.2f * (this.amplifier + 1))); + + ((Player) entity).setMovementSpeed(entity.isSprinting() ? Player.DEFAULT_SPEED * 1.3f : Player.DEFAULT_SPEED, false); + ((Player) entity).setAttribute(Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(Player.DEFAULT_SPEED).setDefaultValue(Player.DEFAULT_SPEED)); } if (this.id == Effect.SLOWNESS) { - ((Player) entity).setMovementSpeed(((Player) entity).getMovementSpeed() / (1 - 0.15f * (this.amplifier + 1))); + //((Player) entity).setMovementSpeed(((Player) entity).getMovementSpeed() / (1 - 0.15f * (this.amplifier + 1))); + + ((Player) entity).setMovementSpeed(entity.isSprinting() ? Player.DEFAULT_SPEED * 1.3f : Player.DEFAULT_SPEED, false); + ((Player) entity).setAttribute(Attribute.getAttribute(Attribute.MOVEMENT_SPEED).setValue(Player.DEFAULT_SPEED).setDefaultValue(Player.DEFAULT_SPEED)); } } diff --git a/src/main/java/cn/nukkit/potion/InstantEffect.java b/src/main/java/cn/nukkit/potion/InstantEffect.java index 90501c6bca0..db549b51c60 100644 --- a/src/main/java/cn/nukkit/potion/InstantEffect.java +++ b/src/main/java/cn/nukkit/potion/InstantEffect.java @@ -1,10 +1,11 @@ package cn.nukkit.potion; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class InstantEffect extends Effect { + public InstantEffect(int id, String name, int r, int g, int b) { super(id, name, r, g, b); } diff --git a/src/main/java/cn/nukkit/potion/Potion.java b/src/main/java/cn/nukkit/potion/Potion.java index 5218df2e1b4..70cd0129cb6 100644 --- a/src/main/java/cn/nukkit/potion/Potion.java +++ b/src/main/java/cn/nukkit/potion/Potion.java @@ -1,9 +1,12 @@ package cn.nukkit.potion; -import cn.nukkit.Player; import cn.nukkit.entity.Entity; import cn.nukkit.entity.EntityLiving; import cn.nukkit.entity.EntitySmite; +import cn.nukkit.entity.mob.EntityBlaze; +import cn.nukkit.entity.mob.EntityEnderman; +import cn.nukkit.entity.mob.EntitySnowGolem; +import cn.nukkit.entity.passive.EntityStrider; import cn.nukkit.event.entity.EntityDamageEvent; import cn.nukkit.event.entity.EntityDamageEvent.DamageCause; import cn.nukkit.event.entity.EntityRegainHealthEvent; @@ -11,7 +14,7 @@ import cn.nukkit.utils.ServerException; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class Potion implements Cloneable { @@ -60,6 +63,7 @@ public class Potion implements Cloneable { public static final int SLOW_FALLING = 40; public static final int SLOW_FALLING_LONG = 41; public static final int SLOWNESS_LONG_II = 42; + public static final int SLOWNESS_IV = 43; protected static Potion[] potions; @@ -109,6 +113,7 @@ public static void init() { potions[Potion.SLOW_FALLING] = new Potion(Potion.SLOW_FALLING); potions[Potion.SLOW_FALLING_LONG] = new Potion(Potion.SLOW_FALLING_LONG); potions[Potion.SLOWNESS_LONG_II] = new Potion(Potion.SLOWNESS_LONG_II, 2); + potions[Potion.SLOWNESS_IV] = new Potion(Potion.SLOWNESS, 4); } public static Potion getPotion(int id) { @@ -132,7 +137,7 @@ public static Potion getPotionByName(String name) { protected final int level; - protected boolean splash = false; + protected boolean splash; public Potion(int id) { this(id, 1); @@ -149,7 +154,7 @@ public Potion(int id, int level, boolean splash) { } public Effect getEffect() { - return getEffect(this.getId(), this.isSplash()); + return getEffect(this.id, this.splash); } public int getId() { @@ -178,17 +183,22 @@ public void applyPotion(Entity entity, double health) { return; } - Effect applyEffect = getEffect(this.getId(), this.isSplash()); + if (this.id == WATER && (entity instanceof EntityEnderman || entity instanceof EntityStrider || entity instanceof EntitySnowGolem || entity instanceof EntityBlaze)) { + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, 1f)); + return; + } + + Effect applyEffect = getEffect(this.id, this.splash); if (applyEffect == null) { return; } - if (entity instanceof Player) { + /*if (entity instanceof Player) { if (!((Player) entity).isSurvival() && !((Player) entity).isAdventure() && applyEffect.isBad()) { return; } - } + }*/ PotionApplyEvent event = new PotionApplyEvent(this, applyEffect, entity); @@ -199,26 +209,31 @@ public void applyPotion(Entity entity, double health) { applyEffect = event.getApplyEffect(); - switch (this.getId()) { + switch (this.id) { case INSTANT_HEALTH: case INSTANT_HEALTH_II: if (entity instanceof EntitySmite) { - entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (double) (6 << (applyEffect.getAmplifier() + 1))))); + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (6 << (applyEffect.getAmplifier() + 1))))); } else { entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); } break; case HARMING: + if (entity instanceof EntitySmite) { + entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); + } else { + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * 6))); + } + break; case HARMING_II: if (entity instanceof EntitySmite) { entity.heal(new EntityRegainHealthEvent(entity, (float) (health * (double) (4 << (applyEffect.getAmplifier() + 1))), EntityRegainHealthEvent.CAUSE_MAGIC)); } else { - entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * (double) (6 << (applyEffect.getAmplifier() + 1))))); + entity.attack(new EntityDamageEvent(entity, DamageCause.MAGIC, (float) (health * 12))); } break; default: - int duration = (int) ((isSplash() ? health : 1) * (double) applyEffect.getDuration() + 0.5); - applyEffect.setDuration(duration); + applyEffect.setDuration((int) ((splash ? health : 1) * (double) applyEffect.getDuration() + 0.5)); entity.addEffect(applyEffect); } } @@ -235,6 +250,12 @@ public Potion clone() { public static Effect getEffect(int potionType, boolean isSplash) { Effect effect; switch (potionType) { + case NO_EFFECTS: + case MUNDANE: + case MUNDANE_II: + case THICK: + case AWKWARD: + return null; case NIGHT_VISION: case NIGHT_VISION_LONG: effect = Effect.getEffect(Effect.NIGHT_VISION); @@ -259,6 +280,8 @@ public static Effect getEffect(int potionType, boolean isSplash) { break; case SLOWNESS: case SLOWNESS_LONG: + case SLOWNESS_LONG_II: + case SLOWNESS_IV: effect = Effect.getEffect(Effect.SLOWNESS); break; case WATER_BREATHING: @@ -310,6 +333,8 @@ public static Effect getEffect(int potionType, boolean isSplash) { public static int getLevel(int potionType) { switch (potionType) { + case SLOWNESS_IV: + return 4; case MUNDANE_II: case LEAPING_II: case SPEED_II: @@ -320,6 +345,7 @@ public static int getLevel(int potionType) { case STRENGTH_II: case WITHER_II: case TURTLE_MASTER_II: + case SLOWNESS_LONG_II: return 2; default: return 1; @@ -341,83 +367,163 @@ public static boolean isInstant(int potionType) { public static int getApplySeconds(int potionType, boolean isSplash) { if (isSplash) { switch (potionType) { + case NO_EFFECTS: + return 0; + case MUNDANE: + return 0; + case MUNDANE_II: + return 0; + case THICK: + return 0; + case AWKWARD: + return 0; case NIGHT_VISION: - case STRENGTH: - case WATER_BREATHING: - case SPEED: - case FIRE_RESISTANCE: - case LEAPING: - case INVISIBLE: return 135; case NIGHT_VISION_LONG: - case STRENGTH_LONG: - case WATER_BREATHING_LONG: - case SPEED_LONG: - case FIRE_RESISTANCE_LONG: - case LEAPING_LONG: + return 360; + case INVISIBLE: + return 135; case INVISIBLE_LONG: return 360; + case LEAPING: + return 135; + case LEAPING_LONG: + return 360; case LEAPING_II: - case WEAKNESS: - case STRENGTH_II: - case SLOWNESS: + return 67; + case FIRE_RESISTANCE: + return 135; + case FIRE_RESISTANCE_LONG: + return 360; + case SPEED: + return 135; + case SPEED_LONG: + return 360; case SPEED_II: return 67; + case SLOWNESS: + return 67; case SLOWNESS_LONG: - case WEAKNESS_LONG: return 180; + case WATER_BREATHING: + return 135; + case WATER_BREATHING_LONG: + return 360; + case INSTANT_HEALTH: + return 0; + case INSTANT_HEALTH_II: + return 0; + case HARMING: + return 0; + case HARMING_II: + return 0; case POISON: - case REGENERATION: return 33; case POISON_LONG: - case REGENERATION_LONG: return 90; case POISON_II: + return 16; + case REGENERATION: + return 33; + case REGENERATION_LONG: + return 90; case REGENERATION_II: return 16; + case STRENGTH: + return 135; + case STRENGTH_LONG: + return 360; + case STRENGTH_II: + return 67; + case WEAKNESS: + return 67; + case WEAKNESS_LONG: + return 180; case WITHER_II: return 30; + case SLOWNESS_IV: + return 15; default: return 0; } } else { switch (potionType) { + case NO_EFFECTS: + return 0; + case MUNDANE: + return 0; + case MUNDANE_II: + return 0; + case THICK: + return 0; + case AWKWARD: + return 0; case NIGHT_VISION: - case STRENGTH: - case WATER_BREATHING: - case SPEED: - case FIRE_RESISTANCE: - case LEAPING: - case INVISIBLE: return 180; case NIGHT_VISION_LONG: - case STRENGTH_LONG: - case WATER_BREATHING_LONG: - case SPEED_LONG: - case FIRE_RESISTANCE_LONG: - case LEAPING_LONG: + return 480; + case INVISIBLE: + return 180; case INVISIBLE_LONG: return 480; - case SPEED_II: + case LEAPING: + return 180; + case LEAPING_LONG: + return 480; case LEAPING_II: - case WEAKNESS: - case STRENGTH_II: + return 90; + case FIRE_RESISTANCE: + return 180; + case FIRE_RESISTANCE_LONG: + return 480; + case SPEED: + return 180; + case SPEED_LONG: + return 480; + case SPEED_II: + return 90; case SLOWNESS: return 90; case SLOWNESS_LONG: - case WEAKNESS_LONG: return 240; + case WATER_BREATHING: + return 180; + case WATER_BREATHING_LONG: + return 480; + case INSTANT_HEALTH: + return 0; + case INSTANT_HEALTH_II: + return 0; + case HARMING: + return 0; + case HARMING_II: + return 0; case POISON: - case REGENERATION: return 45; case POISON_LONG: - case REGENERATION_LONG: return 120; case POISON_II: + return 22; + case REGENERATION: + return 45; + case REGENERATION_LONG: + return 120; case REGENERATION_II: return 22; + case STRENGTH: + return 180; + case STRENGTH_LONG: + return 480; + case STRENGTH_II: + return 90; + case WEAKNESS: + return 90; + case WEAKNESS_LONG: + return 240; case WITHER_II: return 30; + case SLOWNESS_IV: + return 20; default: return 0; } diff --git a/src/main/java/cn/nukkit/resourcepacks/AbstractResourcePack.java b/src/main/java/cn/nukkit/resourcepacks/AbstractResourcePack.java index 062dd26fb33..08d1c3e8d3c 100644 --- a/src/main/java/cn/nukkit/resourcepacks/AbstractResourcePack.java +++ b/src/main/java/cn/nukkit/resourcepacks/AbstractResourcePack.java @@ -6,6 +6,7 @@ import java.util.UUID; public abstract class AbstractResourcePack implements ResourcePack { + protected JsonObject manifest; private UUID id = null; diff --git a/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java b/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java index 876e7d0cc09..c2c915d3987 100644 --- a/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java +++ b/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java @@ -3,6 +3,7 @@ import java.util.UUID; public interface ResourcePack { + String getPackName(); UUID getPackId(); @@ -15,5 +16,7 @@ public interface ResourcePack { byte[] getPackChunk(int off, int len); - String getEncryptionKey(); + default String getEncryptionKey() { // Default for backwards compatibility + return ""; + } } diff --git a/src/main/java/cn/nukkit/resourcepacks/ResourcePackManager.java b/src/main/java/cn/nukkit/resourcepacks/ResourcePackManager.java index 6ef33d51d34..3e2221681ff 100644 --- a/src/main/java/cn/nukkit/resourcepacks/ResourcePackManager.java +++ b/src/main/java/cn/nukkit/resourcepacks/ResourcePackManager.java @@ -7,8 +7,9 @@ import java.util.*; public class ResourcePackManager { + private final Map resourcePacksById = new HashMap<>(); - private ResourcePack[] resourcePacks; + @SuppressWarnings("FieldMayBeFinal") private ResourcePack[] resourcePacks; public ResourcePackManager(File path) { if (!path.exists()) { @@ -24,7 +25,7 @@ public ResourcePackManager(File path) { ResourcePack resourcePack = null; String fileExt = Files.getFileExtension(pack.getName()); - if (!pack.isDirectory() && !fileExt.equals("key")) { //directory resource packs temporarily unsupported + if (!pack.isDirectory() && !fileExt.equals("key")) { // Directory resource packs temporarily unsupported switch (fileExt) { case "zip": case "mcpack": diff --git a/src/main/java/cn/nukkit/resourcepacks/ZippedResourcePack.java b/src/main/java/cn/nukkit/resourcepacks/ZippedResourcePack.java index 04fcf624c2a..b518064f5d9 100644 --- a/src/main/java/cn/nukkit/resourcepacks/ZippedResourcePack.java +++ b/src/main/java/cn/nukkit/resourcepacks/ZippedResourcePack.java @@ -14,9 +14,9 @@ import java.util.zip.ZipFile; public class ZippedResourcePack extends AbstractResourcePack { - private File file; - private byte[] sha256 = null; + private final File file; + private byte[] sha256; private String encryptionKey = ""; public ZippedResourcePack(File file) { @@ -30,13 +30,24 @@ public ZippedResourcePack(File file) { try (ZipFile zip = new ZipFile(file)) { ZipEntry entry = zip.getEntry("manifest.json"); if (entry == null) { - throw new IllegalArgumentException(Server.getInstance().getLanguage() - .translateString("nukkit.resources.zip.no-manifest")); - } else { - this.manifest = new JsonParser() - .parse(new InputStreamReader(zip.getInputStream(entry), StandardCharsets.UTF_8)) - .getAsJsonObject(); + entry = zip.stream() + .filter(e-> e.getName().toLowerCase().endsWith("manifest.json") && !e.isDirectory()) + .filter(e-> { + File fe = new File(e.getName()); + if (!fe.getName().equalsIgnoreCase("manifest.json")) { + return false; + } + return fe.getParent() == null || fe.getParentFile().getParent() == null; + }) + .findFirst() + .orElseThrow(()-> new IllegalArgumentException( + Server.getInstance().getLanguage().translateString("nukkit.resources.zip.no-manifest"))); } + + this.manifest = new JsonParser() + .parse(new InputStreamReader(zip.getInputStream(entry), StandardCharsets.UTF_8)) + .getAsJsonObject(); + File parentFolder = this.file.getParentFile(); if (parentFolder == null || !parentFolder.isDirectory()) { throw new IOException("Invalid resource pack path"); diff --git a/src/main/java/cn/nukkit/scheduler/AsyncPool.java b/src/main/java/cn/nukkit/scheduler/AsyncPool.java index ed3b03fdca7..a825aa27601 100644 --- a/src/main/java/cn/nukkit/scheduler/AsyncPool.java +++ b/src/main/java/cn/nukkit/scheduler/AsyncPool.java @@ -2,7 +2,7 @@ import cn.nukkit.Server; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -10,10 +10,11 @@ * @author Nukkit Project Team */ public class AsyncPool extends ThreadPoolExecutor { + private final Server server; public AsyncPool(Server server, int size) { - super(size, Integer.MAX_VALUE, 60, TimeUnit.MILLISECONDS, new SynchronousQueue<>()); + super(size, size, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); this.setThreadFactory(runnable -> new Thread(runnable) {{ setDaemon(true); setName(String.format("Nukkit Asynchronous Task Handler #%s", getPoolSize())); diff --git a/src/main/java/cn/nukkit/scheduler/AsyncTask.java b/src/main/java/cn/nukkit/scheduler/AsyncTask.java index 29698d7c5f8..9552dbc1f18 100644 --- a/src/main/java/cn/nukkit/scheduler/AsyncTask.java +++ b/src/main/java/cn/nukkit/scheduler/AsyncTask.java @@ -2,7 +2,6 @@ import cn.nukkit.Server; import cn.nukkit.utils.ThreadStore; -import co.aikar.timings.Timings; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -50,11 +49,11 @@ public int getTaskId() { } public Object getFromThreadStore(String identifier) { - return this.isFinished() ? null : ThreadStore.store.get(identifier); + return this.finished ? null : ThreadStore.store.get(identifier); } public void saveToThreadStore(String identifier, Object value) { - if (!this.isFinished()) { + if (!this.finished) { if (value == null) { ThreadStore.store.remove(identifier); } else { @@ -76,18 +75,15 @@ public void cleanObject() { } public static void collectTask() { - Timings.schedulerAsyncTimer.startTiming(); while (!FINISHED_LIST.isEmpty()) { AsyncTask task = FINISHED_LIST.poll(); try { task.onCompletion(Server.getInstance()); } catch (Exception e) { Server.getInstance().getLogger().critical("Exception while async task " - + task.getTaskId() + + task.taskId + " invoking onCompletion", e); } } - Timings.schedulerAsyncTimer.stopTiming(); } - } diff --git a/src/main/java/cn/nukkit/scheduler/AsyncWorker.java b/src/main/java/cn/nukkit/scheduler/AsyncWorker.java index 868233478bd..338627342ac 100644 --- a/src/main/java/cn/nukkit/scheduler/AsyncWorker.java +++ b/src/main/java/cn/nukkit/scheduler/AsyncWorker.java @@ -5,10 +5,11 @@ import java.util.LinkedList; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class AsyncWorker extends Thread implements InterruptibleThread { + private final LinkedList stack = new LinkedList<>(); public AsyncWorker() { @@ -44,10 +45,7 @@ public void run() { } try { sleep(5); - } catch (InterruptedException e) { - //igonre - } + } catch (InterruptedException ignored) {} } } - } diff --git a/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java b/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java index bb1a26a24b6..f3592969968 100644 --- a/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java +++ b/src/main/java/cn/nukkit/scheduler/BlockUpdateScheduler.java @@ -3,23 +3,23 @@ import cn.nukkit.block.Block; import cn.nukkit.level.Level; import cn.nukkit.math.AxisAlignedBB; -import cn.nukkit.math.NukkitMath; +import cn.nukkit.math.SimpleAxisAlignedBB; import cn.nukkit.math.Vector3; import cn.nukkit.utils.BlockUpdateEntry; -import com.google.common.collect.Maps; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public class BlockUpdateScheduler { + private final Level level; private long lastTick; - private Map> queuedUpdates; + private final Map> queuedUpdates = new ConcurrentHashMap<>(); private Set pendingUpdates; public BlockUpdateScheduler(Level level, long currentTick) { - queuedUpdates = Maps.newHashMap(); // Change to ConcurrentHashMap if this needs to be concurrent - lastTick = currentTick; + this.lastTick = currentTick; this.level = level; } @@ -49,9 +49,8 @@ private void perform(long tick) { Set updates = pendingUpdates = queuedUpdates.remove(tick); if (updates != null) { for (BlockUpdateEntry entry : updates) { - Vector3 pos = entry.pos; - if (level.isChunkLoaded(NukkitMath.floorDouble(pos.x) >> 4, NukkitMath.floorDouble(pos.z) >> 4)) { - Block block = level.getBlock(entry.pos); + if (level.isAreaLoaded(new SimpleAxisAlignedBB(entry.pos, entry.pos))) { + Block block = level.getBlock(entry.pos, entry.block.getLayer(), true); if (Block.equals(block, entry.block, false)) { block.onUpdate(Level.BLOCK_UPDATE_SCHEDULED); @@ -127,7 +126,7 @@ public boolean remove(BlockUpdateEntry entry) { public boolean remove(Vector3 pos) { for (Map.Entry> tickUpdateSet : queuedUpdates.entrySet()) { - if (tickUpdateSet.getValue().remove(pos)) { + if (tickUpdateSet.getValue().remove(new BlockUpdateEntry(pos, null))) { return true; } } diff --git a/src/main/java/cn/nukkit/scheduler/FileWriteTask.java b/src/main/java/cn/nukkit/scheduler/FileWriteTask.java index e2911d4a587..982c638834c 100644 --- a/src/main/java/cn/nukkit/scheduler/FileWriteTask.java +++ b/src/main/java/cn/nukkit/scheduler/FileWriteTask.java @@ -10,10 +10,11 @@ import java.nio.charset.StandardCharsets; /** - * author: MagicDroidX + * @author MagicDroidX * Nukkit Project */ public class FileWriteTask extends AsyncTask { + private final File file; private final InputStream contents; @@ -53,5 +54,4 @@ public void onRun() { Server.getInstance().getLogger().logException(e); } } - } diff --git a/src/main/java/cn/nukkit/scheduler/NukkitRunnable.java b/src/main/java/cn/nukkit/scheduler/NukkitRunnable.java index 55db31ab632..888f3d41f6d 100644 --- a/src/main/java/cn/nukkit/scheduler/NukkitRunnable.java +++ b/src/main/java/cn/nukkit/scheduler/NukkitRunnable.java @@ -7,6 +7,7 @@ * This class is provided as an easy way to handle scheduling tasks. */ public abstract class NukkitRunnable implements Runnable { + private TaskHandler taskHandler; /** @@ -64,8 +65,7 @@ public synchronized int getTaskId() throws IllegalStateException { if (taskHandler == null) { throw new IllegalStateException("Not scheduled yet"); } - final int id = taskHandler.getTaskId(); - return id; + return taskHandler.getTaskId(); } private void checkState() { diff --git a/src/main/java/cn/nukkit/scheduler/PluginTask.java b/src/main/java/cn/nukkit/scheduler/PluginTask.java index cde6a544dd8..1e84126f999 100644 --- a/src/main/java/cn/nukkit/scheduler/PluginTask.java +++ b/src/main/java/cn/nukkit/scheduler/PluginTask.java @@ -5,34 +5,33 @@ /** * 插件创建的任务。
    Task that created by a plugin. - * - *

    对于插件作者,通过继承这个类创建的任务,可以在插件被禁用时不被执行。
    - * For plugin developers: Tasks that extend this class, won't be executed when the plugin is disabled.

    - * - *

    另外,继承这个类的任务可以通过{@link #getOwner()}来获得这个任务所属的插件。
    - * Otherwise, tasks that extend this class can use {@link #getOwner()} to get its owner.

    - * + * + * 对于插件作者,通过继承这个类创建的任务,可以在插件被禁用时不被执行。
    + * For plugin developers: Tasks that extend this class, won't be executed when the plugin is disabled. + * + * 另外,继承这个类的任务可以通过{@link #getOwner()}来获得这个任务所属的插件。
    + * Otherwise, tasks that extend this class can use {@link #getOwner()} to get its owner. + * * 下面是一个插件创建任务的例子:
    An example for plugin create a task: *
      *     public class ExampleTask extends PluginTask<ExamplePlugin>{
    - *         public ExampleTask(ExamplePlugin plugin){
    + *         public ExampleTask(ExamplePlugin plugin) {
      *             super(plugin);
      *         }
      *
      *        {@code @Override}
    - *         public void onRun(int currentTick){
    + *         public void onRun(int currentTick) {
      *             getOwner().getLogger().info("Task is executed in tick "+currentTick);
      *         }
      *     }
    - * 
    + * * - *

    如果要让Nukkit能够延时或循环执行这个任务,请使用{@link ServerScheduler}。
    - * If you want Nukkit to execute this task with delay or repeat, use {@link ServerScheduler}.

    + * 如果要让Nukkit能够延时或循环执行这个任务,请使用{@link ServerScheduler}。
    + * If you want Nukkit to execute this task with delay or repeat, use {@link ServerScheduler}. * * @param 这个任务所属的插件。
    The plugin that owns this task. * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public abstract class PluginTask extends Task { @@ -42,7 +41,6 @@ public abstract class PluginTask extends Task { * 构造一个插件拥有的任务的方法。
    Constructs a plugin-owned task. * * @param owner 这个任务的所有者插件。
    The plugin object that owns this task. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public PluginTask(T owner) { this.owner = owner; @@ -53,10 +51,8 @@ public PluginTask(T owner) { * Returns the owner of this task. * * @return 这个任务的所有者插件。
    The plugin that owns this task. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public final T getOwner() { return this.owner; } - } diff --git a/src/main/java/cn/nukkit/scheduler/ServerScheduler.java b/src/main/java/cn/nukkit/scheduler/ServerScheduler.java index d36de738939..a3d23087a5c 100644 --- a/src/main/java/cn/nukkit/scheduler/ServerScheduler.java +++ b/src/main/java/cn/nukkit/scheduler/ServerScheduler.java @@ -3,10 +3,10 @@ import cn.nukkit.Server; import cn.nukkit.plugin.Plugin; import cn.nukkit.utils.PluginException; -import cn.nukkit.utils.Utils; -import java.util.ArrayDeque; import java.util.Map; +import java.util.Optional; +import java.util.PriorityQueue; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -22,16 +22,22 @@ public class ServerScheduler { private final AsyncPool asyncPool; private final Queue pending; - private final Map> queueMap; + private final Queue queue; private final Map taskMap; private final AtomicInteger currentTaskId; - private volatile int currentTick = -1; + private volatile int currentTick; public ServerScheduler() { this.pending = new ConcurrentLinkedQueue<>(); this.currentTaskId = new AtomicInteger(); - this.queueMap = new ConcurrentHashMap<>(); + this.queue = new PriorityQueue<>(11, (left, right) -> { + int i = left.getNextRunTick() - right.getNextRunTick(); + if (i == 0) { + return left.getTaskId() - right.getTaskId(); + } + return i; + }); this.taskMap = new ConcurrentHashMap<>(); this.asyncPool = new AsyncPool(Server.getInstance(), WORKERS); } @@ -85,10 +91,6 @@ public int getAsyncTaskPoolSize() { return asyncPool.getCorePoolSize(); } - public void increaseAsyncTaskPoolSize(int newSize) { - throw new UnsupportedOperationException("Cannot increase a working pool size."); //wtf? - } - public TaskHandler scheduleDelayedTask(Task task, int delay) { return this.addTask(task, delay, 0, false); } @@ -205,7 +207,7 @@ public void cancelTask(Plugin plugin) { // It is only there for backwards compatibility! if (taskHandler.getPlugin() == null || plugin.equals(taskHandler.getPlugin())) { try { - taskHandler.cancel(); /* It will remove from task map automatic in next main heartbeat. */ + taskHandler.cancel(); // It will remove from task map automatic in next main heartbeat } catch (RuntimeException ex) { Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); } @@ -222,7 +224,7 @@ public void cancelAllTasks() { } } this.taskMap.clear(); - this.queueMap .clear(); + this.queue.clear(); this.currentTaskId.set(0); } @@ -258,73 +260,50 @@ private TaskHandler addTask(Plugin plugin, Runnable task, int delay, int period, } public void mainThreadHeartbeat(int currentTick) { - // Accepts pending. - TaskHandler task; - while ((task = pending.poll()) != null) { - int tick = Math.max(currentTick, task.getNextRunTick()); // Do not schedule in the past - ArrayDeque queue = Utils.getOrCreate(queueMap, ArrayDeque.class, tick); - queue.add(task); + this.currentTick = currentTick; + // Accepts pending + while (!pending.isEmpty()) { + queue.offer(pending.poll()); } - if (currentTick - this.currentTick > queueMap.size()) { // A large number of ticks have passed since the last execution - for (Map.Entry> entry : queueMap.entrySet()) { - int tick = entry.getKey(); - if (tick <= currentTick) { - runTasks(tick); + // Main heart beat + while (isReady(currentTick)) { + TaskHandler taskHandler = queue.poll(); + if (taskHandler.isCancelled()) { + taskMap.remove(taskHandler.getTaskId()); + continue; + } else if (taskHandler.isAsynchronous()) { + asyncPool.execute(taskHandler.getTask()); + } else { + try { + taskHandler.run(currentTick); + } catch (Throwable e) { + Server.getInstance().getLogger().critical("Could not execute taskHandler " + taskHandler.getTaskId() + ": " + e.getMessage()); + Server.getInstance().getLogger().logException(e instanceof Exception ? e : new RuntimeException(e)); } } - } else { // Normal server tick - for (int i = this.currentTick + 1; i <= currentTick; i++) { - runTasks(currentTick); + if (taskHandler.isRepeating()) { + taskHandler.setNextRunTick(currentTick + taskHandler.getPeriod()); + pending.offer(taskHandler); + } else { + try { + Optional.ofNullable(taskMap.remove(taskHandler.getTaskId())).ifPresent(TaskHandler::cancel); + } catch (RuntimeException ex) { + Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); + } } } - this.currentTick = currentTick; AsyncTask.collectTask(); } - private void runTasks(int currentTick) { - ArrayDeque queue = queueMap.remove(currentTick); - if (queue != null) { - for (TaskHandler taskHandler : queue) { - if (taskHandler.isCancelled()) { - taskMap.remove(taskHandler.getTaskId()); - continue; - } else if (taskHandler.isAsynchronous()) { - asyncPool.execute(taskHandler.getTask()); - } else { - taskHandler.timing.startTiming(); - try { - taskHandler.run(currentTick); - } catch (Throwable e) { - Server.getInstance().getLogger().critical("Could not execute taskHandler " + taskHandler.getTaskId() + ": " + e.getMessage()); - Server.getInstance().getLogger().logException(e instanceof Exception ? (Exception) e : new RuntimeException(e)); - } - taskHandler.timing.stopTiming(); - } - if (taskHandler.isRepeating()) { - taskHandler.setNextRunTick(currentTick + taskHandler.getPeriod()); - pending.offer(taskHandler); - } else { - try { - TaskHandler removed = taskMap.remove(taskHandler.getTaskId()); - if (removed != null) removed.cancel(); - } catch (RuntimeException ex) { - Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); - } - } - } - } + public int getQueueSize() { + return queue.size() + pending.size(); } - public int getQueueSize() { - int size = pending.size(); - for (ArrayDeque queue : queueMap.values()) { - size += queue.size(); - } - return size; + private boolean isReady(int currentTick) { + return this.queue.peek() != null && this.queue.peek().getNextRunTick() <= currentTick; } private int nextTaskId() { return currentTaskId.incrementAndGet(); } - } diff --git a/src/main/java/cn/nukkit/scheduler/Task.java b/src/main/java/cn/nukkit/scheduler/Task.java index 089fa3025d7..fd0b954c839 100644 --- a/src/main/java/cn/nukkit/scheduler/Task.java +++ b/src/main/java/cn/nukkit/scheduler/Task.java @@ -4,22 +4,22 @@ /** * 表达一个任务的类。
    A class that describes a task. - * - *

    一个任务可以被Nukkit服务器立即,延时,循环或延时循环执行。参见:{@link ServerScheduler}
    + * + * 一个任务可以被Nukkit服务器立即,延时,循环或延时循环执行。参见:{@link ServerScheduler}
    * A task can be executed by Nukkit server with a/an express, delay, repeat or delay&repeat. - * See:{@link ServerScheduler}

    - * - *

    对于插件开发者,为确保自己任务能够在安全的情况下执行(比如:在插件被禁用时不执行), + * See:{@link ServerScheduler} + * + * 对于插件开发者,为确保自己任务能够在安全的情况下执行(比如:在插件被禁用时不执行), * 建议让任务继承{@link PluginTask}类而不是这个类。
    * For plugin developers: To make sure your task will only be executed in the case of safety * (such as: prevent this task from running if its owner plugin is disabled), - * it's suggested to use {@link PluginTask} instead of extend this class.

    + * it's suggested to use {@link PluginTask} instead of extend this class. * * @author MagicDroidX(code) @ Nukkit Project * @author 粉鞋大妈(javadoc) @ Nukkit Project - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public abstract class Task implements Runnable { + private TaskHandler taskHandler = null; public final TaskHandler getHandler() { @@ -42,7 +42,6 @@ public final void setHandler(TaskHandler taskHandler) { * * @param currentTick 服务器从开始运行到现在所经过的tick数,20ticks = 1秒,1tick = 0.05秒。
    * The elapsed tick count from the server is started. 20ticks = 1second, 1tick = 0.05second. - * @since Nukkit 1.0 | Nukkit API 1.0.0 */ public abstract void onRun(int currentTick); @@ -57,10 +56,9 @@ public void onCancel() { public void cancel() { try { - this.getHandler().cancel(); + this.taskHandler.cancel(); } catch (RuntimeException ex) { Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); } } - } diff --git a/src/main/java/cn/nukkit/scheduler/TaskHandler.java b/src/main/java/cn/nukkit/scheduler/TaskHandler.java index 99e2d8c88a9..09444cbbcaa 100644 --- a/src/main/java/cn/nukkit/scheduler/TaskHandler.java +++ b/src/main/java/cn/nukkit/scheduler/TaskHandler.java @@ -2,13 +2,12 @@ import cn.nukkit.Server; import cn.nukkit.plugin.Plugin; -import co.aikar.timings.Timing; -import co.aikar.timings.Timings; /** * @author MagicDroidX */ public class TaskHandler { + private final int taskId; private final boolean asynchronous; @@ -23,14 +22,11 @@ public class TaskHandler { private boolean cancelled; - public final Timing timing; - public TaskHandler(Plugin plugin, Runnable task, int taskId, boolean asynchronous) { this.asynchronous = asynchronous; this.plugin = plugin; this.task = task; this.taskId = taskId; - this.timing = Timings.getTaskTiming(this, period); } public boolean isCancelled() { @@ -82,7 +78,7 @@ public void setLastRunTick(int lastRunTick) { } public void cancel() { - if (!this.isCancelled() && this.task instanceof Task) { + if (!this.cancelled && this.task instanceof Task) { ((Task) this.task).onCancel(); } this.cancelled = true; @@ -96,17 +92,12 @@ public void remove() { public void run(int currentTick) { try { setLastRunTick(currentTick); - getTask().run(); + task.run(); } catch (RuntimeException ex) { Server.getInstance().getLogger().critical("Exception while invoking run", ex); } } - @Deprecated - public String getTaskName() { - return "Unknown"; - } - public boolean isAsynchronous() { return asynchronous; } @@ -118,5 +109,4 @@ public void setDelay(int delay) { public void setPeriod(int period) { this.period = period; } - } diff --git a/src/main/java/cn/nukkit/timings/JsonUtil.java b/src/main/java/cn/nukkit/timings/JsonUtil.java deleted file mode 100644 index 104ffd0eb8f..00000000000 --- a/src/main/java/cn/nukkit/timings/JsonUtil.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.nukkit.timings; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import java.util.*; -import java.util.function.Function; - -/** - * @author Tee7even - *

    - * Various methods for more compact JSON object constructing - */ -@SuppressWarnings("unchecked") -public class JsonUtil { - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - public static JsonArray toArray(Object... objects) { - List array = new ArrayList(); - Collections.addAll(array, objects); - return GSON.toJsonTree(array).getAsJsonArray(); - } - - public static JsonObject toObject(Object object) { - return GSON.toJsonTree(object).getAsJsonObject(); - } - - public static JsonObject mapToObject(Iterable collection, Function mapper) { - Map object = new LinkedHashMap(); - for (E e : collection) { - JSONPair pair = mapper.apply(e); - if (pair != null) { - object.put(pair.key, pair.value); - } - } - return GSON.toJsonTree(object).getAsJsonObject(); - } - - public static JsonArray mapToArray(E[] elements, Function mapper) { - ArrayList array = new ArrayList(); - Collections.addAll(array, elements); - return mapToArray(array, mapper); - } - - public static JsonArray mapToArray(Iterable collection, Function mapper) { - List array = new ArrayList(); - for (E e : collection) { - Object obj = mapper.apply(e); - if (obj != null) { - array.add(obj); - } - } - return GSON.toJsonTree(array).getAsJsonArray(); - } - - public static class JSONPair { - public final String key; - public final Object value; - - public JSONPair(String key, Object value) { - this.key = key; - this.value = value; - } - - public JSONPair(int key, Object value) { - this.key = String.valueOf(key); - this.value = value; - } - } -} diff --git a/src/main/java/cn/nukkit/timings/LevelTimings.java b/src/main/java/cn/nukkit/timings/LevelTimings.java deleted file mode 100644 index be2416b236d..00000000000 --- a/src/main/java/cn/nukkit/timings/LevelTimings.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.nukkit.timings; - -import cn.nukkit.level.Level; -import co.aikar.timings.Timing; -import co.aikar.timings.TimingsManager; - -/** - * @author Pub4Game - * @author Tee7even - */ -public class LevelTimings { - public final Timing doChunkUnload; - public final Timing doTickPending; - public final Timing doChunkGC; - public final Timing doTick; - - public final Timing tickChunks; - public final Timing entityTick; - public final Timing blockEntityTick; - - public final Timing syncChunkSendTimer; - public final Timing syncChunkSendPrepareTimer; - public final Timing syncChunkLoadTimer; - public final Timing syncChunkLoadDataTimer; - public final Timing syncChunkLoadEntitiesTimer; - public final Timing syncChunkLoadBlockEntitiesTimer; - - public LevelTimings(Level level) { - String name = level.getFolderName() + " - "; - - this.doChunkUnload = TimingsManager.getTiming(name + "doChunkUnload"); - this.doTickPending = TimingsManager.getTiming(name + "doTickPending"); - this.doChunkGC = TimingsManager.getTiming(name + "doChunkGC"); - this.doTick = TimingsManager.getTiming(name + "doTick"); - - this.tickChunks = TimingsManager.getTiming(name + "tickChunks"); - this.entityTick = TimingsManager.getTiming(name + "entityTick"); - this.blockEntityTick = TimingsManager.getTiming(name + "blockEntityTick"); - - this.syncChunkSendTimer = TimingsManager.getTiming(name + "syncChunkSend"); - this.syncChunkSendPrepareTimer = TimingsManager.getTiming(name + "syncChunkSendPrepare"); - this.syncChunkLoadTimer = TimingsManager.getTiming(name + "syncChunkLoad"); - this.syncChunkLoadDataTimer = TimingsManager.getTiming(name + "syncChunkLoad - Data"); - this.syncChunkLoadEntitiesTimer = TimingsManager.getTiming(name + "syncChunkLoad - Entities"); - this.syncChunkLoadBlockEntitiesTimer = TimingsManager.getTiming(name + "syncChunkLoad - BlockEntities"); - } -} diff --git a/src/main/java/cn/nukkit/utils/BannerPattern.java b/src/main/java/cn/nukkit/utils/BannerPattern.java index 2b5804afe33..3d8dd2f0cae 100644 --- a/src/main/java/cn/nukkit/utils/BannerPattern.java +++ b/src/main/java/cn/nukkit/utils/BannerPattern.java @@ -5,30 +5,51 @@ import java.util.HashMap; import java.util.Map; +/** + * Banner pattern + */ public class BannerPattern { - private Type type; - private DyeColor color; + private final Type type; + private final DyeColor color; public BannerPattern(Type type, DyeColor color) { this.type = type; this.color = color; } + /** + * Get banner pattern color + * + * @return color as DyeColor + */ public DyeColor getColor() { return this.color; } + /** + * Get banner pattern type + * + * @return type as BannerPattern.Type + */ public Type getType() { return this.type; } + /** + * Read banner pattern from CompoundTag + * + * @param compoundTag CompoundTag in + * @return BannerPattern out + */ public static BannerPattern fromCompoundTag(CompoundTag compoundTag) { return new BannerPattern(Type.getByName(compoundTag.contains("Pattern") ? compoundTag.getString("Pattern") : ""), compoundTag.contains("Color") ? DyeColor.getByDyeData(compoundTag.getInt("Color")) : DyeColor.BLACK); } + /** + * Banner pattern type enum + */ public enum Type { - PATTERN_BOTTOM_STRIPE("bs"), PATTERN_TOP_STRIPE("ts"), PATTERN_LEFT_STRIPE("ls"), @@ -70,7 +91,7 @@ public enum Type { private final static Map BY_NAME = new HashMap<>(); - private String name; + private final String name; Type(String name) { this.name = name; @@ -89,7 +110,5 @@ public String getName() { public static Type getByName(String name) { return BY_NAME.get(name); } - } - } diff --git a/src/main/java/cn/nukkit/utils/Binary.java b/src/main/java/cn/nukkit/utils/Binary.java index 2d5bbef300a..2921bf647c0 100644 --- a/src/main/java/cn/nukkit/utils/Binary.java +++ b/src/main/java/cn/nukkit/utils/Binary.java @@ -17,7 +17,9 @@ import java.util.UUID; /** - * author: MagicDroidX + * Binary + * + * @author MagicDroidX * Nukkit Project */ public class Binary { @@ -102,11 +104,16 @@ public static byte[] writeUUID(UUID uuid) { public static byte[] writeMetadata(EntityMetadata metadata) { BinaryStream stream = new BinaryStream(); Map map = metadata.getMap(); + stream.putUnsignedVarInt(map.size()); - for (int id : map.keySet()) { - EntityData d = map.get(id); + for (Map.Entry entry : map.entrySet()) { + EntityData d = entry.getValue(); + int id = entry.getKey(); + + stream.putUnsignedVarInt(id); stream.putUnsignedVarInt(d.getType()); + switch (d.getType()) { case Entity.DATA_TYPE_BYTE: stream.putByte(((ByteEntityData) d).getData().byteValue()); @@ -383,13 +390,13 @@ public static byte[] writeLLong(long l) { } public static byte[] writeVarInt(int v) { - BinaryStream stream = new BinaryStream(); + BinaryStream stream = new BinaryStream(new byte[5]).reset(); stream.putVarInt(v); return stream.getBuffer(); } public static byte[] writeUnsignedVarInt(long v) { - BinaryStream stream = new BinaryStream(); + BinaryStream stream = new BinaryStream(new byte[5]).reset(); stream.putUnsignedVarInt(v); return stream.getBuffer(); } @@ -408,13 +415,13 @@ public static String bytesToHexString(byte[] src) { public static String bytesToHexString(byte[] src, boolean blank) { StringBuilder stringBuilder = new StringBuilder(); - if (src == null || src.length <= 0) { + if (src == null || src.length == 0) { return null; } for (byte b : src) { if (!(stringBuilder.length() == 0) && blank) { - stringBuilder.append(" "); + stringBuilder.append(' '); } int v = b & 0xFF; String hv = Integer.toHexString(v); @@ -427,16 +434,16 @@ public static String bytesToHexString(byte[] src, boolean blank) { } public static byte[] hexStringToBytes(String hexString) { - if (hexString == null || hexString.equals("")) { + if (hexString == null || hexString.isEmpty()) { return null; } String str = "0123456789ABCDEF"; hexString = hexString.toUpperCase().replace(" ", ""); - int length = hexString.length() / 2; + int length = hexString.length() >> 1; char[] hexChars = hexString.toCharArray(); byte[] d = new byte[length]; for (int i = 0; i < length; i++) { - int pos = i * 2; + int pos = i << 1; d[i] = (byte) (((byte) str.indexOf(hexChars[pos]) << 4) | ((byte) str.indexOf(hexChars[pos + 1]))); } return d; @@ -472,7 +479,6 @@ public static byte[] appendBytes(byte[][] bytes) { for (byte[] b : bytes) { length += b.length; } - byte[] appendedBytes = new byte[length]; int index = 0; for (byte[] b : bytes) { @@ -500,7 +506,6 @@ public static byte[] appendBytes(byte[] bytes1, byte[]... bytes2) { for (byte[] bytes : bytes2) { length += bytes.length; } - byte[] appendedBytes = new byte[length]; System.arraycopy(bytes1, 0, appendedBytes, 0, bytes1.length); int index = bytes1.length; @@ -511,6 +516,4 @@ public static byte[] appendBytes(byte[] bytes1, byte[]... bytes2) { } return appendedBytes; } - - } diff --git a/src/main/java/cn/nukkit/utils/BinaryStream.java b/src/main/java/cn/nukkit/utils/BinaryStream.java index bdad8974787..4008d814452 100644 --- a/src/main/java/cn/nukkit/utils/BinaryStream.java +++ b/src/main/java/cn/nukkit/utils/BinaryStream.java @@ -19,10 +19,13 @@ import cn.nukkit.network.LittleEndianByteBufInputStream; import cn.nukkit.network.LittleEndianByteBufOutputStream; import cn.nukkit.network.protocol.types.EntityLink; -import io.netty.buffer.AbstractByteBufAllocator; +import cn.nukkit.network.protocol.types.ExperimentData; +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.nio.ByteOrder; @@ -32,7 +35,9 @@ import java.util.function.Function; /** - * author: MagicDroidX + * BinaryStream + * + * @author MagicDroidX * Nukkit Project */ public class BinaryStream { @@ -87,6 +92,10 @@ public byte[] getBuffer() { return Arrays.copyOf(buffer, count); } + public byte[] getRawBuffer() { + return buffer; + } + public int getCount() { return count; } @@ -100,15 +109,15 @@ public byte[] get(int len) { this.offset = this.count - 1; return new byte[0]; } - len = Math.min(len, this.getCount() - this.offset); + len = Math.min(len, this.count - this.offset); this.offset += len; return Arrays.copyOfRange(this.buffer, this.offset - len, this.offset); } public void put(byte[] bytes) { - if (bytes == null) { + /*if (bytes == null) { return; - } + }*/ this.ensureCapacity(this.count + bytes.length); @@ -238,7 +247,7 @@ public Attribute[] getAttributeList() throws Exception { attr.setMaxValue(this.getLFloat()); list.add(attr); } else { - throw new Exception("Unknown attribute type \"" + name + "\""); + throw new Exception("Unknown attribute type \"" + name + '"'); } } @@ -268,6 +277,7 @@ public UUID getUUID() { public void putSkin(Skin skin) { this.putString(skin.getSkinId()); + this.putString(skin.getPlayFabId()); this.putString(skin.getSkinResourcePatch()); this.putImage(skin.getSkinData()); @@ -289,6 +299,7 @@ public void putSkin(Skin skin) { this.putString(skin.getFullSkinId()); this.putString(skin.getArmSize()); this.putString(skin.getSkinColor()); + List pieces = skin.getPersonaPieces(); this.putLInt(pieces.size()); for (PersonaPiece piece : pieces) { @@ -317,6 +328,19 @@ public void putSkin(Skin skin) { this.putBoolean(skin.isOverridingPlayerAppearance()); } + public void putImage(SerializedImage image) { + this.putLInt(image.width); + this.putLInt(image.height); + this.putByteArray(image.data); + } + + public SerializedImage getImage() { + int width = this.getLInt(); + int height = this.getLInt(); + byte[] data = this.getByteArray(); + return new SerializedImage(width, height, data); + } + public Skin getSkin() { Skin skin = new Skin(); skin.setSkinId(this.getString()); @@ -371,19 +395,6 @@ public Skin getSkin() { return skin; } - public void putImage(SerializedImage image) { - this.putLInt(image.width); - this.putLInt(image.height); - this.putByteArray(image.data); - } - - public SerializedImage getImage() { - int width = this.getLInt(); - int height = this.getLInt(); - byte[] data = this.getByteArray(); - return new SerializedImage(width, height, data); - } - public Item getSlot() { int runtimeId = this.getVarInt(); if (runtimeId == 0) { @@ -414,7 +425,7 @@ public Item getSlot() { } byte[] bytes = this.getByteArray(); - ByteBuf buf = AbstractByteBufAllocator.DEFAULT.ioBuffer(bytes.length); + ByteBuf buf = ByteBufAllocator.DEFAULT.ioBuffer(bytes.length); buf.writeBytes(bytes); byte[] nbt = new byte[0]; @@ -433,8 +444,8 @@ public Item getSlot() { compoundTag = NBTIO.read(stream, ByteOrder.LITTLE_ENDIAN); } - if (compoundTag != null && compoundTag.getAllTags().size() > 0) { - if (compoundTag.contains("Damage") && !legacyEntry.isHasDamage()) { + if (compoundTag != null && !compoundTag.getAllTags().isEmpty()) { + if (!legacyEntry.isHasDamage() && compoundTag.contains("Damage")) { damage = compoundTag.getInt("Damage"); compoundTag.remove("Damage"); } @@ -446,12 +457,22 @@ public Item getSlot() { } } - canPlace = new String[stream.readInt()]; + int canPlaceCount = stream.readInt(); + if (canPlaceCount > 4096) { + throw new RuntimeException("Too many CanPlaceOn blocks: " + canPlaceCount); + } + + canPlace = new String[canPlaceCount]; for (int i = 0; i < canPlace.length; i++) { canPlace[i] = stream.readUTF(); } - canBreak = new String[stream.readInt()]; + int canBreakCount = stream.readInt(); + if (canBreakCount > 4096) { + throw new RuntimeException("Too many CanDestroy blocks: " + canBreakCount); + } + + canBreak = new String[canBreakCount]; for (int i = 0; i < canBreak.length; i++) { canBreak[i] = stream.readUTF(); } @@ -521,7 +542,7 @@ public void putSlot(Item item, boolean instanceItem) { if (!instanceItem) { this.putBoolean(true); - this.putVarInt(1); + this.putVarInt(1); // Item is present } Block block = isBlock ? item.getBlockUnsafe() : null; @@ -585,16 +606,17 @@ public Item getRecipeIngredient() { return Item.get(Item.AIR, 0, 0); } - RuntimeItemMapping mapping = RuntimeItems.getMapping(); - LegacyEntry legacyEntry = mapping.fromRuntime(runtimeId); - - int id = legacyEntry.getLegacyId(); int damage = this.getVarInt(); + if (damage == 0x7fff) { + damage = -1; + } + int id; + RuntimeItemMapping mapping = RuntimeItems.getMapping(); + LegacyEntry legacyEntry = mapping.fromRuntime(runtimeId); + id = legacyEntry.getLegacyId(); if (legacyEntry.isHasDamage()) { damage = legacyEntry.getDamage(); - } else if (damage == 0x7fff) { - damage = -1; } int count = this.getVarInt(); @@ -610,9 +632,10 @@ public void putRecipeIngredient(Item item) { this.putBoolean(true); // isValid? - true - RuntimeItemMapping mapping = RuntimeItems.getMapping(); - int runtimeId, damage; + int runtimeId; + int damage = item.hasMeta() ? item.getDamage() : 0x7fff; + RuntimeItemMapping mapping = RuntimeItems.getMapping(); if (!item.hasMeta()) { RuntimeEntry runtimeEntry = mapping.toRuntime(item.getId(), 0); runtimeId = runtimeEntry.getRuntimeId(); @@ -622,12 +645,13 @@ public void putRecipeIngredient(Item item) { runtimeId = runtimeEntry.getRuntimeId(); damage = runtimeEntry.isHasDamage() ? 0 : item.getDamage(); } + this.putLShort(runtimeId); this.putLShort(damage); this.putVarInt(item.getCount()); } - private List extractStringList(Item item, String tagName) { + private static List extractStringList(Item item, String tagName) { CompoundTag namedTag = item.getNamedTag(); if (namedTag == null) { return Collections.emptyList(); @@ -720,7 +744,7 @@ public void putBlockVector3(BlockVector3 v) { public void putBlockVector3(int x, int y, int z) { this.putVarInt(x); - this.putUnsignedVarInt(y); + this.putUnsignedVarInt(Integer.toUnsignedLong(y)); // we have even negative coordinates this.putVarInt(z); } @@ -739,10 +763,26 @@ public void putVector3f(float x, float y, float z) { } public void putGameRules(GameRules gameRules) { - Map rules = gameRules.getGameRules(); - this.putUnsignedVarInt(rules.size()); - rules.forEach((gameRule, value) -> { - this.putString(gameRule.getName().toLowerCase()); + Map rulesToSend = new HashMap<>(gameRules.getGameRules()); + this.putUnsignedVarInt(rulesToSend.size()); + rulesToSend.forEach((gameRule, value) -> { + putString(gameRule.getName().toLowerCase()); + value.write(this); + }); + } + + public void putGameRulesMap(Map allGameRules) { + Map rulesToSend = new HashMap<>(); + allGameRules.forEach((gameRule, value) -> { + if (gameRule == GameRule.NATURAL_REGENERATION) { + rulesToSend.put(gameRule, new GameRules.Value<>(GameRules.Type.BOOLEAN, false)); // Fix client-side desync? + } else { + rulesToSend.put(gameRule, value); + } + }); + this.putUnsignedVarInt(rulesToSend.size()); + rulesToSend.forEach((gameRule, value) -> { + putString(gameRule.getName().toLowerCase()); value.write(this); }); } @@ -854,4 +894,23 @@ private static int hugeCapacity(int minCapacity) { Integer.MAX_VALUE : MAX_ARRAY_SIZE; } + + public void putNbtTag(T tag) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try (NBTOutputStream writer = NbtUtils.createNetworkWriter(stream)) { + writer.writeTag(tag); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.put(stream.toByteArray()); + } + + public void putExperiments(Collection experiments) { + this.putLInt(experiments.size()); + for (ExperimentData experimentData : experiments) { + this.putString(experimentData.getName()); + this.putBoolean(experimentData.isEnabled()); + } + this.putBoolean(!experiments.isEmpty()); + } } diff --git a/src/main/java/cn/nukkit/utils/BlockColor.java b/src/main/java/cn/nukkit/utils/BlockColor.java index 0812f295992..c3423147ace 100644 --- a/src/main/java/cn/nukkit/utils/BlockColor.java +++ b/src/main/java/cn/nukkit/utils/BlockColor.java @@ -1,8 +1,10 @@ package cn.nukkit.utils; /** + * Block color + * * Created by Snake1999 on 2016/1/10. - * Package cn.nukkit.utils in project nukkit + * Package cn.nukkit.utils in project Nukkit */ public class BlockColor { @@ -70,6 +72,16 @@ public class BlockColor { public static final BlockColor RED_TERRACOTA_BLOCK_COLOR = new BlockColor(0x8e, 0x3c, 0x2e); public static final BlockColor BLACK_TERRACOTA_BLOCK_COLOR = new BlockColor(0x25, 0x16, 0x10); + public static final BlockColor CRIMSON_NYLIUM_BLOCK_COLOR = new BlockColor(0xBD, 0x30, 0x31); + public static final BlockColor CRIMSON_STEM_BLOCK_COLOR = new BlockColor(0x94, 0x3F, 0x61); + public static final BlockColor CRIMSON_HYPHAE_BLOCK_COLOR = new BlockColor(0x5C, 0x19, 0x1D); + public static final BlockColor WARPED_NYLIUM_BLOCK_COLOR = new BlockColor(0x16, 0x7E, 0x86); + public static final BlockColor WARPED_STEM_BLOCK_COLOR = new BlockColor(0x3A, 0x8E, 0x8C); + public static final BlockColor WARPED_HYPHAE_BLOCK_COLOR = new BlockColor(0x56, 0x2C, 0x3E); + public static final BlockColor WARPED_WART_BLOCK_COLOR = new BlockColor(0x14, 0xB4, 0x85); + + public static final BlockColor DEEPSLATE_GRAY = new BlockColor(0x64, 0x64, 0x64); + private final int red; private final int green; private final int blue; @@ -97,6 +109,13 @@ public BlockColor(int rgb, boolean hasAlpha) { this.alpha = hasAlpha ? (rgb >> 24) & 0xff : 0xff; } + public BlockColor(String colorStr) { + this.red = Integer.valueOf(colorStr.substring(1, 3), 16); + this.green = Integer.valueOf(colorStr.substring(3, 5), 16); + this.blue = Integer.valueOf(colorStr.substring(5, 7), 16); + this.alpha = 0xff; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof BlockColor)) { @@ -109,7 +128,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return "BlockColor[r=" + this.red + ",g=" + this.green + ",b=" + this.blue + ",a=" + this.alpha + "]"; + return "BlockColor[r=" + this.red + ",g=" + this.green + ",b=" + this.blue + ",a=" + this.alpha + ']'; } public int getRed() { @@ -136,7 +155,12 @@ public int getARGB() { return this.alpha << 24 | this.red << 16 | this.green << 8 | this.blue; } - @Deprecated + /** + * Get BlockColor by dye item meta value + * + * @param dyeColorMeta dye item meta value + * @return BlockColor + */ public static BlockColor getDyeColor(int dyeColorMeta) { return DyeColor.getByDyeData(dyeColorMeta).getColor(); } diff --git a/src/main/java/cn/nukkit/utils/BlockIterator.java b/src/main/java/cn/nukkit/utils/BlockIterator.java index 1c9c6bb48c0..f54fa490e1f 100644 --- a/src/main/java/cn/nukkit/utils/BlockIterator.java +++ b/src/main/java/cn/nukkit/utils/BlockIterator.java @@ -3,16 +3,19 @@ import cn.nukkit.block.Block; import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; +import cn.nukkit.math.NukkitMath; import cn.nukkit.math.Vector3; import java.util.Iterator; /** - * author: MagicDroidX + * Block iterator + * + * @author MagicDroidX * Nukkit Project */ public class BlockIterator implements Iterator { - private final Level level; + private final int maxDistance; private static final int gridSize = 1 << 24; @@ -20,11 +23,11 @@ public class BlockIterator implements Iterator { private boolean end = false; private final Block[] blockQueue; - private int currentBlock = 0; + private int currentBlock; private Block currentBlockObject = null; private int currentDistance; - private int maxDistanceInt = 0; + private final int maxDistanceInt; private int secondError; private int thirdError; @@ -45,7 +48,6 @@ public BlockIterator(Level level, Vector3 start, Vector3 direction, double yOffs } public BlockIterator(Level level, Vector3 start, Vector3 direction, double yOffset, int maxDistance) { - this.level = level; this.maxDistance = maxDistance; this.blockQueue = new Block[3]; @@ -63,7 +65,7 @@ public BlockIterator(Level level, Vector3 start, Vector3 direction, double yOffs double thirdPosition = 0; Vector3 pos = new Vector3(startClone.x, startClone.y, startClone.z); - Block startBlock = this.level.getBlock(new Vector3(Math.floor(pos.x), Math.floor(pos.y), Math.floor(pos.z))); + Block startBlock = level.getBlock(NukkitMath.floorDouble(pos.x), NukkitMath.floorDouble(pos.y), NukkitMath.floorDouble(pos.z)); if (this.getXLength(direction) > mainDirection) { this.mainFace = this.getXFace(direction); diff --git a/src/main/java/cn/nukkit/utils/BlockUpdateEntry.java b/src/main/java/cn/nukkit/utils/BlockUpdateEntry.java index 8ca0e3dcdfd..3dd32cd5879 100644 --- a/src/main/java/cn/nukkit/utils/BlockUpdateEntry.java +++ b/src/main/java/cn/nukkit/utils/BlockUpdateEntry.java @@ -4,10 +4,13 @@ import cn.nukkit.math.Vector3; /** - * author: MagicDroidX + * Entry of a block update + * + * @author MagicDroidX * Nukkit Project */ public class BlockUpdateEntry implements Comparable { + private static long entryID = 0; public int priority; @@ -54,4 +57,4 @@ public boolean equals(Object object) { public int hashCode() { return this.pos.hashCode(); } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/utils/BossBarColor.java b/src/main/java/cn/nukkit/utils/BossBarColor.java index a23b8d2401a..8d7eda6ccaa 100644 --- a/src/main/java/cn/nukkit/utils/BossBarColor.java +++ b/src/main/java/cn/nukkit/utils/BossBarColor.java @@ -1,5 +1,8 @@ package cn.nukkit.utils; +/** + * Boss bar colors (1.18+) + */ public enum BossBarColor { PINK, @@ -9,5 +12,4 @@ public enum BossBarColor { YELLOW, PURPLE, WHITE - } diff --git a/src/main/java/cn/nukkit/utils/ChunkException.java b/src/main/java/cn/nukkit/utils/ChunkException.java index 0fa2468c85c..fad2b68ae9d 100644 --- a/src/main/java/cn/nukkit/utils/ChunkException.java +++ b/src/main/java/cn/nukkit/utils/ChunkException.java @@ -1,7 +1,9 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * ChunkException + * + * @author MagicDroidX * Nukkit Project */ public class ChunkException extends RuntimeException { @@ -17,5 +19,4 @@ public ChunkException(String message, Throwable cause) { public ChunkException(Throwable cause) { super(cause); } - } diff --git a/src/main/java/cn/nukkit/utils/ClientChainData.java b/src/main/java/cn/nukkit/utils/ClientChainData.java index 9f3a4f304e2..5e624a9d8b5 100644 --- a/src/main/java/cn/nukkit/utils/ClientChainData.java +++ b/src/main/java/cn/nukkit/utils/ClientChainData.java @@ -5,7 +5,6 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; -import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.crypto.ECDSAVerifier; @@ -20,19 +19,21 @@ /** * ClientChainData is a container of chain data sent from clients. - *

    + * * Device information such as client UUID, xuid and serverAddress, can be * read from instances of this object. - *

    + * * To get chain data, you can use player.getLoginChainData() or read(loginPacket) - *

    + * * =============== - * author: boybook + * @author boybook * Nukkit Project * =============== */ public final class ClientChainData implements LoginChainData { + private static final Gson GSON = new Gson(); + public static ClientChainData of(byte[] buffer) { return new ClientChainData(buffer); } @@ -126,6 +127,11 @@ public int getUIProfile() { return UIProfile; } + @Override + public String getTitleId() { + return titleId; + } + @Override public JsonObject getRawData() { return rawData; @@ -169,14 +175,13 @@ private static ECPublicKey generateKey(String base64) throws NoSuchAlgorithmExce private String languageCode; private int currentInputMode; private int defaultInputMode; - private int UIProfile; - private String capeData; + private String titleId; private JsonObject rawData; - private BinaryStream bs = new BinaryStream(); + private final BinaryStream bs = new BinaryStream(); private ClientChainData(byte[] buffer) { bs.setBuffer(buffer, 0); @@ -190,8 +195,13 @@ public boolean isXboxAuthed() { } private void decodeSkinData() { - JsonObject skinToken = decodeToken(new String(bs.get(bs.getLInt()))); - if (skinToken == null) return; + int size = bs.getLInt(); + if (size > 10485760) { + throw new TooBigSkinException("The skin data is too big: " + size); + } + + JsonObject skinToken = decodeToken(new String(bs.get(size), StandardCharsets.UTF_8)); + if (skinToken == null) throw new RuntimeException("Invalid null skin token"); if (skinToken.has("ClientRandomId")) this.clientId = skinToken.get("ClientRandomId").getAsLong(); if (skinToken.has("ServerAddress")) this.serverAddress = skinToken.get("ServerAddress").getAsString(); if (skinToken.has("DeviceModel")) this.deviceModel = skinToken.get("DeviceModel").getAsString(); @@ -204,22 +214,22 @@ private void decodeSkinData() { if (skinToken.has("DefaultInputMode")) this.defaultInputMode = skinToken.get("DefaultInputMode").getAsInt(); if (skinToken.has("UIProfile")) this.UIProfile = skinToken.get("UIProfile").getAsInt(); if (skinToken.has("CapeData")) this.capeData = skinToken.get("CapeData").getAsString(); - this.rawData = skinToken; } - private JsonObject decodeToken(String token) { + public static JsonObject decodeToken(String token) { String[] base = token.split("\\."); if (base.length < 2) return null; - String json = new String(Base64.getDecoder().decode(base[1]), StandardCharsets.UTF_8); - //Server.getInstance().getLogger().debug(json); - return new Gson().fromJson(json, JsonObject.class); + return GSON.fromJson(new String(Base64.getDecoder().decode(base[1]), StandardCharsets.UTF_8), JsonObject.class); } private void decodeChainData() { - Map> map = new Gson().fromJson(new String(bs.get(bs.getLInt()), StandardCharsets.UTF_8), - new TypeToken>>() { - }.getType()); + int size = bs.getLInt(); + if (size > 3145728) { + throw new IllegalArgumentException("The chain data is too big: " + size); + } + + Map> map = GSON.fromJson(new String(bs.get(size), StandardCharsets.UTF_8), new MapTypeToken().getType()); if (map.isEmpty() || !map.containsKey("chain") || map.get("chain").isEmpty()) return; List chains = map.get("chain"); @@ -233,14 +243,18 @@ private void decodeChainData() { for (String c : chains) { JsonObject chainMap = decodeToken(c); if (chainMap == null) continue; + if (chainMap.has("extraData")) { JsonObject extra = chainMap.get("extraData").getAsJsonObject(); if (extra.has("displayName")) this.username = extra.get("displayName").getAsString(); if (extra.has("identity")) this.clientUUID = UUID.fromString(extra.get("identity").getAsString()); if (extra.has("XUID")) this.xuid = extra.get("XUID").getAsString(); + if (extra.has("titleId")) this.titleId = extra.get("titleId").getAsString(); } - if (chainMap.has("identityPublicKey")) + + if (chainMap.has("identityPublicKey")) { this.identityPublicKey = chainMap.get("identityPublicKey").getAsString(); + } } if (!xboxAuthed) { @@ -248,7 +262,7 @@ private void decodeChainData() { } } - private boolean verifyChain(List chains) throws Exception { + private static boolean verifyChain(List chains) throws Exception { ECPublicKey lastKey = null; boolean mojangKeyVerified = false; Iterator iterator = chains.iterator(); @@ -268,7 +282,7 @@ private boolean verifyChain(List chains) throws Exception { return false; } - if (!verify(lastKey, jws)) { + if (!jws.verify(new ECDSAVerifier(lastKey))) { return false; } @@ -280,8 +294,7 @@ private boolean verifyChain(List chains) throws Exception { mojangKeyVerified = true; } - Map payload = jws.getPayload().toJSONObject(); - Object base64key = payload.get("identityPublicKey"); + Object base64key = jws.getPayload().toJSONObject().get("identityPublicKey"); if (!(base64key instanceof String)) { throw new RuntimeException("No key found"); } @@ -290,7 +303,13 @@ private boolean verifyChain(List chains) throws Exception { return mojangKeyVerified; } - private boolean verify(ECPublicKey key, JWSObject object) throws JOSEException { - return object.verify(new ECDSAVerifier(key)); + private static class MapTypeToken extends TypeToken>> { + } + + public static class TooBigSkinException extends RuntimeException { + + public TooBigSkinException(String s) { + super(s); + } } } diff --git a/src/main/java/cn/nukkit/utils/Config.java b/src/main/java/cn/nukkit/utils/Config.java index 9f1ac665c56..ae1394416fc 100644 --- a/src/main/java/cn/nukkit/utils/Config.java +++ b/src/main/java/cn/nukkit/utils/Config.java @@ -16,7 +16,9 @@ import java.util.regex.Pattern; /** - * author: MagicDroidX + * Config + * + * @author MagicDroidX * Nukkit */ public class Config { @@ -31,12 +33,14 @@ public class Config { public static final int ENUM = 5; // .txt, .list, .enum public static final int ENUMERATION = Config.ENUM; - //private LinkedHashMap config = new LinkedHashMap<>(); private ConfigSection config = new ConfigSection(); private File file; private boolean correct = false; private int type = Config.DETECT; + /** + * List of supported config file formats and their types + */ public static final Map format = new TreeMap<>(); static { @@ -107,23 +111,45 @@ public Config(File file, int type, LinkedHashMap defaultMap) { this(file.toString(), type, new ConfigSection(defaultMap)); } + /** + * Reload config from disk + */ public void reload() { this.config.clear(); this.correct = false; - //this.load(this.file.toString()); if (this.file == null) throw new IllegalStateException("Failed to reload Config. File object is undefined."); this.load(this.file.toString(), this.type); - } + /** + * Try to load a config file and automatically detect its type + * + * @param file file path + * @return loaded + */ public boolean load(String file) { return this.load(file, Config.DETECT); } + /** + * Try to load a config file with a given type + * + * @param file file path + * @param type file type + * @return loaded + */ public boolean load(String file, int type) { return this.load(file, type, new ConfigSection()); } + /** + * Try to load a config file with a given type and default content + * + * @param file file path + * @param type file type + * @param defaultMap default content + * @return loaded + */ public boolean load(String file, int type, ConfigSection defaultMap) { this.correct = true; this.type = type; @@ -140,8 +166,8 @@ public boolean load(String file, int type, ConfigSection defaultMap) { } else { if (this.type == Config.DETECT) { String extension = ""; - if (this.file.getName().lastIndexOf(".") != -1 && this.file.getName().lastIndexOf(".") != 0) { - extension = this.file.getName().substring(this.file.getName().lastIndexOf(".") + 1); + if (this.file.getName().lastIndexOf('.') != -1 && this.file.getName().lastIndexOf('.') != 0) { + extension = this.file.getName().substring(this.file.getName().lastIndexOf('.') + 1); } if (format.containsKey(extension)) { this.type = format.get(extension); @@ -168,6 +194,12 @@ public boolean load(String file, int type, ConfigSection defaultMap) { return true; } + /** + * Load Config from InputStream + * + * @param inputStream InputStream + * @return loaded + */ public boolean load(InputStream inputStream) { if (inputStream == null) return false; if (this.correct) { @@ -183,36 +215,84 @@ public boolean load(InputStream inputStream) { return correct; } + /** + * Load and return a Config from InputStream + * + * @param inputStream InputStream + * @return Config + */ + public Config loadFromStream(InputStream inputStream) { + if (inputStream == null) return null; + if (this.correct) { + String content; + try { + content = Utils.readFile(inputStream); + } catch (IOException e) { + Server.getInstance().getLogger().logException(e); + return null; + } + this.parseContent(content); + } + return this; + } + + /** + * Check if the config is valid + * + * @return valid + */ public boolean check() { return this.correct; } + /** + * Check if the config is valid + * + * @return valid + */ public boolean isCorrect() { - return correct; + return this.correct; } /** * Save configuration into provided file. Internal file object will be set to new file. * - * @param file - * @param async - * @return + * @param file file + * @param async async + * @return save success */ public boolean save(File file, boolean async) { this.file = file; return save(async); } + /** + * Save configuration into provided file. Internal file object will be set to new file. + * + * @param file file + * @return save success + */ public boolean save(File file) { this.file = file; return save(); } + /** + * Save the config to disk + * + * @return saved + */ public boolean save() { return this.save(false); } - public boolean save(Boolean async) { + /** + * Save the config to disk + * + * @param async async + * @return saved + */ + public boolean save(Boolean async) { // Note: do not change to 'boolean' or plugins will break if (this.file == null) throw new IllegalStateException("Failed to save Config. File object is undefined."); if (this.correct) { StringBuilder content = new StringBuilder(); @@ -238,7 +318,6 @@ public boolean save(Boolean async) { } if (async) { Server.getInstance().getScheduler().scheduleAsyncTask(new FileWriteTask(this.file, content.toString())); - } else { try { Utils.writeFile(this.file, content.toString()); @@ -252,10 +331,22 @@ public boolean save(Boolean async) { } } + /** + * Set a value in the config + * + * @param key key + * @param value value + */ public void set(final String key, Object value) { this.config.set(key, value); } + /** + * Get a value in the config + * + * @param key key + * @return value + */ public Object get(String key) { return this.get(key, null); } @@ -419,7 +510,7 @@ public Map getAll() { /** * Get root (main) config section of the Config * - * @return + * @return root config section of the Config */ public ConfigSection getRootSection() { return config; @@ -456,7 +547,7 @@ private void parseList(String content) { } private String writeProperties() { - StringBuilder content = new StringBuilder("#Properties Config file\r\n#" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + "\r\n"); + StringBuilder content = new StringBuilder("#Properties Config File\r\n#" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) + "\r\n"); for (Object o : this.config.entrySet()) { Map.Entry entry = (Map.Entry) o; Object v = entry.getValue(); @@ -464,7 +555,7 @@ private String writeProperties() { if (v instanceof Boolean) { v = (Boolean) v ? "on" : "off"; } - content.append(k).append("=").append(v).append("\r\n"); + content.append(k).append('=').append(v).append("\r\n"); } return content.toString(); } @@ -478,11 +569,10 @@ private void parseProperties(String content) { } final String key = line.substring(0, splitIndex); final String value = line.substring(splitIndex + 1); - final String valueLower = value.toLowerCase(); if (this.config.containsKey(key)) { - MainLogger.getLogger().debug("[Config] Repeated property " + key + " on file " + this.file.toString()); + MainLogger.getLogger().debug("[Config] Repeated property " + key + " in file " + this.file.toString()); } - switch (valueLower) { + switch (value.toLowerCase()) { case "on": case "true": case "yes": @@ -534,6 +624,7 @@ public void removeNested(String key) { remove(key); } + @SuppressWarnings("unchecked") private void parseContent(String content) { switch (this.type) { case Config.PROPERTIES: @@ -542,8 +633,7 @@ private void parseContent(String content) { case Config.JSON: GsonBuilder builder = new GsonBuilder(); Gson gson = builder.create(); - this.config = new ConfigSection(gson.fromJson(content, new TypeToken>() { - }.getType())); + this.config = new ConfigSection(gson.fromJson(content, new LinkedHashMapTypeToken().getType())); break; case Config.YAML: DumperOptions dumperOptions = new DumperOptions(); @@ -569,4 +659,7 @@ public Set getKeys(boolean child) { if (this.correct) return config.getKeys(child); return new HashSet<>(); } + + private static class LinkedHashMapTypeToken extends TypeToken> { + } } diff --git a/src/main/java/cn/nukkit/utils/ConfigSection.java b/src/main/java/cn/nukkit/utils/ConfigSection.java index 572813221e7..ba73380b6b1 100644 --- a/src/main/java/cn/nukkit/utils/ConfigSection.java +++ b/src/main/java/cn/nukkit/utils/ConfigSection.java @@ -3,8 +3,11 @@ import java.util.*; /** + * Config section + * * Created by fromgate on 26.04.2016. */ +@SuppressWarnings("serial") public class ConfigSection extends LinkedHashMap { /** @@ -17,8 +20,8 @@ public ConfigSection() { /** * Constructor of ConfigSection that contains initial key/value data * - * @param key - * @param value + * @param key key + * @param value value */ public ConfigSection(String key, Object value) { this(); @@ -28,8 +31,9 @@ public ConfigSection(String key, Object value) { /** * Constructor of ConfigSection, based on values stored in map. * - * @param map + * @param map map */ + @SuppressWarnings("unchecked") public ConfigSection(LinkedHashMap map) { this(); if (map == null || map.isEmpty()) return; @@ -44,6 +48,7 @@ public ConfigSection(LinkedHashMap map) { } } + @SuppressWarnings("unchecked") private List parseList(List list) { List newList = new ArrayList<>(); @@ -61,7 +66,7 @@ private List parseList(List list) { /** * Get root section as LinkedHashMap * - * @return + * @return root section as LinkedHashMap */ public Map getAllMap() { return new LinkedHashMap<>(this); @@ -71,7 +76,7 @@ public Map getAllMap() { /** * Get new instance of config section * - * @return + * @return new instance of config section */ public ConfigSection getAll() { return new ConfigSection(this); @@ -87,10 +92,11 @@ public Object get(String key) { /** * Get object by key. If section does not contain value, return default value * - * @param key - * @param defaultValue - * @return + * @param key key + * @param defaultValue default value + * @return object by key or default value */ + @SuppressWarnings("unchecked") public T get(String key, T defaultValue) { if (key == null || key.isEmpty()) return defaultValue; if (super.containsKey(key)) return (T) super.get(key); @@ -107,8 +113,8 @@ public T get(String key, T defaultValue) { /** * Store value into config section * - * @param key - * @param value + * @param key key + * @param value value */ public void set(String key, Object value) { String[] subKeys = key.split("\\.", 2); @@ -122,21 +128,20 @@ public void set(String key, Object value) { } /** - * Check type of section element defined by key. Return true this element is ConfigSection + * Check type of section element defined by key. Return true if this element is ConfigSection * - * @param key - * @return + * @param key key + * @return true if this element is ConfigSection */ public boolean isSection(String key) { - Object value = this.get(key); - return value instanceof ConfigSection; + return this.get(key) instanceof ConfigSection; } /** * Get config section element defined by key * - * @param key - * @return + * @param key key + * @return config section element defined by key */ public ConfigSection getSection(String key) { return this.get(key, new ConfigSection()); @@ -161,7 +166,7 @@ public ConfigSection getSection(String key) { *

    * getSections() will return new ConfigSection, that contains sections a1 and a2 only. * - * @return + * @return all ConfigSections in root path */ //@formatter:on public ConfigSection getSections() { @@ -172,7 +177,7 @@ public ConfigSection getSections() { * Get sections (and only sections) from provided path * * @param key - config section path, if null or empty root path will used. - * @return + * @return sections */ public ConfigSection getSections(String key) { ConfigSection sections = new ConfigSection(); @@ -189,7 +194,7 @@ public ConfigSection getSections(String key) { * Get int value of config section element * * @param key - key (inside) current section (default value equals to 0) - * @return + * @return int value of config section element */ public int getInt(String key) { return this.getInt(key, 0); @@ -200,28 +205,27 @@ public int getInt(String key) { * * @param key - key (inside) current section * @param defaultValue - default value that will returned if section element is not exists - * @return + * @return int value of config section element */ public int getInt(String key, int defaultValue) { return this.get(key, ((Number) defaultValue)).intValue(); } /** - * Check type of section element defined by key. Return true this element is Integer + * Check type of section element defined by key. Return true if this element is Integer * - * @param key - * @return + * @param key key + * @return true if this element is Integer */ public boolean isInt(String key) { - Object val = get(key); - return val instanceof Integer; + return get(key) instanceof Integer; } /** * Get long value of config section element * * @param key - key (inside) current section - * @return + * @return long value of config section element */ public long getLong(String key) { return this.getLong(key, 0); @@ -232,28 +236,27 @@ public long getLong(String key) { * * @param key - key (inside) current section * @param defaultValue - default value that will returned if section element is not exists - * @return + * @return long value of config section element */ public long getLong(String key, long defaultValue) { return this.get(key, ((Number) defaultValue)).longValue(); } /** - * Check type of section element defined by key. Return true this element is Long + * Check type of section element defined by key. Return true if this element is Long * - * @param key - * @return + * @param key key + * @return true if this element is Long */ public boolean isLong(String key) { - Object val = get(key); - return val instanceof Long; + return get(key) instanceof Long; } /** * Get double value of config section element * * @param key - key (inside) current section - * @return + * @return double value of config section element */ public double getDouble(String key) { return this.getDouble(key, 0); @@ -264,28 +267,27 @@ public double getDouble(String key) { * * @param key - key (inside) current section * @param defaultValue - default value that will returned if section element is not exists - * @return + * @return double value of config section element */ public double getDouble(String key, double defaultValue) { return this.get(key, ((Number) defaultValue)).doubleValue(); } /** - * Check type of section element defined by key. Return true this element is Double + * Check type of section element defined by key. Return true if this element is Double * - * @param key - * @return + * @param key key + * @return true if this element is Double */ public boolean isDouble(String key) { - Object val = get(key); - return val instanceof Double; + return get(key) instanceof Double; } /** * Get String value of config section element * * @param key - key (inside) current section - * @return + * @return String value of config section element */ public String getString(String key) { return this.getString(key, ""); @@ -296,29 +298,27 @@ public String getString(String key) { * * @param key - key (inside) current section * @param defaultValue - default value that will returned if section element is not exists - * @return + * @return String value of config section element */ public String getString(String key, String defaultValue) { - Object result = this.get(key, defaultValue); - return String.valueOf(result); + return String.valueOf(this.get(key, defaultValue)); } /** - * Check type of section element defined by key. Return true this element is String + * Check type of section element defined by key. Return true if this element is String * - * @param key - * @return + * @param key key + * @return true if this element is String */ public boolean isString(String key) { - Object val = get(key); - return val instanceof String; + return get(key) instanceof String; } /** * Get boolean value of config section element * * @param key - key (inside) current section - * @return + * @return boolean value of config section element */ public boolean getBoolean(String key) { return this.getBoolean(key, false); @@ -329,28 +329,27 @@ public boolean getBoolean(String key) { * * @param key - key (inside) current section * @param defaultValue - default value that will returned if section element is not exists - * @return + * @return boolean value of config section element */ public boolean getBoolean(String key, boolean defaultValue) { return this.get(key, defaultValue); } /** - * Check type of section element defined by key. Return true this element is Integer + * Check type of section element defined by key. Return true if this element is Integer * - * @param key - * @return + * @param key key + * @return true if this element is Integer */ public boolean isBoolean(String key) { - Object val = get(key); - return val instanceof Boolean; + return get(key) instanceof Boolean; } /** * Get List value of config section element * * @param key - key (inside) current section - * @return + * @return List value of config section element */ public List getList(String key) { return this.getList(key, null); @@ -361,28 +360,27 @@ public List getList(String key) { * * @param key - key (inside) current section * @param defaultList - default value that will returned if section element is not exists - * @return + * @return List value of config section element */ public List getList(String key, List defaultList) { return this.get(key, defaultList); } /** - * Check type of section element defined by key. Return true this element is List + * Check type of section element defined by key. Return true if this element is List * - * @param key - * @return + * @param key key + * @return true if this element is List */ public boolean isList(String key) { - Object val = get(key); - return val instanceof List; + return get(key) instanceof List; } /** * Get String List value of config section element * * @param key - key (inside) current section - * @return + * @return String List value of config section element */ public List getStringList(String key) { List value = this.getList(key); @@ -402,7 +400,7 @@ public List getStringList(String key) { * Get Integer List value of config section element * * @param key - key (inside) current section - * @return + * @return Integer List value of config section element */ public List getIntegerList(String key) { List list = getList(key); @@ -417,8 +415,7 @@ public List getIntegerList(String key) { } else if (object instanceof String) { try { result.add(Integer.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((int) (Character) object); @@ -433,7 +430,7 @@ public List getIntegerList(String key) { * Get Boolean List value of config section element * * @param key - key (inside) current section - * @return + * @return Boolean List value of config section element */ public List getBooleanList(String key) { List list = getList(key); @@ -459,7 +456,7 @@ public List getBooleanList(String key) { * Get Double List value of config section element * * @param key - key (inside) current section - * @return + * @return Double List value of config section element */ public List getDoubleList(String key) { List list = getList(key); @@ -473,8 +470,7 @@ public List getDoubleList(String key) { } else if (object instanceof String) { try { result.add(Double.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((double) (Character) object); @@ -489,7 +485,7 @@ public List getDoubleList(String key) { * Get Float List value of config section element * * @param key - key (inside) current section - * @return + * @return Float List value of config section element */ public List getFloatList(String key) { List list = getList(key); @@ -503,8 +499,7 @@ public List getFloatList(String key) { } else if (object instanceof String) { try { result.add(Float.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((float) (Character) object); @@ -519,7 +514,7 @@ public List getFloatList(String key) { * Get Long List value of config section element * * @param key - key (inside) current section - * @return + * @return Long List value of config section element */ public List getLongList(String key) { List list = getList(key); @@ -533,8 +528,7 @@ public List getLongList(String key) { } else if (object instanceof String) { try { result.add(Long.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((long) (Character) object); @@ -549,7 +543,7 @@ public List getLongList(String key) { * Get Byte List value of config section element * * @param key - key (inside) current section - * @return + * @return Byte List value of config section element */ public List getByteList(String key) { List list = getList(key); @@ -566,8 +560,7 @@ public List getByteList(String key) { } else if (object instanceof String) { try { result.add(Byte.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((byte) ((Character) object).charValue()); @@ -583,7 +576,7 @@ public List getByteList(String key) { * Get Character List value of config section element * * @param key - key (inside) current section - * @return + * @return Character List value of config section element */ public List getCharacterList(String key) { List list = getList(key); @@ -615,7 +608,7 @@ public List getCharacterList(String key) { * Get Short List value of config section element * * @param key - key (inside) current section - * @return + * @return Short List value of config section element */ public List getShortList(String key) { List list = getList(key); @@ -632,8 +625,7 @@ public List getShortList(String key) { } else if (object instanceof String) { try { result.add(Short.valueOf((String) object)); - } catch (Exception ex) { - //ignore + } catch (Exception ignored) { } } else if (object instanceof Character) { result.add((short) ((Character) object).charValue()); @@ -649,8 +641,9 @@ public List getShortList(String key) { * Get Map List value of config section element * * @param key - key (inside) current section - * @return + * @return Map List value of config section element */ + @SuppressWarnings("unchecked") public List getMapList(String key) { List list = getList(key); List result = new ArrayList<>(); @@ -671,15 +664,17 @@ public List getMapList(String key) { /** * Check existence of config section element * - * @param key - * @param ignoreCase - * @return + * @param key key + * @param ignoreCase ignore case + * @return existence of config section element */ public boolean exists(String key, boolean ignoreCase) { - if (ignoreCase) key = key.toLowerCase(); for (String existKey : this.getKeys(true)) { - if (ignoreCase) existKey = existKey.toLowerCase(); - if (existKey.equals(key)) return true; + if (ignoreCase) { + if (existKey.equalsIgnoreCase(key)) return true; + } else { + if (existKey.equals(key)) return true; + } } return false; } @@ -687,8 +682,8 @@ public boolean exists(String key, boolean ignoreCase) { /** * Check existence of config section element * - * @param key - * @return + * @param key key + * @return existence of config section element */ public boolean exists(String key) { return exists(key, false); @@ -697,7 +692,7 @@ public boolean exists(String key) { /** * Remove config section element * - * @param key + * @param key key */ public void remove(String key) { if (key == null || key.isEmpty()) return; @@ -715,7 +710,7 @@ else if (this.containsKey(".")) { * Get all keys * * @param child - true = include child keys - * @return + * @return all keys */ public Set getKeys(boolean child) { Set keys = new LinkedHashSet<>(); @@ -723,7 +718,7 @@ public Set getKeys(boolean child) { keys.add(key); if (value instanceof ConfigSection) { if (child) - ((ConfigSection) value).getKeys(true).forEach(childKey -> keys.add(key + "." + childKey)); + ((ConfigSection) value).getKeys(true).forEach(childKey -> keys.add(key + '.' + childKey)); } }); return keys; @@ -732,7 +727,7 @@ public Set getKeys(boolean child) { /** * Get all keys * - * @return + * @return all keys */ public Set getKeys() { return this.getKeys(true); diff --git a/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java b/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java index 8c9cf530e67..acb9640db6d 100644 --- a/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java +++ b/src/main/java/cn/nukkit/utils/DefaultPlayerDataSerializer.java @@ -4,36 +4,39 @@ import com.google.common.base.Preconditions; import java.io.*; +import java.nio.file.Files; import java.util.Optional; import java.util.UUID; +/** + * Default player data serializer that saves the player data as .dat files into the 'players' folder. + */ public class DefaultPlayerDataSerializer implements PlayerDataSerializer { - private String dataPath; - + + private final String dataPath; + public DefaultPlayerDataSerializer(Server server) { this(server.getDataPath()); } - + public DefaultPlayerDataSerializer(String dataPath) { this.dataPath = dataPath; } @Override public Optional read(String name, UUID uuid) throws IOException { - String path = dataPath + "players/" + name + ".dat"; - File file = new File(path); + File file = new File(dataPath + "players/" + name + ".dat"); if (!file.exists()) { return Optional.empty(); } - return Optional.of(new FileInputStream(file)); + return Optional.of(Files.newInputStream(file.toPath())); } @Override public OutputStream write(String name, UUID uuid) throws IOException { Preconditions.checkNotNull(name, "name"); - String path = dataPath + "players/" + name + ".dat"; - File file = new File(path); - return new FileOutputStream(file); + File file = new File(dataPath + "players/" + name + ".dat"); + return Files.newOutputStream(file.toPath()); } } diff --git a/src/main/java/cn/nukkit/utils/DummyBossBar.java b/src/main/java/cn/nukkit/utils/DummyBossBar.java index 7dc1f9811eb..b2b0103267f 100644 --- a/src/main/java/cn/nukkit/utils/DummyBossBar.java +++ b/src/main/java/cn/nukkit/utils/DummyBossBar.java @@ -7,12 +7,10 @@ import cn.nukkit.entity.mob.EntityCreeper; import cn.nukkit.network.protocol.*; -import java.util.concurrent.ThreadLocalRandom; - /** * DummyBossBar * =============== - * author: boybook + * @author boybook * Nukkit Project * =============== */ @@ -33,6 +31,9 @@ private DummyBossBar(Builder builder) { this.color = builder.color; } + /** + * Boss bar builder + */ public static class Builder { private final Player player; private final long bossBarId; @@ -43,7 +44,7 @@ public static class Builder { public Builder(Player player) { this.player = player; - this.bossBarId = 1095216660480L + ThreadLocalRandom.current().nextLong(0, 0x7fffffffL); + this.bossBarId = 1095216660480L + Utils.random.nextLong(0, 0x7fffffffL); } public Builder text(String text) { @@ -66,18 +67,38 @@ public DummyBossBar build() { } } + /** + * Get boss bar owner + * + * @return player + */ public Player getPlayer() { return player; } + /** + * Get boss bar id + * + * @return boss bar id + */ public long getBossBarId() { return bossBarId; } + /** + * Get boss bar text + * + * @return current text + */ public String getText() { return text; } + /** + * Set the boss bar text and send it to player if changed + * + * @param text new text + */ public void setText(String text) { if (!this.text.equals(text)) { this.text = text; @@ -86,10 +107,20 @@ public void setText(String text) { } } + /** + * Get boss bar length + * + * @return length + */ public float getLength() { return length; } + /** + * Set boss bar length + * + * @param length new length + */ public void setLength(float length) { if (this.length != length) { this.length = length; @@ -98,6 +129,10 @@ public void setLength(float length) { } } + /** + * Set boss bar color. Requires client version 1.18 or newer. + * @param color the boss bar color + */ public void setColor(BossBarColor color) { if (this.color == null || !this.color.equals(color)) { this.color = color; @@ -105,6 +140,10 @@ public void setColor(BossBarColor color) { } } + /** + * Get boss bar color + * @return current color of the boss bar + */ public BossBarColor getColor() { return this.color; } @@ -128,7 +167,6 @@ private void createBossEntity() { .putLong(Entity.DATA_LEAD_HOLDER_EID, -1) .putString(Entity.DATA_NAMETAG, text) // Set the entity name .putFloat(Entity.DATA_SCALE, 0); // And make it invisible - player.dataPacket(pkAdd); } @@ -212,6 +250,9 @@ private void removeBossEntity() { player.dataPacket(pkRemove); } + /** + * Create boss bar entity and send its data + */ public void create() { createBossEntity(); sendAttributes(); @@ -233,5 +274,4 @@ public void destroy() { sendHideBossBar(); removeBossEntity(); } - } diff --git a/src/main/java/cn/nukkit/utils/DyeColor.java b/src/main/java/cn/nukkit/utils/DyeColor.java index dda243ae37e..9998895d0b5 100644 --- a/src/main/java/cn/nukkit/utils/DyeColor.java +++ b/src/main/java/cn/nukkit/utils/DyeColor.java @@ -1,6 +1,10 @@ package cn.nukkit.utils; +/** + * Dye color enum + */ public enum DyeColor { + BLACK(0, 15, "Black", "Ink Sac", BlockColor.BLACK_BLOCK_COLOR, new BlockColor(0x00, 0x00, 0x00)), RED(1, 14, "Red", BlockColor.RED_BLOCK_COLOR, new BlockColor(0xb0, 0x2e, 0x26)), GREEN(2, 13, "Green", BlockColor.GREEN_BLOCK_COLOR, new BlockColor(0x5e, 0x7c, 0x16)), @@ -41,6 +45,11 @@ public enum DyeColor { this.dyeName = dyeName; } + /** + * Get as BlockColor + * + * @return BlockColor of the DyeColor + */ public BlockColor getColor() { return this.blockColor; } @@ -49,18 +58,38 @@ public BlockColor getSignColor() { return this.signColor; } + /** + * Get as dye item meta value + * + * @return dye item meta value of the DyeColor + */ public int getDyeData() { return this.dyeColorMeta; } + /** + * Get as wool block meta value + * + * @return wool block meta value of the DyeColor + */ public int getWoolData() { return this.woolColorMeta; } + /** + * Get color name + * + * @return color name + */ public String getName() { return this.colorName; } + /** + * Get dye name + * + * @return dye name + */ public String getDyeName() { return this.dyeName; } @@ -75,10 +104,22 @@ public String getDyeName() { } } + /** + * Get DyeColor by dye item meta value + * + * @param dyeColorMeta dye item meta value + * @return DyeColor + */ public static DyeColor getByDyeData(int dyeColorMeta) { return BY_DYE_DATA[dyeColorMeta & 0x0f]; } + /** + * Get DyeColor by wool block meta value + * + * @param woolColorMeta wool block meta value + * @return DyeColor + */ public static DyeColor getByWoolData(int woolColorMeta) { return BY_WOOL_DATA[woolColorMeta & 0x0f]; } diff --git a/src/main/java/cn/nukkit/utils/EventException.java b/src/main/java/cn/nukkit/utils/EventException.java index f7f313907c5..ca0b487b0a0 100644 --- a/src/main/java/cn/nukkit/utils/EventException.java +++ b/src/main/java/cn/nukkit/utils/EventException.java @@ -1,10 +1,13 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * EventException + * + * @author MagicDroidX * Nukkit Project */ public class EventException extends RuntimeException { + private final Throwable cause; /** diff --git a/src/main/java/cn/nukkit/utils/Faceable.java b/src/main/java/cn/nukkit/utils/Faceable.java index 02d2cfc700b..53ea2dc7286 100644 --- a/src/main/java/cn/nukkit/utils/Faceable.java +++ b/src/main/java/cn/nukkit/utils/Faceable.java @@ -2,8 +2,15 @@ import cn.nukkit.math.BlockFace; +/** + * Interface of a faceable Block + */ public interface Faceable { + /** + * Get BlockFace of the direction the block is facing + * + * @return BlockFace of the direction the block is facing + */ BlockFace getBlockFace(); - -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/Hash.java b/src/main/java/cn/nukkit/utils/Hash.java index 3864583db8b..d4b56175b04 100644 --- a/src/main/java/cn/nukkit/utils/Hash.java +++ b/src/main/java/cn/nukkit/utils/Hash.java @@ -1,20 +1,23 @@ package cn.nukkit.utils; +/** + * Hash utils + */ public class Hash { + public static long hashBlock(int x, int y, int z) { - return y + (((long) x & 0x3FFFFFF) << 8) + (((long) z & 0x3FFFFFF) << 34); + return ((long) y << 52) + (((long) z & 0x3ffffff) << 26) + ((long) x & 0x3ffffff); } - - public static final int hashBlockX(long triple) { - return (int) ((((triple >> 8) & 0x3FFFFFF) << 38) >> 38); + public static int hashBlockX(long triple) { + return (int) (((triple & 0x3ffffff) << 38) >> 38); } - public static final int hashBlockY(long triple) { - return (int) (triple & 0xFF); + public static int hashBlockY(long triple) { + return (int) (triple >> 52); } - public static final int hashBlockZ(long triple) { - return (int) ((((triple >> 34) & 0x3FFFFFF) << 38) >> 38); + public static int hashBlockZ(long triple) { + return (int) ((((triple >> 26) & 0x3ffffff) << 38) >> 38); } } diff --git a/src/main/java/cn/nukkit/utils/HastebinUtility.java b/src/main/java/cn/nukkit/utils/HastebinUtility.java deleted file mode 100644 index cf75be3f12e..00000000000 --- a/src/main/java/cn/nukkit/utils/HastebinUtility.java +++ /dev/null @@ -1,65 +0,0 @@ -package cn.nukkit.utils; - -import java.io.*; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class HastebinUtility { - - public static final String BIN_URL = "https://hastebin.com/documents", USER_AGENT = "Mozilla/5.0"; - public static final Pattern PATTERN = Pattern.compile("\\{\"key\":\"([\\S\\s]*)\"}"); - - public static String upload(final String string) throws IOException { - final URL url = new URL(BIN_URL); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - connection.setRequestMethod("POST"); - connection.setRequestProperty("User-Agent", USER_AGENT); - connection.setDoOutput(true); - - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(string.getBytes()); - outputStream.flush(); - } - - StringBuilder response; - try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - response = new StringBuilder(); - - String inputLine; - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - } - - Matcher matcher = PATTERN.matcher(response.toString()); - if (matcher.matches()) { - return "https://hastebin.com/" + matcher.group(1); - } else { - throw new RuntimeException("Couldn't read response!"); - } - } - - public static String upload(final File file) throws IOException { - final StringBuilder content = new StringBuilder(); - List lines = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { - String line; - int i = 0; - while ((line = reader.readLine()) != null) { - if (!line.contains("rcon.password=")) { - lines.add(line); - } - } - } - for (int i = Math.max(0, lines.size() - 1000); i < lines.size(); i++) { - content.append(lines.get(i)).append("\n"); - } - return upload(content.toString()); - } - -} diff --git a/src/main/java/cn/nukkit/utils/LevelException.java b/src/main/java/cn/nukkit/utils/LevelException.java index 0a24194f3ff..44f9a5c9530 100644 --- a/src/main/java/cn/nukkit/utils/LevelException.java +++ b/src/main/java/cn/nukkit/utils/LevelException.java @@ -1,10 +1,13 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * LevelException + * + * @author MagicDroidX * Nukkit Project */ public class LevelException extends ServerException { + public LevelException(String message) { super(message); } diff --git a/src/main/java/cn/nukkit/utils/LogLevel.java b/src/main/java/cn/nukkit/utils/LogLevel.java index 192e4c62486..0d4ee19399e 100644 --- a/src/main/java/cn/nukkit/utils/LogLevel.java +++ b/src/main/java/cn/nukkit/utils/LogLevel.java @@ -5,10 +5,13 @@ import java.util.function.BiConsumer; /** - * author: MagicDroidX + * Log level enum + * + * @author MagicDroidX * Nukkit Project */ public enum LogLevel implements Comparable { + NONE((logger, message) -> {}, (mainLogger, s, throwable) -> {}), EMERGENCY(MainLogger::emergency, MainLogger::emergency), ALERT(MainLogger::alert, MainLogger::alert), @@ -19,6 +22,9 @@ public enum LogLevel implements Comparable { INFO(MainLogger::info, MainLogger::info), DEBUG(MainLogger::debug, MainLogger::debug); + /** + * Default logging level: INFO + */ public static final LogLevel DEFAULT_LEVEL = INFO; private final BiConsumer logTo; @@ -37,7 +43,12 @@ public void log(MainLogger logger, String message, Throwable throwable) { logThrowableTo.accept(logger, message, throwable); } + /** + * Get log level + * + * @return log level + */ public int getLevel() { return ordinal(); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/Logger.java b/src/main/java/cn/nukkit/utils/Logger.java index 7f635409162..0db36e86b28 100644 --- a/src/main/java/cn/nukkit/utils/Logger.java +++ b/src/main/java/cn/nukkit/utils/Logger.java @@ -1,7 +1,9 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * Logger + * + * @author MagicDroidX * Nukkit Project */ public interface Logger { diff --git a/src/main/java/cn/nukkit/utils/LoginChainData.java b/src/main/java/cn/nukkit/utils/LoginChainData.java index 398d9367d10..c4dacc85662 100644 --- a/src/main/java/cn/nukkit/utils/LoginChainData.java +++ b/src/main/java/cn/nukkit/utils/LoginChainData.java @@ -5,6 +5,8 @@ import java.util.UUID; /** + * Login chain data + * * @author CreeperFace */ public interface LoginChainData { @@ -44,4 +46,6 @@ public interface LoginChainData { int getUIProfile(); JsonObject getRawData(); + + String getTitleId(); } diff --git a/src/main/java/cn/nukkit/utils/MainLogger.java b/src/main/java/cn/nukkit/utils/MainLogger.java index 02a430cc0d4..24ab552d591 100644 --- a/src/main/java/cn/nukkit/utils/MainLogger.java +++ b/src/main/java/cn/nukkit/utils/MainLogger.java @@ -1,11 +1,14 @@ package cn.nukkit.utils; +import cn.nukkit.Nukkit; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; /** - * author: MagicDroidX + * Main logger + * + * @author MagicDroidX * Nukkit */ /* @@ -13,7 +16,7 @@ */ @Log4j2 @NoArgsConstructor(access = AccessLevel.PRIVATE) -public class MainLogger extends ThreadedLogger { +public class MainLogger extends Thread implements Logger { private static final MainLogger logger = new MainLogger(); @@ -58,11 +61,9 @@ public void info(String message) { @Override public void debug(String message) { - log.debug(message); - } - - public void setLogDebug(Boolean logDebug) { - throw new UnsupportedOperationException(); + if (Nukkit.DEBUG > 1) { + log.debug(message); + } } public void logException(Throwable t) { @@ -74,10 +75,6 @@ public void log(LogLevel level, String message) { level.log(this, message); } - public void shutdown() { - throw new UnsupportedOperationException(); - } - @Override public void emergency(String message, Throwable t) { log.fatal(message, t); @@ -122,4 +119,4 @@ public void debug(String message, Throwable t) { public void log(LogLevel level, String message, Throwable t) { level.log(this, message, t); } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/MinecartType.java b/src/main/java/cn/nukkit/utils/MinecartType.java index dda0805e554..b88b9854eb4 100644 --- a/src/main/java/cn/nukkit/utils/MinecartType.java +++ b/src/main/java/cn/nukkit/utils/MinecartType.java @@ -1,17 +1,14 @@ package cn.nukkit.utils; -import cn.nukkit.api.API; - -import java.util.HashMap; -import java.util.Map; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; /** * Helper class of Minecart variants - *

    + * * By Adam Matthew * Creation time: 2017/7/17 19:55. */ -@API(usage = API.Usage.STABLE, definition = API.Definition.INTERNAL) public enum MinecartType { /** * Represents an empty vehicle. @@ -49,11 +46,11 @@ public enum MinecartType { private final int type; private final boolean hasBlockInside; private final String realName; - private static final Map TYPES = new HashMap<>(); + private static final Int2ObjectMap TYPES = new Int2ObjectOpenHashMap<>(); static { for (MinecartType var3 : values()) { - TYPES.put(var3.getId(), var3); + TYPES.put(var3.type, var3); } } diff --git a/src/main/java/cn/nukkit/utils/PersonaPiece.java b/src/main/java/cn/nukkit/utils/PersonaPiece.java index 7bd7bf372d2..6edaed09480 100644 --- a/src/main/java/cn/nukkit/utils/PersonaPiece.java +++ b/src/main/java/cn/nukkit/utils/PersonaPiece.java @@ -2,8 +2,12 @@ import lombok.ToString; +/** + * Persona skin piece + */ @ToString public class PersonaPiece { + public final String id; public final String type; public final String packId; diff --git a/src/main/java/cn/nukkit/utils/PersonaPieceTint.java b/src/main/java/cn/nukkit/utils/PersonaPieceTint.java index a24088c25a4..fb6cfedc98e 100644 --- a/src/main/java/cn/nukkit/utils/PersonaPieceTint.java +++ b/src/main/java/cn/nukkit/utils/PersonaPieceTint.java @@ -5,8 +5,12 @@ import java.util.List; +/** + * Persona skin piece tint + */ @ToString public class PersonaPieceTint { + public final String pieceType; public final ImmutableList colors; diff --git a/src/main/java/cn/nukkit/utils/PlayerDataSerializer.java b/src/main/java/cn/nukkit/utils/PlayerDataSerializer.java index fad786cbec0..bd9388e52cd 100644 --- a/src/main/java/cn/nukkit/utils/PlayerDataSerializer.java +++ b/src/main/java/cn/nukkit/utils/PlayerDataSerializer.java @@ -6,6 +6,11 @@ import java.util.Optional; import java.util.UUID; +/** + * A serializer that handles the player data saving. + * + * Use setPlayerDataSerializer() in Server to register it. + */ public interface PlayerDataSerializer { /** diff --git a/src/main/java/cn/nukkit/utils/PluginException.java b/src/main/java/cn/nukkit/utils/PluginException.java index 6e7751e97cf..c9d19c71d10 100644 --- a/src/main/java/cn/nukkit/utils/PluginException.java +++ b/src/main/java/cn/nukkit/utils/PluginException.java @@ -1,10 +1,13 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * PluginException + * + * @author MagicDroidX * Nukkit Project */ public class PluginException extends ServerException { + public PluginException(String message) { super(message); } diff --git a/src/main/java/cn/nukkit/utils/Rail.java b/src/main/java/cn/nukkit/utils/Rail.java index 7cb9d958e2a..d0ba8620092 100644 --- a/src/main/java/cn/nukkit/utils/Rail.java +++ b/src/main/java/cn/nukkit/utils/Rail.java @@ -1,6 +1,5 @@ package cn.nukkit.utils; -import cn.nukkit.api.API; import cn.nukkit.block.Block; import cn.nukkit.math.BlockFace; @@ -15,18 +14,26 @@ /** * INTERNAL helper class of railway - *

    + * * By lmlstarqaq http://snake1999.com/ * Creation time: 2017/7/1 17:42. */ -@API(usage = API.Usage.BLEEDING, definition = API.Definition.INTERNAL) public final class Rail { + /** + * Check if the block is a rail block + * + * @param block block + * @return is rail block + */ public static boolean isRailBlock(Block block) { Objects.requireNonNull(block, "Rail block predicate can not accept null block"); return isRailBlock(block.getId()); } + /** + * Rail orientation enum + */ public enum Orientation { STRAIGHT_NORTH_SOUTH(0, STRAIGHT, NORTH, SOUTH, null), STRAIGHT_EAST_WEST(1, STRAIGHT, EAST, WEST, null), @@ -120,6 +127,9 @@ public Optional ascendingDirection() { return Optional.ofNullable(ascendingDirection); } + /** + * Rail orientation state enum + */ public enum State { STRAIGHT, ASCENDING, CURVED } @@ -143,6 +153,12 @@ public boolean isCurved() { } } + /** + * Check if the block is a rail block + * + * @param blockId block id + * @return is rail block + */ public static boolean isRailBlock(int blockId) { switch (blockId) { case Block.RAIL: diff --git a/src/main/java/cn/nukkit/utils/SerializedImage.java b/src/main/java/cn/nukkit/utils/SerializedImage.java index 2fddf118d96..546e8882676 100644 --- a/src/main/java/cn/nukkit/utils/SerializedImage.java +++ b/src/main/java/cn/nukkit/utils/SerializedImage.java @@ -6,8 +6,15 @@ import static cn.nukkit.entity.data.Skin.*; -@ToString(exclude = {"data"}) +/** + * Serialized image + */ +@ToString(exclude = "data") public class SerializedImage { + + /** + * Empty SerializedImage + */ public static final SerializedImage EMPTY = new SerializedImage(0, 0, new byte[0]); public final int width; @@ -20,6 +27,12 @@ public SerializedImage(int width, int height, byte[] data) { this.data = data; } + /** + * Get SerializedImage from legacy skin data + * + * @param skinData legacy skin data bytes + * @return SerializedImage + */ public static SerializedImage fromLegacy(byte[] skinData) { Objects.requireNonNull(skinData, "skinData"); switch (skinData.length) { diff --git a/src/main/java/cn/nukkit/utils/ServerException.java b/src/main/java/cn/nukkit/utils/ServerException.java index 77987b1ba4a..365011fc934 100644 --- a/src/main/java/cn/nukkit/utils/ServerException.java +++ b/src/main/java/cn/nukkit/utils/ServerException.java @@ -1,10 +1,13 @@ package cn.nukkit.utils; /** - * author: MagicDroidX + * ServerException + * + * @author MagicDroidX * Nukkit Project */ public class ServerException extends RuntimeException { + public ServerException(String message) { super(message); } diff --git a/src/main/java/cn/nukkit/utils/ServerKiller.java b/src/main/java/cn/nukkit/utils/ServerKiller.java index 28f16d39d00..b03a8b8c6e5 100644 --- a/src/main/java/cn/nukkit/utils/ServerKiller.java +++ b/src/main/java/cn/nukkit/utils/ServerKiller.java @@ -3,7 +3,9 @@ import java.util.concurrent.TimeUnit; /** - * author: MagicDroidX + * A task that kills the server process after given time. + * + * @author MagicDroidX * Nukkit Project */ public class ServerKiller extends Thread { @@ -23,10 +25,7 @@ public ServerKiller(long time, TimeUnit unit) { public void run() { try { sleep(sleepTime); - } catch (InterruptedException e) { - // ignore - } - System.out.println("\nTook too long to stop, server was killed forcefully!\n"); + } catch (InterruptedException ignored) {} System.exit(1); } } diff --git a/src/main/java/cn/nukkit/utils/SimpleConfig.java b/src/main/java/cn/nukkit/utils/SimpleConfig.java index 8aa73c526a1..bf15371b5d8 100644 --- a/src/main/java/cn/nukkit/utils/SimpleConfig.java +++ b/src/main/java/cn/nukkit/utils/SimpleConfig.java @@ -15,8 +15,9 @@ import java.util.List; /** - * SimpleConfig for Nukkit - * added 11/02/2016 by fromgate + * Simple config for Nukkit + * + * Added 11/02/2016 by fromgate */ public abstract class SimpleConfig { @@ -35,10 +36,21 @@ public SimpleConfig(File file) { configFile.getParentFile().mkdirs(); } + /** + * Save the config to disk + * + * @return saved + */ public boolean save() { return save(false); } + /** + * Save the config to disk + * + * @param async async + * @return saved + */ public boolean save(boolean async) { if (configFile.exists()) try { configFile.createNewFile(); @@ -59,12 +71,17 @@ public boolean save(boolean async) { return true; } + /** + * Load + * + * @return loaded + */ public boolean load() { if (!this.configFile.exists()) return false; Config cfg = new Config(configFile, Config.YAML); for (Field field : this.getClass().getDeclaredFields()) { if (field.getName().equals("configFile")) continue; - if (skipSave(field)) continue; + if (skipLoad(field)) continue; String path = getPath(field); if (path == null) continue; if (path.isEmpty()) continue; @@ -106,7 +123,7 @@ else if (field.getType() == List.class) { return true; } - private String getPath(Field field) { + private static String getPath(Field field) { String path = null; if (field.isAnnotationPresent(Path.class)) { Path pathDefine = field.getAnnotation(Path.class); @@ -118,7 +135,7 @@ private String getPath(Field field) { return path; } - private boolean skipSave(Field field) { + private static boolean skipSave(Field field) { if (!field.isAnnotationPresent(Skip.class)) return false; return field.getAnnotation(Skip.class).skipSave(); } diff --git a/src/main/java/cn/nukkit/utils/SkinAnimation.java b/src/main/java/cn/nukkit/utils/SkinAnimation.java index 236261d21ae..10bcf2ba171 100644 --- a/src/main/java/cn/nukkit/utils/SkinAnimation.java +++ b/src/main/java/cn/nukkit/utils/SkinAnimation.java @@ -2,17 +2,21 @@ import lombok.ToString; +/** + * Skin animation + */ @ToString public class SkinAnimation { + public final SerializedImage image; public final int type; public final float frames; public final int expression; public SkinAnimation(SerializedImage image, int type, float frames, int expression) { - this.image = image; - this.type = type; - this.frames = frames; - this.expression = expression; + this.image = image; + this.type = type; + this.frames = frames; + this.expression = expression; } } diff --git a/src/main/java/cn/nukkit/utils/SnappyCompression.java b/src/main/java/cn/nukkit/utils/SnappyCompression.java new file mode 100644 index 00000000000..c46bc9e9084 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/SnappyCompression.java @@ -0,0 +1,22 @@ +package cn.nukkit.utils; + +import org.xerial.snappy.Snappy; + +import java.io.IOException; + +public class SnappyCompression { + + public static byte[] compress(byte[] data) throws IOException { + return Snappy.compress(data); + } + + public static byte[] decompress(byte[] data, int maxSize) throws IOException { + int size = Snappy.uncompressedLength(data); + if (size > maxSize && maxSize > 0) { + throw new IllegalArgumentException("Input is too big"); + } + byte[] decompressed = new byte[size]; + Snappy.uncompress(data, 0, data.length, decompressed, 0); + return decompressed; + } +} diff --git a/src/main/java/cn/nukkit/utils/TerracottaColor.java b/src/main/java/cn/nukkit/utils/TerracottaColor.java index 519532ff562..d43747ebe2f 100644 --- a/src/main/java/cn/nukkit/utils/TerracottaColor.java +++ b/src/main/java/cn/nukkit/utils/TerracottaColor.java @@ -1,5 +1,8 @@ package cn.nukkit.utils; +/** + * Terracotta color enum + */ public enum TerracottaColor { BLACK(0, 15, "Black", "Ink Sac", BlockColor.BLACK_TERRACOTA_BLOCK_COLOR), @@ -20,11 +23,11 @@ public enum TerracottaColor { WHITE(15, 0, "White", "Bone Meal", BlockColor.WHITE_TERRACOTA_BLOCK_COLOR); - private int dyeColorMeta; - private int terracottaColorMeta; - private String colorName; - private String dyeName; - private BlockColor blockColor; + private final int dyeColorMeta; + private final int terracottaColorMeta; + private final String colorName; + private final String dyeName; + private final BlockColor blockColor; private final static TerracottaColor[] BY_TERRACOTA_DATA; @@ -42,22 +45,47 @@ public enum TerracottaColor { this.dyeName = dyeName; } + /** + * Get as BlockColor + * + * @return BlockColor of the TerracottaColor + */ public BlockColor getColor() { return this.blockColor; } + /** + * Get as dye item meta value + * + * @return dye item meta value of the TerracottaColor + */ public int getDyeData() { return this.dyeColorMeta; } + /** + * Get as terracotta block meta value + * + * @return terracotta block meta value of the TerracottaColor + */ public int getTerracottaData() { return this.terracottaColorMeta; } + /** + * Get color name + * + * @return color name + */ public String getName() { return this.colorName; } + /** + * Get dye name + * + * @return dye name + */ public String getDyeName() { return this.dyeName; } @@ -72,10 +100,21 @@ public String getDyeName() { } } + /** + * Get TerracottaColor by dye item meta value + * @param dyeColorMeta dye item meta value + * @return TerracottaColor + */ public static TerracottaColor getByDyeData(int dyeColorMeta) { return BY_DYE_DATA[dyeColorMeta & 0x0f]; } - public static TerracottaColor getByTerracottaData(int terracottaColorMeta) { return BY_TERRACOTA_DATA[terracottaColorMeta & 0x0f]; } - + /** + * Get TerracottaColor by terracotta block meta value + * @param terracottaColorMeta terracotta block meta value + * @return TerracottaColor + */ + public static TerracottaColor getByTerracottaData(int terracottaColorMeta) { + return BY_TERRACOTA_DATA[terracottaColorMeta & 0x0f]; + } } diff --git a/src/main/java/cn/nukkit/utils/TextFormat.java b/src/main/java/cn/nukkit/utils/TextFormat.java index e0f0413cafc..79194fd9fe9 100644 --- a/src/main/java/cn/nukkit/utils/TextFormat.java +++ b/src/main/java/cn/nukkit/utils/TextFormat.java @@ -119,17 +119,14 @@ public enum TextFormat { public static final char ESCAPE = '\u00A7'; private static final Pattern CLEAN_PATTERN = Pattern.compile("(?i)" + ESCAPE + "[0-9A-U]"); - //private final static Map BY_ID = new TreeMap<>(); //unused private final static Map BY_CHAR = new HashMap<>(); static { for (TextFormat color : values()) { - //BY_ID.put(color.intCode, color); BY_CHAR.put(color.code, color); } } - //private final int intCode; private final char code; private final boolean isFormat; private final String toString; @@ -140,7 +137,6 @@ public enum TextFormat { TextFormat(char code, int intCode, boolean isFormat) { this.code = code; - //this.intCode = intCode; this.isFormat = isFormat; this.toString = new String(new char[]{ESCAPE, code}); } @@ -149,7 +145,7 @@ public enum TextFormat { * Gets the TextFormat represented by the specified format code. * * @param code Code to check - * @return Associative {@link TextFormat} with the given code, + * @return Associative with the given code, * or null if it doesn't exist */ public static TextFormat getByChar(char code) { @@ -160,7 +156,7 @@ public static TextFormat getByChar(char code) { * Gets the TextFormat represented by the specified format code. * * @param code Code to check - * @return Associative {@link TextFormat} with the given code, + * @return Associative with the given code, * or null if it doesn't exist */ public static TextFormat getByChar(String code) { @@ -181,6 +177,13 @@ public static String clean(final String input) { return clean(input, false); } + /** + * Cleans the given message of all format codes. + * + * @param input String to clean. + * @param recursive Do recursively. + * @return A copy of the input string, without any formatting. + */ public static String clean(final String input, final boolean recursive) { if (input == null) { return null; @@ -200,7 +203,7 @@ public static String clean(final String input, final boolean recursive) { * character. The alternate format code character will only be replaced if * it is immediately followed by 0-9, A-G, a-g, K-O, k-o, R or r. * - * @param altFormatChar The alternate format code character to replace. Ex: & + * @param altFormatChar The alternate format code character to replace. Ex: &amp; * @param textToTranslate Text containing the alternate format code character. * @return Text containing the TextFormat.ESCAPE format code character. */ @@ -241,10 +244,8 @@ public static String getLastColors(String input) { // Search backwards from the end as it is faster for (int index = length - 1; index > -1; index--) { - char section = input.charAt(index); - if (section == ESCAPE && index < length - 1) { - char c = input.charAt(index + 1); - TextFormat color = getByChar(c); + if (input.charAt(index) == ESCAPE && index < length - 1) { + TextFormat color = getByChar(input.charAt(index + 1)); if (color != null) { result.insert(0, color.toString()); @@ -287,4 +288,4 @@ public boolean isFormat() { public boolean isColor() { return !isFormat && this != RESET; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/ThreadCache.java b/src/main/java/cn/nukkit/utils/ThreadCache.java index 1bb09c06f2b..b4522107b14 100644 --- a/src/main/java/cn/nukkit/utils/ThreadCache.java +++ b/src/main/java/cn/nukkit/utils/ThreadCache.java @@ -4,6 +4,9 @@ import java.util.BitSet; +/** + * Thread cache + */ public class ThreadCache { @Deprecated diff --git a/src/main/java/cn/nukkit/utils/ThreadStore.java b/src/main/java/cn/nukkit/utils/ThreadStore.java index 1aa2bb836e2..41285f61f71 100644 --- a/src/main/java/cn/nukkit/utils/ThreadStore.java +++ b/src/main/java/cn/nukkit/utils/ThreadStore.java @@ -4,9 +4,12 @@ import java.util.concurrent.ConcurrentHashMap; /** - * author: MagicDroidX + * Thread store + * + * @author MagicDroidX * Nukkit Project */ public class ThreadStore { + public static final Map store = new ConcurrentHashMap<>(); } diff --git a/src/main/java/cn/nukkit/utils/ThreadedLogger.java b/src/main/java/cn/nukkit/utils/ThreadedLogger.java deleted file mode 100644 index b567b1d964d..00000000000 --- a/src/main/java/cn/nukkit/utils/ThreadedLogger.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.nukkit.utils; - -/** - * author: MagicDroidX - * Nukkit Project - */ -public abstract class ThreadedLogger extends Thread implements Logger { -} diff --git a/src/main/java/cn/nukkit/utils/Utils.java b/src/main/java/cn/nukkit/utils/Utils.java index 377fa3db031..639e3e4d88f 100644 --- a/src/main/java/cn/nukkit/utils/Utils.java +++ b/src/main/java/cn/nukkit/utils/Utils.java @@ -1,21 +1,44 @@ package cn.nukkit.utils; +import cn.nukkit.Player; +import cn.nukkit.Server; +import cn.nukkit.math.NukkitRandom; +import cn.nukkit.nbt.NBTIO; +import cn.nukkit.nbt.tag.Tag; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; +import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.Arrays; import java.util.Map; +import java.util.SplittableRandom; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.GZIPInputStream; /** - * author: MagicDroidX + * This class contains miscellaneous stuff used in other parts of the program. + * + * @author MagicDroidX * Nukkit Project */ public class Utils { + /** + * A SplittableRandom you can use without having to create a new object every time. + */ + public static final SplittableRandom random = new SplittableRandom(); + /** + * A NukkitRandom you can use without having to create a new object every time. + */ + public static final NukkitRandom nukkitRandom = new NukkitRandom(); + public static void writeFile(String fileName, String content) throws IOException { writeFile(fileName, new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); } @@ -49,7 +72,7 @@ public static String readFile(File file) throws IOException { if (!file.exists() || file.isDirectory()) { throw new FileNotFoundException(); } - return readFile(new FileInputStream(file)); + return readFile(Files.newInputStream(file.toPath())); } public static String readFile(String filename) throws IOException { @@ -57,7 +80,7 @@ public static String readFile(String filename) throws IOException { if (!file.exists() || file.isDirectory()) { throw new FileNotFoundException(); } - return readFile(new FileInputStream(file)); + return readFile(Files.newInputStream(file.toPath())); } public static String readFile(InputStream inputStream) throws IOException { @@ -71,7 +94,7 @@ private static String readFile(Reader reader) throws IOException { temp = br.readLine(); while (temp != null) { if (stringBuilder.length() != 0) { - stringBuilder.append("\n"); + stringBuilder.append('\n'); } stringBuilder.append(temp); temp = br.readLine(); @@ -209,7 +232,7 @@ public static T[] reverseArray(T[] array, boolean copy) { } for (int left = 0, right = data.length - 1; left < right; left++, right--) { - // swap the values at the left and right indices + // Swap the values at the left and right indices T temp = data[left]; data[left] = data[right]; data[right] = temp; @@ -268,28 +291,135 @@ public static int toInt(Object number) { public static byte[] parseHexBinary(String s) { final int len = s.length(); - // "111" is not a valid hex encoding. - if(len % 2 != 0) + // "111" is not a valid hex encoding + if (len % 2 != 0) throw new IllegalArgumentException("hexBinary needs to be even-length: " + s); - byte[] out = new byte[len / 2]; + byte[] out = new byte[(len >> 1)]; - for(int i = 0; i < len; i += 2) { + for (int i = 0; i < len; i += 2) { int h = hexToBin(s.charAt(i)); int l = hexToBin(s.charAt(i + 1)); - if(h == -1 || l == -1) + if (h == -1 || l == -1) throw new IllegalArgumentException("contains illegal character for hexBinary: " + s); - out[i / 2] = (byte)(h * 16 + l); + out[(i >> 1)] = (byte)((h << 4) + l); } return out; } private static int hexToBin( char ch ) { - if('0' <= ch && ch <= '9') return ch - '0'; - if('A' <= ch && ch <= 'F') return ch - 'A' + 10; - if('a' <= ch && ch <= 'f') return ch - 'a' + 10; + if ('0' <= ch && ch <= '9') return ch - '0'; + if ('A' <= ch && ch <= 'F') return ch - 'A' + 10; + if ('a' <= ch && ch <= 'f') return ch - 'a' + 10; return -1; } + + /** + * Get a random int + * + * @param min minimum value + * @param max maximum value + * @return random int between min and max + */ + public static int rand(int min, int max) { + if (min == max) { + return max; + } + return random.nextInt(max + 1 - min) + min; + } + + /** + * Get a random double + * + * @param min minimum value + * @param max maximum value + * @return random double between min and max + */ + public static double rand(double min, double max) { + if (min == max) { + return max; + } + return min + random.nextDouble() * (max-min); + } + + /** + * Get a random boolean + * + * @return random boolean + */ + public static boolean rand() { + return random.nextBoolean(); + } + + /** + * Get player's operating system/device name from login chain data. + * NOTICE: It's possible to spoof this. + * + * @param player player + * @return operating system/device name + */ + public static String getOS(Player player) { + switch (player.getLoginChainData().getDeviceOS()) { + case 1: + return "Android"; + case 2: + return "iOS"; + case 3: + return "macOS"; + case 4: + return "Fire OS"; + case 5: + return "Gear VR"; + case 6: + return "HoloLens"; + case 7: + return "Windows 10"; + case 8: + return "Windows"; + case 9: + return "Dedicated"; + case 10: + return "tvOS"; + case 11: + return "PlayStation"; + case 12: + return "Switch"; + case 13: + return "Xbox"; + case 14: + return "Windows Phone"; + default: + return "Unknown"; + } + } + + public static JsonElement loadJsonResource(String file) { + try { + InputStream stream = Server.class.getClassLoader().getResourceAsStream(file); + if (stream == null) { + throw new AssertionError("Unable to load " + file); + } + + JsonElement element = JsonParser.parseReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); + stream.close(); + return element; + } catch (Exception e) { + throw new RuntimeException("Unable to load " + file, e); + } + } + + public static Tag loadTagResource(String file) { + try { + InputStream stream = Server.class.getClassLoader().getResourceAsStream(file); + if (stream == null) { + throw new AssertionError("Unable to load " + file); + } + + return NBTIO.readTag(new BufferedInputStream(new GZIPInputStream(stream)), ByteOrder.BIG_ENDIAN, false); + } catch (Exception e) { + throw new RuntimeException("Unable to load " + file, e); + } + } } diff --git a/src/main/java/cn/nukkit/utils/VarInt.java b/src/main/java/cn/nukkit/utils/VarInt.java index b820d21144c..a791838375f 100644 --- a/src/main/java/cn/nukkit/utils/VarInt.java +++ b/src/main/java/cn/nukkit/utils/VarInt.java @@ -1,23 +1,17 @@ package cn.nukkit.utils; -import cn.nukkit.api.API; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import static cn.nukkit.api.API.Definition.UNIVERSAL; -import static cn.nukkit.api.API.Usage.EXPERIMENTAL; - /** * Tool class for VarInt or VarLong operations. - *

    + * * Some code from http://wiki.vg/Protocol. * * @author MagicDroidX * @author lmlstarqaq */ -@API(usage = EXPERIMENTAL, definition = UNIVERSAL) public final class VarInt { private VarInt() { diff --git a/src/main/java/cn/nukkit/utils/Watchdog.java b/src/main/java/cn/nukkit/utils/Watchdog.java index 54ce13617e4..b42925c3abb 100644 --- a/src/main/java/cn/nukkit/utils/Watchdog.java +++ b/src/main/java/cn/nukkit/utils/Watchdog.java @@ -1,49 +1,59 @@ package cn.nukkit.utils; import cn.nukkit.Server; + import java.lang.management.ManagementFactory; import java.lang.management.MonitorInfo; import java.lang.management.ThreadInfo; +/** + * Watchdog monitors the server's main thread and kills the server if it gets frozen. + */ public class Watchdog extends Thread { private final Server server; - private final long time; - public boolean running; - private boolean responding = true; + /** + * Watchdog threshold + */ + public volatile long time; + /** + * Watchdog running + */ + public volatile boolean running; public Watchdog(Server server, long time) { this.server = server; this.time = time; this.running = true; this.setName("Watchdog"); + this.setDaemon(true); } + /** + * Disable Watchdog + */ public void kill() { - running = false; - synchronized (this) { - this.notifyAll(); - } + this.running = false; + this.interrupt(); } @Override public void run() { while (this.running) { - long current = server.getNextTick(); + long current = this.server.getNextTick(); if (current != 0) { long diff = System.currentTimeMillis() - current; - if (!responding && diff > time * 2) { - System.exit(1); // Kill the server if it gets stuck on shutdown - } - if (server.isRunning() && diff > time) { - if (responding) { + if (diff > this.time) { + if (this.server.isRunning()) { MainLogger logger = this.server.getLogger(); - logger.emergency("--------- Server stopped responding --------- (" + Math.round(diff / 1000d) + "s)"); - logger.emergency("Please report this to Nukkit:"); - logger.emergency(" - https://github.com/NukkitX/Nukkit/issues/new"); + long lastResponse = Math.round(diff / 1000d); + + logger.emergency("--------- Server stopped responding ---------"); + logger.emergency("Last response " + lastResponse + " seconds ago"); logger.emergency("---------------- Main thread ----------------"); - dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(this.server.getPrimaryThread().getId(), Integer.MAX_VALUE), logger); + ThreadInfo mainThread = ManagementFactory.getThreadMXBean().getThreadInfo(this.server.getPrimaryThread().getId(), Integer.MAX_VALUE); + dumpThread(mainThread, logger); logger.emergency("---------------- All threads ----------------"); ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); @@ -52,25 +62,41 @@ public void run() { dumpThread(threads[i], logger); } logger.emergency("---------------------------------------------"); - responding = false; - this.server.forceShutdown(); + + if ("TIMED_WAITING".equals(mainThread.getThreadState().toString())) { + logger.warning("Make sure your plugins are not calling sleep() on main thread and that your terminal doesn't suspend server process when not focused"); + } + + this.server.forceShutdown("§cServer stopped responding"); + } else if (diff > time << 1) { + System.out.println("\nTook too long to stop, server was killed forcefully!\n"); + System.exit(1); + return; } - } else { - responding = true; } } try { - synchronized (this) { - this.wait(Math.max(time / 4, 1000)); + Thread.sleep(Math.max(this.time >> 2, 1000)); + } catch (InterruptedException ignore) { + if (this.running) { + this.running = false; + this.server.getLogger().emergency("The Watchdog thread has been interrupted and is no longer monitoring the server state"); } - } catch (InterruptedException ignore) {} + return; + } } } + /** + * Dump thread stack trace + * + * @param thread thread to dump + * @param logger logger + */ private static void dumpThread(ThreadInfo thread, Logger logger) { - logger.emergency("Current Thread: " + thread.getThreadName()); + logger.emergency("Thread: " + thread.getThreadName()); logger.emergency("\tPID: " + thread.getThreadId() + " | Suspended: " + thread.isSuspended() + " | Native: " + thread.isInNative() + " | State: " + thread.getThreadState()); - // Monitors + if (thread.getLockedMonitors().length != 0) { logger.emergency("\tThread is waiting on monitor(s):"); for (MonitorInfo monitor : thread.getLockedMonitors()) { diff --git a/src/main/java/cn/nukkit/utils/Zlib.java b/src/main/java/cn/nukkit/utils/Zlib.java index c4ca79a275e..81ad00c778f 100644 --- a/src/main/java/cn/nukkit/utils/Zlib.java +++ b/src/main/java/cn/nukkit/utils/Zlib.java @@ -1,42 +1,13 @@ package cn.nukkit.utils; +import cn.nukkit.Server; + import java.io.IOException; import java.util.zip.Deflater; - public abstract class Zlib { - private static ZlibProvider[] providers; - private static ZlibProvider provider; - - static { - providers = new ZlibProvider[3]; - providers[2] = new ZlibThreadLocal(); - provider = providers[2]; - } - - public static void setProvider(int providerIndex) { - MainLogger.getLogger().info("Selected Zlib Provider: " + providerIndex + " (" + provider.getClass().getCanonicalName() + ")"); - switch (providerIndex) { - case 0: - if (providers[providerIndex] == null) - providers[providerIndex] = new ZlibOriginal(); - break; - case 1: - if (providers[providerIndex] == null) - providers[providerIndex] = new ZlibSingleThreadLowMem(); - break; - case 2: - if (providers[providerIndex] == null) - providers[providerIndex] = new ZlibThreadLocal(); - break; - default: - throw new UnsupportedOperationException("Invalid provider: " + providerIndex); - } - if (providerIndex != 2) { - MainLogger.getLogger().warning(" - This Zlib will negatively affect performance"); - } - provider = providers[providerIndex]; - } + + private static final ZlibProvider provider = new ZlibThreadLocal(); public static byte[] deflate(byte[] data) throws Exception { return deflate(data, Deflater.DEFAULT_COMPRESSION); @@ -46,10 +17,22 @@ public static byte[] deflate(byte[] data, int level) throws Exception { return provider.deflate(data, level); } + public static byte[] deflatePre16Packet(byte[] data, int level) throws Exception { + return provider.deflate(data, data.length < Server.getInstance().networkCompressionThreshold ? 0 : level); + } + public static byte[] deflate(byte[][] data, int level) throws Exception { return provider.deflate(data, level); } + public static byte[] deflateRaw(byte[] data, int level) throws Exception { + return provider.deflateRaw(data, level); + } + + public static byte[] deflateRaw(byte[][] data, int level) throws Exception { + return provider.deflateRaw(data, level); + } + public static byte[] inflate(byte[] data) throws IOException { return inflate(data, -1); } @@ -57,4 +40,8 @@ public static byte[] inflate(byte[] data) throws IOException { public static byte[] inflate(byte[] data, int maxSize) throws IOException { return provider.inflate(data, maxSize); } + + public static byte[] inflateRaw(byte[] data, int maxSize) throws IOException { + return provider.inflateRaw(data, maxSize); + } } diff --git a/src/main/java/cn/nukkit/utils/ZlibOriginal.java b/src/main/java/cn/nukkit/utils/ZlibOriginal.java deleted file mode 100644 index ae3ebeb41a8..00000000000 --- a/src/main/java/cn/nukkit/utils/ZlibOriginal.java +++ /dev/null @@ -1,80 +0,0 @@ -package cn.nukkit.utils; - -import cn.nukkit.nbt.stream.FastByteArrayOutputStream; - -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Deflater; -import java.util.zip.Inflater; - -public class ZlibOriginal implements ZlibProvider { - - @Override - public byte[] deflate(byte[][] datas, int level) throws IOException { - Deflater deflater = new Deflater(level); - byte[] buffer = new byte[1024]; - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - try { - for (byte[] data : datas) { - deflater.setInput(data); - while (!deflater.needsInput()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - } - deflater.finish(); - while (!deflater.finished()) { - int i = deflater.deflate(buffer); - bos.write(buffer, 0, i); - } - } finally { - deflater.end(); - } - return bos.toByteArray(); - } - - @Override - public byte[] deflate(byte[] data, int level) throws IOException { - Deflater deflater = new Deflater(level); - deflater.setInput(data); - deflater.finish(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - byte[] buf = new byte[1024]; - try { - while (!deflater.finished()) { - int i = deflater.deflate(buf); - bos.write(buf, 0, i); - } - } finally { - deflater.end(); - } - return bos.toByteArray(); - } - - @Override - public byte[] inflate(byte[] data, int maxSize) throws IOException { - Inflater inflater = new Inflater(); - inflater.setInput(data); - inflater.finished(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - - byte[] buffer = new byte[1024]; - try { - int length = 0; - while (!inflater.finished()) { - int i = inflater.inflate(buffer); - length += i; - if (maxSize > 0 && length >= maxSize) { - throw new IOException("Inflated data exceeds maximum size"); - } - bos.write(buffer, 0, i); - } - return bos.toByteArray(); - } catch (DataFormatException e) { - throw new IOException("Unable to inflate zlib stream", e); - } - } -} diff --git a/src/main/java/cn/nukkit/utils/ZlibProvider.java b/src/main/java/cn/nukkit/utils/ZlibProvider.java index 936d8bbbbf6..1c4dad79026 100644 --- a/src/main/java/cn/nukkit/utils/ZlibProvider.java +++ b/src/main/java/cn/nukkit/utils/ZlibProvider.java @@ -3,13 +3,21 @@ import java.io.IOException; /** + * ZlibProvider * * @author ScraMTeam */ interface ZlibProvider { + byte[] deflate(byte[][] data, int level) throws IOException; byte[] deflate(byte[] data, int level) throws IOException; + byte[] deflateRaw(byte[][] data, int level) throws IOException; + + byte[] deflateRaw(byte[] data, int level) throws IOException; + byte[] inflate(byte[] data, int maxSize) throws IOException; + + byte[] inflateRaw(byte[] data, int maxSize) throws IOException; } diff --git a/src/main/java/cn/nukkit/utils/ZlibSingleThreadLowMem.java b/src/main/java/cn/nukkit/utils/ZlibSingleThreadLowMem.java deleted file mode 100644 index 030646d0c7d..00000000000 --- a/src/main/java/cn/nukkit/utils/ZlibSingleThreadLowMem.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.nukkit.utils; - -import cn.nukkit.nbt.stream.FastByteArrayOutputStream; - -import java.io.IOException; -import java.util.zip.DataFormatException; -import java.util.zip.Deflater; -import java.util.zip.Inflater; - -public class ZlibSingleThreadLowMem implements ZlibProvider { - private static final int BUFFER_SIZE = 8192; - private static final Deflater DEFLATER = new Deflater(Deflater.BEST_COMPRESSION); - private static final Inflater INFLATER = new Inflater(); - private static final byte[] BUFFER = new byte[BUFFER_SIZE]; - - @Override - public synchronized byte[] deflate(byte[][] datas, int level) throws IOException { - DEFLATER.reset(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - for (byte[] data : datas) { - DEFLATER.setInput(data); - while (!DEFLATER.needsInput()) { - int i = DEFLATER.deflate(BUFFER); - bos.write(BUFFER, 0, i); - } - } - DEFLATER.finish(); - while (!DEFLATER.finished()) { - int i = DEFLATER.deflate(BUFFER); - bos.write(BUFFER, 0, i); - } - //Deflater::end is called the time when the process exits. - return bos.toByteArray(); - } - - @Override - public synchronized byte[] deflate(byte[] data, int level) throws IOException { - DEFLATER.reset(); - DEFLATER.setInput(data); - DEFLATER.finish(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - try { - while (!DEFLATER.finished()) { - int i = DEFLATER.deflate(BUFFER); - bos.write(BUFFER, 0, i); - } - } finally { - //deflater.end(); - } - return bos.toByteArray(); - } - - @Override - public synchronized byte[] inflate(byte[] data, int maxSize) throws IOException { - INFLATER.reset(); - INFLATER.setInput(data); - INFLATER.finished(); - FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); - bos.reset(); - try { - int length = 0; - while (!INFLATER.finished()) { - int i = INFLATER.inflate(BUFFER); - length += i; - if (maxSize > 0 && length >= maxSize) { - throw new IOException("Inflated data exceeds maximum size"); - } - bos.write(BUFFER, 0, i); - } - return bos.toByteArray(); - } catch (DataFormatException e) { - throw new IOException("Unable to inflate zlib stream", e); - } - } -} diff --git a/src/main/java/cn/nukkit/utils/ZlibThreadLocal.java b/src/main/java/cn/nukkit/utils/ZlibThreadLocal.java index 5be7557bfa2..33b5807f90a 100644 --- a/src/main/java/cn/nukkit/utils/ZlibThreadLocal.java +++ b/src/main/java/cn/nukkit/utils/ZlibThreadLocal.java @@ -1,15 +1,20 @@ package cn.nukkit.utils; +import cn.nukkit.Server; import cn.nukkit.nbt.stream.FastByteArrayOutputStream; + import java.io.IOException; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; public final class ZlibThreadLocal implements ZlibProvider { + private static final ThreadLocal INFLATER = ThreadLocal.withInitial(Inflater::new); private static final ThreadLocal DEFLATER = ThreadLocal.withInitial(Deflater::new); - private static final ThreadLocal BUFFER = ThreadLocal.withInitial(() -> new byte[8192]); + private static final ThreadLocal INFLATER_RAW = ThreadLocal.withInitial(() -> new Inflater(true)); + private static final ThreadLocal DEFLATER_RAW = ThreadLocal.withInitial(() -> new Deflater(7, true)); + private static final ThreadLocal BUFFER = ThreadLocal.withInitial(() -> new byte[32768]); @Override public byte[] deflate(byte[][] datas, int level) throws IOException { @@ -54,6 +59,49 @@ public byte[] deflate(byte[] data, int level) throws IOException { return bos.toByteArray(); } + @Override + public byte[] deflateRaw(byte[][] datas, int level) throws IOException { + Deflater deflater = DEFLATER_RAW.get(); + deflater.reset(); + deflater.setLevel(level); + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + byte[] buffer = BUFFER.get(); + + for (byte[] data : datas) { + deflater.setInput(data); + while (!deflater.needsInput()) { + int i = deflater.deflate(buffer); + bos.write(buffer, 0, i); + } + } + deflater.finish(); + while (!deflater.finished()) { + int i = deflater.deflate(buffer); + bos.write(buffer, 0, i); + } + //Deflater::end is called the time when the process exits. + return bos.toByteArray(); + } + + @Override + public byte[] deflateRaw(byte[] data, int level) throws IOException { + Deflater deflater = DEFLATER_RAW.get(); + deflater.reset(); + deflater.setLevel(data.length < Server.getInstance().networkCompressionThreshold ? 0 : level); + deflater.setInput(data); + deflater.finish(); + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + byte[] buffer = BUFFER.get(); + while (!deflater.finished()) { + int i = deflater.deflate(buffer); + bos.write(buffer, 0, i); + } + //Deflater::end is called the time when the process exits. + return bos.toByteArray(); + } + @Override public byte[] inflate(byte[] data, int maxSize) throws IOException { Inflater inflater = INFLATER.get(); @@ -76,7 +124,36 @@ public byte[] inflate(byte[] data, int maxSize) throws IOException { } return bos.toByteArray(); } catch (DataFormatException e) { - throw new IOException("Unable to inflate zlib stream", e); + throw new IOException("Unable to inflate Zlib stream", e); + } + } + + @Override + public byte[] inflateRaw(byte[] data, int maxSize) throws IOException { + Inflater inflater = INFLATER_RAW.get(); + inflater.reset(); + inflater.setInput(data); + inflater.finished(); + FastByteArrayOutputStream bos = ThreadCache.fbaos.get(); + bos.reset(); + + byte[] buffer = BUFFER.get(); + try { + int length = 0; + while (!inflater.finished()) { + int i = inflater.inflate(buffer); + if (i == 0) { + throw new IOException("Could not decompress data"); + } + length += i; + if (maxSize > 0 && length >= maxSize) { + throw new IOException("Inflated data exceeds maximum size"); + } + bos.write(buffer, 0, i); + } + return bos.toByteArray(); + } catch (DataFormatException e) { + throw new IOException("Unable to inflate Zlib stream", e); } } } diff --git a/src/main/java/cn/nukkit/utils/bugreport/BugReportGenerator.java b/src/main/java/cn/nukkit/utils/bugreport/BugReportGenerator.java index 8753c6a6059..d1d459a8da2 100644 --- a/src/main/java/cn/nukkit/utils/bugreport/BugReportGenerator.java +++ b/src/main/java/cn/nukkit/utils/bugreport/BugReportGenerator.java @@ -18,17 +18,23 @@ import java.text.SimpleDateFormat; import java.util.Date; -/** - * Project nukkit - */ public class BugReportGenerator extends Thread { - private Throwable throwable; + private final Throwable throwable; BugReportGenerator(Throwable throwable) { this.throwable = throwable; } + //Code section from SOF + private static String getCount(long bytes) { + int unit = 1000; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = ("kMGTPE").charAt(exp - 1) + (""); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + @Override public void run() { BaseLang baseLang = Server.getInstance().getLanguage(); @@ -58,12 +64,9 @@ private String generate() throws IOException { for (Path root : FileSystems.getDefault().getRootDirectories()) { try { FileStore store = Files.getFileStore(root); - model.append("Disk ").append(diskNum++).append(":(avail=").append(getCount(store.getUsableSpace(), true)) - .append(", total=").append(getCount(store.getTotalSpace(), true)) - .append(") "); + model.append("Disk ").append(diskNum++).append(":(avail=").append(getCount(store.getUsableSpace())).append(", total=").append(getCount(store.getTotalSpace())).append(") "); totalDiskSpace += store.getTotalSpace(); - } catch (IOException e) { - // + } catch (IOException ignore) { } } @@ -76,7 +79,6 @@ private String generate() throws IOException { pluginError = !throwable.getStackTrace()[0].getClassName().startsWith("cn.nukkit"); } - File mdReport = new File(reports, date + "_" + throwable.getClass().getSimpleName() + ".md"); mdReport.createNewFile(); String content = Utils.readFile(this.getClass().getClassLoader().getResourceAsStream("report_template.md")); @@ -86,8 +88,8 @@ private String generate() throws IOException { content = content.replace("${NUKKIT_VERSION}", Nukkit.VERSION); content = content.replace("${JAVA_VERSION}", System.getProperty("java.vm.name") + " (" + System.getProperty("java.runtime.version") + ")"); content = content.replace("${HOSTOS}", osMXBean.getName() + "-" + osMXBean.getArch() + " [" + osMXBean.getVersion() + "]"); - content = content.replace("${MEMORY}", getCount(osMXBean.getTotalPhysicalMemorySize(), true)); - content = content.replace("${STORAGE_SIZE}", getCount(totalDiskSpace, true)); + content = content.replace("${MEMORY}", getCount(osMXBean.getTotalPhysicalMemorySize())); + content = content.replace("${STORAGE_SIZE}", getCount(totalDiskSpace)); content = content.replace("${CPU_TYPE}", cpuType == null ? "UNKNOWN" : cpuType); content = content.replace("${AVAILABLE_CORE}", String.valueOf(osMXBean.getAvailableProcessors())); content = content.replace("${STACKTRACE}", stringWriter.toString()); @@ -98,14 +100,4 @@ private String generate() throws IOException { return mdReport.getAbsolutePath(); } - - //Code section from SOF - public static String getCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - } diff --git a/src/main/java/cn/nukkit/utils/bugreport/ExceptionHandler.java b/src/main/java/cn/nukkit/utils/bugreport/ExceptionHandler.java index 1493dd189a3..b3ba667f076 100644 --- a/src/main/java/cn/nukkit/utils/bugreport/ExceptionHandler.java +++ b/src/main/java/cn/nukkit/utils/bugreport/ExceptionHandler.java @@ -1,8 +1,5 @@ package cn.nukkit.utils.bugreport; -/** - * Project nukkit - */ public class ExceptionHandler implements Thread.UncaughtExceptionHandler { public static void registerExceptionHandler() { @@ -23,5 +20,4 @@ public void handle(Thread thread, Throwable throwable) { // Fail Safe } } - } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/functional/ToIntTriFunctionTwoInts.java b/src/main/java/cn/nukkit/utils/functional/ToIntTriFunctionTwoInts.java new file mode 100644 index 00000000000..9d973b55ed1 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/functional/ToIntTriFunctionTwoInts.java @@ -0,0 +1,46 @@ +package cn.nukkit.utils.functional; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.IntUnaryOperator; + +/** + * Represents a function that accepts three arguments where the last two are int and produces an int result. + * This is the three-arity specialization of {@link Function}. + * + *

    This is a functional interface + * whose functional method is {@link #apply(Object, int, int)}. + * + * @param the type of the first argument to the function + * + * @see Function + */ +@FunctionalInterface +public interface ToIntTriFunctionTwoInts { + + /** + * Applies this function to the given arguments. + * + * @param f the first function argument + * @param s the second function argument + * @param t the third function argument + * @return the function result + */ + int apply(F f, int s, int t); + + /** + * Returns a composed function that first applies this function to + * its input, and then applies the {@code after} function to the result. + * If evaluation of either function throws an exception, it is relayed to + * the caller of the composed function. + * + * @param after the function to apply after this function is applied + * @return a composed function that first applies this function and then + * applies the {@code after} function + * @throws NullPointerException if after is null + */ + default ToIntTriFunctionTwoInts andThen(IntUnaryOperator after) { + Objects.requireNonNull(after); + return (F f, int s, int t) -> after.applyAsInt(apply(f, s, t)); + } +} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/utils/functional/ToLongTriFunctionOneIntOneLong.java b/src/main/java/cn/nukkit/utils/functional/ToLongTriFunctionOneIntOneLong.java new file mode 100644 index 00000000000..b7cf07a6424 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/functional/ToLongTriFunctionOneIntOneLong.java @@ -0,0 +1,46 @@ +package cn.nukkit.utils.functional; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.LongUnaryOperator; + +/** + * Represents a function that accepts three arguments mixing F, int and long and produces a long result. + * This is the three-arity specialization of {@link Function}. + * + *

    This is a functional interface + * whose functional method is {@link #apply(Object, int, long)}. + * + * @param the type of the first argument to the function + * + * @see Function + */ +@FunctionalInterface +public interface ToLongTriFunctionOneIntOneLong { + + /** + * Applies this function to the given arguments. + * + * @param f the first function argument + * @param s the second function argument + * @param t the third function argument + * @return the function result + */ + long apply(F f, int s, long t); + + /** + * Returns a composed function that first applies this function to + * its input, and then applies the {@code after} function to the result. + * If evaluation of either function throws an exception, it is relayed to + * the caller of the composed function. + * + * @param after the function to apply after this function is applied + * @return a composed function that first applies this function and then + * applies the {@code after} function + * @throws NullPointerException if after is null + */ + default ToLongTriFunctionOneIntOneLong andThen(LongUnaryOperator after) { + Objects.requireNonNull(after); + return (F f, int s, long t) -> after.applyAsLong(apply(f, s, t)); + } +} diff --git a/src/main/java/cn/nukkit/utils/material/BlockType.java b/src/main/java/cn/nukkit/utils/material/BlockType.java new file mode 100644 index 00000000000..239061fe383 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/BlockType.java @@ -0,0 +1,4 @@ +package cn.nukkit.utils.material; + +public interface BlockType extends MaterialType { +} diff --git a/src/main/java/cn/nukkit/utils/material/ItemType.java b/src/main/java/cn/nukkit/utils/material/ItemType.java new file mode 100644 index 00000000000..1f64047484a --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/ItemType.java @@ -0,0 +1,4 @@ +package cn.nukkit.utils.material; + +public interface ItemType extends MaterialType { +} diff --git a/src/main/java/cn/nukkit/utils/material/MaterialType.java b/src/main/java/cn/nukkit/utils/material/MaterialType.java new file mode 100644 index 00000000000..e5b4a460d5c --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/MaterialType.java @@ -0,0 +1,7 @@ +package cn.nukkit.utils.material; + +public interface MaterialType { + int getLegacyId(); + + String getIdentifier(); +} diff --git a/src/main/java/cn/nukkit/utils/material/TypesGeneratorHelper.java b/src/main/java/cn/nukkit/utils/material/TypesGeneratorHelper.java new file mode 100644 index 00000000000..097bb0473fa --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/TypesGeneratorHelper.java @@ -0,0 +1,123 @@ +package cn.nukkit.utils.material; + +import cn.nukkit.block.BlockID; +import cn.nukkit.item.ItemID; +import cn.nukkit.item.RuntimeItems; +import cn.nukkit.utils.material.tags.MaterialTags; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.log4j.Log4j2; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.*; + +@Log4j2 +public class TypesGeneratorHelper { + + private static final String ITEMS_FILE = "item_types.txt"; + private static final String BLOCKS_FILE = "block_types.txt"; + private static final String TAGS_FILE = "material_tags.txt"; + + public static void main(String[] args) throws Exception { + RuntimeItems.init(); + + generateItems(); + generateBlocks(); + generateTags(); + } + + private static void generateItems() throws Exception { + Map> types = new TreeMap<>(); + + for (Field field : ItemID.class.getDeclaredFields()) { + field.setAccessible(true); + + String name = field.getName(); + int legacyId = field.getInt(null); + + types.computeIfAbsent(field.getInt(null), id -> new ArrayList<>()) + .add("public static final ItemType " + name + " = register(\"" + getIdentifierFromId(legacyId) + "\", ItemID." + name + ");"); + } + + log.info("Saving {} item types to {}", types.size(), ITEMS_FILE); + saveFile(toString(types), ITEMS_FILE); + } + + private static void generateBlocks() throws Exception { + Map> types = new TreeMap<>(); + + for (Field field : BlockID.class.getDeclaredFields()) { + field.setAccessible(true); + + String name = field.getName(); + int legacyId = field.getInt(null); + + + if (legacyId > 255) { + legacyId = 255 - legacyId; + } + + types.computeIfAbsent(field.getInt(null), id -> new ArrayList<>()) + .add("public static final BlockType " + name + " = register(\"" + getIdentifierFromId(legacyId) + "\", BlockID." + name + ");"); + } + + log.info("Saving {} block types to {}", types.size(), BLOCKS_FILE); + saveFile(toString(types), BLOCKS_FILE); + } + + private static void generateTags() throws Exception { + StringJoiner joiner = new StringJoiner("\n"); + + try (InputStream stream = MaterialTags.class.getClassLoader().getResourceAsStream("item_tags.json")) { + if (stream == null) { + throw new IllegalStateException("Resource file item_tags.json is missing"); + } + try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) { + JsonObject json = JsonParser.parseReader(reader).getAsJsonObject(); + for (String tag : json.keySet()) { + String name = tag.split(":")[1].toUpperCase(); + joiner.add("public static final MaterialTag " + name + " = register(\"" + tag + "\", new LazilyInitializedMaterialTag(\"" + tag + "\"));"); + } + } + } + + log.info("Saving tags to {}", TAGS_FILE); + saveFile(joiner.toString(), TAGS_FILE); + } + + private static String toString(Map> types) { + StringJoiner joiner = new StringJoiner("\n"); + for (List value : types.values()) { + for (String type : value) { + joiner.add(type); + } + } + return joiner.toString(); + } + + private static void saveFile(String buffer, String path) { + try { + Files.write(Paths.get(path), buffer.getBytes(StandardCharsets.UTF_8), + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } catch (IOException e) { + log.error("Unable to save file " + path, e); + } + } + + private static String getIdentifierFromId(int legacyId) { + // yes, we still use legacy identifiers internally + String identifier = RuntimeItems.getLegacyStringFromLegacyId(legacyId); + if (legacyId == 0) { + identifier = "minecraft:air"; + } + return identifier; + } + +} diff --git a/src/main/java/cn/nukkit/utils/material/tags/LazilyInitializedMaterialTag.java b/src/main/java/cn/nukkit/utils/material/tags/LazilyInitializedMaterialTag.java new file mode 100644 index 00000000000..ceef48f52c4 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/tags/LazilyInitializedMaterialTag.java @@ -0,0 +1,48 @@ +package cn.nukkit.utils.material.tags; + +import cn.nukkit.block.BlockTypes; +import cn.nukkit.item.ItemTypes; +import cn.nukkit.utils.material.MaterialType; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Collections; +import java.util.Set; + +public class LazilyInitializedMaterialTag implements MaterialTag { + + private final String tag; + private Set materials; + + public LazilyInitializedMaterialTag(String tag) { + this.tag = tag; + } + + private void init() { + Set definitions = MaterialTags.getVanillaDefinitions(tag); + if (definitions == null) { + throw new IllegalStateException("Unknown vanilla tag " + this.tag); + } + + Set materials = new ObjectOpenHashSet<>(); + for (String definition : definitions) { + MaterialType material = BlockTypes.get(definition); + if (material == null) { + material = ItemTypes.get(definition); + } + + if (material != null) { + materials.add(material); + } + } + + this.materials = Collections.unmodifiableSet(materials); + } + + @Override + public Set getMaterials() { + if (this.materials == null) { + this.init(); + } + return this.materials; + } +} diff --git a/src/main/java/cn/nukkit/utils/material/tags/MaterialTag.java b/src/main/java/cn/nukkit/utils/material/tags/MaterialTag.java new file mode 100644 index 00000000000..f7699797de6 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/tags/MaterialTag.java @@ -0,0 +1,18 @@ +package cn.nukkit.utils.material.tags; + +import cn.nukkit.utils.material.MaterialType; + +import java.util.Set; + +public interface MaterialTag { + + default boolean has(MaterialType type) { + return this.getMaterials().contains(type); + } + + Set getMaterials(); + + static MaterialTag of(MaterialType... materials) { + return new SimpleMaterialTag(materials); + } +} diff --git a/src/main/java/cn/nukkit/utils/material/tags/MaterialTags.java b/src/main/java/cn/nukkit/utils/material/tags/MaterialTags.java new file mode 100644 index 00000000000..368f345dcc4 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/tags/MaterialTags.java @@ -0,0 +1,111 @@ +package cn.nukkit.utils.material.tags; + +import cn.nukkit.item.RuntimeItems; +import cn.nukkit.utils.Utils; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import lombok.extern.log4j.Log4j2; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@Log4j2 +public class MaterialTags { + + private static final Map tags = new HashMap<>(); + private static final Map> vanillaTagDefinitions = new HashMap<>(); + + static { + JsonObject json = Utils.loadJsonResource("item_tags.json").getAsJsonObject(); + for (Map.Entry entry : json.entrySet()) { + Set materials = vanillaTagDefinitions.computeIfAbsent(entry.getKey(), key -> new ObjectOpenHashSet<>()); + for (JsonElement element : entry.getValue().getAsJsonArray()) { + String identifier = element.getAsString(); + // We use here legacy name as all items in Nukkit still use those, + // I guess it is also better in terms of updating stuff + String legacyName = RuntimeItems.getLegacyStringFromNew(identifier); + if (legacyName == null) { + log.warn("No legacy name for " + identifier); + } else { + materials.add(legacyName); + } + } + } + } + + public static final MaterialTag ARROW = register("minecraft:arrow", new LazilyInitializedMaterialTag("minecraft:arrow")); + public static final MaterialTag BANNER = register("minecraft:banner", new LazilyInitializedMaterialTag("minecraft:banner")); + public static final MaterialTag BOAT = register("minecraft:boat", new LazilyInitializedMaterialTag("minecraft:boat")); + public static final MaterialTag BOATS = register("minecraft:boats", new LazilyInitializedMaterialTag("minecraft:boats")); + public static final MaterialTag BOOKSHELF_BOOKS = register("minecraft:bookshelf_books", new LazilyInitializedMaterialTag("minecraft:bookshelf_books")); + public static final MaterialTag CHAINMAIL_TIER = register("minecraft:chainmail_tier", new LazilyInitializedMaterialTag("minecraft:chainmail_tier")); + public static final MaterialTag COALS = register("minecraft:coals", new LazilyInitializedMaterialTag("minecraft:coals")); + public static final MaterialTag CRIMSON_STEMS = register("minecraft:crimson_stems", new LazilyInitializedMaterialTag("minecraft:crimson_stems")); + public static final MaterialTag DECORATED_POT_SHERDS = register("minecraft:decorated_pot_sherds", new LazilyInitializedMaterialTag("minecraft:decorated_pot_sherds")); + public static final MaterialTag DIAMOND_TIER = register("minecraft:diamond_tier", new LazilyInitializedMaterialTag("minecraft:diamond_tier")); + public static final MaterialTag DIGGER = register("minecraft:digger", new LazilyInitializedMaterialTag("minecraft:digger")); + public static final MaterialTag DOOR = register("minecraft:door", new LazilyInitializedMaterialTag("minecraft:door")); + public static final MaterialTag GOLDEN_TIER = register("minecraft:golden_tier", new LazilyInitializedMaterialTag("minecraft:golden_tier")); + public static final MaterialTag HANGING_ACTOR = register("minecraft:hanging_actor", new LazilyInitializedMaterialTag("minecraft:hanging_actor")); + public static final MaterialTag HANGING_SIGN = register("minecraft:hanging_sign", new LazilyInitializedMaterialTag("minecraft:hanging_sign")); + public static final MaterialTag HORSE_ARMOR = register("minecraft:horse_armor", new LazilyInitializedMaterialTag("minecraft:horse_armor")); + public static final MaterialTag IRON_TIER = register("minecraft:iron_tier", new LazilyInitializedMaterialTag("minecraft:iron_tier")); + public static final MaterialTag IS_ARMOR = register("minecraft:is_armor", new LazilyInitializedMaterialTag("minecraft:is_armor")); + public static final MaterialTag IS_AXE = register("minecraft:is_axe", new LazilyInitializedMaterialTag("minecraft:is_axe")); + public static final MaterialTag IS_COOKED = register("minecraft:is_cooked", new LazilyInitializedMaterialTag("minecraft:is_cooked")); + public static final MaterialTag IS_FISH = register("minecraft:is_fish", new LazilyInitializedMaterialTag("minecraft:is_fish")); + public static final MaterialTag IS_FOOD = register("minecraft:is_food", new LazilyInitializedMaterialTag("minecraft:is_food")); + public static final MaterialTag IS_HOE = register("minecraft:is_hoe", new LazilyInitializedMaterialTag("minecraft:is_hoe")); + public static final MaterialTag IS_MEAT = register("minecraft:is_meat", new LazilyInitializedMaterialTag("minecraft:is_meat")); + public static final MaterialTag IS_MINECART = register("minecraft:is_minecart", new LazilyInitializedMaterialTag("minecraft:is_minecart")); + public static final MaterialTag IS_PICKAXE = register("minecraft:is_pickaxe", new LazilyInitializedMaterialTag("minecraft:is_pickaxe")); + public static final MaterialTag IS_SHOVEL = register("minecraft:is_shovel", new LazilyInitializedMaterialTag("minecraft:is_shovel")); + public static final MaterialTag IS_SWORD = register("minecraft:is_sword", new LazilyInitializedMaterialTag("minecraft:is_sword")); + public static final MaterialTag IS_TOOL = register("minecraft:is_tool", new LazilyInitializedMaterialTag("minecraft:is_tool")); + public static final MaterialTag IS_TRIDENT = register("minecraft:is_trident", new LazilyInitializedMaterialTag("minecraft:is_trident")); + public static final MaterialTag LEATHER_TIER = register("minecraft:leather_tier", new LazilyInitializedMaterialTag("minecraft:leather_tier")); + public static final MaterialTag LECTERN_BOOKS = register("minecraft:lectern_books", new LazilyInitializedMaterialTag("minecraft:lectern_books")); + public static final MaterialTag LOGS = register("minecraft:logs", new LazilyInitializedMaterialTag("minecraft:logs")); + public static final MaterialTag LOGS_THAT_BURN = register("minecraft:logs_that_burn", new LazilyInitializedMaterialTag("minecraft:logs_that_burn")); + public static final MaterialTag MANGROVE_LOGS = register("minecraft:mangrove_logs", new LazilyInitializedMaterialTag("minecraft:mangrove_logs")); + public static final MaterialTag MUSIC_DISC = register("minecraft:music_disc", new LazilyInitializedMaterialTag("minecraft:music_disc")); + public static final MaterialTag NETHERITE_TIER = register("minecraft:netherite_tier", new LazilyInitializedMaterialTag("minecraft:netherite_tier")); + public static final MaterialTag PLANKS = register("minecraft:planks", new LazilyInitializedMaterialTag("minecraft:planks")); + public static final MaterialTag SAND = register("minecraft:sand", new LazilyInitializedMaterialTag("minecraft:sand")); + public static final MaterialTag SIGN = register("minecraft:sign", new LazilyInitializedMaterialTag("minecraft:sign")); + public static final MaterialTag SOUL_FIRE_BASE_BLOCKS = register("minecraft:soul_fire_base_blocks", new LazilyInitializedMaterialTag("minecraft:soul_fire_base_blocks")); + public static final MaterialTag SPAWN_EGG = register("minecraft:spawn_egg", new LazilyInitializedMaterialTag("minecraft:spawn_egg")); + public static final MaterialTag STONE_BRICKS = register("minecraft:stone_bricks", new LazilyInitializedMaterialTag("minecraft:stone_bricks")); + public static final MaterialTag STONE_CRAFTING_MATERIALS = register("minecraft:stone_crafting_materials", new LazilyInitializedMaterialTag("minecraft:stone_crafting_materials")); + public static final MaterialTag STONE_TIER = register("minecraft:stone_tier", new LazilyInitializedMaterialTag("minecraft:stone_tier")); + public static final MaterialTag STONE_TOOL_MATERIALS = register("minecraft:stone_tool_materials", new LazilyInitializedMaterialTag("minecraft:stone_tool_materials")); + public static final MaterialTag TRANSFORM_MATERIALS = register("minecraft:transform_materials", new LazilyInitializedMaterialTag("minecraft:transform_materials")); + public static final MaterialTag TRANSFORM_TEMPLATES = register("minecraft:transform_templates", new LazilyInitializedMaterialTag("minecraft:transform_templates")); + public static final MaterialTag TRANSFORMABLE_ITEMS = register("minecraft:transformable_items", new LazilyInitializedMaterialTag("minecraft:transformable_items")); + public static final MaterialTag TRIM_MATERIALS = register("minecraft:trim_materials", new LazilyInitializedMaterialTag("minecraft:trim_materials")); + public static final MaterialTag TRIM_TEMPLATES = register("minecraft:trim_templates", new LazilyInitializedMaterialTag("minecraft:trim_templates")); + public static final MaterialTag TRIMMABLE_ARMORS = register("minecraft:trimmable_armors", new LazilyInitializedMaterialTag("minecraft:trimmable_armors")); + public static final MaterialTag VIBRATION_DAMPER = register("minecraft:vibration_damper", new LazilyInitializedMaterialTag("minecraft:vibration_damper")); + public static final MaterialTag WARPED_STEMS = register("minecraft:warped_stems", new LazilyInitializedMaterialTag("minecraft:warped_stems")); + public static final MaterialTag WOODEN_SLABS = register("minecraft:wooden_slabs", new LazilyInitializedMaterialTag("minecraft:wooden_slabs")); + public static final MaterialTag WOODEN_TIER = register("minecraft:wooden_tier", new LazilyInitializedMaterialTag("minecraft:wooden_tier")); + public static final MaterialTag WOOL = register("minecraft:wool", new LazilyInitializedMaterialTag("minecraft:wool")); + + public static MaterialTag register(String tagName, MaterialTag tag) { + if (tags.containsKey(tagName)) { + throw new IllegalArgumentException("Tag " + tagName + " is already registered"); + } + tags.put(tagName, tag); + return tag; + } + + public static MaterialTag get(String tag) { + return tags.get(tag); + } + + protected static Set getVanillaDefinitions(String tag) { + return vanillaTagDefinitions.get(tag); + } +} diff --git a/src/main/java/cn/nukkit/utils/material/tags/SimpleMaterialTag.java b/src/main/java/cn/nukkit/utils/material/tags/SimpleMaterialTag.java new file mode 100644 index 00000000000..192e5229d57 --- /dev/null +++ b/src/main/java/cn/nukkit/utils/material/tags/SimpleMaterialTag.java @@ -0,0 +1,27 @@ +package cn.nukkit.utils.material.tags; + +import cn.nukkit.utils.material.MaterialType; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +public class SimpleMaterialTag implements MaterialTag { + + private final Set materials; + + public SimpleMaterialTag(MaterialType... materials) { + this(Arrays.asList(materials)); + } + + public SimpleMaterialTag(Collection materials) { + this.materials = Collections.unmodifiableSet(new ObjectOpenHashSet<>(materials)); + } + + @Override + public Set getMaterials() { + return this.materials; + } +} diff --git a/src/main/java/co/aikar/timings/FullServerTickTiming.java b/src/main/java/co/aikar/timings/FullServerTickTiming.java deleted file mode 100644 index 66526bf8ec6..00000000000 --- a/src/main/java/co/aikar/timings/FullServerTickTiming.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import static co.aikar.timings.TimingIdentifier.DEFAULT_GROUP; -import static co.aikar.timings.TimingsManager.*; - -public class FullServerTickTiming extends Timing { - private static final TimingIdentifier IDENTIFIER = new TimingIdentifier(DEFAULT_GROUP.name, "Full Server Tick", null); - final TimingData minuteData; - double avgFreeMemory = -1D; - double avgUsedMemory = -1D; - - FullServerTickTiming() { - super(IDENTIFIER); - this.minuteData = new TimingData(this.id); - - TIMING_MAP.put(IDENTIFIER, this); - } - - @Override - public Timing startTiming() { - if (TimingsManager.needsFullReset) { - TimingsManager.resetTimings(); - } else if (TimingsManager.needsRecheckEnabled) { - TimingsManager.recheckEnabled(); - } - super.startTiming(); - return this; - } - - @Override - public void stopTiming() { - super.stopTiming(); - if (!this.enabled) { - return; - } - - if (TimingsHistory.timedTicks % 20 == 0) { - final Runtime runtime = Runtime.getRuntime(); - double usedMemory = runtime.totalMemory() - runtime.freeMemory(); - double freeMemory = runtime.maxMemory() - usedMemory; - - if (this.avgFreeMemory == -1) { - this.avgFreeMemory = freeMemory; - } else { - this.avgFreeMemory = (this.avgFreeMemory * (59 / 60D)) + (freeMemory * (1 / 60D)); - } - - if (this.avgUsedMemory == -1) { - this.avgUsedMemory = usedMemory; - } else { - this.avgUsedMemory = (this.avgUsedMemory * (59 / 60D)) + (usedMemory * (1 / 60D)); - } - } - - long start = System.nanoTime(); - TimingsManager.tick(); - long diff = System.nanoTime() - start; - - CURRENT = Timings.timingsTickTimer; - Timings.timingsTickTimer.addDiff(diff); - //addDiff for timingsTickTimer incremented this, bring it back down to 1 per tick. - this.record.curTickCount--; - this.minuteData.curTickTotal = this.record.curTickTotal; - this.minuteData.curTickCount = 1; - boolean violated = isViolated(); - this.minuteData.tick(violated); - Timings.timingsTickTimer.tick(violated); - tick(violated); - - if (TimingsHistory.timedTicks % 1200 == 0) { - MINUTE_REPORTS.add(new TimingsHistory.MinuteReport()); - TimingsHistory.resetTicks(false); - this.minuteData.reset(); - } - - if (TimingsHistory.timedTicks % Timings.getHistoryInterval() == 0) { - TimingsManager.HISTORY.add(new TimingsHistory()); - TimingsManager.resetTimings(); - } - } - - boolean isViolated() { - return this.record.curTickTotal > 50000000; - } -} diff --git a/src/main/java/co/aikar/timings/Timing.java b/src/main/java/co/aikar/timings/Timing.java deleted file mode 100644 index b9430d2d4ae..00000000000 --- a/src/main/java/co/aikar/timings/Timing.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import java.util.HashMap; -import java.util.Map; - -public class Timing implements AutoCloseable { - private static int idPool = 1; - final int id = idPool++; - - final String name; - private final boolean verbose; - - final Map children = new HashMap<>(); - private Timing parent; - - private final Timing groupTiming; - final TimingData record; - - private long start = 0; - private int timingDepth = 0; - private boolean added; - boolean timed; - boolean enabled; - - Timing(TimingIdentifier id) { - if (id.name.startsWith("##")) { - this.verbose = true; - this.name = id.name.substring(3); - } else { - this.name = id.name; - this.verbose = false; - } - - this.record = new TimingData(this.id); - this.groupTiming = id.groupTiming; - - TimingIdentifier.getGroup(id.group).timings.add(this); - this.checkEnabled(); - } - - final void checkEnabled() { - this.enabled = Timings.isTimingsEnabled() && (!this.verbose || Timings.isVerboseEnabled()); - } - - void tick(boolean violated) { - if (this.timingDepth != 0 || this.record.curTickCount == 0) { - this.timingDepth = 0; - this.start = 0; - return; - } - - this.record.tick(violated); - for (TimingData data : this.children.values()) { - data.tick(violated); - } - } - - public Timing startTiming() { - if (!this.enabled) { - return this; - } - - if (++this.timingDepth == 1) { - this.start = System.nanoTime(); - this.parent = TimingsManager.CURRENT; - TimingsManager.CURRENT = this; - } - - return this; - } - - public void stopTiming() { - if (!this.enabled) { - return; - } - - if (--this.timingDepth == 0 && this.start != 0) { - this.addDiff(System.nanoTime() - this.start); - this.start = 0; - } - } - - public void abort() { - if (this.enabled && this.timingDepth > 0) { - this.start = 0; - } - } - - void addDiff(long diff) { - if (TimingsManager.CURRENT == this) { - TimingsManager.CURRENT = this.parent; - if (this.parent != null) { - if (!this.parent.children.containsKey(this.id)) - this.parent.children.put(this.id, new TimingData(this.id)); - this.parent.children.get(this.id).add(diff); - } - } - - this.record.add(diff); - if (!this.added) { - this.added = true; - this.timed = true; - TimingsManager.TIMINGS.add(this); - } - - if (this.groupTiming != null) { - this.groupTiming.addDiff(diff); - - if (!this.groupTiming.children.containsKey(this.id)) - this.groupTiming.children.put(this.id, new TimingData(this.id)); - this.groupTiming.children.get(this.id).add(diff); - } - } - - void reset(boolean full) { - this.record.reset(); - if (full) { - this.timed = false; - } - this.start = 0; - this.timingDepth = 0; - this.added = false; - this.children.clear(); - this.checkEnabled(); - } - - @Override - public boolean equals(Object o) { - return (o instanceof Timing && this == o); - } - - @Override - public int hashCode() { - return this.id; - } - - //For try-with-resources - @Override - public void close() { - this.stopTiming(); - } - - boolean isSpecial() { - return this == Timings.fullServerTickTimer || this == Timings.timingsTickTimer; - } -} diff --git a/src/main/java/co/aikar/timings/TimingData.java b/src/main/java/co/aikar/timings/TimingData.java deleted file mode 100644 index 8757d6990cf..00000000000 --- a/src/main/java/co/aikar/timings/TimingData.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import com.google.gson.JsonArray; - -import cn.nukkit.timings.JsonUtil; - -class TimingData { - private int id; - int count = 0; - private int lagCount = 0; - long totalTime = 0; - private long lagTotalTime = 0; - - int curTickCount = 0; - int curTickTotal = 0; - - TimingData(int id) { - this.id = id; - } - - TimingData(TimingData data) { - this.id = data.id; - this.count = data.count; - this.lagCount = data.lagCount; - this.totalTime = data.totalTime; - this.lagTotalTime = data.lagTotalTime; - } - - void add(long diff) { - ++this.curTickCount; - this.curTickTotal += diff; - } - - void tick(boolean violated) { - this.count += this.curTickCount; - this.totalTime += this.curTickTotal; - - if (violated) { - this.lagCount += this.curTickCount; - this.lagTotalTime += this.curTickTotal; - } - - this.curTickCount = 0; - this.curTickTotal = 0; - } - - void reset() { - this.count = 0; - this.lagCount = 0; - this.totalTime = 0; - this.lagTotalTime = 0; - this.curTickCount = 0; - this.curTickTotal = 0; - } - - protected TimingData clone() { - return new TimingData(this); - } - - JsonArray export() { - JsonArray json = JsonUtil.toArray(this.id, this.count, this.totalTime); - if (this.lagCount > 0) { - json.add(this.lagCount); - json.add(this.lagTotalTime); - } - return json; - } -} diff --git a/src/main/java/co/aikar/timings/TimingIdentifier.java b/src/main/java/co/aikar/timings/TimingIdentifier.java deleted file mode 100644 index 845f071a03d..00000000000 --- a/src/main/java/co/aikar/timings/TimingIdentifier.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import java.util.ArrayDeque; -import java.util.IdentityHashMap; -import java.util.Map; - -class TimingIdentifier { - static final Map GROUP_MAP = new IdentityHashMap<>(64); - static final TimingGroup DEFAULT_GROUP = getGroup("Nukkit"); - - final String group; - final String name; - final Timing groupTiming; - private final int hashCode; - - TimingIdentifier(String group, String name, Timing groupTiming) { - this.group = group != null ? group.intern() : DEFAULT_GROUP.name; - this.name = name.intern(); - this.groupTiming = groupTiming; - this.hashCode = (31 * this.group.hashCode()) + this.name.hashCode(); - } - - static TimingGroup getGroup(String name) { - if (name == null) { - return DEFAULT_GROUP; - } - - return GROUP_MAP.computeIfAbsent(name, k -> new TimingGroup(name)); - } - - @Override - @SuppressWarnings("all") - public boolean equals(Object o) { - if (o == null || !(o instanceof TimingIdentifier)) { - return false; - } - - TimingIdentifier that = (TimingIdentifier) o; - //Using intern() method on strings makes possible faster string comparison with == - return this.group == that.group && this.name == that.name; - } - - @Override - public int hashCode() { - return this.hashCode; - } - - static class TimingGroup { - private static int idPool = 1; - final int id = idPool++; - - final String name; - ArrayDeque timings = new ArrayDeque<>(64); - - TimingGroup(String name) { - this.name = name.intern(); - } - } -} diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java deleted file mode 100644 index 0dc60448421..00000000000 --- a/src/main/java/co/aikar/timings/Timings.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import cn.nukkit.Server; -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.command.Command; -import cn.nukkit.entity.Entity; -import cn.nukkit.event.Event; -import cn.nukkit.event.Listener; -import cn.nukkit.network.protocol.DataPacket; -import cn.nukkit.plugin.EventExecutor; -import cn.nukkit.plugin.MethodEventExecutor; -import cn.nukkit.plugin.Plugin; -import cn.nukkit.scheduler.PluginTask; -import cn.nukkit.scheduler.TaskHandler; - -import java.util.HashSet; -import java.util.Queue; -import java.util.Set; - -import static co.aikar.timings.TimingIdentifier.DEFAULT_GROUP; - -public final class Timings { - private static boolean timingsEnabled = false; - private static boolean verboseEnabled = false; - private static boolean privacy = false; - private static Set ignoredConfigSections = new HashSet<>(); - - private static final int MAX_HISTORY_FRAMES = 12; - private static int historyInterval = -1; - private static int historyLength = -1; - - public static final FullServerTickTiming fullServerTickTimer; - public static final Timing timingsTickTimer; - public static final Timing pluginEventTimer; - - public static final Timing connectionTimer; - public static final Timing schedulerTimer; - public static final Timing schedulerAsyncTimer; - public static final Timing schedulerSyncTimer; - public static final Timing commandTimer; - public static final Timing serverCommandTimer; - public static final Timing levelSaveTimer; - - public static final Timing playerNetworkSendTimer; - public static final Timing playerNetworkReceiveTimer; - public static final Timing playerChunkOrderTimer; - public static final Timing playerChunkSendTimer; - public static final Timing playerCommandTimer; - - public static final Timing tickEntityTimer; - public static final Timing tickBlockEntityTimer; - public static final Timing entityMoveTimer; - public static final Timing entityBaseTickTimer; - public static final Timing livingEntityBaseTickTimer; - - public static final Timing generationTimer; - public static final Timing populationTimer; - public static final Timing generationCallbackTimer; - - public static final Timing permissibleCalculationTimer; - public static final Timing permissionDefaultTimer; - - static { - setTimingsEnabled(Server.getInstance().getConfig("timings.enabled", false)); - setVerboseEnabled(Server.getInstance().getConfig("timings.verbose", false)); - setHistoryInterval(Server.getInstance().getConfig("timings.history-interval", 6000)); - setHistoryLength(Server.getInstance().getConfig("timings.history-length", 72000)); - - privacy = Server.getInstance().getConfig("timings.privacy", false); - ignoredConfigSections.addAll(Server.getInstance().getConfig().getStringList("timings.ignore")); - - Server.getInstance().getLogger().debug("Timings: \n" + - "Enabled - " + isTimingsEnabled() + "\n" + - "Verbose - " + isVerboseEnabled() + "\n" + - "History Interval - " + getHistoryInterval() + "\n" + - "History Length - " + getHistoryLength()); - - fullServerTickTimer = new FullServerTickTiming(); - timingsTickTimer = TimingsManager.getTiming(DEFAULT_GROUP.name, "Timings Tick", fullServerTickTimer); - pluginEventTimer = TimingsManager.getTiming("Plugin Events"); - - connectionTimer = TimingsManager.getTiming("Connection Handler"); - schedulerTimer = TimingsManager.getTiming("Scheduler"); - schedulerAsyncTimer = TimingsManager.getTiming("## Scheduler - Async Tasks"); - schedulerSyncTimer = TimingsManager.getTiming("## Scheduler - Sync Tasks"); - commandTimer = TimingsManager.getTiming("Commands"); - serverCommandTimer = TimingsManager.getTiming("Server Command"); - levelSaveTimer = TimingsManager.getTiming("Level Save"); - - playerNetworkSendTimer = TimingsManager.getTiming("Player Network Send"); - playerNetworkReceiveTimer = TimingsManager.getTiming("Player Network Receive"); - playerChunkOrderTimer = TimingsManager.getTiming("Player Order Chunks"); - playerChunkSendTimer = TimingsManager.getTiming("Player Send Chunks"); - playerCommandTimer = TimingsManager.getTiming("Player Command"); - - tickEntityTimer = TimingsManager.getTiming("## Entity Tick"); - tickBlockEntityTimer = TimingsManager.getTiming("## BlockEntity Tick"); - entityMoveTimer = TimingsManager.getTiming("## Entity Move"); - entityBaseTickTimer = TimingsManager.getTiming("## Entity Base Tick"); - livingEntityBaseTickTimer = TimingsManager.getTiming("## LivingEntity Base Tick"); - - generationTimer = TimingsManager.getTiming("Level Generation"); - populationTimer = TimingsManager.getTiming("Level Population"); - generationCallbackTimer = TimingsManager.getTiming("Level Generation Callback"); - - permissibleCalculationTimer = TimingsManager.getTiming("Permissible Calculation"); - permissionDefaultTimer = TimingsManager.getTiming("Default Permission Calculation"); - } - - public static boolean isTimingsEnabled() { - return timingsEnabled; - } - - public static void setTimingsEnabled(boolean enabled) { - timingsEnabled = enabled; - TimingsManager.reset(); - } - - public static boolean isVerboseEnabled() { - return verboseEnabled; - } - - public static void setVerboseEnabled(boolean enabled) { - verboseEnabled = enabled; - TimingsManager.needsRecheckEnabled = true; - } - - public static boolean isPrivacy() { - return privacy; - } - - public static Set getIgnoredConfigSections() { - return ignoredConfigSections; - } - - public static int getHistoryInterval() { - return historyInterval; - } - - public static void setHistoryInterval(int interval) { - historyInterval = Math.max(20 * 60, interval); - //Recheck the history length with the new Interval - if (historyLength != -1) { - setHistoryLength(historyLength); - } - } - - public static int getHistoryLength() { - return historyLength; - } - - public static void setHistoryLength(int length) { - //Cap at 12 History Frames, 1 hour at 5 minute frames. - int maxLength = historyInterval * MAX_HISTORY_FRAMES; - //For special cases of servers with special permission to bypass the max. - //This max helps keep data file sizes reasonable for processing on Aikar's Timing parser side. - //Setting this will not help you bypass the max unless Aikar has added an exception on the API side. - if (Server.getInstance().getConfig().getBoolean("timings.bypass-max", false)) { - maxLength = Integer.MAX_VALUE; - } - - historyLength = Math.max(Math.min(maxLength, length), historyInterval); - - Queue oldQueue = TimingsManager.HISTORY; - int frames = (getHistoryLength() / getHistoryInterval()); - if (length > maxLength) { - Server.getInstance().getLogger().warning( - "Timings Length too high. Requested " + length + ", max is " + maxLength - + ". To get longer history, you must increase your interval. Set Interval to " - + Math.ceil((float) length / MAX_HISTORY_FRAMES) - + " to achieve this length."); - } - - TimingsManager.HISTORY = new TimingsManager.BoundedQueue<>(frames); - TimingsManager.HISTORY.addAll(oldQueue); - } - - public static void reset() { - TimingsManager.reset(); - } - - - public static Timing getCommandTiming(Command command) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "Command: " + command.getLabel(), commandTimer); - } - - public static Timing getTaskTiming(TaskHandler handler, long period) { - String repeating = " "; - if (period > 0) { - repeating += "(interval:" + period + ")"; - } else { - repeating += "(Single)"; - } - - if (handler.getTask() instanceof PluginTask) { - String owner = ((PluginTask) handler.getTask()).getOwner().getName(); - return TimingsManager.getTiming(owner, "PluginTask: " + handler.getTaskId() + repeating, schedulerSyncTimer); - } else if (!handler.isAsynchronous()) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "Task: " + handler.getTaskId() + repeating, schedulerSyncTimer); - } else { - return null; - } - } - - public static Timing getPluginEventTiming(Class event, Listener listener, EventExecutor executor, Plugin plugin) { - Timing group = TimingsManager.getTiming(plugin.getName(), "Combined Total", pluginEventTimer); - - return TimingsManager.getTiming(plugin.getName(), "Event: " + listener.getClass().getName() + "." - + (executor instanceof MethodEventExecutor ? ((MethodEventExecutor) executor).getMethod().getName() : "???") - + " (" + event.getSimpleName() + ")", group); - } - - public static Timing getEntityTiming(Entity entity) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "## Entity Tick: " + entity.getClass().getSimpleName(), tickEntityTimer); - } - - public static Timing getBlockEntityTiming(BlockEntity blockEntity) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "## BlockEntity Tick: " + blockEntity.getClass().getSimpleName(), tickBlockEntityTimer); - } - - public static Timing getReceiveDataPacketTiming(DataPacket pk) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "## Receive Packet: " + pk.getClass().getSimpleName(), playerNetworkReceiveTimer); - } - - public static Timing getSendDataPacketTiming(DataPacket pk) { - return TimingsManager.getTiming(DEFAULT_GROUP.name, "## Send Packet: " + pk.getClass().getSimpleName(), playerNetworkSendTimer); - } - - public static void stopServer() { - setTimingsEnabled(false); - TimingsManager.recheckEnabled(); - } -} diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java deleted file mode 100644 index 9c322a3c82c..00000000000 --- a/src/main/java/co/aikar/timings/TimingsExport.java +++ /dev/null @@ -1,272 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import cn.nukkit.Server; -import cn.nukkit.command.CommandSender; -import cn.nukkit.command.ConsoleCommandSender; -import cn.nukkit.command.RemoteConsoleCommandSender; -import cn.nukkit.lang.TranslationContainer; -import cn.nukkit.nbt.stream.PGZIPOutputStream; -import cn.nukkit.timings.JsonUtil; -import cn.nukkit.utils.TextFormat; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; - -import java.io.*; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.zip.Deflater; - -import static co.aikar.timings.TimingsManager.HISTORY; - -public class TimingsExport extends Thread { - private final CommandSender sender; - private final JsonObject out; - private final TimingsHistory[] history; - - private TimingsExport(CommandSender sender, JsonObject out, TimingsHistory[] history) { - super("Timings paste thread"); - this.sender = sender; - this.out = out; - this.history = history; - } - - /** - * Builds a JSON timings report and sends it to Aikar's viewer - * - * @param sender Sender that issued the command - */ - public static void reportTimings(CommandSender sender) { - JsonObject out = new JsonObject(); - out.addProperty("version", Server.getInstance().getVersion()); - out.addProperty("maxplayers", Server.getInstance().getMaxPlayers()); - out.addProperty("start", TimingsManager.timingStart / 1000); - out.addProperty("end", System.currentTimeMillis() / 1000); - out.addProperty("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000); - - if (!Timings.isPrivacy()) { - out.addProperty("server", Server.getInstance().getName()); - out.addProperty("motd", Server.getInstance().getMotd()); - out.addProperty("online-mode", false); //In MCPE we have permanent offline mode. - out.addProperty("icon", ""); //"data:image/png;base64," - } - - final Runtime runtime = Runtime.getRuntime(); - RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); - - JsonObject system = new JsonObject(); - system.addProperty("timingcost", getCost()); - system.addProperty("name", System.getProperty("os.name")); - system.addProperty("version", System.getProperty("os.version")); - system.addProperty("jvmversion", System.getProperty("java.version")); - system.addProperty("arch", System.getProperty("os.arch")); - system.addProperty("maxmem", runtime.maxMemory()); - system.addProperty("cpu", runtime.availableProcessors()); - system.addProperty("runtime", ManagementFactory.getRuntimeMXBean().getUptime()); - system.addProperty("flags", String.join(" ", runtimeBean.getInputArguments())); - system.add("gc", JsonUtil.mapToObject(ManagementFactory.getGarbageCollectorMXBeans(), (input) -> - new JsonUtil.JSONPair(input.getName(), JsonUtil.toArray(input.getCollectionCount(), input.getCollectionTime())))); - out.add("system", system); - - TimingsHistory[] history = HISTORY.toArray(new TimingsHistory[HISTORY.size() + 1]); - history[HISTORY.size()] = new TimingsHistory(); //Current snapshot - - JsonObject timings = new JsonObject(); - for (TimingIdentifier.TimingGroup group : TimingIdentifier.GROUP_MAP.values()) { - for (Timing id : group.timings) { - if (!id.timed && !id.isSpecial()) { - continue; - } - - timings.add(String.valueOf(id.id), JsonUtil.toArray(group.id, id.name)); - } - } - - JsonObject idmap = new JsonObject(); - idmap.add("groups", JsonUtil.mapToObject(TimingIdentifier.GROUP_MAP.values(), (group) -> - new JsonUtil.JSONPair(group.id, group.name))); - idmap.add("handlers", timings); - idmap.add("worlds", JsonUtil.mapToObject(TimingsHistory.levelMap.entrySet(), (entry) -> - new JsonUtil.JSONPair(entry.getValue(), entry.getKey()))); - idmap.add("tileentity", JsonUtil.mapToObject(TimingsHistory.blockEntityMap.entrySet(), (entry) -> - new JsonUtil.JSONPair(entry.getKey(), entry.getValue()))); - idmap.add("entity", JsonUtil.mapToObject(TimingsHistory.entityMap.entrySet(), (entry) -> - new JsonUtil.JSONPair(entry.getKey(), entry.getValue()))); - out.add("idmap", idmap); - - //Information about loaded plugins - out.add("plugins", JsonUtil.mapToObject(Server.getInstance().getPluginManager().getPlugins().values(), (plugin) -> { - JsonObject jsonPlugin = new JsonObject(); - jsonPlugin.addProperty("version", plugin.getDescription().getVersion()); - jsonPlugin.addProperty("description", plugin.getDescription().getDescription());// Sounds legit - jsonPlugin.addProperty("website", plugin.getDescription().getWebsite()); - jsonPlugin.addProperty("authors", String.join(", ", plugin.getDescription().getAuthors())); - return new JsonUtil.JSONPair(plugin.getName(), jsonPlugin); - })); - - //Information on the users Config - JsonObject config = new JsonObject(); - if (!Timings.getIgnoredConfigSections().contains("all")) { - JsonObject nukkit = JsonUtil.toObject(Server.getInstance().getConfig().getRootSection()); - Timings.getIgnoredConfigSections().forEach(nukkit::remove); - config.add("nukkit", nukkit); - } else { - config.add("nukkit", null); - } - out.add("config", config); - - new TimingsExport(sender, out, history).start(); - } - - private static long getCost() { - int passes = 200; - Timing SAMPLER1 = TimingsManager.getTiming(null, "Timings sampler 1", null); - Timing SAMPLER2 = TimingsManager.getTiming(null, "Timings sampler 2", null); - Timing SAMPLER3 = TimingsManager.getTiming(null, "Timings sampler 3", null); - Timing SAMPLER4 = TimingsManager.getTiming(null, "Timings sampler 4", null); - Timing SAMPLER5 = TimingsManager.getTiming(null, "Timings sampler 5", null); - Timing SAMPLER6 = TimingsManager.getTiming(null, "Timings sampler 6", null); - - long start = System.nanoTime(); - for (int i = 0; i < passes; i++) { - SAMPLER1.startTiming(); - SAMPLER2.startTiming(); - SAMPLER3.startTiming(); - SAMPLER4.startTiming(); - SAMPLER5.startTiming(); - SAMPLER6.startTiming(); - SAMPLER6.stopTiming(); - SAMPLER5.stopTiming(); - SAMPLER4.stopTiming(); - SAMPLER3.stopTiming(); - SAMPLER2.stopTiming(); - SAMPLER1.stopTiming(); - } - - long timingsCost = (System.nanoTime() - start) / passes / 6; - - SAMPLER1.reset(true); - SAMPLER2.reset(true); - SAMPLER3.reset(true); - SAMPLER4.reset(true); - SAMPLER5.reset(true); - SAMPLER6.reset(true); - - return timingsCost; - } - - @Override - public synchronized void start() { - if (this.sender instanceof RemoteConsoleCommandSender) { - this.sender.sendMessage(new TranslationContainer("nukkit.command.timings.rcon")); - run(); - } else { - super.start(); - } - } - - @Override - public void run() { - this.sender.sendMessage(new TranslationContainer("nukkit.command.timings.uploadStart")); - this.out.add("data", JsonUtil.mapToArray(this.history, TimingsHistory::export)); - - String response = null; - try { - HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection(); - con.setDoOutput(true); - con.setRequestProperty("User-Agent", "Nukkit/" + Server.getInstance().getName() + "/" + InetAddress.getLocalHost().getHostName()); - con.setRequestMethod("POST"); - con.setInstanceFollowRedirects(false); - - PGZIPOutputStream request = new PGZIPOutputStream(con.getOutputStream()); - request.setLevel(Deflater.BEST_COMPRESSION); - - request.write(new Gson().toJson(this.out).getBytes(StandardCharsets.UTF_8)); - request.close(); - - response = getResponse(con); - - if (con.getResponseCode() != 302) { - this.sender.sendMessage(new TranslationContainer("nukkit.command.timings.uploadError", String.valueOf(con.getResponseCode()), con.getResponseMessage())); - if (response != null) { - Server.getInstance().getLogger().alert(response); - } - return; - } - - String location = con.getHeaderField("Location"); - this.sender.sendMessage(new TranslationContainer("nukkit.command.timings.timingsLocation", location)); - if (!(this.sender instanceof ConsoleCommandSender)) { - Server.getInstance().getLogger().info(Server.getInstance().getLanguage().translateString("nukkit.command.timings.timingsLocation", location)); - } - - if (response != null && !response.isEmpty()) { - Server.getInstance().getLogger().info(Server.getInstance().getLanguage().translateString("nukkit.command.timings.timingsResponse", response)); - } - - File timingFolder = new File(Server.getInstance().getDataPath() + File.separator + "timings"); - timingFolder.mkdirs(); - String fileName = timingFolder + File.separator + new SimpleDateFormat("'timings-'yyyy-MM-dd-hh-mm'.txt'").format(new Date()); - - FileWriter writer = new FileWriter(fileName); - writer.write(Server.getInstance().getLanguage().translateString("nukkit.command.timings.timingsLocation", location) + "\n\n"); - writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(this.out)); - writer.close(); - - Server.getInstance().getLogger().info(Server.getInstance().getLanguage().translateString("nukkit.command.timings.timingsWrite", fileName)); - } catch (IOException exception) { - this.sender.sendMessage(TextFormat.RED + "" + new TranslationContainer("nukkit.command.timings.reportError")); - if (response != null) { - Server.getInstance().getLogger().alert(response); - } - Server.getInstance().getLogger().logException(exception); - } - } - - private String getResponse(HttpURLConnection con) throws IOException { - try (InputStream is = con.getInputStream()) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - - byte[] b = new byte[1024]; - int bytesRead; - while ((bytesRead = is.read(b)) != -1) { - bos.write(b, 0, bytesRead); - } - return bos.toString(); - - } catch (IOException exception) { - this.sender.sendMessage(TextFormat.RED + "" + new TranslationContainer("nukkit.command.timings.reportError")); - Server.getInstance().getLogger().warning(con.getResponseMessage(), exception); - return null; - } - } -} diff --git a/src/main/java/co/aikar/timings/TimingsHistory.java b/src/main/java/co/aikar/timings/TimingsHistory.java deleted file mode 100644 index e7e25c07cd9..00000000000 --- a/src/main/java/co/aikar/timings/TimingsHistory.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import cn.nukkit.Player; -import cn.nukkit.Server; -import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.entity.Entity; -import cn.nukkit.level.Level; -import cn.nukkit.level.format.FullChunk; -import cn.nukkit.timings.JsonUtil; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; - -import java.lang.management.ManagementFactory; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import static co.aikar.timings.Timings.fullServerTickTimer; -import static co.aikar.timings.TimingsManager.MINUTE_REPORTS; - -public class TimingsHistory { - public static long lastMinuteTime; - public static long timedTicks; - public static long playerTicks; - public static long entityTicks; - public static long tileEntityTicks; - public static long activatedEntityTicks; - - private static int levelIdPool = 1; - static Map levelMap = new HashMap<>(); - static Map entityMap = new HashMap<>(); - static Map blockEntityMap = new HashMap<>(); - - private final long endTime; - private final long startTime; - private final long totalTicks; - // Represents all time spent running the server this history - private final long totalTime; - private final MinuteReport[] minuteReports; - - private final TimingsHistoryEntry[] entries; - private final JsonObject levels = new JsonObject(); - - TimingsHistory() { - this.endTime = System.currentTimeMillis() / 1000; - this.startTime = TimingsManager.historyStart / 1000; - - if (timedTicks % 1200 != 0 || MINUTE_REPORTS.isEmpty()) { - this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[MINUTE_REPORTS.size() + 1]); - this.minuteReports[this.minuteReports.length - 1] = new MinuteReport(); - } else { - this.minuteReports = MINUTE_REPORTS.toArray(new MinuteReport[0]); - } - - long ticks = 0; - for (MinuteReport mr : this.minuteReports) { - ticks += mr.ticksRecord.timed; - } - - this.totalTicks = ticks; - this.totalTime = fullServerTickTimer.record.totalTime; - this.entries = new TimingsHistoryEntry[TimingsManager.TIMINGS.size()]; - - int i = 0; - for (Timing timing : TimingsManager.TIMINGS) { - this.entries[i++] = new TimingsHistoryEntry(timing); - } - - final Map entityCounts = new HashMap<>(); - final Map blockEntityCounts = new HashMap<>(); - final Gson GSON = new Gson(); - // Information about all loaded entities/block entities - for (Level level : Server.getInstance().getLevels().values()) { - JsonArray jsonLevel = new JsonArray(); - for (FullChunk chunk : level.getChunks().values()) { - entityCounts.clear(); - blockEntityCounts.clear(); - - //count entities - for (Entity entity : chunk.getEntities().values()) { - if (!entityCounts.containsKey(entity.getNetworkId())) - entityCounts.put(entity.getNetworkId(), new AtomicInteger(0)); - entityCounts.get(entity.getNetworkId()).incrementAndGet(); - entityMap.put(entity.getNetworkId(), entity.getClass().getSimpleName()); - } - - //count block entities - for (BlockEntity blockEntity : chunk.getBlockEntities().values()) { - if (!blockEntityCounts.containsKey(blockEntity.getBlock().getId())) - blockEntityCounts.put(blockEntity.getBlock().getId(), new AtomicInteger(0)); - blockEntityCounts.get(blockEntity.getBlock().getId()).incrementAndGet(); - blockEntityMap.put(blockEntity.getBlock().getId(), blockEntity.getClass().getSimpleName()); - } - - if (blockEntityCounts.isEmpty() && entityCounts.isEmpty()) { - continue; - } - - JsonArray jsonChunk = new JsonArray(); - jsonChunk.add(chunk.getX()); - jsonChunk.add(chunk.getZ()); - jsonChunk.add(GSON.toJsonTree(JsonUtil.mapToObject(entityCounts.entrySet(), (entry) -> new JsonUtil.JSONPair(entry.getKey(), entry.getValue().get()))).getAsJsonObject()); - jsonChunk.add(GSON.toJsonTree(JsonUtil.mapToObject(blockEntityCounts.entrySet(), (entry) -> new JsonUtil.JSONPair(entry.getKey(), entry.getValue().get()))).getAsJsonObject()); - jsonLevel.add(jsonChunk); - } - - if (!levelMap.containsKey(level.getName())) levelMap.put(level.getName(), levelIdPool++); - levels.add(String.valueOf(levelMap.get(level.getName())), jsonLevel); - } - } - - static void resetTicks(boolean fullReset) { - if (fullReset) { - timedTicks = 0; - } - lastMinuteTime = System.nanoTime(); - playerTicks = 0; - tileEntityTicks = 0; - entityTicks = 0; - activatedEntityTicks = 0; - } - - JsonObject export() { - JsonObject json = new JsonObject(); - json.addProperty("s", this.startTime); - json.addProperty("e", this.endTime); - json.addProperty("tk", this.totalTicks); - json.addProperty("tm", this.totalTime); - json.add("w", this.levels); - json.add("h", JsonUtil.mapToArray(this.entries, (entry) -> { - if (entry.data.count == 0) { - return null; - } - return entry.export(); - })); - json.add("mp", JsonUtil.mapToArray(this.minuteReports, MinuteReport::export)); - return json; - } - - static class MinuteReport { - final long time = System.currentTimeMillis() / 1000; - - final TicksRecord ticksRecord = new TicksRecord(); - final PingRecord pingRecord = new PingRecord(); - final TimingData fst = Timings.fullServerTickTimer.minuteData.clone(); - final double tps = 1E9 / (System.nanoTime() - lastMinuteTime) * this.ticksRecord.timed; - final double usedMemory = Timings.fullServerTickTimer.avgUsedMemory; - final double freeMemory = Timings.fullServerTickTimer.avgFreeMemory; - final double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); - - JsonArray export() { - return JsonUtil.toArray(this.time, - Math.round(this.tps * 100D) / 100D, - Math.round(this.pingRecord.avg * 100D) / 100D, - this.fst.export(), - JsonUtil.toArray(this.ticksRecord.timed, - this.ticksRecord.player, - this.ticksRecord.entity, - this.ticksRecord.activatedEntity, - this.ticksRecord.tileEntity), - this.usedMemory, - this.freeMemory, - this.loadAvg); - } - } - - private static class TicksRecord { - final long timed; - final long player; - final long entity; - final long activatedEntity; - final long tileEntity; - - TicksRecord() { - this.timed = timedTicks - (TimingsManager.MINUTE_REPORTS.size() * 1200); - this.player = playerTicks; - this.entity = entityTicks; - this.activatedEntity = activatedEntityTicks; - this.tileEntity = tileEntityTicks; - } - } - - private static class PingRecord { - final double avg; - - PingRecord() { - final Collection onlinePlayers = Server.getInstance().getOnlinePlayers().values(); - int totalPing = 0; - for (Player player : onlinePlayers) { - totalPing += player.getPing(); - } - - this.avg = onlinePlayers.isEmpty() ? 0 : (float) totalPing / onlinePlayers.size(); - } - } -} diff --git a/src/main/java/co/aikar/timings/TimingsHistoryEntry.java b/src/main/java/co/aikar/timings/TimingsHistoryEntry.java deleted file mode 100644 index 6782b7184dd..00000000000 --- a/src/main/java/co/aikar/timings/TimingsHistoryEntry.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import com.google.gson.JsonArray; - -import cn.nukkit.timings.JsonUtil; - -class TimingsHistoryEntry { - final TimingData data; - final TimingData[] children; - - TimingsHistoryEntry(Timing timing) { - this.data = timing.record.clone(); - this.children = new TimingData[timing.children.size()]; - - int i = 0; - for (TimingData child : timing.children.values()) { - this.children[i++] = child.clone(); - } - } - - JsonArray export() { - JsonArray json = this.data.export(); - if (this.children.length > 0) json.add(JsonUtil.mapToArray(this.children, TimingData::export)); - return json; - } -} diff --git a/src/main/java/co/aikar/timings/TimingsManager.java b/src/main/java/co/aikar/timings/TimingsManager.java deleted file mode 100644 index 3622b329cd6..00000000000 --- a/src/main/java/co/aikar/timings/TimingsManager.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * This file is licensed under the MIT License (MIT). - * - * Copyright (c) 2014 Daniel Ennis - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package co.aikar.timings; - -import cn.nukkit.Server; - -import java.util.*; - -public class TimingsManager { - static final Map TIMING_MAP = Collections.synchronizedMap(new HashMap<>(256, 0.5f)); - - static final Queue TIMINGS = new ArrayDeque<>(); - static final ArrayDeque MINUTE_REPORTS = new ArrayDeque<>(); - - static Queue HISTORY = new BoundedQueue<>(12); - - static Timing CURRENT; - - static long timingStart = 0; - static long historyStart = 0; - static boolean needsFullReset = false; - static boolean needsRecheckEnabled = false; - - static void reset() { - needsFullReset = true; - } - - /** - * Called every tick to count the number of times a timer caused TPS loss. - */ - static void tick() { - if (Timings.isTimingsEnabled()) { - boolean violated = Timings.fullServerTickTimer.isViolated(); - - synchronized (TIMINGS) { - for (Timing timing : TIMINGS) { - if (timing.isSpecial()) { - // Called manually - continue; - } - - timing.tick(violated); - } - } - - TimingsHistory.playerTicks += Server.getInstance().getOnlinePlayers().size(); - TimingsHistory.timedTicks++; - } - } - - static void recheckEnabled() { - synchronized (TIMING_MAP) { - TIMING_MAP.values().forEach(Timing::checkEnabled); - } - - needsRecheckEnabled = false; - } - - static void resetTimings() { - if (needsFullReset) { - // Full resets need to re-check every handlers enabled state - // Timing map can be modified from async so we must sync on it. - synchronized (TIMING_MAP) { - for (Timing timing : TIMING_MAP.values()) { - timing.reset(true); - } - } - - HISTORY.clear(); - needsFullReset = false; - needsRecheckEnabled = false; - timingStart = System.currentTimeMillis(); - } else { - // Soft resets only need to act on timings that have done something - // Handlers can only be modified on main thread. - for (Timing timing : TIMINGS) { - timing.reset(false); - } - } - - TIMINGS.clear(); - MINUTE_REPORTS.clear(); - - TimingsHistory.resetTicks(true); - historyStart = System.currentTimeMillis(); - } - - public static Timing getTiming(String name) { - return getTiming(null, name, null); - } - - static Timing getTiming(String group, String name, Timing groupTiming) { - TimingIdentifier id = new TimingIdentifier(group, name, groupTiming); - return TIMING_MAP.computeIfAbsent(id, k -> new Timing(id)); - } - - static final class BoundedQueue extends LinkedList { - final int maxSize; - - BoundedQueue(int maxSize) { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize must be greater than zero"); - } - - this.maxSize = maxSize; - } - - @Override - public boolean add(E e) { - if (this.size() == maxSize) { - this.remove(); - } - - return super.add(e); - } - } -} diff --git a/src/main/resources/biome_id_map.json b/src/main/resources/biome_id_map.json index 2995c043916..e23def0c1a4 100644 --- a/src/main/resources/biome_id_map.json +++ b/src/main/resources/biome_id_map.json @@ -1,87 +1 @@ -{ - "bamboo_jungle": 48, - "bamboo_jungle_hills": 49, - "basalt_deltas": 181, - "beach": 16, - "birch_forest": 27, - "birch_forest_hills": 28, - "birch_forest_hills_mutated": 156, - "birch_forest_mutated": 155, - "cold_beach": 26, - "cold_ocean": 44, - "cold_taiga": 30, - "cold_taiga_hills": 31, - "cold_taiga_mutated": 158, - "crimson_forest": 179, - "deep_cold_ocean": 45, - "deep_dark": 190, - "deep_frozen_ocean": 47, - "deep_lukewarm_ocean": 43, - "deep_ocean": 24, - "deep_warm_ocean": 41, - "desert": 2, - "desert_hills": 17, - "desert_mutated": 130, - "dripstone_caves": 188, - "extreme_hills": 3, - "extreme_hills_edge": 20, - "extreme_hills_mutated": 131, - "extreme_hills_plus_trees": 34, - "extreme_hills_plus_trees_mutated": 162, - "flower_forest": 132, - "forest": 4, - "forest_hills": 18, - "frozen_ocean": 46, - "frozen_peaks": 183, - "frozen_river": 11, - "grove": 185, - "hell": 8, - "ice_mountains": 13, - "ice_plains": 12, - "ice_plains_spikes": 140, - "jagged_peaks": 182, - "jungle": 21, - "jungle_edge": 23, - "jungle_edge_mutated": 151, - "jungle_hills": 22, - "jungle_mutated": 149, - "legacy_frozen_ocean": 10, - "lukewarm_ocean": 42, - "lush_caves": 187, - "mangrove_swamp": 191, - "meadow": 186, - "mega_taiga": 32, - "mega_taiga_hills": 33, - "mesa": 37, - "mesa_bryce": 165, - "mesa_plateau": 39, - "mesa_plateau_mutated": 167, - "mesa_plateau_stone": 38, - "mesa_plateau_stone_mutated": 166, - "mushroom_island": 14, - "mushroom_island_shore": 15, - "ocean": 0, - "plains": 1, - "redwood_taiga_hills_mutated": 161, - "redwood_taiga_mutated": 160, - "river": 7, - "roofed_forest": 29, - "roofed_forest_mutated": 157, - "savanna": 35, - "savanna_mutated": 163, - "savanna_plateau": 36, - "savanna_plateau_mutated": 164, - "snowy_slopes": 184, - "soulsand_valley": 178, - "stone_beach": 25, - "stony_peaks": 189, - "sunflower_plains": 129, - "swampland": 6, - "swampland_mutated": 134, - "taiga": 5, - "taiga_hills": 19, - "taiga_mutated": 133, - "the_end": 9, - "warm_ocean": 40, - "warped_forest": 180 -} \ No newline at end of file +{"bamboo_jungle":48,"bamboo_jungle_hills":49,"basalt_deltas":181,"beach":16,"birch_forest":27,"birch_forest_hills":28,"birch_forest_hills_mutated":156,"birch_forest_mutated":155,"cold_beach":26,"cold_ocean":44,"cold_taiga":30,"cold_taiga_hills":31,"cold_taiga_mutated":158,"crimson_forest":179,"deep_cold_ocean":45,"deep_frozen_ocean":47,"deep_lukewarm_ocean":43,"deep_ocean":24,"deep_warm_ocean":41,"desert":2,"desert_hills":17,"desert_mutated":130,"dripstone_caves":188,"extreme_hills":3,"extreme_hills_edge":20,"extreme_hills_mutated":131,"extreme_hills_plus_trees":34,"extreme_hills_plus_trees_mutated":162,"flower_forest":132,"forest":4,"forest_hills":18,"frozen_ocean":46,"frozen_peaks":183,"frozen_river":11,"grove":185,"hell":8,"ice_mountains":13,"ice_plains":12,"ice_plains_spikes":140,"jagged_peaks":182,"jungle":21,"jungle_edge":23,"jungle_edge_mutated":151,"jungle_hills":22,"jungle_mutated":149,"legacy_frozen_ocean":10,"lukewarm_ocean":42,"lush_caves":187,"meadow":186,"mega_taiga":32,"mega_taiga_hills":33,"mesa":37,"mesa_bryce":165,"mesa_plateau":39,"mesa_plateau_mutated":167,"mesa_plateau_stone":38,"mesa_plateau_stone_mutated":166,"mushroom_island":14,"mushroom_island_shore":15,"ocean":0,"plains":1,"redwood_taiga_hills_mutated":161,"redwood_taiga_mutated":160,"river":7,"roofed_forest":29,"roofed_forest_mutated":157,"savanna":35,"savanna_mutated":163,"savanna_plateau":36,"savanna_plateau_mutated":164,"snowy_slopes":184,"soulsand_valley":178,"stone_beach":25,"stony_peaks":189,"sunflower_plains":129,"swampland":6,"swampland_mutated":134,"taiga":5,"taiga_hills":19,"taiga_mutated":133,"the_end":9,"warm_ocean":40,"warped_forest":180} \ No newline at end of file diff --git a/src/main/resources/creative_items.json b/src/main/resources/creative_items.json index d1d198cd3cc..09600fa1b5a 100644 --- a/src/main/resources/creative_items.json +++ b/src/main/resources/creative_items.json @@ -1 +1 @@ -{"items":[{"id":"minecraft:oak_planks","blockRuntimeId":13245},{"id":"minecraft:spruce_planks","blockRuntimeId":13607},{"id":"minecraft:birch_planks","blockRuntimeId":9112},{"id":"minecraft:jungle_planks","blockRuntimeId":11827},{"id":"minecraft:acacia_planks","blockRuntimeId":7303},{"id":"minecraft:dark_oak_planks","blockRuntimeId":5933},{"id":"minecraft:mangrove_planks","blockRuntimeId":2432},{"id":"minecraft:cherry_planks","blockRuntimeId":13475},{"id":"minecraft:bamboo_planks","blockRuntimeId":9443},{"id":"minecraft:bamboo_mosaic","blockRuntimeId":14084},{"id":"minecraft:crimson_planks","blockRuntimeId":8613},{"id":"minecraft:warped_planks","blockRuntimeId":2403},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2657},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2658},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2659},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2660},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2661},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2662},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2669},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2664},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2665},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2663},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2666},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2670},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2667},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2668},{"id":"minecraft:blackstone_wall","blockRuntimeId":6715},{"id":"minecraft:polished_blackstone_wall","blockRuntimeId":11934},{"id":"minecraft:polished_blackstone_brick_wall","blockRuntimeId":2446},{"id":"minecraft:cobbled_deepslate_wall","blockRuntimeId":13888},{"id":"minecraft:deepslate_tile_wall","blockRuntimeId":8861},{"id":"minecraft:polished_deepslate_wall","blockRuntimeId":13622},{"id":"minecraft:deepslate_brick_wall","blockRuntimeId":1196},{"id":"minecraft:mud_brick_wall","blockRuntimeId":2197},{"id":"minecraft:oak_fence","blockRuntimeId":9575},{"id":"minecraft:spruce_fence","blockRuntimeId":1152},{"id":"minecraft:birch_fence","blockRuntimeId":13873},{"id":"minecraft:jungle_fence","blockRuntimeId":1145},{"id":"minecraft:acacia_fence","blockRuntimeId":13886},{"id":"minecraft:dark_oak_fence","blockRuntimeId":12348},{"id":"minecraft:mangrove_fence","blockRuntimeId":11845},{"id":"minecraft:cherry_fence","blockRuntimeId":2431},{"id":"minecraft:bamboo_fence","blockRuntimeId":1384},{"id":"minecraft:nether_brick_fence","blockRuntimeId":7099},{"id":"minecraft:crimson_fence","blockRuntimeId":13802},{"id":"minecraft:warped_fence","blockRuntimeId":10103},{"id":"minecraft:fence_gate","blockRuntimeId":88},{"id":"minecraft:spruce_fence_gate","blockRuntimeId":11795},{"id":"minecraft:birch_fence_gate","blockRuntimeId":6176},{"id":"minecraft:jungle_fence_gate","blockRuntimeId":9152},{"id":"minecraft:acacia_fence_gate","blockRuntimeId":13300},{"id":"minecraft:dark_oak_fence_gate","blockRuntimeId":6967},{"id":"minecraft:mangrove_fence_gate","blockRuntimeId":7525},{"id":"minecraft:cherry_fence_gate","blockRuntimeId":14061},{"id":"minecraft:bamboo_fence_gate","blockRuntimeId":8836},{"id":"minecraft:crimson_fence_gate","blockRuntimeId":7977},{"id":"minecraft:warped_fence_gate","blockRuntimeId":9190},{"id":"minecraft:normal_stone_stairs","blockRuntimeId":1385},{"id":"minecraft:stone_stairs","blockRuntimeId":6096},{"id":"minecraft:mossy_cobblestone_stairs","blockRuntimeId":6877},{"id":"minecraft:oak_stairs","blockRuntimeId":728},{"id":"minecraft:spruce_stairs","blockRuntimeId":143},{"id":"minecraft:birch_stairs","blockRuntimeId":12228},{"id":"minecraft:jungle_stairs","blockRuntimeId":12190},{"id":"minecraft:acacia_stairs","blockRuntimeId":11351},{"id":"minecraft:dark_oak_stairs","blockRuntimeId":8853},{"id":"minecraft:mangrove_stairs","blockRuntimeId":7483},{"id":"minecraft:cherry_stairs","blockRuntimeId":12395},{"id":"minecraft:bamboo_stairs","blockRuntimeId":2183},{"id":"minecraft:bamboo_mosaic_stairs","blockRuntimeId":11359},{"id":"minecraft:stone_brick_stairs","blockRuntimeId":2415},{"id":"minecraft:mossy_stone_brick_stairs","blockRuntimeId":10538},{"id":"minecraft:sandstone_stairs","blockRuntimeId":5966},{"id":"minecraft:smooth_sandstone_stairs","blockRuntimeId":6014},{"id":"minecraft:red_sandstone_stairs","blockRuntimeId":9137},{"id":"minecraft:smooth_red_sandstone_stairs","blockRuntimeId":9370},{"id":"minecraft:granite_stairs","blockRuntimeId":5513},{"id":"minecraft:polished_granite_stairs","blockRuntimeId":6937},{"id":"minecraft:diorite_stairs","blockRuntimeId":7242},{"id":"minecraft:polished_diorite_stairs","blockRuntimeId":11917},{"id":"minecraft:andesite_stairs","blockRuntimeId":9104},{"id":"minecraft:polished_andesite_stairs","blockRuntimeId":12258},{"id":"minecraft:brick_stairs","blockRuntimeId":11723},{"id":"minecraft:nether_brick_stairs","blockRuntimeId":118},{"id":"minecraft:red_nether_brick_stairs","blockRuntimeId":11813},{"id":"minecraft:end_brick_stairs","blockRuntimeId":11533},{"id":"minecraft:quartz_stairs","blockRuntimeId":8139},{"id":"minecraft:smooth_quartz_stairs","blockRuntimeId":13414},{"id":"minecraft:purpur_stairs","blockRuntimeId":13479},{"id":"minecraft:prismarine_stairs","blockRuntimeId":12952},{"id":"minecraft:dark_prismarine_stairs","blockRuntimeId":13128},{"id":"minecraft:prismarine_bricks_stairs","blockRuntimeId":628},{"id":"minecraft:crimson_stairs","blockRuntimeId":11426},{"id":"minecraft:warped_stairs","blockRuntimeId":6109},{"id":"minecraft:blackstone_stairs","blockRuntimeId":12247},{"id":"minecraft:polished_blackstone_stairs","blockRuntimeId":7142},{"id":"minecraft:polished_blackstone_brick_stairs","blockRuntimeId":7338},{"id":"minecraft:cut_copper_stairs","blockRuntimeId":7492},{"id":"minecraft:exposed_cut_copper_stairs","blockRuntimeId":7475},{"id":"minecraft:weathered_cut_copper_stairs","blockRuntimeId":7150},{"id":"minecraft:oxidized_cut_copper_stairs","blockRuntimeId":1107},{"id":"minecraft:waxed_cut_copper_stairs","blockRuntimeId":1155},{"id":"minecraft:waxed_exposed_cut_copper_stairs","blockRuntimeId":6686},{"id":"minecraft:waxed_weathered_cut_copper_stairs","blockRuntimeId":11334},{"id":"minecraft:waxed_oxidized_cut_copper_stairs","blockRuntimeId":10058},{"id":"minecraft:cobbled_deepslate_stairs","blockRuntimeId":568},{"id":"minecraft:deepslate_tile_stairs","blockRuntimeId":7969},{"id":"minecraft:polished_deepslate_stairs","blockRuntimeId":1043},{"id":"minecraft:deepslate_brick_stairs","blockRuntimeId":13120},{"id":"minecraft:mud_brick_stairs","blockRuntimeId":9346},{"id":"minecraft:wooden_door"},{"id":"minecraft:spruce_door"},{"id":"minecraft:birch_door"},{"id":"minecraft:jungle_door"},{"id":"minecraft:acacia_door"},{"id":"minecraft:dark_oak_door"},{"id":"minecraft:mangrove_door"},{"id":"minecraft:cherry_door"},{"id":"minecraft:bamboo_door"},{"id":"minecraft:iron_door"},{"id":"minecraft:crimson_door"},{"id":"minecraft:warped_door"},{"id":"minecraft:trapdoor","blockRuntimeId":660},{"id":"minecraft:spruce_trapdoor","blockRuntimeId":11762},{"id":"minecraft:birch_trapdoor","blockRuntimeId":11863},{"id":"minecraft:jungle_trapdoor","blockRuntimeId":9171},{"id":"minecraft:acacia_trapdoor","blockRuntimeId":9411},{"id":"minecraft:dark_oak_trapdoor","blockRuntimeId":13202},{"id":"minecraft:mangrove_trapdoor","blockRuntimeId":7346},{"id":"minecraft:cherry_trapdoor","blockRuntimeId":2377},{"id":"minecraft:bamboo_trapdoor","blockRuntimeId":9054},{"id":"minecraft:iron_trapdoor","blockRuntimeId":1072},{"id":"minecraft:crimson_trapdoor","blockRuntimeId":7183},{"id":"minecraft:warped_trapdoor","blockRuntimeId":8089},{"id":"minecraft:iron_bars","blockRuntimeId":8174},{"id":"minecraft:glass","blockRuntimeId":11331},{"id":"minecraft:white_stained_glass","blockRuntimeId":8618},{"id":"minecraft:light_gray_stained_glass","blockRuntimeId":1191},{"id":"minecraft:gray_stained_glass","blockRuntimeId":6105},{"id":"minecraft:black_stained_glass","blockRuntimeId":10583},{"id":"minecraft:brown_stained_glass","blockRuntimeId":1788},{"id":"minecraft:red_stained_glass","blockRuntimeId":9037},{"id":"minecraft:orange_stained_glass","blockRuntimeId":7413},{"id":"minecraft:yellow_stained_glass","blockRuntimeId":13293},{"id":"minecraft:lime_stained_glass","blockRuntimeId":129},{"id":"minecraft:green_stained_glass","blockRuntimeId":7098},{"id":"minecraft:cyan_stained_glass","blockRuntimeId":10582},{"id":"minecraft:light_blue_stained_glass","blockRuntimeId":9616},{"id":"minecraft:blue_stained_glass","blockRuntimeId":9580},{"id":"minecraft:purple_stained_glass","blockRuntimeId":2645},{"id":"minecraft:magenta_stained_glass","blockRuntimeId":12911},{"id":"minecraft:pink_stained_glass","blockRuntimeId":7166},{"id":"minecraft:tinted_glass","blockRuntimeId":10712},{"id":"minecraft:glass_pane","blockRuntimeId":9023},{"id":"minecraft:white_stained_glass_pane","blockRuntimeId":7414},{"id":"minecraft:light_gray_stained_glass_pane","blockRuntimeId":1193},{"id":"minecraft:gray_stained_glass_pane","blockRuntimeId":12116},{"id":"minecraft:black_stained_glass_pane","blockRuntimeId":2656},{"id":"minecraft:brown_stained_glass_pane","blockRuntimeId":740},{"id":"minecraft:red_stained_glass_pane","blockRuntimeId":12340},{"id":"minecraft:orange_stained_glass_pane","blockRuntimeId":651},{"id":"minecraft:yellow_stained_glass_pane","blockRuntimeId":564},{"id":"minecraft:lime_stained_glass_pane","blockRuntimeId":13291},{"id":"minecraft:green_stained_glass_pane","blockRuntimeId":6936},{"id":"minecraft:cyan_stained_glass_pane","blockRuntimeId":11678},{"id":"minecraft:light_blue_stained_glass_pane","blockRuntimeId":6668},{"id":"minecraft:blue_stained_glass_pane","blockRuntimeId":87},{"id":"minecraft:purple_stained_glass_pane","blockRuntimeId":8622},{"id":"minecraft:magenta_stained_glass_pane","blockRuntimeId":8173},{"id":"minecraft:pink_stained_glass_pane","blockRuntimeId":12345},{"id":"minecraft:ladder","blockRuntimeId":14090},{"id":"minecraft:scaffolding","blockRuntimeId":5950},{"id":"minecraft:stone_block_slab","blockRuntimeId":7076},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9651},{"id":"minecraft:stone_block_slab","blockRuntimeId":7079},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9622},{"id":"minecraft:oak_slab","blockRuntimeId":7100},{"id":"minecraft:spruce_slab","blockRuntimeId":12245},{"id":"minecraft:birch_slab","blockRuntimeId":6609},{"id":"minecraft:jungle_slab","blockRuntimeId":6666},{"id":"minecraft:acacia_slab","blockRuntimeId":13280},{"id":"minecraft:dark_oak_slab","blockRuntimeId":1194},{"id":"minecraft:mangrove_slab","blockRuntimeId":2608},{"id":"minecraft:cherry_slab","blockRuntimeId":11368},{"id":"minecraft:bamboo_slab","blockRuntimeId":11720},{"id":"minecraft:bamboo_mosaic_slab","blockRuntimeId":4935},{"id":"minecraft:stone_block_slab","blockRuntimeId":7081},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9649},{"id":"minecraft:stone_block_slab","blockRuntimeId":7077},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9652},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9623},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9617},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9653},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9634},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9639},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9640},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9637},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9638},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9636},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9635},{"id":"minecraft:stone_block_slab","blockRuntimeId":7080},{"id":"minecraft:stone_block_slab","blockRuntimeId":7083},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9624},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9633},{"id":"minecraft:stone_block_slab","blockRuntimeId":7082},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9650},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9618},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9619},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9620},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9621},{"id":"minecraft:crimson_slab","blockRuntimeId":10558},{"id":"minecraft:warped_slab","blockRuntimeId":11683},{"id":"minecraft:blackstone_slab","blockRuntimeId":2393},{"id":"minecraft:polished_blackstone_slab","blockRuntimeId":11140},{"id":"minecraft:polished_blackstone_brick_slab","blockRuntimeId":6993},{"id":"minecraft:cut_copper_slab","blockRuntimeId":9025},{"id":"minecraft:exposed_cut_copper_slab","blockRuntimeId":11811},{"id":"minecraft:weathered_cut_copper_slab","blockRuntimeId":11187},{"id":"minecraft:oxidized_cut_copper_slab","blockRuntimeId":9078},{"id":"minecraft:waxed_cut_copper_slab","blockRuntimeId":13620},{"id":"minecraft:waxed_exposed_cut_copper_slab","blockRuntimeId":680},{"id":"minecraft:waxed_weathered_cut_copper_slab","blockRuntimeId":11755},{"id":"minecraft:waxed_oxidized_cut_copper_slab","blockRuntimeId":2001},{"id":"minecraft:cobbled_deepslate_slab","blockRuntimeId":13010},{"id":"minecraft:polished_deepslate_slab","blockRuntimeId":749},{"id":"minecraft:deepslate_tile_slab","blockRuntimeId":7102},{"id":"minecraft:deepslate_brick_slab","blockRuntimeId":6107},{"id":"minecraft:mud_brick_slab","blockRuntimeId":6695},{"id":"minecraft:brick_block","blockRuntimeId":8121},{"id":"minecraft:chiseled_nether_bricks","blockRuntimeId":12930},{"id":"minecraft:cracked_nether_bricks","blockRuntimeId":7418},{"id":"minecraft:quartz_bricks","blockRuntimeId":11502},{"id":"minecraft:stonebrick","blockRuntimeId":11757},{"id":"minecraft:stonebrick","blockRuntimeId":11758},{"id":"minecraft:stonebrick","blockRuntimeId":11759},{"id":"minecraft:stonebrick","blockRuntimeId":11760},{"id":"minecraft:end_bricks","blockRuntimeId":741},{"id":"minecraft:prismarine","blockRuntimeId":11236},{"id":"minecraft:polished_blackstone_bricks","blockRuntimeId":7996},{"id":"minecraft:cracked_polished_blackstone_bricks","blockRuntimeId":12877},{"id":"minecraft:gilded_blackstone","blockRuntimeId":7472},{"id":"minecraft:chiseled_polished_blackstone","blockRuntimeId":8852},{"id":"minecraft:deepslate_tiles","blockRuntimeId":7448},{"id":"minecraft:cracked_deepslate_tiles","blockRuntimeId":6954},{"id":"minecraft:deepslate_bricks","blockRuntimeId":9287},{"id":"minecraft:cracked_deepslate_bricks","blockRuntimeId":9151},{"id":"minecraft:chiseled_deepslate","blockRuntimeId":9024},{"id":"minecraft:cobblestone","blockRuntimeId":5997},{"id":"minecraft:mossy_cobblestone","blockRuntimeId":683},{"id":"minecraft:cobbled_deepslate","blockRuntimeId":11884},{"id":"minecraft:smooth_stone","blockRuntimeId":7449},{"id":"minecraft:sandstone","blockRuntimeId":6041},{"id":"minecraft:sandstone","blockRuntimeId":6042},{"id":"minecraft:sandstone","blockRuntimeId":6043},{"id":"minecraft:sandstone","blockRuntimeId":6044},{"id":"minecraft:red_sandstone","blockRuntimeId":11790},{"id":"minecraft:red_sandstone","blockRuntimeId":11791},{"id":"minecraft:red_sandstone","blockRuntimeId":11792},{"id":"minecraft:red_sandstone","blockRuntimeId":11793},{"id":"minecraft:coal_block","blockRuntimeId":9188},{"id":"minecraft:dried_kelp_block","blockRuntimeId":13784},{"id":"minecraft:gold_block","blockRuntimeId":784},{"id":"minecraft:iron_block","blockRuntimeId":14089},{"id":"minecraft:copper_block","blockRuntimeId":7967},{"id":"minecraft:exposed_copper","blockRuntimeId":1361},{"id":"minecraft:weathered_copper","blockRuntimeId":14053},{"id":"minecraft:oxidized_copper","blockRuntimeId":5931},{"id":"minecraft:waxed_copper","blockRuntimeId":13454},{"id":"minecraft:waxed_exposed_copper","blockRuntimeId":1981},{"id":"minecraft:waxed_weathered_copper","blockRuntimeId":2000},{"id":"minecraft:waxed_oxidized_copper","blockRuntimeId":13244},{"id":"minecraft:cut_copper","blockRuntimeId":8005},{"id":"minecraft:exposed_cut_copper","blockRuntimeId":11333},{"id":"minecraft:weathered_cut_copper","blockRuntimeId":12860},{"id":"minecraft:oxidized_cut_copper","blockRuntimeId":9301},{"id":"minecraft:waxed_cut_copper","blockRuntimeId":12993},{"id":"minecraft:waxed_exposed_cut_copper","blockRuntimeId":6208},{"id":"minecraft:waxed_weathered_cut_copper","blockRuntimeId":8617},{"id":"minecraft:waxed_oxidized_cut_copper","blockRuntimeId":636},{"id":"minecraft:emerald_block","blockRuntimeId":2620},{"id":"minecraft:diamond_block","blockRuntimeId":727},{"id":"minecraft:lapis_block","blockRuntimeId":7092},{"id":"minecraft:raw_iron_block","blockRuntimeId":14086},{"id":"minecraft:raw_copper_block","blockRuntimeId":9077},{"id":"minecraft:raw_gold_block","blockRuntimeId":1117},{"id":"minecraft:quartz_block","blockRuntimeId":6084},{"id":"minecraft:quartz_block","blockRuntimeId":6086},{"id":"minecraft:quartz_block","blockRuntimeId":6085},{"id":"minecraft:quartz_block","blockRuntimeId":6087},{"id":"minecraft:prismarine","blockRuntimeId":11234},{"id":"minecraft:prismarine","blockRuntimeId":11235},{"id":"minecraft:slime","blockRuntimeId":7036},{"id":"minecraft:honey_block","blockRuntimeId":2359},{"id":"minecraft:honeycomb_block","blockRuntimeId":7337},{"id":"minecraft:hay_block","blockRuntimeId":1985},{"id":"minecraft:bone_block","blockRuntimeId":7037},{"id":"minecraft:nether_brick","blockRuntimeId":12972},{"id":"minecraft:red_nether_brick","blockRuntimeId":567},{"id":"minecraft:netherite_block","blockRuntimeId":6173},{"id":"minecraft:lodestone","blockRuntimeId":14083},{"id":"minecraft:white_wool","blockRuntimeId":9189},{"id":"minecraft:light_gray_wool","blockRuntimeId":13848},{"id":"minecraft:gray_wool","blockRuntimeId":650},{"id":"minecraft:black_wool","blockRuntimeId":1120},{"id":"minecraft:brown_wool","blockRuntimeId":704},{"id":"minecraft:red_wool","blockRuntimeId":130},{"id":"minecraft:orange_wool","blockRuntimeId":1962},{"id":"minecraft:yellow_wool","blockRuntimeId":557},{"id":"minecraft:lime_wool","blockRuntimeId":11129},{"id":"minecraft:green_wool","blockRuntimeId":6121},{"id":"minecraft:cyan_wool","blockRuntimeId":9125},{"id":"minecraft:light_blue_wool","blockRuntimeId":12346},{"id":"minecraft:blue_wool","blockRuntimeId":9302},{"id":"minecraft:purple_wool","blockRuntimeId":14088},{"id":"minecraft:magenta_wool","blockRuntimeId":2439},{"id":"minecraft:pink_wool","blockRuntimeId":6174},{"id":"minecraft:white_carpet","blockRuntimeId":12960},{"id":"minecraft:light_gray_carpet","blockRuntimeId":14087},{"id":"minecraft:gray_carpet","blockRuntimeId":653},{"id":"minecraft:black_carpet","blockRuntimeId":11168},{"id":"minecraft:brown_carpet","blockRuntimeId":2410},{"id":"minecraft:red_carpet","blockRuntimeId":13118},{"id":"minecraft:orange_carpet","blockRuntimeId":12305},{"id":"minecraft:yellow_carpet","blockRuntimeId":10581},{"id":"minecraft:lime_carpet","blockRuntimeId":11928},{"id":"minecraft:green_carpet","blockRuntimeId":6122},{"id":"minecraft:cyan_carpet","blockRuntimeId":6000},{"id":"minecraft:light_blue_carpet","blockRuntimeId":8179},{"id":"minecraft:blue_carpet","blockRuntimeId":600},{"id":"minecraft:purple_carpet","blockRuntimeId":13471},{"id":"minecraft:magenta_carpet","blockRuntimeId":687},{"id":"minecraft:pink_carpet","blockRuntimeId":13413},{"id":"minecraft:white_concrete_powder","blockRuntimeId":8564},{"id":"minecraft:light_gray_concrete_powder","blockRuntimeId":12944},{"id":"minecraft:gray_concrete_powder","blockRuntimeId":13064},{"id":"minecraft:black_concrete_powder","blockRuntimeId":1154},{"id":"minecraft:brown_concrete_powder","blockRuntimeId":10129},{"id":"minecraft:red_concrete_powder","blockRuntimeId":12943},{"id":"minecraft:orange_concrete_powder","blockRuntimeId":14050},{"id":"minecraft:yellow_concrete_powder","blockRuntimeId":13286},{"id":"minecraft:lime_concrete_powder","blockRuntimeId":13804},{"id":"minecraft:green_concrete_powder","blockRuntimeId":12242},{"id":"minecraft:cyan_concrete_powder","blockRuntimeId":5908},{"id":"minecraft:light_blue_concrete_powder","blockRuntimeId":65},{"id":"minecraft:blue_concrete_powder","blockRuntimeId":11925},{"id":"minecraft:purple_concrete_powder","blockRuntimeId":11748},{"id":"minecraft:magenta_concrete_powder","blockRuntimeId":7304},{"id":"minecraft:pink_concrete_powder","blockRuntimeId":7104},{"id":"minecraft:white_concrete","blockRuntimeId":13879},{"id":"minecraft:light_gray_concrete","blockRuntimeId":1972},{"id":"minecraft:gray_concrete","blockRuntimeId":13065},{"id":"minecraft:black_concrete","blockRuntimeId":11909},{"id":"minecraft:brown_concrete","blockRuntimeId":11367},{"id":"minecraft:red_concrete","blockRuntimeId":13412},{"id":"minecraft:orange_concrete","blockRuntimeId":11926},{"id":"minecraft:yellow_concrete","blockRuntimeId":5999},{"id":"minecraft:lime_concrete","blockRuntimeId":7468},{"id":"minecraft:green_concrete","blockRuntimeId":10555},{"id":"minecraft:cyan_concrete","blockRuntimeId":12961},{"id":"minecraft:light_blue_concrete","blockRuntimeId":13137},{"id":"minecraft:blue_concrete","blockRuntimeId":12971},{"id":"minecraft:purple_concrete","blockRuntimeId":6988},{"id":"minecraft:magenta_concrete","blockRuntimeId":7209},{"id":"minecraft:pink_concrete","blockRuntimeId":5521},{"id":"minecraft:clay","blockRuntimeId":12394},{"id":"minecraft:hardened_clay","blockRuntimeId":1393},{"id":"minecraft:white_terracotta","blockRuntimeId":8175},{"id":"minecraft:light_gray_terracotta","blockRuntimeId":1783},{"id":"minecraft:gray_terracotta","blockRuntimeId":7451},{"id":"minecraft:black_terracotta","blockRuntimeId":11255},{"id":"minecraft:brown_terracotta","blockRuntimeId":13830},{"id":"minecraft:red_terracotta","blockRuntimeId":2434},{"id":"minecraft:orange_terracotta","blockRuntimeId":13243},{"id":"minecraft:yellow_terracotta","blockRuntimeId":6996},{"id":"minecraft:lime_terracotta","blockRuntimeId":14060},{"id":"minecraft:green_terracotta","blockRuntimeId":6106},{"id":"minecraft:cyan_terracotta"},{"id":"minecraft:light_blue_terracotta","blockRuntimeId":7203},{"id":"minecraft:blue_terracotta","blockRuntimeId":6040},{"id":"minecraft:purple_terracotta","blockRuntimeId":11541},{"id":"minecraft:magenta_terracotta","blockRuntimeId":7474},{"id":"minecraft:pink_terracotta","blockRuntimeId":6949},{"id":"minecraft:white_glazed_terracotta","blockRuntimeId":9397},{"id":"minecraft:silver_glazed_terracotta","blockRuntimeId":5507},{"id":"minecraft:gray_glazed_terracotta","blockRuntimeId":14077},{"id":"minecraft:black_glazed_terracotta","blockRuntimeId":10052},{"id":"minecraft:brown_glazed_terracotta","blockRuntimeId":5909},{"id":"minecraft:red_glazed_terracotta","blockRuntimeId":6961},{"id":"minecraft:orange_glazed_terracotta","blockRuntimeId":2610},{"id":"minecraft:yellow_glazed_terracotta","blockRuntimeId":2396},{"id":"minecraft:lime_glazed_terracotta","blockRuntimeId":654},{"id":"minecraft:green_glazed_terracotta","blockRuntimeId":11821},{"id":"minecraft:cyan_glazed_terracotta","blockRuntimeId":9145},{"id":"minecraft:light_blue_glazed_terracotta","blockRuntimeId":9294},{"id":"minecraft:blue_glazed_terracotta","blockRuntimeId":9288},{"id":"minecraft:purple_glazed_terracotta","blockRuntimeId":12236},{"id":"minecraft:magenta_glazed_terracotta","blockRuntimeId":2440},{"id":"minecraft:pink_glazed_terracotta","blockRuntimeId":11749},{"id":"minecraft:purpur_block","blockRuntimeId":13428},{"id":"minecraft:purpur_block","blockRuntimeId":13430},{"id":"minecraft:packed_mud","blockRuntimeId":744},{"id":"minecraft:mud_bricks","blockRuntimeId":12100},{"id":"minecraft:nether_wart_block","blockRuntimeId":7106},{"id":"minecraft:warped_wart_block","blockRuntimeId":10563},{"id":"minecraft:shroomlight","blockRuntimeId":8835},{"id":"minecraft:crimson_nylium","blockRuntimeId":6985},{"id":"minecraft:warped_nylium","blockRuntimeId":11500},{"id":"minecraft:netherrack","blockRuntimeId":12268},{"id":"minecraft:basalt","blockRuntimeId":7199},{"id":"minecraft:polished_basalt","blockRuntimeId":29},{"id":"minecraft:smooth_basalt","blockRuntimeId":2617},{"id":"minecraft:soul_soil","blockRuntimeId":9659},{"id":"minecraft:dirt","blockRuntimeId":9578},{"id":"minecraft:dirt","blockRuntimeId":9579},{"id":"minecraft:farmland","blockRuntimeId":6697},{"id":"minecraft:grass_block","blockRuntimeId":10617},{"id":"minecraft:grass_path","blockRuntimeId":13887},{"id":"minecraft:podzol","blockRuntimeId":7966},{"id":"minecraft:mycelium","blockRuntimeId":6071},{"id":"minecraft:mud","blockRuntimeId":11886},{"id":"minecraft:stone","blockRuntimeId":1791},{"id":"minecraft:iron_ore","blockRuntimeId":8006},{"id":"minecraft:gold_ore","blockRuntimeId":2395},{"id":"minecraft:diamond_ore","blockRuntimeId":7207},{"id":"minecraft:lapis_ore","blockRuntimeId":13411},{"id":"minecraft:redstone_ore","blockRuntimeId":7095},{"id":"minecraft:coal_ore","blockRuntimeId":7093},{"id":"minecraft:copper_ore","blockRuntimeId":5932},{"id":"minecraft:emerald_ore","blockRuntimeId":13047},{"id":"minecraft:quartz_ore","blockRuntimeId":7362},{"id":"minecraft:nether_gold_ore","blockRuntimeId":32},{"id":"minecraft:ancient_debris","blockRuntimeId":11257},{"id":"minecraft:deepslate_iron_ore","blockRuntimeId":12973},{"id":"minecraft:deepslate_gold_ore","blockRuntimeId":11256},{"id":"minecraft:deepslate_diamond_ore","blockRuntimeId":13831},{"id":"minecraft:deepslate_lapis_ore","blockRuntimeId":12945},{"id":"minecraft:deepslate_redstone_ore","blockRuntimeId":11828},{"id":"minecraft:deepslate_emerald_ore","blockRuntimeId":11501},{"id":"minecraft:deepslate_coal_ore","blockRuntimeId":12859},{"id":"minecraft:deepslate_copper_ore","blockRuntimeId":117},{"id":"minecraft:gravel","blockRuntimeId":14115},{"id":"minecraft:granite","blockRuntimeId":86},{"id":"minecraft:diorite","blockRuntimeId":152},{"id":"minecraft:andesite","blockRuntimeId":1789},{"id":"minecraft:blackstone","blockRuntimeId":13299},{"id":"minecraft:deepslate","blockRuntimeId":684},{"id":"minecraft:polished_granite","blockRuntimeId":1164},{"id":"minecraft:polished_diorite","blockRuntimeId":10045},{"id":"minecraft:polished_andesite","blockRuntimeId":13249},{"id":"minecraft:polished_blackstone","blockRuntimeId":6070},{"id":"minecraft:polished_deepslate","blockRuntimeId":13476},{"id":"minecraft:sand","blockRuntimeId":6998},{"id":"minecraft:sand","blockRuntimeId":6999},{"id":"minecraft:cactus","blockRuntimeId":12208},{"id":"minecraft:oak_log","blockRuntimeId":737},{"id":"minecraft:stripped_oak_log","blockRuntimeId":13246},{"id":"minecraft:spruce_log","blockRuntimeId":7073},{"id":"minecraft:stripped_spruce_log","blockRuntimeId":11434},{"id":"minecraft:birch_log","blockRuntimeId":1792},{"id":"minecraft:stripped_birch_log","blockRuntimeId":10709},{"id":"minecraft:jungle_log","blockRuntimeId":642},{"id":"minecraft:stripped_jungle_log","blockRuntimeId":1778},{"id":"minecraft:acacia_log","blockRuntimeId":7180},{"id":"minecraft:stripped_acacia_log","blockRuntimeId":10098},{"id":"minecraft:dark_oak_log","blockRuntimeId":4937},{"id":"minecraft:stripped_dark_oak_log","blockRuntimeId":638},{"id":"minecraft:mangrove_log","blockRuntimeId":1104},{"id":"minecraft:stripped_mangrove_log","blockRuntimeId":14112},{"id":"minecraft:cherry_log","blockRuntimeId":12949},{"id":"minecraft:stripped_cherry_log","blockRuntimeId":7288},{"id":"minecraft:crimson_stem","blockRuntimeId":10552},{"id":"minecraft:stripped_crimson_stem","blockRuntimeId":12170},{"id":"minecraft:warped_stem","blockRuntimeId":11685},{"id":"minecraft:stripped_warped_stem","blockRuntimeId":13097},{"id":"minecraft:oak_wood","blockRuntimeId":8619},{"id":"minecraft:spruce_wood","blockRuntimeId":13296},{"id":"minecraft:birch_wood","blockRuntimeId":1982},{"id":"minecraft:jungle_wood","blockRuntimeId":1997},{"id":"minecraft:acacia_wood","blockRuntimeId":12113},{"id":"minecraft:dark_oak_wood","blockRuntimeId":10},{"id":"minecraft:stripped_oak_wood","blockRuntimeId":8176},{"id":"minecraft:stripped_spruce_wood","blockRuntimeId":1959},{"id":"minecraft:stripped_birch_wood","blockRuntimeId":7415},{"id":"minecraft:stripped_jungle_wood","blockRuntimeId":1099},{"id":"minecraft:stripped_acacia_wood","blockRuntimeId":724},{"id":"minecraft:stripped_dark_oak_wood","blockRuntimeId":8614},{"id":"minecraft:mangrove_wood","blockRuntimeId":6955},{"id":"minecraft:stripped_mangrove_wood","blockRuntimeId":7032},{"id":"minecraft:cherry_wood","blockRuntimeId":12385},{"id":"minecraft:stripped_cherry_wood","blockRuntimeId":8646},{"id":"minecraft:crimson_hyphae","blockRuntimeId":7139},{"id":"minecraft:stripped_crimson_hyphae","blockRuntimeId":11689},{"id":"minecraft:warped_hyphae","blockRuntimeId":10560},{"id":"minecraft:stripped_warped_hyphae","blockRuntimeId":9403},{"id":"minecraft:bamboo_block","blockRuntimeId":66},{"id":"minecraft:stripped_bamboo_block","blockRuntimeId":5976},{"id":"minecraft:oak_leaves","blockRuntimeId":2003},{"id":"minecraft:spruce_leaves","blockRuntimeId":7250},{"id":"minecraft:birch_leaves","blockRuntimeId":6945},{"id":"minecraft:jungle_leaves","blockRuntimeId":9132},{"id":"minecraft:acacia_leaves","blockRuntimeId":2652},{"id":"minecraft:dark_oak_leaves","blockRuntimeId":11189},{"id":"minecraft:mangrove_leaves","blockRuntimeId":11880},{"id":"minecraft:cherry_leaves","blockRuntimeId":10048},{"id":"minecraft:azalea_leaves","blockRuntimeId":13424},{"id":"minecraft:azalea_leaves_flowered","blockRuntimeId":11489},{"id":"minecraft:sapling","blockRuntimeId":2171},{"id":"minecraft:sapling","blockRuntimeId":2172},{"id":"minecraft:sapling","blockRuntimeId":2173},{"id":"minecraft:sapling","blockRuntimeId":2174},{"id":"minecraft:sapling","blockRuntimeId":2175},{"id":"minecraft:sapling","blockRuntimeId":2176},{"id":"minecraft:mangrove_propagule","blockRuntimeId":12198},{"id":"minecraft:cherry_sapling","blockRuntimeId":12947},{"id":"minecraft:bee_nest","blockRuntimeId":9582},{"id":"minecraft:wheat_seeds"},{"id":"minecraft:pumpkin_seeds"},{"id":"minecraft:melon_seeds"},{"id":"minecraft:beetroot_seeds"},{"id":"minecraft:torchflower_seeds"},{"id":"minecraft:pitcher_pod"},{"id":"minecraft:wheat"},{"id":"minecraft:beetroot"},{"id":"minecraft:potato"},{"id":"minecraft:poisonous_potato"},{"id":"minecraft:carrot"},{"id":"minecraft:golden_carrot"},{"id":"minecraft:apple"},{"id":"minecraft:golden_apple"},{"id":"minecraft:enchanted_golden_apple"},{"id":"minecraft:melon_block","blockRuntimeId":1153},{"id":"minecraft:melon_slice"},{"id":"minecraft:glistering_melon_slice"},{"id":"minecraft:sweet_berries"},{"id":"minecraft:glow_berries"},{"id":"minecraft:pumpkin","blockRuntimeId":7444},{"id":"minecraft:carved_pumpkin","blockRuntimeId":13075},{"id":"minecraft:lit_pumpkin","blockRuntimeId":11887},{"id":"minecraft:honeycomb"},{"id":"minecraft:tallgrass","blockRuntimeId":2413},{"id":"minecraft:double_plant","blockRuntimeId":9246},{"id":"minecraft:tallgrass","blockRuntimeId":2412},{"id":"minecraft:double_plant","blockRuntimeId":9245},{"id":"minecraft:nether_sprouts"},{"id":"minecraft:fire_coral","blockRuntimeId":1790},{"id":"minecraft:brain_coral","blockRuntimeId":1958},{"id":"minecraft:bubble_coral","blockRuntimeId":11370},{"id":"minecraft:tube_coral","blockRuntimeId":13487},{"id":"minecraft:horn_coral","blockRuntimeId":5998},{"id":"minecraft:dead_fire_coral","blockRuntimeId":11425},{"id":"minecraft:dead_brain_coral","blockRuntimeId":12858},{"id":"minecraft:dead_bubble_coral","blockRuntimeId":12946},{"id":"minecraft:dead_tube_coral","blockRuntimeId":7105},{"id":"minecraft:dead_horn_coral","blockRuntimeId":10616},{"id":"minecraft:coral_fan","blockRuntimeId":7504},{"id":"minecraft:coral_fan","blockRuntimeId":7502},{"id":"minecraft:coral_fan","blockRuntimeId":7503},{"id":"minecraft:coral_fan","blockRuntimeId":7501},{"id":"minecraft:coral_fan","blockRuntimeId":7505},{"id":"minecraft:coral_fan_dead","blockRuntimeId":79},{"id":"minecraft:coral_fan_dead","blockRuntimeId":77},{"id":"minecraft:coral_fan_dead","blockRuntimeId":78},{"id":"minecraft:coral_fan_dead","blockRuntimeId":76},{"id":"minecraft:coral_fan_dead","blockRuntimeId":80},{"id":"minecraft:crimson_roots","blockRuntimeId":13279},{"id":"minecraft:warped_roots","blockRuntimeId":7208},{"id":"minecraft:yellow_flower","blockRuntimeId":1051},{"id":"minecraft:red_flower","blockRuntimeId":6001},{"id":"minecraft:red_flower","blockRuntimeId":6002},{"id":"minecraft:red_flower","blockRuntimeId":6003},{"id":"minecraft:red_flower","blockRuntimeId":6004},{"id":"minecraft:red_flower","blockRuntimeId":6005},{"id":"minecraft:red_flower","blockRuntimeId":6006},{"id":"minecraft:red_flower","blockRuntimeId":6007},{"id":"minecraft:red_flower","blockRuntimeId":6008},{"id":"minecraft:red_flower","blockRuntimeId":6009},{"id":"minecraft:red_flower","blockRuntimeId":6010},{"id":"minecraft:red_flower","blockRuntimeId":6011},{"id":"minecraft:double_plant","blockRuntimeId":9243},{"id":"minecraft:double_plant","blockRuntimeId":9244},{"id":"minecraft:double_plant","blockRuntimeId":9247},{"id":"minecraft:double_plant","blockRuntimeId":9248},{"id":"minecraft:pitcher_plant","blockRuntimeId":6155},{"id":"minecraft:pink_petals","blockRuntimeId":7541},{"id":"minecraft:wither_rose","blockRuntimeId":11332},{"id":"minecraft:torchflower","blockRuntimeId":11209},{"id":"minecraft:white_dye"},{"id":"minecraft:light_gray_dye"},{"id":"minecraft:gray_dye"},{"id":"minecraft:black_dye"},{"id":"minecraft:brown_dye"},{"id":"minecraft:red_dye"},{"id":"minecraft:orange_dye"},{"id":"minecraft:yellow_dye"},{"id":"minecraft:lime_dye"},{"id":"minecraft:green_dye"},{"id":"minecraft:cyan_dye"},{"id":"minecraft:light_blue_dye"},{"id":"minecraft:blue_dye"},{"id":"minecraft:purple_dye"},{"id":"minecraft:magenta_dye"},{"id":"minecraft:pink_dye"},{"id":"minecraft:ink_sac"},{"id":"minecraft:glow_ink_sac"},{"id":"minecraft:cocoa_beans"},{"id":"minecraft:lapis_lazuli"},{"id":"minecraft:bone_meal"},{"id":"minecraft:vine","blockRuntimeId":2361},{"id":"minecraft:weeping_vines","blockRuntimeId":9303},{"id":"minecraft:twisting_vines","blockRuntimeId":9514},{"id":"minecraft:waterlily","blockRuntimeId":2618},{"id":"minecraft:seagrass","blockRuntimeId":677},{"id":"minecraft:kelp"},{"id":"minecraft:deadbush","blockRuntimeId":7993},{"id":"minecraft:bamboo","blockRuntimeId":6072},{"id":"minecraft:snow","blockRuntimeId":6997},{"id":"minecraft:ice","blockRuntimeId":11891},{"id":"minecraft:packed_ice","blockRuntimeId":743},{"id":"minecraft:blue_ice","blockRuntimeId":12255},{"id":"minecraft:snow_layer","blockRuntimeId":576},{"id":"minecraft:pointed_dripstone","blockRuntimeId":13113},{"id":"minecraft:dripstone_block","blockRuntimeId":2360},{"id":"minecraft:moss_carpet","blockRuntimeId":747},{"id":"minecraft:moss_block","blockRuntimeId":11747},{"id":"minecraft:dirt_with_roots","blockRuntimeId":9187},{"id":"minecraft:hanging_roots","blockRuntimeId":627},{"id":"minecraft:mangrove_roots","blockRuntimeId":11342},{"id":"minecraft:muddy_mangrove_roots","blockRuntimeId":1096},{"id":"minecraft:big_dripleaf","blockRuntimeId":10717},{"id":"minecraft:small_dripleaf_block","blockRuntimeId":7165},{"id":"minecraft:spore_blossom","blockRuntimeId":13012},{"id":"minecraft:azalea","blockRuntimeId":12099},{"id":"minecraft:flowering_azalea","blockRuntimeId":9300},{"id":"minecraft:glow_lichen","blockRuntimeId":9507},{"id":"minecraft:amethyst_block","blockRuntimeId":783},{"id":"minecraft:budding_amethyst","blockRuntimeId":12224},{"id":"minecraft:amethyst_cluster","blockRuntimeId":13614},{"id":"minecraft:large_amethyst_bud","blockRuntimeId":8044},{"id":"minecraft:medium_amethyst_bud","blockRuntimeId":7221},{"id":"minecraft:small_amethyst_bud","blockRuntimeId":1055},{"id":"minecraft:tuff","blockRuntimeId":1103},{"id":"minecraft:calcite","blockRuntimeId":637},{"id":"minecraft:chicken"},{"id":"minecraft:porkchop"},{"id":"minecraft:beef"},{"id":"minecraft:mutton"},{"id":"minecraft:rabbit"},{"id":"minecraft:cod"},{"id":"minecraft:salmon"},{"id":"minecraft:tropical_fish"},{"id":"minecraft:pufferfish"},{"id":"minecraft:brown_mushroom","blockRuntimeId":5907},{"id":"minecraft:red_mushroom","blockRuntimeId":7471},{"id":"minecraft:crimson_fungus","blockRuntimeId":13474},{"id":"minecraft:warped_fungus","blockRuntimeId":748},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13062},{"id":"minecraft:red_mushroom_block","blockRuntimeId":5993},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13063},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13048},{"id":"minecraft:egg"},{"id":"minecraft:sugar_cane"},{"id":"minecraft:sugar"},{"id":"minecraft:rotten_flesh"},{"id":"minecraft:bone"},{"id":"minecraft:web","blockRuntimeId":11916},{"id":"minecraft:spider_eye"},{"id":"minecraft:mob_spawner","blockRuntimeId":1163},{"id":"minecraft:end_portal_frame","blockRuntimeId":11210},{"id":"minecraft:monster_egg","blockRuntimeId":6930},{"id":"minecraft:monster_egg","blockRuntimeId":6931},{"id":"minecraft:monster_egg","blockRuntimeId":6932},{"id":"minecraft:monster_egg","blockRuntimeId":6933},{"id":"minecraft:monster_egg","blockRuntimeId":6934},{"id":"minecraft:monster_egg","blockRuntimeId":6935},{"id":"minecraft:infested_deepslate","blockRuntimeId":7957},{"id":"minecraft:dragon_egg","blockRuntimeId":12970},{"id":"minecraft:turtle_egg","blockRuntimeId":13805},{"id":"minecraft:sniffer_egg","blockRuntimeId":12225},{"id":"minecraft:frog_spawn","blockRuntimeId":7254},{"id":"minecraft:pearlescent_froglight","blockRuntimeId":11620},{"id":"minecraft:verdant_froglight","blockRuntimeId":11679},{"id":"minecraft:ochre_froglight","blockRuntimeId":5324},{"id":"minecraft:chicken_spawn_egg"},{"id":"minecraft:bee_spawn_egg"},{"id":"minecraft:cow_spawn_egg"},{"id":"minecraft:pig_spawn_egg"},{"id":"minecraft:sheep_spawn_egg"},{"id":"minecraft:wolf_spawn_egg"},{"id":"minecraft:polar_bear_spawn_egg"},{"id":"minecraft:ocelot_spawn_egg"},{"id":"minecraft:cat_spawn_egg"},{"id":"minecraft:mooshroom_spawn_egg"},{"id":"minecraft:bat_spawn_egg"},{"id":"minecraft:parrot_spawn_egg"},{"id":"minecraft:rabbit_spawn_egg"},{"id":"minecraft:llama_spawn_egg"},{"id":"minecraft:horse_spawn_egg"},{"id":"minecraft:donkey_spawn_egg"},{"id":"minecraft:mule_spawn_egg"},{"id":"minecraft:skeleton_horse_spawn_egg"},{"id":"minecraft:zombie_horse_spawn_egg"},{"id":"minecraft:tropical_fish_spawn_egg"},{"id":"minecraft:cod_spawn_egg"},{"id":"minecraft:pufferfish_spawn_egg"},{"id":"minecraft:salmon_spawn_egg"},{"id":"minecraft:dolphin_spawn_egg"},{"id":"minecraft:turtle_spawn_egg"},{"id":"minecraft:panda_spawn_egg"},{"id":"minecraft:fox_spawn_egg"},{"id":"minecraft:creeper_spawn_egg"},{"id":"minecraft:enderman_spawn_egg"},{"id":"minecraft:silverfish_spawn_egg"},{"id":"minecraft:skeleton_spawn_egg"},{"id":"minecraft:wither_skeleton_spawn_egg"},{"id":"minecraft:stray_spawn_egg"},{"id":"minecraft:slime_spawn_egg"},{"id":"minecraft:spider_spawn_egg"},{"id":"minecraft:zombie_spawn_egg"},{"id":"minecraft:zombie_pigman_spawn_egg"},{"id":"minecraft:husk_spawn_egg"},{"id":"minecraft:drowned_spawn_egg"},{"id":"minecraft:squid_spawn_egg"},{"id":"minecraft:glow_squid_spawn_egg"},{"id":"minecraft:cave_spider_spawn_egg"},{"id":"minecraft:witch_spawn_egg"},{"id":"minecraft:guardian_spawn_egg"},{"id":"minecraft:elder_guardian_spawn_egg"},{"id":"minecraft:endermite_spawn_egg"},{"id":"minecraft:magma_cube_spawn_egg"},{"id":"minecraft:strider_spawn_egg"},{"id":"minecraft:hoglin_spawn_egg"},{"id":"minecraft:piglin_spawn_egg"},{"id":"minecraft:zoglin_spawn_egg"},{"id":"minecraft:piglin_brute_spawn_egg"},{"id":"minecraft:goat_spawn_egg"},{"id":"minecraft:axolotl_spawn_egg"},{"id":"minecraft:warden_spawn_egg"},{"id":"minecraft:allay_spawn_egg"},{"id":"minecraft:frog_spawn_egg"},{"id":"minecraft:tadpole_spawn_egg"},{"id":"minecraft:trader_llama_spawn_egg"},{"id":"minecraft:camel_spawn_egg"},{"id":"minecraft:ghast_spawn_egg"},{"id":"minecraft:blaze_spawn_egg"},{"id":"minecraft:shulker_spawn_egg"},{"id":"minecraft:vindicator_spawn_egg"},{"id":"minecraft:evoker_spawn_egg"},{"id":"minecraft:vex_spawn_egg"},{"id":"minecraft:villager_spawn_egg"},{"id":"minecraft:wandering_trader_spawn_egg"},{"id":"minecraft:zombie_villager_spawn_egg"},{"id":"minecraft:phantom_spawn_egg"},{"id":"minecraft:pillager_spawn_egg"},{"id":"minecraft:ravager_spawn_egg"},{"id":"minecraft:iron_golem_spawn_egg"},{"id":"minecraft:snow_golem_spawn_egg"},{"id":"minecraft:sniffer_spawn_egg"},{"id":"minecraft:obsidian","blockRuntimeId":1192},{"id":"minecraft:crying_obsidian","blockRuntimeId":11927},{"id":"minecraft:bedrock","blockRuntimeId":12243},{"id":"minecraft:soul_sand","blockRuntimeId":9660},{"id":"minecraft:magma","blockRuntimeId":13817},{"id":"minecraft:nether_wart"},{"id":"minecraft:end_stone","blockRuntimeId":6615},{"id":"minecraft:chorus_flower","blockRuntimeId":7391},{"id":"minecraft:chorus_plant","blockRuntimeId":9329},{"id":"minecraft:chorus_fruit"},{"id":"minecraft:popped_chorus_fruit"},{"id":"minecraft:sponge","blockRuntimeId":1380},{"id":"minecraft:sponge","blockRuntimeId":1381},{"id":"minecraft:coral_block","blockRuntimeId":9027},{"id":"minecraft:coral_block","blockRuntimeId":9028},{"id":"minecraft:coral_block","blockRuntimeId":9029},{"id":"minecraft:coral_block","blockRuntimeId":9030},{"id":"minecraft:coral_block","blockRuntimeId":9031},{"id":"minecraft:coral_block","blockRuntimeId":9032},{"id":"minecraft:coral_block","blockRuntimeId":9033},{"id":"minecraft:coral_block","blockRuntimeId":9034},{"id":"minecraft:coral_block","blockRuntimeId":9035},{"id":"minecraft:coral_block","blockRuntimeId":9036},{"id":"minecraft:sculk","blockRuntimeId":12266},{"id":"minecraft:sculk_vein","blockRuntimeId":12794},{"id":"minecraft:sculk_catalyst","blockRuntimeId":5995},{"id":"minecraft:sculk_shrieker","blockRuntimeId":646},{"id":"minecraft:sculk_sensor","blockRuntimeId":7235},{"id":"minecraft:calibrated_sculk_sensor","blockRuntimeId":10070},{"id":"minecraft:reinforced_deepslate","blockRuntimeId":10046},{"id":"minecraft:leather_helmet"},{"id":"minecraft:chainmail_helmet"},{"id":"minecraft:iron_helmet"},{"id":"minecraft:golden_helmet"},{"id":"minecraft:diamond_helmet"},{"id":"minecraft:netherite_helmet"},{"id":"minecraft:leather_chestplate"},{"id":"minecraft:chainmail_chestplate"},{"id":"minecraft:iron_chestplate"},{"id":"minecraft:golden_chestplate"},{"id":"minecraft:diamond_chestplate"},{"id":"minecraft:netherite_chestplate"},{"id":"minecraft:leather_leggings"},{"id":"minecraft:chainmail_leggings"},{"id":"minecraft:iron_leggings"},{"id":"minecraft:golden_leggings"},{"id":"minecraft:diamond_leggings"},{"id":"minecraft:netherite_leggings"},{"id":"minecraft:leather_boots"},{"id":"minecraft:chainmail_boots"},{"id":"minecraft:iron_boots"},{"id":"minecraft:golden_boots"},{"id":"minecraft:diamond_boots"},{"id":"minecraft:netherite_boots"},{"id":"minecraft:wooden_sword"},{"id":"minecraft:stone_sword"},{"id":"minecraft:iron_sword"},{"id":"minecraft:golden_sword"},{"id":"minecraft:diamond_sword"},{"id":"minecraft:netherite_sword"},{"id":"minecraft:wooden_axe"},{"id":"minecraft:stone_axe"},{"id":"minecraft:iron_axe"},{"id":"minecraft:golden_axe"},{"id":"minecraft:diamond_axe"},{"id":"minecraft:netherite_axe"},{"id":"minecraft:wooden_pickaxe"},{"id":"minecraft:stone_pickaxe"},{"id":"minecraft:iron_pickaxe"},{"id":"minecraft:golden_pickaxe"},{"id":"minecraft:diamond_pickaxe"},{"id":"minecraft:netherite_pickaxe"},{"id":"minecraft:wooden_shovel"},{"id":"minecraft:stone_shovel"},{"id":"minecraft:iron_shovel"},{"id":"minecraft:golden_shovel"},{"id":"minecraft:diamond_shovel"},{"id":"minecraft:netherite_shovel"},{"id":"minecraft:wooden_hoe"},{"id":"minecraft:stone_hoe"},{"id":"minecraft:iron_hoe"},{"id":"minecraft:golden_hoe"},{"id":"minecraft:diamond_hoe"},{"id":"minecraft:netherite_hoe"},{"id":"minecraft:bow"},{"id":"minecraft:crossbow"},{"id":"minecraft:arrow"},{"id":"minecraft:arrow","damage":6},{"id":"minecraft:arrow","damage":7},{"id":"minecraft:arrow","damage":8},{"id":"minecraft:arrow","damage":9},{"id":"minecraft:arrow","damage":10},{"id":"minecraft:arrow","damage":11},{"id":"minecraft:arrow","damage":12},{"id":"minecraft:arrow","damage":13},{"id":"minecraft:arrow","damage":14},{"id":"minecraft:arrow","damage":15},{"id":"minecraft:arrow","damage":16},{"id":"minecraft:arrow","damage":17},{"id":"minecraft:arrow","damage":18},{"id":"minecraft:arrow","damage":19},{"id":"minecraft:arrow","damage":20},{"id":"minecraft:arrow","damage":21},{"id":"minecraft:arrow","damage":22},{"id":"minecraft:arrow","damage":23},{"id":"minecraft:arrow","damage":24},{"id":"minecraft:arrow","damage":25},{"id":"minecraft:arrow","damage":26},{"id":"minecraft:arrow","damage":27},{"id":"minecraft:arrow","damage":28},{"id":"minecraft:arrow","damage":29},{"id":"minecraft:arrow","damage":30},{"id":"minecraft:arrow","damage":31},{"id":"minecraft:arrow","damage":32},{"id":"minecraft:arrow","damage":33},{"id":"minecraft:arrow","damage":34},{"id":"minecraft:arrow","damage":35},{"id":"minecraft:arrow","damage":36},{"id":"minecraft:arrow","damage":37},{"id":"minecraft:arrow","damage":38},{"id":"minecraft:arrow","damage":39},{"id":"minecraft:arrow","damage":40},{"id":"minecraft:arrow","damage":41},{"id":"minecraft:arrow","damage":42},{"id":"minecraft:arrow","damage":43},{"id":"minecraft:shield"},{"id":"minecraft:cooked_chicken"},{"id":"minecraft:cooked_porkchop"},{"id":"minecraft:cooked_beef"},{"id":"minecraft:cooked_mutton"},{"id":"minecraft:cooked_rabbit"},{"id":"minecraft:cooked_cod"},{"id":"minecraft:cooked_salmon"},{"id":"minecraft:bread"},{"id":"minecraft:mushroom_stew"},{"id":"minecraft:beetroot_soup"},{"id":"minecraft:rabbit_stew"},{"id":"minecraft:baked_potato"},{"id":"minecraft:cookie"},{"id":"minecraft:pumpkin_pie"},{"id":"minecraft:cake"},{"id":"minecraft:dried_kelp"},{"id":"minecraft:fishing_rod"},{"id":"minecraft:carrot_on_a_stick"},{"id":"minecraft:warped_fungus_on_a_stick"},{"id":"minecraft:snowball"},{"id":"minecraft:shears"},{"id":"minecraft:flint_and_steel"},{"id":"minecraft:lead"},{"id":"minecraft:clock"},{"id":"minecraft:compass"},{"id":"minecraft:recovery_compass"},{"id":"minecraft:goat_horn"},{"id":"minecraft:goat_horn","damage":1},{"id":"minecraft:goat_horn","damage":2},{"id":"minecraft:goat_horn","damage":3},{"id":"minecraft:goat_horn","damage":4},{"id":"minecraft:goat_horn","damage":5},{"id":"minecraft:goat_horn","damage":6},{"id":"minecraft:goat_horn","damage":7},{"id":"minecraft:empty_map"},{"id":"minecraft:empty_map","damage":2},{"id":"minecraft:saddle"},{"id":"minecraft:leather_horse_armor"},{"id":"minecraft:iron_horse_armor"},{"id":"minecraft:golden_horse_armor"},{"id":"minecraft:diamond_horse_armor"},{"id":"minecraft:trident"},{"id":"minecraft:turtle_helmet"},{"id":"minecraft:elytra"},{"id":"minecraft:totem_of_undying"},{"id":"minecraft:glass_bottle"},{"id":"minecraft:experience_bottle"},{"id":"minecraft:potion"},{"id":"minecraft:potion","damage":1},{"id":"minecraft:potion","damage":2},{"id":"minecraft:potion","damage":3},{"id":"minecraft:potion","damage":4},{"id":"minecraft:potion","damage":5},{"id":"minecraft:potion","damage":6},{"id":"minecraft:potion","damage":7},{"id":"minecraft:potion","damage":8},{"id":"minecraft:potion","damage":9},{"id":"minecraft:potion","damage":10},{"id":"minecraft:potion","damage":11},{"id":"minecraft:potion","damage":12},{"id":"minecraft:potion","damage":13},{"id":"minecraft:potion","damage":14},{"id":"minecraft:potion","damage":15},{"id":"minecraft:potion","damage":16},{"id":"minecraft:potion","damage":17},{"id":"minecraft:potion","damage":18},{"id":"minecraft:potion","damage":19},{"id":"minecraft:potion","damage":20},{"id":"minecraft:potion","damage":21},{"id":"minecraft:potion","damage":22},{"id":"minecraft:potion","damage":23},{"id":"minecraft:potion","damage":24},{"id":"minecraft:potion","damage":25},{"id":"minecraft:potion","damage":26},{"id":"minecraft:potion","damage":27},{"id":"minecraft:potion","damage":28},{"id":"minecraft:potion","damage":29},{"id":"minecraft:potion","damage":30},{"id":"minecraft:potion","damage":31},{"id":"minecraft:potion","damage":32},{"id":"minecraft:potion","damage":33},{"id":"minecraft:potion","damage":34},{"id":"minecraft:potion","damage":35},{"id":"minecraft:potion","damage":36},{"id":"minecraft:potion","damage":37},{"id":"minecraft:potion","damage":38},{"id":"minecraft:potion","damage":39},{"id":"minecraft:potion","damage":40},{"id":"minecraft:potion","damage":41},{"id":"minecraft:potion","damage":42},{"id":"minecraft:splash_potion"},{"id":"minecraft:splash_potion","damage":1},{"id":"minecraft:splash_potion","damage":2},{"id":"minecraft:splash_potion","damage":3},{"id":"minecraft:splash_potion","damage":4},{"id":"minecraft:splash_potion","damage":5},{"id":"minecraft:splash_potion","damage":6},{"id":"minecraft:splash_potion","damage":7},{"id":"minecraft:splash_potion","damage":8},{"id":"minecraft:splash_potion","damage":9},{"id":"minecraft:splash_potion","damage":10},{"id":"minecraft:splash_potion","damage":11},{"id":"minecraft:splash_potion","damage":12},{"id":"minecraft:splash_potion","damage":13},{"id":"minecraft:splash_potion","damage":14},{"id":"minecraft:splash_potion","damage":15},{"id":"minecraft:splash_potion","damage":16},{"id":"minecraft:splash_potion","damage":17},{"id":"minecraft:splash_potion","damage":18},{"id":"minecraft:splash_potion","damage":19},{"id":"minecraft:splash_potion","damage":20},{"id":"minecraft:splash_potion","damage":21},{"id":"minecraft:splash_potion","damage":22},{"id":"minecraft:splash_potion","damage":23},{"id":"minecraft:splash_potion","damage":24},{"id":"minecraft:splash_potion","damage":25},{"id":"minecraft:splash_potion","damage":26},{"id":"minecraft:splash_potion","damage":27},{"id":"minecraft:splash_potion","damage":28},{"id":"minecraft:splash_potion","damage":29},{"id":"minecraft:splash_potion","damage":30},{"id":"minecraft:splash_potion","damage":31},{"id":"minecraft:splash_potion","damage":32},{"id":"minecraft:splash_potion","damage":33},{"id":"minecraft:splash_potion","damage":34},{"id":"minecraft:splash_potion","damage":35},{"id":"minecraft:splash_potion","damage":36},{"id":"minecraft:splash_potion","damage":37},{"id":"minecraft:splash_potion","damage":38},{"id":"minecraft:splash_potion","damage":39},{"id":"minecraft:splash_potion","damage":40},{"id":"minecraft:splash_potion","damage":41},{"id":"minecraft:splash_potion","damage":42},{"id":"minecraft:lingering_potion"},{"id":"minecraft:lingering_potion","damage":1},{"id":"minecraft:lingering_potion","damage":2},{"id":"minecraft:lingering_potion","damage":3},{"id":"minecraft:lingering_potion","damage":4},{"id":"minecraft:lingering_potion","damage":5},{"id":"minecraft:lingering_potion","damage":6},{"id":"minecraft:lingering_potion","damage":7},{"id":"minecraft:lingering_potion","damage":8},{"id":"minecraft:lingering_potion","damage":9},{"id":"minecraft:lingering_potion","damage":10},{"id":"minecraft:lingering_potion","damage":11},{"id":"minecraft:lingering_potion","damage":12},{"id":"minecraft:lingering_potion","damage":13},{"id":"minecraft:lingering_potion","damage":14},{"id":"minecraft:lingering_potion","damage":15},{"id":"minecraft:lingering_potion","damage":16},{"id":"minecraft:lingering_potion","damage":17},{"id":"minecraft:lingering_potion","damage":18},{"id":"minecraft:lingering_potion","damage":19},{"id":"minecraft:lingering_potion","damage":20},{"id":"minecraft:lingering_potion","damage":21},{"id":"minecraft:lingering_potion","damage":22},{"id":"minecraft:lingering_potion","damage":23},{"id":"minecraft:lingering_potion","damage":24},{"id":"minecraft:lingering_potion","damage":25},{"id":"minecraft:lingering_potion","damage":26},{"id":"minecraft:lingering_potion","damage":27},{"id":"minecraft:lingering_potion","damage":28},{"id":"minecraft:lingering_potion","damage":29},{"id":"minecraft:lingering_potion","damage":30},{"id":"minecraft:lingering_potion","damage":31},{"id":"minecraft:lingering_potion","damage":32},{"id":"minecraft:lingering_potion","damage":33},{"id":"minecraft:lingering_potion","damage":34},{"id":"minecraft:lingering_potion","damage":35},{"id":"minecraft:lingering_potion","damage":36},{"id":"minecraft:lingering_potion","damage":37},{"id":"minecraft:lingering_potion","damage":38},{"id":"minecraft:lingering_potion","damage":39},{"id":"minecraft:lingering_potion","damage":40},{"id":"minecraft:lingering_potion","damage":41},{"id":"minecraft:lingering_potion","damage":42},{"id":"minecraft:spyglass"},{"id":"minecraft:brush"},{"id":"minecraft:stick"},{"id":"minecraft:bed"},{"id":"minecraft:bed","damage":8},{"id":"minecraft:bed","damage":7},{"id":"minecraft:bed","damage":15},{"id":"minecraft:bed","damage":12},{"id":"minecraft:bed","damage":14},{"id":"minecraft:bed","damage":1},{"id":"minecraft:bed","damage":4},{"id":"minecraft:bed","damage":5},{"id":"minecraft:bed","damage":13},{"id":"minecraft:bed","damage":9},{"id":"minecraft:bed","damage":3},{"id":"minecraft:bed","damage":11},{"id":"minecraft:bed","damage":10},{"id":"minecraft:bed","damage":2},{"id":"minecraft:bed","damage":6},{"id":"minecraft:torch","blockRuntimeId":2191},{"id":"minecraft:soul_torch","blockRuntimeId":7960},{"id":"minecraft:sea_pickle","blockRuntimeId":10105},{"id":"minecraft:lantern","blockRuntimeId":12306},{"id":"minecraft:soul_lantern","blockRuntimeId":9576},{"id":"minecraft:candle","blockRuntimeId":13100},{"id":"minecraft:white_candle","blockRuntimeId":9096},{"id":"minecraft:orange_candle","blockRuntimeId":1121},{"id":"minecraft:magenta_candle","blockRuntimeId":1181},{"id":"minecraft:light_blue_candle","blockRuntimeId":7435},{"id":"minecraft:yellow_candle","blockRuntimeId":11343},{"id":"minecraft:lime_candle","blockRuntimeId":11519},{"id":"minecraft:pink_candle","blockRuntimeId":13066},{"id":"minecraft:gray_candle","blockRuntimeId":2423},{"id":"minecraft:light_gray_candle","blockRuntimeId":11372},{"id":"minecraft:cyan_candle","blockRuntimeId":13446},{"id":"minecraft:purple_candle","blockRuntimeId":12269},{"id":"minecraft:blue_candle","blockRuntimeId":2},{"id":"minecraft:brown_candle","blockRuntimeId":10530},{"id":"minecraft:green_candle","blockRuntimeId":1973},{"id":"minecraft:red_candle","blockRuntimeId":7997},{"id":"minecraft:black_candle","blockRuntimeId":592},{"id":"minecraft:crafting_table","blockRuntimeId":10104},{"id":"minecraft:cartography_table","blockRuntimeId":14116},{"id":"minecraft:fletching_table","blockRuntimeId":10047},{"id":"minecraft:smithing_table","blockRuntimeId":6117},{"id":"minecraft:beehive","blockRuntimeId":11274},{"id":"minecraft:suspicious_sand","blockRuntimeId":2622},{"id":"minecraft:suspicious_gravel","blockRuntimeId":8082},{"id":"minecraft:campfire"},{"id":"minecraft:soul_campfire"},{"id":"minecraft:furnace","blockRuntimeId":13610},{"id":"minecraft:blast_furnace","blockRuntimeId":13275},{"id":"minecraft:smoker","blockRuntimeId":1784},{"id":"minecraft:respawn_anchor","blockRuntimeId":1967},{"id":"minecraft:brewing_stand"},{"id":"minecraft:anvil","blockRuntimeId":11847},{"id":"minecraft:anvil","blockRuntimeId":11851},{"id":"minecraft:anvil","blockRuntimeId":11855},{"id":"minecraft:grindstone","blockRuntimeId":13832},{"id":"minecraft:enchanting_table","blockRuntimeId":11933},{"id":"minecraft:bookshelf","blockRuntimeId":11885},{"id":"minecraft:chiseled_bookshelf","blockRuntimeId":787},{"id":"minecraft:lectern","blockRuntimeId":12162},{"id":"minecraft:cauldron"},{"id":"minecraft:composter","blockRuntimeId":9206},{"id":"minecraft:chest","blockRuntimeId":12383},{"id":"minecraft:trapped_chest","blockRuntimeId":9409},{"id":"minecraft:ender_chest","blockRuntimeId":7218},{"id":"minecraft:barrel","blockRuntimeId":7379},{"id":"minecraft:undyed_shulker_box","blockRuntimeId":6069},{"id":"minecraft:white_shulker_box","blockRuntimeId":1118},{"id":"minecraft:light_gray_shulker_box","blockRuntimeId":10564},{"id":"minecraft:gray_shulker_box","blockRuntimeId":9136},{"id":"minecraft:black_shulker_box","blockRuntimeId":11158},{"id":"minecraft:brown_shulker_box","blockRuntimeId":12098},{"id":"minecraft:red_shulker_box","blockRuntimeId":7287},{"id":"minecraft:orange_shulker_box","blockRuntimeId":11371},{"id":"minecraft:yellow_shulker_box","blockRuntimeId":126},{"id":"minecraft:lime_shulker_box","blockRuntimeId":1052},{"id":"minecraft:green_shulker_box","blockRuntimeId":11688},{"id":"minecraft:cyan_shulker_box","blockRuntimeId":12256},{"id":"minecraft:light_blue_shulker_box","blockRuntimeId":12189},{"id":"minecraft:blue_shulker_box","blockRuntimeId":11420},{"id":"minecraft:purple_shulker_box","blockRuntimeId":13074},{"id":"minecraft:magenta_shulker_box","blockRuntimeId":742},{"id":"minecraft:pink_shulker_box","blockRuntimeId":7226},{"id":"minecraft:armor_stand"},{"id":"minecraft:noteblock","blockRuntimeId":1102},{"id":"minecraft:jukebox","blockRuntimeId":8645},{"id":"minecraft:music_disc_13"},{"id":"minecraft:music_disc_cat"},{"id":"minecraft:music_disc_blocks"},{"id":"minecraft:music_disc_chirp"},{"id":"minecraft:music_disc_far"},{"id":"minecraft:music_disc_mall"},{"id":"minecraft:music_disc_mellohi"},{"id":"minecraft:music_disc_stal"},{"id":"minecraft:music_disc_strad"},{"id":"minecraft:music_disc_ward"},{"id":"minecraft:music_disc_11"},{"id":"minecraft:music_disc_wait"},{"id":"minecraft:music_disc_otherside"},{"id":"minecraft:music_disc_5"},{"id":"minecraft:music_disc_pigstep"},{"id":"minecraft:music_disc_relic"},{"id":"minecraft:disc_fragment_5"},{"id":"minecraft:glowstone_dust"},{"id":"minecraft:glowstone","blockRuntimeId":6669},{"id":"minecraft:redstone_lamp","blockRuntimeId":682},{"id":"minecraft:sea_lantern","blockRuntimeId":13250},{"id":"minecraft:oak_sign"},{"id":"minecraft:spruce_sign"},{"id":"minecraft:birch_sign"},{"id":"minecraft:jungle_sign"},{"id":"minecraft:acacia_sign"},{"id":"minecraft:dark_oak_sign"},{"id":"minecraft:mangrove_sign"},{"id":"minecraft:cherry_sign"},{"id":"minecraft:bamboo_sign"},{"id":"minecraft:crimson_sign"},{"id":"minecraft:warped_sign"},{"id":"minecraft:oak_hanging_sign"},{"id":"minecraft:spruce_hanging_sign"},{"id":"minecraft:birch_hanging_sign"},{"id":"minecraft:jungle_hanging_sign"},{"id":"minecraft:acacia_hanging_sign"},{"id":"minecraft:dark_oak_hanging_sign"},{"id":"minecraft:mangrove_hanging_sign"},{"id":"minecraft:cherry_hanging_sign"},{"id":"minecraft:bamboo_hanging_sign"},{"id":"minecraft:crimson_hanging_sign"},{"id":"minecraft:warped_hanging_sign"},{"id":"minecraft:painting"},{"id":"minecraft:frame"},{"id":"minecraft:glow_frame"},{"id":"minecraft:honey_bottle"},{"id":"minecraft:flower_pot"},{"id":"minecraft:bowl"},{"id":"minecraft:bucket"},{"id":"minecraft:milk_bucket"},{"id":"minecraft:water_bucket"},{"id":"minecraft:lava_bucket"},{"id":"minecraft:cod_bucket"},{"id":"minecraft:salmon_bucket"},{"id":"minecraft:tropical_fish_bucket"},{"id":"minecraft:pufferfish_bucket"},{"id":"minecraft:powder_snow_bucket"},{"id":"minecraft:axolotl_bucket"},{"id":"minecraft:tadpole_bucket"},{"id":"minecraft:skull","damage":3},{"id":"minecraft:skull","damage":2},{"id":"minecraft:skull","damage":4},{"id":"minecraft:skull","damage":5},{"id":"minecraft:skull"},{"id":"minecraft:skull","damage":1},{"id":"minecraft:skull","damage":6},{"id":"minecraft:beacon","blockRuntimeId":566},{"id":"minecraft:bell","blockRuntimeId":12130},{"id":"minecraft:conduit","blockRuntimeId":7035},{"id":"minecraft:stonecutter_block","blockRuntimeId":13284},{"id":"minecraft:coal"},{"id":"minecraft:charcoal"},{"id":"minecraft:diamond"},{"id":"minecraft:iron_nugget"},{"id":"minecraft:raw_iron"},{"id":"minecraft:raw_gold"},{"id":"minecraft:raw_copper"},{"id":"minecraft:copper_ingot"},{"id":"minecraft:iron_ingot"},{"id":"minecraft:netherite_scrap"},{"id":"minecraft:netherite_ingot"},{"id":"minecraft:gold_nugget"},{"id":"minecraft:gold_ingot"},{"id":"minecraft:emerald"},{"id":"minecraft:quartz"},{"id":"minecraft:clay_ball"},{"id":"minecraft:brick"},{"id":"minecraft:netherbrick"},{"id":"minecraft:prismarine_shard"},{"id":"minecraft:amethyst_shard"},{"id":"minecraft:prismarine_crystals"},{"id":"minecraft:nautilus_shell"},{"id":"minecraft:heart_of_the_sea"},{"id":"minecraft:turtle_scute"},{"id":"minecraft:phantom_membrane"},{"id":"minecraft:string"},{"id":"minecraft:feather"},{"id":"minecraft:flint"},{"id":"minecraft:gunpowder"},{"id":"minecraft:leather"},{"id":"minecraft:rabbit_hide"},{"id":"minecraft:rabbit_foot"},{"id":"minecraft:fire_charge"},{"id":"minecraft:blaze_rod"},{"id":"minecraft:blaze_powder"},{"id":"minecraft:magma_cream"},{"id":"minecraft:fermented_spider_eye"},{"id":"minecraft:echo_shard"},{"id":"minecraft:dragon_breath"},{"id":"minecraft:shulker_shell"},{"id":"minecraft:ghast_tear"},{"id":"minecraft:slime_ball"},{"id":"minecraft:ender_pearl"},{"id":"minecraft:ender_eye"},{"id":"minecraft:nether_star"},{"id":"minecraft:end_rod","blockRuntimeId":10546},{"id":"minecraft:lightning_rod","blockRuntimeId":2646},{"id":"minecraft:end_crystal"},{"id":"minecraft:paper"},{"id":"minecraft:book"},{"id":"minecraft:writable_book"},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA="},{"id":"minecraft:oak_boat"},{"id":"minecraft:spruce_boat"},{"id":"minecraft:birch_boat"},{"id":"minecraft:jungle_boat"},{"id":"minecraft:acacia_boat"},{"id":"minecraft:dark_oak_boat"},{"id":"minecraft:mangrove_boat"},{"id":"minecraft:cherry_boat"},{"id":"minecraft:bamboo_raft"},{"id":"minecraft:oak_chest_boat"},{"id":"minecraft:spruce_chest_boat"},{"id":"minecraft:birch_chest_boat"},{"id":"minecraft:jungle_chest_boat"},{"id":"minecraft:acacia_chest_boat"},{"id":"minecraft:dark_oak_chest_boat"},{"id":"minecraft:mangrove_chest_boat"},{"id":"minecraft:cherry_chest_boat"},{"id":"minecraft:bamboo_chest_raft"},{"id":"minecraft:rail","blockRuntimeId":6705},{"id":"minecraft:golden_rail","blockRuntimeId":9113},{"id":"minecraft:detector_rail","blockRuntimeId":6918},{"id":"minecraft:activator_rail","blockRuntimeId":1060},{"id":"minecraft:minecart"},{"id":"minecraft:chest_minecart"},{"id":"minecraft:hopper_minecart"},{"id":"minecraft:tnt_minecart"},{"id":"minecraft:redstone"},{"id":"minecraft:redstone_block","blockRuntimeId":6175},{"id":"minecraft:redstone_torch","blockRuntimeId":5501},{"id":"minecraft:lever","blockRuntimeId":11704},{"id":"minecraft:wooden_button","blockRuntimeId":11543},{"id":"minecraft:spruce_button","blockRuntimeId":7168},{"id":"minecraft:birch_button","blockRuntimeId":13523},{"id":"minecraft:jungle_button","blockRuntimeId":131},{"id":"minecraft:acacia_button","blockRuntimeId":12913},{"id":"minecraft:dark_oak_button","blockRuntimeId":105},{"id":"minecraft:mangrove_button","blockRuntimeId":12293},{"id":"minecraft:cherry_button","blockRuntimeId":7511},{"id":"minecraft:bamboo_button","blockRuntimeId":11665},{"id":"minecraft:stone_button","blockRuntimeId":1364},{"id":"minecraft:crimson_button","blockRuntimeId":7291},{"id":"minecraft:warped_button","blockRuntimeId":12931},{"id":"minecraft:polished_blackstone_button","blockRuntimeId":13547},{"id":"minecraft:tripwire_hook","blockRuntimeId":10618},{"id":"minecraft:wooden_pressure_plate","blockRuntimeId":13857},{"id":"minecraft:spruce_pressure_plate","blockRuntimeId":6157},{"id":"minecraft:birch_pressure_plate","blockRuntimeId":5934},{"id":"minecraft:jungle_pressure_plate","blockRuntimeId":6022},{"id":"minecraft:acacia_pressure_plate","blockRuntimeId":9038},{"id":"minecraft:dark_oak_pressure_plate","blockRuntimeId":10660},{"id":"minecraft:mangrove_pressure_plate","blockRuntimeId":6650},{"id":"minecraft:cherry_pressure_plate","blockRuntimeId":157},{"id":"minecraft:bamboo_pressure_plate","blockRuntimeId":11218},{"id":"minecraft:crimson_pressure_plate","blockRuntimeId":14096},{"id":"minecraft:warped_pressure_plate","blockRuntimeId":708},{"id":"minecraft:stone_pressure_plate","blockRuntimeId":6670},{"id":"minecraft:light_weighted_pressure_plate","blockRuntimeId":6053},{"id":"minecraft:heavy_weighted_pressure_plate","blockRuntimeId":2629},{"id":"minecraft:polished_blackstone_pressure_plate","blockRuntimeId":11380},{"id":"minecraft:observer","blockRuntimeId":5489},{"id":"minecraft:daylight_detector","blockRuntimeId":7000},{"id":"minecraft:repeater"},{"id":"minecraft:comparator"},{"id":"minecraft:hopper"},{"id":"minecraft:dropper","blockRuntimeId":13082},{"id":"minecraft:dispenser","blockRuntimeId":13821},{"id":"minecraft:piston","blockRuntimeId":2405},{"id":"minecraft:sticky_piston","blockRuntimeId":7211},{"id":"minecraft:tnt","blockRuntimeId":11910},{"id":"minecraft:name_tag"},{"id":"minecraft:loom","blockRuntimeId":6611},{"id":"minecraft:banner","nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":8,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":7,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":12,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":14,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":1,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":4,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":5,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":13,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":9,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":3,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":11,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":10,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":2,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":6,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQEAAAAA"},{"id":"minecraft:creeper_banner_pattern"},{"id":"minecraft:skull_banner_pattern"},{"id":"minecraft:flower_banner_pattern"},{"id":"minecraft:mojang_banner_pattern"},{"id":"minecraft:field_masoned_banner_pattern"},{"id":"minecraft:bordure_indented_banner_pattern"},{"id":"minecraft:piglin_banner_pattern"},{"id":"minecraft:globe_banner_pattern"},{"id":"minecraft:angler_pottery_sherd"},{"id":"minecraft:archer_pottery_sherd"},{"id":"minecraft:arms_up_pottery_sherd"},{"id":"minecraft:blade_pottery_sherd"},{"id":"minecraft:brewer_pottery_sherd"},{"id":"minecraft:burn_pottery_sherd"},{"id":"minecraft:danger_pottery_sherd"},{"id":"minecraft:explorer_pottery_sherd"},{"id":"minecraft:friend_pottery_sherd"},{"id":"minecraft:heart_pottery_sherd"},{"id":"minecraft:heartbreak_pottery_sherd"},{"id":"minecraft:howl_pottery_sherd"},{"id":"minecraft:miner_pottery_sherd"},{"id":"minecraft:mourner_pottery_sherd"},{"id":"minecraft:plenty_pottery_sherd"},{"id":"minecraft:prize_pottery_sherd"},{"id":"minecraft:sheaf_pottery_sherd"},{"id":"minecraft:shelter_pottery_sherd"},{"id":"minecraft:skull_pottery_sherd"},{"id":"minecraft:snort_pottery_sherd"},{"id":"minecraft:netherite_upgrade_smithing_template"},{"id":"minecraft:sentry_armor_trim_smithing_template"},{"id":"minecraft:vex_armor_trim_smithing_template"},{"id":"minecraft:wild_armor_trim_smithing_template"},{"id":"minecraft:coast_armor_trim_smithing_template"},{"id":"minecraft:dune_armor_trim_smithing_template"},{"id":"minecraft:wayfinder_armor_trim_smithing_template"},{"id":"minecraft:shaper_armor_trim_smithing_template"},{"id":"minecraft:raiser_armor_trim_smithing_template"},{"id":"minecraft:host_armor_trim_smithing_template"},{"id":"minecraft:ward_armor_trim_smithing_template"},{"id":"minecraft:silence_armor_trim_smithing_template"},{"id":"minecraft:tide_armor_trim_smithing_template"},{"id":"minecraft:snout_armor_trim_smithing_template"},{"id":"minecraft:rib_armor_trim_smithing_template"},{"id":"minecraft:eye_armor_trim_smithing_template"},{"id":"minecraft:spire_armor_trim_smithing_template"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_star","nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA="},{"id":"minecraft:firework_star","damage":8,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA="},{"id":"minecraft:firework_star","damage":7,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA="},{"id":"minecraft:firework_star","damage":15,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA="},{"id":"minecraft:firework_star","damage":12,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA="},{"id":"minecraft:firework_star","damage":14,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA="},{"id":"minecraft:firework_star","damage":1,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA="},{"id":"minecraft:firework_star","damage":4,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA="},{"id":"minecraft:firework_star","damage":5,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA="},{"id":"minecraft:firework_star","damage":13,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA="},{"id":"minecraft:firework_star","damage":9,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA="},{"id":"minecraft:firework_star","damage":3,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA="},{"id":"minecraft:firework_star","damage":11,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA="},{"id":"minecraft:firework_star","damage":10,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA="},{"id":"minecraft:firework_star","damage":2,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA="},{"id":"minecraft:firework_star","damage":6,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA="},{"id":"minecraft:chain"},{"id":"minecraft:target","blockRuntimeId":11542},{"id":"minecraft:decorated_pot","blockRuntimeId":11929},{"id":"minecraft:lodestone_compass"},{"id":"minecraft:wither_spawn_egg"},{"id":"minecraft:ender_dragon_spawn_egg"}]} \ No newline at end of file +{"items":[{"id":"minecraft:oak_planks","blockRuntimeId":13245},{"id":"minecraft:spruce_planks","blockRuntimeId":13607},{"id":"minecraft:birch_planks","blockRuntimeId":9112},{"id":"minecraft:jungle_planks","blockRuntimeId":11827},{"id":"minecraft:acacia_planks","blockRuntimeId":7303},{"id":"minecraft:dark_oak_planks","blockRuntimeId":5933},{"id":"minecraft:mangrove_planks","blockRuntimeId":2432},{"id":"minecraft:cherry_planks","blockRuntimeId":13475},{"id":"minecraft:bamboo_planks","blockRuntimeId":9443},{"id":"minecraft:bamboo_mosaic","blockRuntimeId":14084},{"id":"minecraft:crimson_planks","blockRuntimeId":8613},{"id":"minecraft:warped_planks","blockRuntimeId":2403},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2657},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2658},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2659},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2660},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2661},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2662},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2669},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2664},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2665},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2663},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2666},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2670},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2667},{"id":"minecraft:cobblestone_wall","blockRuntimeId":2668},{"id":"minecraft:blackstone_wall","blockRuntimeId":6715},{"id":"minecraft:polished_blackstone_wall","blockRuntimeId":11934},{"id":"minecraft:polished_blackstone_brick_wall","blockRuntimeId":2446},{"id":"minecraft:cobbled_deepslate_wall","blockRuntimeId":13888},{"id":"minecraft:deepslate_tile_wall","blockRuntimeId":8861},{"id":"minecraft:polished_deepslate_wall","blockRuntimeId":13622},{"id":"minecraft:deepslate_brick_wall","blockRuntimeId":1196},{"id":"minecraft:mud_brick_wall","blockRuntimeId":2197},{"id":"minecraft:oak_fence","blockRuntimeId":9575},{"id":"minecraft:spruce_fence","blockRuntimeId":1152},{"id":"minecraft:birch_fence","blockRuntimeId":13873},{"id":"minecraft:jungle_fence","blockRuntimeId":1145},{"id":"minecraft:acacia_fence","blockRuntimeId":13886},{"id":"minecraft:dark_oak_fence","blockRuntimeId":12348},{"id":"minecraft:mangrove_fence","blockRuntimeId":11845},{"id":"minecraft:cherry_fence","blockRuntimeId":2431},{"id":"minecraft:bamboo_fence","blockRuntimeId":1384},{"id":"minecraft:nether_brick_fence","blockRuntimeId":7099},{"id":"minecraft:crimson_fence","blockRuntimeId":13802},{"id":"minecraft:warped_fence","blockRuntimeId":10103},{"id":"minecraft:fence_gate","blockRuntimeId":88},{"id":"minecraft:spruce_fence_gate","blockRuntimeId":11795},{"id":"minecraft:birch_fence_gate","blockRuntimeId":6176},{"id":"minecraft:jungle_fence_gate","blockRuntimeId":9152},{"id":"minecraft:acacia_fence_gate","blockRuntimeId":13300},{"id":"minecraft:dark_oak_fence_gate","blockRuntimeId":6967},{"id":"minecraft:mangrove_fence_gate","blockRuntimeId":7525},{"id":"minecraft:cherry_fence_gate","blockRuntimeId":14061},{"id":"minecraft:bamboo_fence_gate","blockRuntimeId":8836},{"id":"minecraft:crimson_fence_gate","blockRuntimeId":7977},{"id":"minecraft:warped_fence_gate","blockRuntimeId":9190},{"id":"minecraft:normal_stone_stairs","blockRuntimeId":1385},{"id":"minecraft:stone_stairs","blockRuntimeId":6096},{"id":"minecraft:mossy_cobblestone_stairs","blockRuntimeId":6877},{"id":"minecraft:oak_stairs","blockRuntimeId":728},{"id":"minecraft:spruce_stairs","blockRuntimeId":143},{"id":"minecraft:birch_stairs","blockRuntimeId":12228},{"id":"minecraft:jungle_stairs","blockRuntimeId":12190},{"id":"minecraft:acacia_stairs","blockRuntimeId":11351},{"id":"minecraft:dark_oak_stairs","blockRuntimeId":8853},{"id":"minecraft:mangrove_stairs","blockRuntimeId":7483},{"id":"minecraft:cherry_stairs","blockRuntimeId":12395},{"id":"minecraft:bamboo_stairs","blockRuntimeId":2183},{"id":"minecraft:bamboo_mosaic_stairs","blockRuntimeId":11359},{"id":"minecraft:stone_brick_stairs","blockRuntimeId":2415},{"id":"minecraft:mossy_stone_brick_stairs","blockRuntimeId":10538},{"id":"minecraft:sandstone_stairs","blockRuntimeId":5966},{"id":"minecraft:smooth_sandstone_stairs","blockRuntimeId":6014},{"id":"minecraft:red_sandstone_stairs","blockRuntimeId":9137},{"id":"minecraft:smooth_red_sandstone_stairs","blockRuntimeId":9370},{"id":"minecraft:granite_stairs","blockRuntimeId":5513},{"id":"minecraft:polished_granite_stairs","blockRuntimeId":6937},{"id":"minecraft:diorite_stairs","blockRuntimeId":7242},{"id":"minecraft:polished_diorite_stairs","blockRuntimeId":11917},{"id":"minecraft:andesite_stairs","blockRuntimeId":9104},{"id":"minecraft:polished_andesite_stairs","blockRuntimeId":12258},{"id":"minecraft:brick_stairs","blockRuntimeId":11723},{"id":"minecraft:nether_brick_stairs","blockRuntimeId":118},{"id":"minecraft:red_nether_brick_stairs","blockRuntimeId":11813},{"id":"minecraft:end_brick_stairs","blockRuntimeId":11533},{"id":"minecraft:quartz_stairs","blockRuntimeId":8139},{"id":"minecraft:smooth_quartz_stairs","blockRuntimeId":13414},{"id":"minecraft:purpur_stairs","blockRuntimeId":13479},{"id":"minecraft:prismarine_stairs","blockRuntimeId":12952},{"id":"minecraft:dark_prismarine_stairs","blockRuntimeId":13128},{"id":"minecraft:prismarine_bricks_stairs","blockRuntimeId":628},{"id":"minecraft:crimson_stairs","blockRuntimeId":11426},{"id":"minecraft:warped_stairs","blockRuntimeId":6109},{"id":"minecraft:blackstone_stairs","blockRuntimeId":12247},{"id":"minecraft:polished_blackstone_stairs","blockRuntimeId":7142},{"id":"minecraft:polished_blackstone_brick_stairs","blockRuntimeId":7338},{"id":"minecraft:cut_copper_stairs","blockRuntimeId":7492},{"id":"minecraft:exposed_cut_copper_stairs","blockRuntimeId":7475},{"id":"minecraft:weathered_cut_copper_stairs","blockRuntimeId":7150},{"id":"minecraft:oxidized_cut_copper_stairs","blockRuntimeId":1107},{"id":"minecraft:waxed_cut_copper_stairs","blockRuntimeId":1155},{"id":"minecraft:waxed_exposed_cut_copper_stairs","blockRuntimeId":6686},{"id":"minecraft:waxed_weathered_cut_copper_stairs","blockRuntimeId":11334},{"id":"minecraft:waxed_oxidized_cut_copper_stairs","blockRuntimeId":10058},{"id":"minecraft:cobbled_deepslate_stairs","blockRuntimeId":568},{"id":"minecraft:deepslate_tile_stairs","blockRuntimeId":7969},{"id":"minecraft:polished_deepslate_stairs","blockRuntimeId":1043},{"id":"minecraft:deepslate_brick_stairs","blockRuntimeId":13120},{"id":"minecraft:mud_brick_stairs","blockRuntimeId":9346},{"id":"minecraft:wooden_door"},{"id":"minecraft:spruce_door"},{"id":"minecraft:birch_door"},{"id":"minecraft:jungle_door"},{"id":"minecraft:acacia_door"},{"id":"minecraft:dark_oak_door"},{"id":"minecraft:mangrove_door"},{"id":"minecraft:cherry_door"},{"id":"minecraft:bamboo_door"},{"id":"minecraft:iron_door"},{"id":"minecraft:crimson_door"},{"id":"minecraft:warped_door"},{"id":"minecraft:trapdoor","blockRuntimeId":660},{"id":"minecraft:spruce_trapdoor","blockRuntimeId":11762},{"id":"minecraft:birch_trapdoor","blockRuntimeId":11863},{"id":"minecraft:jungle_trapdoor","blockRuntimeId":9171},{"id":"minecraft:acacia_trapdoor","blockRuntimeId":9411},{"id":"minecraft:dark_oak_trapdoor","blockRuntimeId":13202},{"id":"minecraft:mangrove_trapdoor","blockRuntimeId":7346},{"id":"minecraft:cherry_trapdoor","blockRuntimeId":2377},{"id":"minecraft:bamboo_trapdoor","blockRuntimeId":9054},{"id":"minecraft:iron_trapdoor","blockRuntimeId":1072},{"id":"minecraft:crimson_trapdoor","blockRuntimeId":7183},{"id":"minecraft:warped_trapdoor","blockRuntimeId":8089},{"id":"minecraft:iron_bars","blockRuntimeId":8174},{"id":"minecraft:glass","blockRuntimeId":11331},{"id":"minecraft:white_stained_glass","blockRuntimeId":8618},{"id":"minecraft:light_gray_stained_glass","blockRuntimeId":1191},{"id":"minecraft:gray_stained_glass","blockRuntimeId":6105},{"id":"minecraft:black_stained_glass","blockRuntimeId":10583},{"id":"minecraft:brown_stained_glass","blockRuntimeId":1788},{"id":"minecraft:red_stained_glass","blockRuntimeId":9037},{"id":"minecraft:orange_stained_glass","blockRuntimeId":7413},{"id":"minecraft:yellow_stained_glass","blockRuntimeId":13293},{"id":"minecraft:lime_stained_glass","blockRuntimeId":129},{"id":"minecraft:green_stained_glass","blockRuntimeId":7098},{"id":"minecraft:cyan_stained_glass","blockRuntimeId":10582},{"id":"minecraft:light_blue_stained_glass","blockRuntimeId":9616},{"id":"minecraft:blue_stained_glass","blockRuntimeId":9580},{"id":"minecraft:purple_stained_glass","blockRuntimeId":2645},{"id":"minecraft:magenta_stained_glass","blockRuntimeId":12911},{"id":"minecraft:pink_stained_glass","blockRuntimeId":7166},{"id":"minecraft:tinted_glass","blockRuntimeId":10712},{"id":"minecraft:glass_pane","blockRuntimeId":9023},{"id":"minecraft:white_stained_glass_pane","blockRuntimeId":7414},{"id":"minecraft:light_gray_stained_glass_pane","blockRuntimeId":1193},{"id":"minecraft:gray_stained_glass_pane","blockRuntimeId":12116},{"id":"minecraft:black_stained_glass_pane","blockRuntimeId":2656},{"id":"minecraft:brown_stained_glass_pane","blockRuntimeId":740},{"id":"minecraft:red_stained_glass_pane","blockRuntimeId":12340},{"id":"minecraft:orange_stained_glass_pane","blockRuntimeId":651},{"id":"minecraft:yellow_stained_glass_pane","blockRuntimeId":564},{"id":"minecraft:lime_stained_glass_pane","blockRuntimeId":13291},{"id":"minecraft:green_stained_glass_pane","blockRuntimeId":6936},{"id":"minecraft:cyan_stained_glass_pane","blockRuntimeId":11678},{"id":"minecraft:light_blue_stained_glass_pane","blockRuntimeId":6668},{"id":"minecraft:blue_stained_glass_pane","blockRuntimeId":87},{"id":"minecraft:purple_stained_glass_pane","blockRuntimeId":8622},{"id":"minecraft:magenta_stained_glass_pane","blockRuntimeId":8173},{"id":"minecraft:pink_stained_glass_pane","blockRuntimeId":12345},{"id":"minecraft:ladder","blockRuntimeId":14090},{"id":"minecraft:scaffolding","blockRuntimeId":5950},{"id":"minecraft:stone_block_slab","blockRuntimeId":7076},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9651},{"id":"minecraft:stone_block_slab","blockRuntimeId":7079},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9622},{"id":"minecraft:oak_slab","blockRuntimeId":7100},{"id":"minecraft:spruce_slab","blockRuntimeId":12245},{"id":"minecraft:birch_slab","blockRuntimeId":6609},{"id":"minecraft:jungle_slab","blockRuntimeId":6666},{"id":"minecraft:acacia_slab","blockRuntimeId":13280},{"id":"minecraft:dark_oak_slab","blockRuntimeId":1194},{"id":"minecraft:mangrove_slab","blockRuntimeId":2608},{"id":"minecraft:cherry_slab","blockRuntimeId":11368},{"id":"minecraft:bamboo_slab","blockRuntimeId":11720},{"id":"minecraft:bamboo_mosaic_slab","blockRuntimeId":4935},{"id":"minecraft:stone_block_slab","blockRuntimeId":7081},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9649},{"id":"minecraft:stone_block_slab","blockRuntimeId":7077},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9652},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9623},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9617},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9653},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9634},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9639},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9640},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9637},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9638},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9636},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9635},{"id":"minecraft:stone_block_slab","blockRuntimeId":7080},{"id":"minecraft:stone_block_slab","blockRuntimeId":7083},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9624},{"id":"minecraft:stone_block_slab3","blockRuntimeId":9633},{"id":"minecraft:stone_block_slab","blockRuntimeId":7082},{"id":"minecraft:stone_block_slab4","blockRuntimeId":9650},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9618},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9619},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9620},{"id":"minecraft:stone_block_slab2","blockRuntimeId":9621},{"id":"minecraft:crimson_slab","blockRuntimeId":10558},{"id":"minecraft:warped_slab","blockRuntimeId":11683},{"id":"minecraft:blackstone_slab","blockRuntimeId":2393},{"id":"minecraft:polished_blackstone_slab","blockRuntimeId":11140},{"id":"minecraft:polished_blackstone_brick_slab","blockRuntimeId":6993},{"id":"minecraft:cut_copper_slab","blockRuntimeId":9025},{"id":"minecraft:exposed_cut_copper_slab","blockRuntimeId":11811},{"id":"minecraft:weathered_cut_copper_slab","blockRuntimeId":11187},{"id":"minecraft:oxidized_cut_copper_slab","blockRuntimeId":9078},{"id":"minecraft:waxed_cut_copper_slab","blockRuntimeId":13620},{"id":"minecraft:waxed_exposed_cut_copper_slab","blockRuntimeId":680},{"id":"minecraft:waxed_weathered_cut_copper_slab","blockRuntimeId":11755},{"id":"minecraft:waxed_oxidized_cut_copper_slab","blockRuntimeId":2001},{"id":"minecraft:cobbled_deepslate_slab","blockRuntimeId":13010},{"id":"minecraft:polished_deepslate_slab","blockRuntimeId":749},{"id":"minecraft:deepslate_tile_slab","blockRuntimeId":7102},{"id":"minecraft:deepslate_brick_slab","blockRuntimeId":6107},{"id":"minecraft:mud_brick_slab","blockRuntimeId":6695},{"id":"minecraft:brick_block","blockRuntimeId":8121},{"id":"minecraft:chiseled_nether_bricks","blockRuntimeId":12930},{"id":"minecraft:cracked_nether_bricks","blockRuntimeId":7418},{"id":"minecraft:quartz_bricks","blockRuntimeId":11502},{"id":"minecraft:stonebrick","blockRuntimeId":11757},{"id":"minecraft:stonebrick","blockRuntimeId":11758},{"id":"minecraft:stonebrick","blockRuntimeId":11759},{"id":"minecraft:stonebrick","blockRuntimeId":11760},{"id":"minecraft:end_bricks","blockRuntimeId":741},{"id":"minecraft:prismarine","blockRuntimeId":11236},{"id":"minecraft:polished_blackstone_bricks","blockRuntimeId":7996},{"id":"minecraft:cracked_polished_blackstone_bricks","blockRuntimeId":12877},{"id":"minecraft:gilded_blackstone","blockRuntimeId":7472},{"id":"minecraft:chiseled_polished_blackstone","blockRuntimeId":8852},{"id":"minecraft:deepslate_tiles","blockRuntimeId":7448},{"id":"minecraft:cracked_deepslate_tiles","blockRuntimeId":6954},{"id":"minecraft:deepslate_bricks","blockRuntimeId":9287},{"id":"minecraft:cracked_deepslate_bricks","blockRuntimeId":9151},{"id":"minecraft:chiseled_deepslate","blockRuntimeId":9024},{"id":"minecraft:cobblestone","blockRuntimeId":5997},{"id":"minecraft:mossy_cobblestone","blockRuntimeId":683},{"id":"minecraft:cobbled_deepslate","blockRuntimeId":11884},{"id":"minecraft:smooth_stone","blockRuntimeId":7449},{"id":"minecraft:sandstone","blockRuntimeId":6041},{"id":"minecraft:sandstone","blockRuntimeId":6042},{"id":"minecraft:sandstone","blockRuntimeId":6043},{"id":"minecraft:sandstone","blockRuntimeId":6044},{"id":"minecraft:red_sandstone","blockRuntimeId":11790},{"id":"minecraft:red_sandstone","blockRuntimeId":11791},{"id":"minecraft:red_sandstone","blockRuntimeId":11792},{"id":"minecraft:red_sandstone","blockRuntimeId":11793},{"id":"minecraft:coal_block","blockRuntimeId":9188},{"id":"minecraft:dried_kelp_block","blockRuntimeId":13784},{"id":"minecraft:gold_block","blockRuntimeId":784},{"id":"minecraft:iron_block","blockRuntimeId":14089},{"id":"minecraft:copper_block","blockRuntimeId":7967},{"id":"minecraft:exposed_copper","blockRuntimeId":1361},{"id":"minecraft:weathered_copper","blockRuntimeId":14053},{"id":"minecraft:oxidized_copper","blockRuntimeId":5931},{"id":"minecraft:waxed_copper","blockRuntimeId":13454},{"id":"minecraft:waxed_exposed_copper","blockRuntimeId":1981},{"id":"minecraft:waxed_weathered_copper","blockRuntimeId":2000},{"id":"minecraft:waxed_oxidized_copper","blockRuntimeId":13244},{"id":"minecraft:cut_copper","blockRuntimeId":8005},{"id":"minecraft:exposed_cut_copper","blockRuntimeId":11333},{"id":"minecraft:weathered_cut_copper","blockRuntimeId":12860},{"id":"minecraft:oxidized_cut_copper","blockRuntimeId":9301},{"id":"minecraft:waxed_cut_copper","blockRuntimeId":12993},{"id":"minecraft:waxed_exposed_cut_copper","blockRuntimeId":6208},{"id":"minecraft:waxed_weathered_cut_copper","blockRuntimeId":8617},{"id":"minecraft:waxed_oxidized_cut_copper","blockRuntimeId":636},{"id":"minecraft:emerald_block","blockRuntimeId":2620},{"id":"minecraft:diamond_block","blockRuntimeId":727},{"id":"minecraft:lapis_block","blockRuntimeId":7092},{"id":"minecraft:raw_iron_block","blockRuntimeId":14086},{"id":"minecraft:raw_copper_block","blockRuntimeId":9077},{"id":"minecraft:raw_gold_block","blockRuntimeId":1117},{"id":"minecraft:quartz_block","blockRuntimeId":6084},{"id":"minecraft:quartz_block","blockRuntimeId":6086},{"id":"minecraft:quartz_block","blockRuntimeId":6085},{"id":"minecraft:quartz_block","blockRuntimeId":6087},{"id":"minecraft:prismarine","blockRuntimeId":11234},{"id":"minecraft:prismarine","blockRuntimeId":11235},{"id":"minecraft:slime","blockRuntimeId":7036},{"id":"minecraft:honey_block","blockRuntimeId":2359},{"id":"minecraft:honeycomb_block","blockRuntimeId":7337},{"id":"minecraft:hay_block","blockRuntimeId":1985},{"id":"minecraft:bone_block","blockRuntimeId":7037},{"id":"minecraft:nether_brick","blockRuntimeId":12972},{"id":"minecraft:red_nether_brick","blockRuntimeId":567},{"id":"minecraft:netherite_block","blockRuntimeId":6173},{"id":"minecraft:lodestone","blockRuntimeId":14083},{"id":"minecraft:white_wool","blockRuntimeId":9189},{"id":"minecraft:light_gray_wool","blockRuntimeId":13848},{"id":"minecraft:gray_wool","blockRuntimeId":650},{"id":"minecraft:black_wool","blockRuntimeId":1120},{"id":"minecraft:brown_wool","blockRuntimeId":704},{"id":"minecraft:red_wool","blockRuntimeId":130},{"id":"minecraft:orange_wool","blockRuntimeId":1962},{"id":"minecraft:yellow_wool","blockRuntimeId":557},{"id":"minecraft:lime_wool","blockRuntimeId":11129},{"id":"minecraft:green_wool","blockRuntimeId":6121},{"id":"minecraft:cyan_wool","blockRuntimeId":9125},{"id":"minecraft:light_blue_wool","blockRuntimeId":12346},{"id":"minecraft:blue_wool","blockRuntimeId":9302},{"id":"minecraft:purple_wool","blockRuntimeId":14088},{"id":"minecraft:magenta_wool","blockRuntimeId":2439},{"id":"minecraft:pink_wool","blockRuntimeId":6174},{"id":"minecraft:white_carpet","blockRuntimeId":12960},{"id":"minecraft:light_gray_carpet","blockRuntimeId":14087},{"id":"minecraft:gray_carpet","blockRuntimeId":653},{"id":"minecraft:black_carpet","blockRuntimeId":11168},{"id":"minecraft:brown_carpet","blockRuntimeId":2410},{"id":"minecraft:red_carpet","blockRuntimeId":13118},{"id":"minecraft:orange_carpet","blockRuntimeId":12305},{"id":"minecraft:yellow_carpet","blockRuntimeId":10581},{"id":"minecraft:lime_carpet","blockRuntimeId":11928},{"id":"minecraft:green_carpet","blockRuntimeId":6122},{"id":"minecraft:cyan_carpet","blockRuntimeId":6000},{"id":"minecraft:light_blue_carpet","blockRuntimeId":8179},{"id":"minecraft:blue_carpet","blockRuntimeId":600},{"id":"minecraft:purple_carpet","blockRuntimeId":13471},{"id":"minecraft:magenta_carpet","blockRuntimeId":687},{"id":"minecraft:pink_carpet","blockRuntimeId":13413},{"id":"minecraft:white_concrete_powder","blockRuntimeId":8564},{"id":"minecraft:light_gray_concrete_powder","blockRuntimeId":12944},{"id":"minecraft:gray_concrete_powder","blockRuntimeId":13064},{"id":"minecraft:black_concrete_powder","blockRuntimeId":1154},{"id":"minecraft:brown_concrete_powder","blockRuntimeId":10129},{"id":"minecraft:red_concrete_powder","blockRuntimeId":12943},{"id":"minecraft:orange_concrete_powder","blockRuntimeId":14050},{"id":"minecraft:yellow_concrete_powder","blockRuntimeId":13286},{"id":"minecraft:lime_concrete_powder","blockRuntimeId":13804},{"id":"minecraft:green_concrete_powder","blockRuntimeId":12242},{"id":"minecraft:cyan_concrete_powder","blockRuntimeId":5908},{"id":"minecraft:light_blue_concrete_powder","blockRuntimeId":65},{"id":"minecraft:blue_concrete_powder","blockRuntimeId":11925},{"id":"minecraft:purple_concrete_powder","blockRuntimeId":11748},{"id":"minecraft:magenta_concrete_powder","blockRuntimeId":7304},{"id":"minecraft:pink_concrete_powder","blockRuntimeId":7104},{"id":"minecraft:white_concrete","blockRuntimeId":13879},{"id":"minecraft:light_gray_concrete","blockRuntimeId":1972},{"id":"minecraft:gray_concrete","blockRuntimeId":13065},{"id":"minecraft:black_concrete","blockRuntimeId":11909},{"id":"minecraft:brown_concrete","blockRuntimeId":11367},{"id":"minecraft:red_concrete","blockRuntimeId":13412},{"id":"minecraft:orange_concrete","blockRuntimeId":11926},{"id":"minecraft:yellow_concrete","blockRuntimeId":5999},{"id":"minecraft:lime_concrete","blockRuntimeId":7468},{"id":"minecraft:green_concrete","blockRuntimeId":10555},{"id":"minecraft:cyan_concrete","blockRuntimeId":12961},{"id":"minecraft:light_blue_concrete","blockRuntimeId":13137},{"id":"minecraft:blue_concrete","blockRuntimeId":12971},{"id":"minecraft:purple_concrete","blockRuntimeId":6988},{"id":"minecraft:magenta_concrete","blockRuntimeId":7209},{"id":"minecraft:pink_concrete","blockRuntimeId":5521},{"id":"minecraft:clay","blockRuntimeId":12394},{"id":"minecraft:hardened_clay","blockRuntimeId":1393},{"id":"minecraft:white_terracotta","blockRuntimeId":8175},{"id":"minecraft:light_gray_terracotta","blockRuntimeId":1783},{"id":"minecraft:gray_terracotta","blockRuntimeId":7451},{"id":"minecraft:black_terracotta","blockRuntimeId":11255},{"id":"minecraft:brown_terracotta","blockRuntimeId":13830},{"id":"minecraft:red_terracotta","blockRuntimeId":2434},{"id":"minecraft:orange_terracotta","blockRuntimeId":13243},{"id":"minecraft:yellow_terracotta","blockRuntimeId":6996},{"id":"minecraft:lime_terracotta","blockRuntimeId":14060},{"id":"minecraft:green_terracotta","blockRuntimeId":6106},{"id":"minecraft:cyan_terracotta"},{"id":"minecraft:light_blue_terracotta","blockRuntimeId":7203},{"id":"minecraft:blue_terracotta","blockRuntimeId":6040},{"id":"minecraft:purple_terracotta","blockRuntimeId":11541},{"id":"minecraft:magenta_terracotta","blockRuntimeId":7474},{"id":"minecraft:pink_terracotta","blockRuntimeId":6949},{"id":"minecraft:white_glazed_terracotta","blockRuntimeId":9397},{"id":"minecraft:silver_glazed_terracotta","blockRuntimeId":5507},{"id":"minecraft:gray_glazed_terracotta","blockRuntimeId":14077},{"id":"minecraft:black_glazed_terracotta","blockRuntimeId":10052},{"id":"minecraft:brown_glazed_terracotta","blockRuntimeId":5909},{"id":"minecraft:red_glazed_terracotta","blockRuntimeId":6961},{"id":"minecraft:orange_glazed_terracotta","blockRuntimeId":2610},{"id":"minecraft:yellow_glazed_terracotta","blockRuntimeId":2396},{"id":"minecraft:lime_glazed_terracotta","blockRuntimeId":654},{"id":"minecraft:green_glazed_terracotta","blockRuntimeId":11821},{"id":"minecraft:cyan_glazed_terracotta","blockRuntimeId":9145},{"id":"minecraft:light_blue_glazed_terracotta","blockRuntimeId":9294},{"id":"minecraft:blue_glazed_terracotta","blockRuntimeId":9288},{"id":"minecraft:purple_glazed_terracotta","blockRuntimeId":12236},{"id":"minecraft:magenta_glazed_terracotta","blockRuntimeId":2440},{"id":"minecraft:pink_glazed_terracotta","blockRuntimeId":11749},{"id":"minecraft:purpur_block","blockRuntimeId":13428},{"id":"minecraft:purpur_block","blockRuntimeId":13430},{"id":"minecraft:packed_mud","blockRuntimeId":744},{"id":"minecraft:mud_bricks","blockRuntimeId":12100},{"id":"minecraft:nether_wart_block","blockRuntimeId":7106},{"id":"minecraft:warped_wart_block","blockRuntimeId":10563},{"id":"minecraft:shroomlight","blockRuntimeId":8835},{"id":"minecraft:crimson_nylium","blockRuntimeId":6985},{"id":"minecraft:warped_nylium","blockRuntimeId":11500},{"id":"minecraft:netherrack","blockRuntimeId":12268},{"id":"minecraft:basalt","blockRuntimeId":7199},{"id":"minecraft:polished_basalt","blockRuntimeId":29},{"id":"minecraft:smooth_basalt","blockRuntimeId":2617},{"id":"minecraft:soul_soil","blockRuntimeId":9659},{"id":"minecraft:dirt","blockRuntimeId":9578},{"id":"minecraft:dirt","blockRuntimeId":9579},{"id":"minecraft:farmland","blockRuntimeId":6697},{"id":"minecraft:grass_block","blockRuntimeId":10617},{"id":"minecraft:grass_path","blockRuntimeId":13887},{"id":"minecraft:podzol","blockRuntimeId":7966},{"id":"minecraft:mycelium","blockRuntimeId":6071},{"id":"minecraft:mud","blockRuntimeId":11886},{"id":"minecraft:stone","blockRuntimeId":1791},{"id":"minecraft:iron_ore","blockRuntimeId":8006},{"id":"minecraft:gold_ore","blockRuntimeId":2395},{"id":"minecraft:diamond_ore","blockRuntimeId":7207},{"id":"minecraft:lapis_ore","blockRuntimeId":13411},{"id":"minecraft:redstone_ore","blockRuntimeId":7095},{"id":"minecraft:coal_ore","blockRuntimeId":7093},{"id":"minecraft:copper_ore","blockRuntimeId":5932},{"id":"minecraft:emerald_ore","blockRuntimeId":13047},{"id":"minecraft:quartz_ore","blockRuntimeId":7362},{"id":"minecraft:nether_gold_ore","blockRuntimeId":32},{"id":"minecraft:ancient_debris","blockRuntimeId":11257},{"id":"minecraft:deepslate_iron_ore","blockRuntimeId":12973},{"id":"minecraft:deepslate_gold_ore","blockRuntimeId":11256},{"id":"minecraft:deepslate_diamond_ore","blockRuntimeId":13831},{"id":"minecraft:deepslate_lapis_ore","blockRuntimeId":12945},{"id":"minecraft:deepslate_redstone_ore","blockRuntimeId":11828},{"id":"minecraft:deepslate_emerald_ore","blockRuntimeId":11501},{"id":"minecraft:deepslate_coal_ore","blockRuntimeId":12859},{"id":"minecraft:deepslate_copper_ore","blockRuntimeId":117},{"id":"minecraft:gravel","blockRuntimeId":14115},{"id":"minecraft:granite","blockRuntimeId":86},{"id":"minecraft:diorite","blockRuntimeId":152},{"id":"minecraft:andesite","blockRuntimeId":1789},{"id":"minecraft:blackstone","blockRuntimeId":13299},{"id":"minecraft:deepslate","blockRuntimeId":684},{"id":"minecraft:polished_granite","blockRuntimeId":1164},{"id":"minecraft:polished_diorite","blockRuntimeId":10045},{"id":"minecraft:polished_andesite","blockRuntimeId":13249},{"id":"minecraft:polished_blackstone","blockRuntimeId":6070},{"id":"minecraft:polished_deepslate","blockRuntimeId":13476},{"id":"minecraft:sand","blockRuntimeId":6998},{"id":"minecraft:sand","blockRuntimeId":6999},{"id":"minecraft:cactus","blockRuntimeId":12208},{"id":"minecraft:oak_log","blockRuntimeId":737},{"id":"minecraft:stripped_oak_log","blockRuntimeId":13246},{"id":"minecraft:spruce_log","blockRuntimeId":7073},{"id":"minecraft:stripped_spruce_log","blockRuntimeId":11434},{"id":"minecraft:birch_log","blockRuntimeId":1792},{"id":"minecraft:stripped_birch_log","blockRuntimeId":10709},{"id":"minecraft:jungle_log","blockRuntimeId":642},{"id":"minecraft:stripped_jungle_log","blockRuntimeId":1778},{"id":"minecraft:acacia_log","blockRuntimeId":7180},{"id":"minecraft:stripped_acacia_log","blockRuntimeId":10098},{"id":"minecraft:dark_oak_log","blockRuntimeId":4937},{"id":"minecraft:stripped_dark_oak_log","blockRuntimeId":638},{"id":"minecraft:mangrove_log","blockRuntimeId":1104},{"id":"minecraft:stripped_mangrove_log","blockRuntimeId":14112},{"id":"minecraft:cherry_log","blockRuntimeId":12949},{"id":"minecraft:stripped_cherry_log","blockRuntimeId":7288},{"id":"minecraft:crimson_stem","blockRuntimeId":10552},{"id":"minecraft:stripped_crimson_stem","blockRuntimeId":12170},{"id":"minecraft:warped_stem","blockRuntimeId":11685},{"id":"minecraft:stripped_warped_stem","blockRuntimeId":13097},{"id":"minecraft:oak_wood","blockRuntimeId":8619},{"id":"minecraft:spruce_wood","blockRuntimeId":13296},{"id":"minecraft:birch_wood","blockRuntimeId":1982},{"id":"minecraft:jungle_wood","blockRuntimeId":1997},{"id":"minecraft:acacia_wood","blockRuntimeId":12113},{"id":"minecraft:dark_oak_wood","blockRuntimeId":10},{"id":"minecraft:stripped_oak_wood","blockRuntimeId":8176},{"id":"minecraft:stripped_spruce_wood","blockRuntimeId":1959},{"id":"minecraft:stripped_birch_wood","blockRuntimeId":7415},{"id":"minecraft:stripped_jungle_wood","blockRuntimeId":1099},{"id":"minecraft:stripped_acacia_wood","blockRuntimeId":724},{"id":"minecraft:stripped_dark_oak_wood","blockRuntimeId":8614},{"id":"minecraft:mangrove_wood","blockRuntimeId":6955},{"id":"minecraft:stripped_mangrove_wood","blockRuntimeId":7032},{"id":"minecraft:cherry_wood","blockRuntimeId":12385},{"id":"minecraft:stripped_cherry_wood","blockRuntimeId":8646},{"id":"minecraft:crimson_hyphae","blockRuntimeId":7139},{"id":"minecraft:stripped_crimson_hyphae","blockRuntimeId":11689},{"id":"minecraft:warped_hyphae","blockRuntimeId":10560},{"id":"minecraft:stripped_warped_hyphae","blockRuntimeId":9403},{"id":"minecraft:bamboo_block","blockRuntimeId":66},{"id":"minecraft:stripped_bamboo_block","blockRuntimeId":5976},{"id":"minecraft:oak_leaves","blockRuntimeId":2003},{"id":"minecraft:spruce_leaves","blockRuntimeId":7250},{"id":"minecraft:birch_leaves","blockRuntimeId":6945},{"id":"minecraft:jungle_leaves","blockRuntimeId":9132},{"id":"minecraft:acacia_leaves","blockRuntimeId":2652},{"id":"minecraft:dark_oak_leaves","blockRuntimeId":11189},{"id":"minecraft:mangrove_leaves","blockRuntimeId":11880},{"id":"minecraft:cherry_leaves","blockRuntimeId":10048},{"id":"minecraft:azalea_leaves","blockRuntimeId":13424},{"id":"minecraft:azalea_leaves_flowered","blockRuntimeId":11489},{"id":"minecraft:sapling","blockRuntimeId":2171},{"id":"minecraft:sapling","blockRuntimeId":2172},{"id":"minecraft:sapling","blockRuntimeId":2173},{"id":"minecraft:sapling","blockRuntimeId":2174},{"id":"minecraft:sapling","blockRuntimeId":2175},{"id":"minecraft:sapling","blockRuntimeId":2176},{"id":"minecraft:mangrove_propagule","blockRuntimeId":12198},{"id":"minecraft:cherry_sapling","blockRuntimeId":12947},{"id":"minecraft:bee_nest","blockRuntimeId":9582},{"id":"minecraft:wheat_seeds"},{"id":"minecraft:pumpkin_seeds"},{"id":"minecraft:melon_seeds"},{"id":"minecraft:beetroot_seeds"},{"id":"minecraft:torchflower_seeds"},{"id":"minecraft:pitcher_pod"},{"id":"minecraft:wheat"},{"id":"minecraft:beetroot"},{"id":"minecraft:potato"},{"id":"minecraft:poisonous_potato"},{"id":"minecraft:carrot"},{"id":"minecraft:golden_carrot"},{"id":"minecraft:apple"},{"id":"minecraft:golden_apple"},{"id":"minecraft:enchanted_golden_apple"},{"id":"minecraft:melon_block","blockRuntimeId":1153},{"id":"minecraft:melon_slice"},{"id":"minecraft:glistering_melon_slice"},{"id":"minecraft:sweet_berries"},{"id":"minecraft:glow_berries"},{"id":"minecraft:pumpkin","blockRuntimeId":7444},{"id":"minecraft:carved_pumpkin","blockRuntimeId":13075},{"id":"minecraft:lit_pumpkin","blockRuntimeId":11887},{"id":"minecraft:honeycomb"},{"id":"minecraft:tallgrass","blockRuntimeId":2413},{"id":"minecraft:double_plant","blockRuntimeId":9246},{"id":"minecraft:tallgrass","blockRuntimeId":2412},{"id":"minecraft:double_plant","blockRuntimeId":9245},{"id":"minecraft:nether_sprouts"},{"id":"minecraft:fire_coral","blockRuntimeId":1790},{"id":"minecraft:brain_coral","blockRuntimeId":1958},{"id":"minecraft:bubble_coral","blockRuntimeId":11370},{"id":"minecraft:tube_coral","blockRuntimeId":13487},{"id":"minecraft:horn_coral","blockRuntimeId":5998},{"id":"minecraft:dead_fire_coral","blockRuntimeId":11425},{"id":"minecraft:dead_brain_coral","blockRuntimeId":12858},{"id":"minecraft:dead_bubble_coral","blockRuntimeId":12946},{"id":"minecraft:dead_tube_coral","blockRuntimeId":7105},{"id":"minecraft:dead_horn_coral","blockRuntimeId":10616},{"id":"minecraft:coral_fan","blockRuntimeId":7504},{"id":"minecraft:coral_fan","blockRuntimeId":7502},{"id":"minecraft:coral_fan","blockRuntimeId":7503},{"id":"minecraft:coral_fan","blockRuntimeId":7501},{"id":"minecraft:coral_fan","blockRuntimeId":7505},{"id":"minecraft:coral_fan_dead","blockRuntimeId":79},{"id":"minecraft:coral_fan_dead","blockRuntimeId":77},{"id":"minecraft:coral_fan_dead","blockRuntimeId":78},{"id":"minecraft:coral_fan_dead","blockRuntimeId":76},{"id":"minecraft:coral_fan_dead","blockRuntimeId":80},{"id":"minecraft:crimson_roots","blockRuntimeId":13279},{"id":"minecraft:warped_roots","blockRuntimeId":7208},{"id":"minecraft:yellow_flower","blockRuntimeId":1051},{"id":"minecraft:red_flower","blockRuntimeId":6001},{"id":"minecraft:red_flower","blockRuntimeId":6002},{"id":"minecraft:red_flower","blockRuntimeId":6003},{"id":"minecraft:red_flower","blockRuntimeId":6004},{"id":"minecraft:red_flower","blockRuntimeId":6005},{"id":"minecraft:red_flower","blockRuntimeId":6006},{"id":"minecraft:red_flower","blockRuntimeId":6007},{"id":"minecraft:red_flower","blockRuntimeId":6008},{"id":"minecraft:red_flower","blockRuntimeId":6009},{"id":"minecraft:red_flower","blockRuntimeId":6010},{"id":"minecraft:red_flower","blockRuntimeId":6011},{"id":"minecraft:double_plant","blockRuntimeId":9243},{"id":"minecraft:double_plant","blockRuntimeId":9244},{"id":"minecraft:double_plant","blockRuntimeId":9247},{"id":"minecraft:double_plant","blockRuntimeId":9248},{"id":"minecraft:pitcher_plant","blockRuntimeId":6155},{"id":"minecraft:pink_petals","blockRuntimeId":7541},{"id":"minecraft:wither_rose","blockRuntimeId":11332},{"id":"minecraft:torchflower","blockRuntimeId":11209},{"id":"minecraft:white_dye"},{"id":"minecraft:light_gray_dye"},{"id":"minecraft:gray_dye"},{"id":"minecraft:black_dye"},{"id":"minecraft:brown_dye"},{"id":"minecraft:red_dye"},{"id":"minecraft:orange_dye"},{"id":"minecraft:yellow_dye"},{"id":"minecraft:lime_dye"},{"id":"minecraft:green_dye"},{"id":"minecraft:cyan_dye"},{"id":"minecraft:light_blue_dye"},{"id":"minecraft:blue_dye"},{"id":"minecraft:purple_dye"},{"id":"minecraft:magenta_dye"},{"id":"minecraft:pink_dye"},{"id":"minecraft:ink_sac"},{"id":"minecraft:glow_ink_sac"},{"id":"minecraft:cocoa_beans"},{"id":"minecraft:lapis_lazuli"},{"id":"minecraft:bone_meal"},{"id":"minecraft:vine","blockRuntimeId":2361},{"id":"minecraft:weeping_vines","blockRuntimeId":9303},{"id":"minecraft:twisting_vines","blockRuntimeId":9514},{"id":"minecraft:waterlily","blockRuntimeId":2618},{"id":"minecraft:seagrass","blockRuntimeId":677},{"id":"minecraft:kelp"},{"id":"minecraft:deadbush","blockRuntimeId":7993},{"id":"minecraft:bamboo","blockRuntimeId":6072},{"id":"minecraft:snow","blockRuntimeId":6997},{"id":"minecraft:ice","blockRuntimeId":11891},{"id":"minecraft:packed_ice","blockRuntimeId":743},{"id":"minecraft:blue_ice","blockRuntimeId":12255},{"id":"minecraft:snow_layer","blockRuntimeId":576},{"id":"minecraft:pointed_dripstone","blockRuntimeId":13113},{"id":"minecraft:dripstone_block","blockRuntimeId":2360},{"id":"minecraft:moss_carpet","blockRuntimeId":747},{"id":"minecraft:moss_block","blockRuntimeId":11747},{"id":"minecraft:dirt_with_roots","blockRuntimeId":9187},{"id":"minecraft:hanging_roots","blockRuntimeId":627},{"id":"minecraft:mangrove_roots","blockRuntimeId":11342},{"id":"minecraft:muddy_mangrove_roots","blockRuntimeId":1096},{"id":"minecraft:big_dripleaf","blockRuntimeId":10717},{"id":"minecraft:small_dripleaf_block","blockRuntimeId":7165},{"id":"minecraft:spore_blossom","blockRuntimeId":13012},{"id":"minecraft:azalea","blockRuntimeId":12099},{"id":"minecraft:flowering_azalea","blockRuntimeId":9300},{"id":"minecraft:glow_lichen","blockRuntimeId":9507},{"id":"minecraft:amethyst_block","blockRuntimeId":783},{"id":"minecraft:budding_amethyst","blockRuntimeId":12224},{"id":"minecraft:amethyst_cluster","blockRuntimeId":13614},{"id":"minecraft:large_amethyst_bud","blockRuntimeId":8044},{"id":"minecraft:medium_amethyst_bud","blockRuntimeId":7221},{"id":"minecraft:small_amethyst_bud","blockRuntimeId":1055},{"id":"minecraft:tuff","blockRuntimeId":1103},{"id":"minecraft:calcite","blockRuntimeId":637},{"id":"minecraft:chicken"},{"id":"minecraft:porkchop"},{"id":"minecraft:beef"},{"id":"minecraft:mutton"},{"id":"minecraft:rabbit"},{"id":"minecraft:cod"},{"id":"minecraft:salmon"},{"id":"minecraft:tropical_fish"},{"id":"minecraft:pufferfish"},{"id":"minecraft:brown_mushroom","blockRuntimeId":5907},{"id":"minecraft:red_mushroom","blockRuntimeId":7471},{"id":"minecraft:crimson_fungus","blockRuntimeId":13474},{"id":"minecraft:warped_fungus","blockRuntimeId":748},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13062},{"id":"minecraft:red_mushroom_block","blockRuntimeId":5993},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13063},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":13048},{"id":"minecraft:egg"},{"id":"minecraft:sugar_cane"},{"id":"minecraft:sugar"},{"id":"minecraft:rotten_flesh"},{"id":"minecraft:bone"},{"id":"minecraft:web","blockRuntimeId":11916},{"id":"minecraft:spider_eye"},{"id":"minecraft:mob_spawner","blockRuntimeId":1163},{"id":"minecraft:end_portal_frame","blockRuntimeId":11210},{"id":"minecraft:monster_egg","blockRuntimeId":6930},{"id":"minecraft:monster_egg","blockRuntimeId":6931},{"id":"minecraft:monster_egg","blockRuntimeId":6932},{"id":"minecraft:monster_egg","blockRuntimeId":6933},{"id":"minecraft:monster_egg","blockRuntimeId":6934},{"id":"minecraft:monster_egg","blockRuntimeId":6935},{"id":"minecraft:infested_deepslate","blockRuntimeId":7957},{"id":"minecraft:dragon_egg","blockRuntimeId":12970},{"id":"minecraft:turtle_egg","blockRuntimeId":13805},{"id":"minecraft:sniffer_egg","blockRuntimeId":12225},{"id":"minecraft:frog_spawn","blockRuntimeId":7254},{"id":"minecraft:pearlescent_froglight","blockRuntimeId":11620},{"id":"minecraft:verdant_froglight","blockRuntimeId":11679},{"id":"minecraft:ochre_froglight","blockRuntimeId":5324},{"id":"minecraft:chicken_spawn_egg"},{"id":"minecraft:bee_spawn_egg"},{"id":"minecraft:cow_spawn_egg"},{"id":"minecraft:pig_spawn_egg"},{"id":"minecraft:sheep_spawn_egg"},{"id":"minecraft:wolf_spawn_egg"},{"id":"minecraft:polar_bear_spawn_egg"},{"id":"minecraft:ocelot_spawn_egg"},{"id":"minecraft:cat_spawn_egg"},{"id":"minecraft:mooshroom_spawn_egg"},{"id":"minecraft:bat_spawn_egg"},{"id":"minecraft:parrot_spawn_egg"},{"id":"minecraft:rabbit_spawn_egg"},{"id":"minecraft:llama_spawn_egg"},{"id":"minecraft:horse_spawn_egg"},{"id":"minecraft:donkey_spawn_egg"},{"id":"minecraft:mule_spawn_egg"},{"id":"minecraft:skeleton_horse_spawn_egg"},{"id":"minecraft:zombie_horse_spawn_egg"},{"id":"minecraft:tropical_fish_spawn_egg"},{"id":"minecraft:cod_spawn_egg"},{"id":"minecraft:pufferfish_spawn_egg"},{"id":"minecraft:salmon_spawn_egg"},{"id":"minecraft:dolphin_spawn_egg"},{"id":"minecraft:turtle_spawn_egg"},{"id":"minecraft:panda_spawn_egg"},{"id":"minecraft:fox_spawn_egg"},{"id":"minecraft:creeper_spawn_egg"},{"id":"minecraft:enderman_spawn_egg"},{"id":"minecraft:silverfish_spawn_egg"},{"id":"minecraft:skeleton_spawn_egg"},{"id":"minecraft:wither_skeleton_spawn_egg"},{"id":"minecraft:stray_spawn_egg"},{"id":"minecraft:slime_spawn_egg"},{"id":"minecraft:spider_spawn_egg"},{"id":"minecraft:zombie_spawn_egg"},{"id":"minecraft:zombie_pigman_spawn_egg"},{"id":"minecraft:husk_spawn_egg"},{"id":"minecraft:drowned_spawn_egg"},{"id":"minecraft:squid_spawn_egg"},{"id":"minecraft:glow_squid_spawn_egg"},{"id":"minecraft:cave_spider_spawn_egg"},{"id":"minecraft:witch_spawn_egg"},{"id":"minecraft:guardian_spawn_egg"},{"id":"minecraft:elder_guardian_spawn_egg"},{"id":"minecraft:endermite_spawn_egg"},{"id":"minecraft:magma_cube_spawn_egg"},{"id":"minecraft:strider_spawn_egg"},{"id":"minecraft:hoglin_spawn_egg"},{"id":"minecraft:piglin_spawn_egg"},{"id":"minecraft:zoglin_spawn_egg"},{"id":"minecraft:piglin_brute_spawn_egg"},{"id":"minecraft:goat_spawn_egg"},{"id":"minecraft:axolotl_spawn_egg"},{"id":"minecraft:warden_spawn_egg"},{"id":"minecraft:allay_spawn_egg"},{"id":"minecraft:frog_spawn_egg"},{"id":"minecraft:tadpole_spawn_egg"},{"id":"minecraft:trader_llama_spawn_egg"},{"id":"minecraft:camel_spawn_egg"},{"id":"minecraft:ghast_spawn_egg"},{"id":"minecraft:blaze_spawn_egg"},{"id":"minecraft:shulker_spawn_egg"},{"id":"minecraft:vindicator_spawn_egg"},{"id":"minecraft:evoker_spawn_egg"},{"id":"minecraft:vex_spawn_egg"},{"id":"minecraft:villager_spawn_egg"},{"id":"minecraft:wandering_trader_spawn_egg"},{"id":"minecraft:zombie_villager_spawn_egg"},{"id":"minecraft:phantom_spawn_egg"},{"id":"minecraft:pillager_spawn_egg"},{"id":"minecraft:ravager_spawn_egg"},{"id":"minecraft:iron_golem_spawn_egg"},{"id":"minecraft:snow_golem_spawn_egg"},{"id":"minecraft:sniffer_spawn_egg"},{"id":"minecraft:obsidian","blockRuntimeId":1192},{"id":"minecraft:crying_obsidian","blockRuntimeId":11927},{"id":"minecraft:bedrock","blockRuntimeId":12243},{"id":"minecraft:soul_sand","blockRuntimeId":9660},{"id":"minecraft:magma","blockRuntimeId":13817},{"id":"minecraft:nether_wart"},{"id":"minecraft:end_stone","blockRuntimeId":6615},{"id":"minecraft:chorus_flower","blockRuntimeId":7391},{"id":"minecraft:chorus_plant","blockRuntimeId":9329},{"id":"minecraft:chorus_fruit"},{"id":"minecraft:popped_chorus_fruit"},{"id":"minecraft:sponge","blockRuntimeId":1380},{"id":"minecraft:sponge","blockRuntimeId":1381},{"id":"minecraft:coral_block","blockRuntimeId":9027},{"id":"minecraft:coral_block","blockRuntimeId":9028},{"id":"minecraft:coral_block","blockRuntimeId":9029},{"id":"minecraft:coral_block","blockRuntimeId":9030},{"id":"minecraft:coral_block","blockRuntimeId":9031},{"id":"minecraft:coral_block","blockRuntimeId":9032},{"id":"minecraft:coral_block","blockRuntimeId":9033},{"id":"minecraft:coral_block","blockRuntimeId":9034},{"id":"minecraft:coral_block","blockRuntimeId":9035},{"id":"minecraft:coral_block","blockRuntimeId":9036},{"id":"minecraft:sculk","blockRuntimeId":12266},{"id":"minecraft:sculk_vein","blockRuntimeId":12794},{"id":"minecraft:sculk_catalyst","blockRuntimeId":5995},{"id":"minecraft:sculk_shrieker","blockRuntimeId":646},{"id":"minecraft:sculk_sensor","blockRuntimeId":7235},{"id":"minecraft:calibrated_sculk_sensor","blockRuntimeId":10070},{"id":"minecraft:reinforced_deepslate","blockRuntimeId":10046},{"id":"minecraft:leather_helmet"},{"id":"minecraft:chainmail_helmet"},{"id":"minecraft:iron_helmet"},{"id":"minecraft:golden_helmet"},{"id":"minecraft:diamond_helmet"},{"id":"minecraft:netherite_helmet"},{"id":"minecraft:leather_chestplate"},{"id":"minecraft:chainmail_chestplate"},{"id":"minecraft:iron_chestplate"},{"id":"minecraft:golden_chestplate"},{"id":"minecraft:diamond_chestplate"},{"id":"minecraft:netherite_chestplate"},{"id":"minecraft:leather_leggings"},{"id":"minecraft:chainmail_leggings"},{"id":"minecraft:iron_leggings"},{"id":"minecraft:golden_leggings"},{"id":"minecraft:diamond_leggings"},{"id":"minecraft:netherite_leggings"},{"id":"minecraft:leather_boots"},{"id":"minecraft:chainmail_boots"},{"id":"minecraft:iron_boots"},{"id":"minecraft:golden_boots"},{"id":"minecraft:diamond_boots"},{"id":"minecraft:netherite_boots"},{"id":"minecraft:wooden_sword"},{"id":"minecraft:stone_sword"},{"id":"minecraft:iron_sword"},{"id":"minecraft:golden_sword"},{"id":"minecraft:diamond_sword"},{"id":"minecraft:netherite_sword"},{"id":"minecraft:wooden_axe"},{"id":"minecraft:stone_axe"},{"id":"minecraft:iron_axe"},{"id":"minecraft:golden_axe"},{"id":"minecraft:diamond_axe"},{"id":"minecraft:netherite_axe"},{"id":"minecraft:wooden_pickaxe"},{"id":"minecraft:stone_pickaxe"},{"id":"minecraft:iron_pickaxe"},{"id":"minecraft:golden_pickaxe"},{"id":"minecraft:diamond_pickaxe"},{"id":"minecraft:netherite_pickaxe"},{"id":"minecraft:wooden_shovel"},{"id":"minecraft:stone_shovel"},{"id":"minecraft:iron_shovel"},{"id":"minecraft:golden_shovel"},{"id":"minecraft:diamond_shovel"},{"id":"minecraft:netherite_shovel"},{"id":"minecraft:wooden_hoe"},{"id":"minecraft:stone_hoe"},{"id":"minecraft:iron_hoe"},{"id":"minecraft:golden_hoe"},{"id":"minecraft:diamond_hoe"},{"id":"minecraft:netherite_hoe"},{"id":"minecraft:bow"},{"id":"minecraft:crossbow"},{"id":"minecraft:arrow"},{"id":"minecraft:arrow","damage":6},{"id":"minecraft:arrow","damage":7},{"id":"minecraft:arrow","damage":8},{"id":"minecraft:arrow","damage":9},{"id":"minecraft:arrow","damage":10},{"id":"minecraft:arrow","damage":11},{"id":"minecraft:arrow","damage":12},{"id":"minecraft:arrow","damage":13},{"id":"minecraft:arrow","damage":14},{"id":"minecraft:arrow","damage":15},{"id":"minecraft:arrow","damage":16},{"id":"minecraft:arrow","damage":17},{"id":"minecraft:arrow","damage":18},{"id":"minecraft:arrow","damage":19},{"id":"minecraft:arrow","damage":20},{"id":"minecraft:arrow","damage":21},{"id":"minecraft:arrow","damage":22},{"id":"minecraft:arrow","damage":23},{"id":"minecraft:arrow","damage":24},{"id":"minecraft:arrow","damage":25},{"id":"minecraft:arrow","damage":26},{"id":"minecraft:arrow","damage":27},{"id":"minecraft:arrow","damage":28},{"id":"minecraft:arrow","damage":29},{"id":"minecraft:arrow","damage":30},{"id":"minecraft:arrow","damage":31},{"id":"minecraft:arrow","damage":32},{"id":"minecraft:arrow","damage":33},{"id":"minecraft:arrow","damage":34},{"id":"minecraft:arrow","damage":35},{"id":"minecraft:arrow","damage":36},{"id":"minecraft:arrow","damage":37},{"id":"minecraft:arrow","damage":38},{"id":"minecraft:arrow","damage":39},{"id":"minecraft:arrow","damage":40},{"id":"minecraft:arrow","damage":41},{"id":"minecraft:arrow","damage":42},{"id":"minecraft:arrow","damage":43},{"id":"minecraft:shield"},{"id":"minecraft:cooked_chicken"},{"id":"minecraft:cooked_porkchop"},{"id":"minecraft:cooked_beef"},{"id":"minecraft:cooked_mutton"},{"id":"minecraft:cooked_rabbit"},{"id":"minecraft:cooked_cod"},{"id":"minecraft:cooked_salmon"},{"id":"minecraft:bread"},{"id":"minecraft:mushroom_stew"},{"id":"minecraft:beetroot_soup"},{"id":"minecraft:rabbit_stew"},{"id":"minecraft:baked_potato"},{"id":"minecraft:cookie"},{"id":"minecraft:pumpkin_pie"},{"id":"minecraft:cake"},{"id":"minecraft:dried_kelp"},{"id":"minecraft:fishing_rod"},{"id":"minecraft:carrot_on_a_stick"},{"id":"minecraft:warped_fungus_on_a_stick"},{"id":"minecraft:snowball"},{"id":"minecraft:shears"},{"id":"minecraft:flint_and_steel"},{"id":"minecraft:lead"},{"id":"minecraft:clock"},{"id":"minecraft:compass"},{"id":"minecraft:recovery_compass"},{"id":"minecraft:goat_horn"},{"id":"minecraft:goat_horn","damage":1},{"id":"minecraft:goat_horn","damage":2},{"id":"minecraft:goat_horn","damage":3},{"id":"minecraft:goat_horn","damage":4},{"id":"minecraft:goat_horn","damage":5},{"id":"minecraft:goat_horn","damage":6},{"id":"minecraft:goat_horn","damage":7},{"id":"minecraft:empty_map"},{"id":"minecraft:empty_map","damage":2},{"id":"minecraft:saddle"},{"id":"minecraft:leather_horse_armor"},{"id":"minecraft:iron_horse_armor"},{"id":"minecraft:golden_horse_armor"},{"id":"minecraft:diamond_horse_armor"},{"id":"minecraft:trident"},{"id":"minecraft:turtle_helmet"},{"id":"minecraft:elytra"},{"id":"minecraft:totem_of_undying"},{"id":"minecraft:glass_bottle"},{"id":"minecraft:experience_bottle"},{"id":"minecraft:potion"},{"id":"minecraft:potion","damage":1},{"id":"minecraft:potion","damage":2},{"id":"minecraft:potion","damage":3},{"id":"minecraft:potion","damage":4},{"id":"minecraft:potion","damage":5},{"id":"minecraft:potion","damage":6},{"id":"minecraft:potion","damage":7},{"id":"minecraft:potion","damage":8},{"id":"minecraft:potion","damage":9},{"id":"minecraft:potion","damage":10},{"id":"minecraft:potion","damage":11},{"id":"minecraft:potion","damage":12},{"id":"minecraft:potion","damage":13},{"id":"minecraft:potion","damage":14},{"id":"minecraft:potion","damage":15},{"id":"minecraft:potion","damage":16},{"id":"minecraft:potion","damage":17},{"id":"minecraft:potion","damage":18},{"id":"minecraft:potion","damage":19},{"id":"minecraft:potion","damage":20},{"id":"minecraft:potion","damage":21},{"id":"minecraft:potion","damage":22},{"id":"minecraft:potion","damage":23},{"id":"minecraft:potion","damage":24},{"id":"minecraft:potion","damage":25},{"id":"minecraft:potion","damage":26},{"id":"minecraft:potion","damage":27},{"id":"minecraft:potion","damage":28},{"id":"minecraft:potion","damage":29},{"id":"minecraft:potion","damage":30},{"id":"minecraft:potion","damage":31},{"id":"minecraft:potion","damage":32},{"id":"minecraft:potion","damage":33},{"id":"minecraft:potion","damage":34},{"id":"minecraft:potion","damage":35},{"id":"minecraft:potion","damage":36},{"id":"minecraft:potion","damage":37},{"id":"minecraft:potion","damage":38},{"id":"minecraft:potion","damage":39},{"id":"minecraft:potion","damage":40},{"id":"minecraft:potion","damage":41},{"id":"minecraft:potion","damage":42},{"id":"minecraft:splash_potion"},{"id":"minecraft:splash_potion","damage":1},{"id":"minecraft:splash_potion","damage":2},{"id":"minecraft:splash_potion","damage":3},{"id":"minecraft:splash_potion","damage":4},{"id":"minecraft:splash_potion","damage":5},{"id":"minecraft:splash_potion","damage":6},{"id":"minecraft:splash_potion","damage":7},{"id":"minecraft:splash_potion","damage":8},{"id":"minecraft:splash_potion","damage":9},{"id":"minecraft:splash_potion","damage":10},{"id":"minecraft:splash_potion","damage":11},{"id":"minecraft:splash_potion","damage":12},{"id":"minecraft:splash_potion","damage":13},{"id":"minecraft:splash_potion","damage":14},{"id":"minecraft:splash_potion","damage":15},{"id":"minecraft:splash_potion","damage":16},{"id":"minecraft:splash_potion","damage":17},{"id":"minecraft:splash_potion","damage":18},{"id":"minecraft:splash_potion","damage":19},{"id":"minecraft:splash_potion","damage":20},{"id":"minecraft:splash_potion","damage":21},{"id":"minecraft:splash_potion","damage":22},{"id":"minecraft:splash_potion","damage":23},{"id":"minecraft:splash_potion","damage":24},{"id":"minecraft:splash_potion","damage":25},{"id":"minecraft:splash_potion","damage":26},{"id":"minecraft:splash_potion","damage":27},{"id":"minecraft:splash_potion","damage":28},{"id":"minecraft:splash_potion","damage":29},{"id":"minecraft:splash_potion","damage":30},{"id":"minecraft:splash_potion","damage":31},{"id":"minecraft:splash_potion","damage":32},{"id":"minecraft:splash_potion","damage":33},{"id":"minecraft:splash_potion","damage":34},{"id":"minecraft:splash_potion","damage":35},{"id":"minecraft:splash_potion","damage":36},{"id":"minecraft:splash_potion","damage":37},{"id":"minecraft:splash_potion","damage":38},{"id":"minecraft:splash_potion","damage":39},{"id":"minecraft:splash_potion","damage":40},{"id":"minecraft:splash_potion","damage":41},{"id":"minecraft:splash_potion","damage":42},{"id":"minecraft:lingering_potion"},{"id":"minecraft:lingering_potion","damage":1},{"id":"minecraft:lingering_potion","damage":2},{"id":"minecraft:lingering_potion","damage":3},{"id":"minecraft:lingering_potion","damage":4},{"id":"minecraft:lingering_potion","damage":5},{"id":"minecraft:lingering_potion","damage":6},{"id":"minecraft:lingering_potion","damage":7},{"id":"minecraft:lingering_potion","damage":8},{"id":"minecraft:lingering_potion","damage":9},{"id":"minecraft:lingering_potion","damage":10},{"id":"minecraft:lingering_potion","damage":11},{"id":"minecraft:lingering_potion","damage":12},{"id":"minecraft:lingering_potion","damage":13},{"id":"minecraft:lingering_potion","damage":14},{"id":"minecraft:lingering_potion","damage":15},{"id":"minecraft:lingering_potion","damage":16},{"id":"minecraft:lingering_potion","damage":17},{"id":"minecraft:lingering_potion","damage":18},{"id":"minecraft:lingering_potion","damage":19},{"id":"minecraft:lingering_potion","damage":20},{"id":"minecraft:lingering_potion","damage":21},{"id":"minecraft:lingering_potion","damage":22},{"id":"minecraft:lingering_potion","damage":23},{"id":"minecraft:lingering_potion","damage":24},{"id":"minecraft:lingering_potion","damage":25},{"id":"minecraft:lingering_potion","damage":26},{"id":"minecraft:lingering_potion","damage":27},{"id":"minecraft:lingering_potion","damage":28},{"id":"minecraft:lingering_potion","damage":29},{"id":"minecraft:lingering_potion","damage":30},{"id":"minecraft:lingering_potion","damage":31},{"id":"minecraft:lingering_potion","damage":32},{"id":"minecraft:lingering_potion","damage":33},{"id":"minecraft:lingering_potion","damage":34},{"id":"minecraft:lingering_potion","damage":35},{"id":"minecraft:lingering_potion","damage":36},{"id":"minecraft:lingering_potion","damage":37},{"id":"minecraft:lingering_potion","damage":38},{"id":"minecraft:lingering_potion","damage":39},{"id":"minecraft:lingering_potion","damage":40},{"id":"minecraft:lingering_potion","damage":41},{"id":"minecraft:lingering_potion","damage":42},{"id":"minecraft:spyglass"},{"id":"minecraft:brush"},{"id":"minecraft:stick"},{"id":"minecraft:bed"},{"id":"minecraft:bed","damage":8},{"id":"minecraft:bed","damage":7},{"id":"minecraft:bed","damage":15},{"id":"minecraft:bed","damage":12},{"id":"minecraft:bed","damage":14},{"id":"minecraft:bed","damage":1},{"id":"minecraft:bed","damage":4},{"id":"minecraft:bed","damage":5},{"id":"minecraft:bed","damage":13},{"id":"minecraft:bed","damage":9},{"id":"minecraft:bed","damage":3},{"id":"minecraft:bed","damage":11},{"id":"minecraft:bed","damage":10},{"id":"minecraft:bed","damage":2},{"id":"minecraft:bed","damage":6},{"id":"minecraft:torch","blockRuntimeId":2191},{"id":"minecraft:soul_torch","blockRuntimeId":7960},{"id":"minecraft:sea_pickle","blockRuntimeId":10105},{"id":"minecraft:lantern","blockRuntimeId":12306},{"id":"minecraft:soul_lantern","blockRuntimeId":9576},{"id":"minecraft:candle","blockRuntimeId":13100},{"id":"minecraft:white_candle","blockRuntimeId":9096},{"id":"minecraft:orange_candle","blockRuntimeId":1121},{"id":"minecraft:magenta_candle","blockRuntimeId":1181},{"id":"minecraft:light_blue_candle","blockRuntimeId":7435},{"id":"minecraft:yellow_candle","blockRuntimeId":11343},{"id":"minecraft:lime_candle","blockRuntimeId":11519},{"id":"minecraft:pink_candle","blockRuntimeId":13066},{"id":"minecraft:gray_candle","blockRuntimeId":2423},{"id":"minecraft:light_gray_candle","blockRuntimeId":11372},{"id":"minecraft:cyan_candle","blockRuntimeId":13446},{"id":"minecraft:purple_candle","blockRuntimeId":12269},{"id":"minecraft:blue_candle","blockRuntimeId":2},{"id":"minecraft:brown_candle","blockRuntimeId":10530},{"id":"minecraft:green_candle","blockRuntimeId":1973},{"id":"minecraft:red_candle","blockRuntimeId":7997},{"id":"minecraft:black_candle","blockRuntimeId":592},{"id":"minecraft:crafting_table","blockRuntimeId":10104},{"id":"minecraft:cartography_table","blockRuntimeId":14116},{"id":"minecraft:fletching_table","blockRuntimeId":10047},{"id":"minecraft:smithing_table","blockRuntimeId":6117},{"id":"minecraft:beehive","blockRuntimeId":11274},{"id":"minecraft:suspicious_sand","blockRuntimeId":2622},{"id":"minecraft:suspicious_gravel","blockRuntimeId":8082},{"id":"minecraft:campfire"},{"id":"minecraft:soul_campfire"},{"id":"minecraft:furnace","blockRuntimeId":13610},{"id":"minecraft:blast_furnace","blockRuntimeId":13277},{"id":"minecraft:smoker","blockRuntimeId":1786},{"id":"minecraft:respawn_anchor","blockRuntimeId":1967},{"id":"minecraft:brewing_stand"},{"id":"minecraft:anvil","blockRuntimeId":11847},{"id":"minecraft:anvil","blockRuntimeId":11851},{"id":"minecraft:anvil","blockRuntimeId":11855},{"id":"minecraft:grindstone","blockRuntimeId":13832},{"id":"minecraft:enchanting_table","blockRuntimeId":11933},{"id":"minecraft:bookshelf","blockRuntimeId":11885},{"id":"minecraft:chiseled_bookshelf","blockRuntimeId":787},{"id":"minecraft:lectern","blockRuntimeId":12162},{"id":"minecraft:cauldron"},{"id":"minecraft:composter","blockRuntimeId":9206},{"id":"minecraft:chest","blockRuntimeId":12383},{"id":"minecraft:trapped_chest","blockRuntimeId":9409},{"id":"minecraft:ender_chest","blockRuntimeId":7218},{"id":"minecraft:barrel","blockRuntimeId":7379},{"id":"minecraft:undyed_shulker_box","blockRuntimeId":6069},{"id":"minecraft:white_shulker_box","blockRuntimeId":1118},{"id":"minecraft:light_gray_shulker_box","blockRuntimeId":10564},{"id":"minecraft:gray_shulker_box","blockRuntimeId":9136},{"id":"minecraft:black_shulker_box","blockRuntimeId":11158},{"id":"minecraft:brown_shulker_box","blockRuntimeId":12098},{"id":"minecraft:red_shulker_box","blockRuntimeId":7287},{"id":"minecraft:orange_shulker_box","blockRuntimeId":11371},{"id":"minecraft:yellow_shulker_box","blockRuntimeId":126},{"id":"minecraft:lime_shulker_box","blockRuntimeId":1052},{"id":"minecraft:green_shulker_box","blockRuntimeId":11688},{"id":"minecraft:cyan_shulker_box","blockRuntimeId":12256},{"id":"minecraft:light_blue_shulker_box","blockRuntimeId":12189},{"id":"minecraft:blue_shulker_box","blockRuntimeId":11420},{"id":"minecraft:purple_shulker_box","blockRuntimeId":13074},{"id":"minecraft:magenta_shulker_box","blockRuntimeId":742},{"id":"minecraft:pink_shulker_box","blockRuntimeId":7226},{"id":"minecraft:armor_stand"},{"id":"minecraft:noteblock","blockRuntimeId":1102},{"id":"minecraft:jukebox","blockRuntimeId":8645},{"id":"minecraft:music_disc_13"},{"id":"minecraft:music_disc_cat"},{"id":"minecraft:music_disc_blocks"},{"id":"minecraft:music_disc_chirp"},{"id":"minecraft:music_disc_far"},{"id":"minecraft:music_disc_mall"},{"id":"minecraft:music_disc_mellohi"},{"id":"minecraft:music_disc_stal"},{"id":"minecraft:music_disc_strad"},{"id":"minecraft:music_disc_ward"},{"id":"minecraft:music_disc_11"},{"id":"minecraft:music_disc_wait"},{"id":"minecraft:music_disc_otherside"},{"id":"minecraft:music_disc_5"},{"id":"minecraft:music_disc_pigstep"},{"id":"minecraft:music_disc_relic"},{"id":"minecraft:disc_fragment_5"},{"id":"minecraft:glowstone_dust"},{"id":"minecraft:glowstone","blockRuntimeId":6669},{"id":"minecraft:redstone_lamp","blockRuntimeId":682},{"id":"minecraft:sea_lantern","blockRuntimeId":13250},{"id":"minecraft:oak_sign"},{"id":"minecraft:spruce_sign"},{"id":"minecraft:birch_sign"},{"id":"minecraft:jungle_sign"},{"id":"minecraft:acacia_sign"},{"id":"minecraft:dark_oak_sign"},{"id":"minecraft:mangrove_sign"},{"id":"minecraft:cherry_sign"},{"id":"minecraft:bamboo_sign"},{"id":"minecraft:crimson_sign"},{"id":"minecraft:warped_sign"},{"id":"minecraft:oak_hanging_sign"},{"id":"minecraft:spruce_hanging_sign"},{"id":"minecraft:birch_hanging_sign"},{"id":"minecraft:jungle_hanging_sign"},{"id":"minecraft:acacia_hanging_sign"},{"id":"minecraft:dark_oak_hanging_sign"},{"id":"minecraft:mangrove_hanging_sign"},{"id":"minecraft:cherry_hanging_sign"},{"id":"minecraft:bamboo_hanging_sign"},{"id":"minecraft:crimson_hanging_sign"},{"id":"minecraft:warped_hanging_sign"},{"id":"minecraft:painting"},{"id":"minecraft:frame"},{"id":"minecraft:glow_frame"},{"id":"minecraft:honey_bottle"},{"id":"minecraft:flower_pot"},{"id":"minecraft:bowl"},{"id":"minecraft:bucket"},{"id":"minecraft:milk_bucket"},{"id":"minecraft:water_bucket"},{"id":"minecraft:lava_bucket"},{"id":"minecraft:cod_bucket"},{"id":"minecraft:salmon_bucket"},{"id":"minecraft:tropical_fish_bucket"},{"id":"minecraft:pufferfish_bucket"},{"id":"minecraft:powder_snow_bucket"},{"id":"minecraft:axolotl_bucket"},{"id":"minecraft:tadpole_bucket"},{"id":"minecraft:skull","damage":3},{"id":"minecraft:skull","damage":2},{"id":"minecraft:skull","damage":4},{"id":"minecraft:skull","damage":5},{"id":"minecraft:skull"},{"id":"minecraft:skull","damage":1},{"id":"minecraft:skull","damage":6},{"id":"minecraft:beacon","blockRuntimeId":566},{"id":"minecraft:bell","blockRuntimeId":12130},{"id":"minecraft:conduit","blockRuntimeId":7035},{"id":"minecraft:stonecutter_block","blockRuntimeId":13284},{"id":"minecraft:coal"},{"id":"minecraft:charcoal"},{"id":"minecraft:diamond"},{"id":"minecraft:iron_nugget"},{"id":"minecraft:raw_iron"},{"id":"minecraft:raw_gold"},{"id":"minecraft:raw_copper"},{"id":"minecraft:copper_ingot"},{"id":"minecraft:iron_ingot"},{"id":"minecraft:netherite_scrap"},{"id":"minecraft:netherite_ingot"},{"id":"minecraft:gold_nugget"},{"id":"minecraft:gold_ingot"},{"id":"minecraft:emerald"},{"id":"minecraft:quartz"},{"id":"minecraft:clay_ball"},{"id":"minecraft:brick"},{"id":"minecraft:netherbrick"},{"id":"minecraft:prismarine_shard"},{"id":"minecraft:amethyst_shard"},{"id":"minecraft:prismarine_crystals"},{"id":"minecraft:nautilus_shell"},{"id":"minecraft:heart_of_the_sea"},{"id":"minecraft:turtle_scute"},{"id":"minecraft:phantom_membrane"},{"id":"minecraft:string"},{"id":"minecraft:feather"},{"id":"minecraft:flint"},{"id":"minecraft:gunpowder"},{"id":"minecraft:leather"},{"id":"minecraft:rabbit_hide"},{"id":"minecraft:rabbit_foot"},{"id":"minecraft:fire_charge"},{"id":"minecraft:blaze_rod"},{"id":"minecraft:blaze_powder"},{"id":"minecraft:magma_cream"},{"id":"minecraft:fermented_spider_eye"},{"id":"minecraft:echo_shard"},{"id":"minecraft:dragon_breath"},{"id":"minecraft:shulker_shell"},{"id":"minecraft:ghast_tear"},{"id":"minecraft:slime_ball"},{"id":"minecraft:ender_pearl"},{"id":"minecraft:ender_eye"},{"id":"minecraft:nether_star"},{"id":"minecraft:end_rod","blockRuntimeId":10546},{"id":"minecraft:lightning_rod","blockRuntimeId":2646},{"id":"minecraft:end_crystal"},{"id":"minecraft:paper"},{"id":"minecraft:book"},{"id":"minecraft:writable_book"},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA="},{"id":"minecraft:oak_boat"},{"id":"minecraft:spruce_boat"},{"id":"minecraft:birch_boat"},{"id":"minecraft:jungle_boat"},{"id":"minecraft:acacia_boat"},{"id":"minecraft:dark_oak_boat"},{"id":"minecraft:mangrove_boat"},{"id":"minecraft:cherry_boat"},{"id":"minecraft:bamboo_raft"},{"id":"minecraft:oak_chest_boat"},{"id":"minecraft:spruce_chest_boat"},{"id":"minecraft:birch_chest_boat"},{"id":"minecraft:jungle_chest_boat"},{"id":"minecraft:acacia_chest_boat"},{"id":"minecraft:dark_oak_chest_boat"},{"id":"minecraft:mangrove_chest_boat"},{"id":"minecraft:cherry_chest_boat"},{"id":"minecraft:bamboo_chest_raft"},{"id":"minecraft:rail","blockRuntimeId":6705},{"id":"minecraft:golden_rail","blockRuntimeId":9113},{"id":"minecraft:detector_rail","blockRuntimeId":6918},{"id":"minecraft:activator_rail","blockRuntimeId":1060},{"id":"minecraft:minecart"},{"id":"minecraft:chest_minecart"},{"id":"minecraft:hopper_minecart"},{"id":"minecraft:tnt_minecart"},{"id":"minecraft:redstone"},{"id":"minecraft:redstone_block","blockRuntimeId":6175},{"id":"minecraft:redstone_torch","blockRuntimeId":5501},{"id":"minecraft:lever","blockRuntimeId":11704},{"id":"minecraft:wooden_button","blockRuntimeId":11543},{"id":"minecraft:spruce_button","blockRuntimeId":7168},{"id":"minecraft:birch_button","blockRuntimeId":13523},{"id":"minecraft:jungle_button","blockRuntimeId":131},{"id":"minecraft:acacia_button","blockRuntimeId":12913},{"id":"minecraft:dark_oak_button","blockRuntimeId":105},{"id":"minecraft:mangrove_button","blockRuntimeId":12293},{"id":"minecraft:cherry_button","blockRuntimeId":7511},{"id":"minecraft:bamboo_button","blockRuntimeId":11665},{"id":"minecraft:stone_button","blockRuntimeId":1364},{"id":"minecraft:crimson_button","blockRuntimeId":7291},{"id":"minecraft:warped_button","blockRuntimeId":12931},{"id":"minecraft:polished_blackstone_button","blockRuntimeId":13547},{"id":"minecraft:tripwire_hook","blockRuntimeId":10618},{"id":"minecraft:wooden_pressure_plate","blockRuntimeId":13857},{"id":"minecraft:spruce_pressure_plate","blockRuntimeId":6157},{"id":"minecraft:birch_pressure_plate","blockRuntimeId":5934},{"id":"minecraft:jungle_pressure_plate","blockRuntimeId":6022},{"id":"minecraft:acacia_pressure_plate","blockRuntimeId":9038},{"id":"minecraft:dark_oak_pressure_plate","blockRuntimeId":10660},{"id":"minecraft:mangrove_pressure_plate","blockRuntimeId":6650},{"id":"minecraft:cherry_pressure_plate","blockRuntimeId":157},{"id":"minecraft:bamboo_pressure_plate","blockRuntimeId":11218},{"id":"minecraft:crimson_pressure_plate","blockRuntimeId":14096},{"id":"minecraft:warped_pressure_plate","blockRuntimeId":708},{"id":"minecraft:stone_pressure_plate","blockRuntimeId":6670},{"id":"minecraft:light_weighted_pressure_plate","blockRuntimeId":6053},{"id":"minecraft:heavy_weighted_pressure_plate","blockRuntimeId":2629},{"id":"minecraft:polished_blackstone_pressure_plate","blockRuntimeId":11380},{"id":"minecraft:observer","blockRuntimeId":5489},{"id":"minecraft:daylight_detector","blockRuntimeId":7000},{"id":"minecraft:repeater"},{"id":"minecraft:comparator"},{"id":"minecraft:hopper"},{"id":"minecraft:dropper","blockRuntimeId":13082},{"id":"minecraft:dispenser","blockRuntimeId":13821},{"id":"minecraft:piston","blockRuntimeId":2405},{"id":"minecraft:sticky_piston","blockRuntimeId":7211},{"id":"minecraft:tnt","blockRuntimeId":11910},{"id":"minecraft:name_tag"},{"id":"minecraft:loom","blockRuntimeId":6611},{"id":"minecraft:banner","nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":8,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":7,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":12,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":14,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":1,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":4,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":5,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":13,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":9,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":3,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":11,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":10,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":2,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":6,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQEAAAAA"},{"id":"minecraft:creeper_banner_pattern"},{"id":"minecraft:skull_banner_pattern"},{"id":"minecraft:flower_banner_pattern"},{"id":"minecraft:mojang_banner_pattern"},{"id":"minecraft:field_masoned_banner_pattern"},{"id":"minecraft:bordure_indented_banner_pattern"},{"id":"minecraft:piglin_banner_pattern"},{"id":"minecraft:globe_banner_pattern"},{"id":"minecraft:angler_pottery_sherd"},{"id":"minecraft:archer_pottery_sherd"},{"id":"minecraft:arms_up_pottery_sherd"},{"id":"minecraft:blade_pottery_sherd"},{"id":"minecraft:brewer_pottery_sherd"},{"id":"minecraft:burn_pottery_sherd"},{"id":"minecraft:danger_pottery_sherd"},{"id":"minecraft:explorer_pottery_sherd"},{"id":"minecraft:friend_pottery_sherd"},{"id":"minecraft:heart_pottery_sherd"},{"id":"minecraft:heartbreak_pottery_sherd"},{"id":"minecraft:howl_pottery_sherd"},{"id":"minecraft:miner_pottery_sherd"},{"id":"minecraft:mourner_pottery_sherd"},{"id":"minecraft:plenty_pottery_sherd"},{"id":"minecraft:prize_pottery_sherd"},{"id":"minecraft:sheaf_pottery_sherd"},{"id":"minecraft:shelter_pottery_sherd"},{"id":"minecraft:skull_pottery_sherd"},{"id":"minecraft:snort_pottery_sherd"},{"id":"minecraft:netherite_upgrade_smithing_template"},{"id":"minecraft:sentry_armor_trim_smithing_template"},{"id":"minecraft:vex_armor_trim_smithing_template"},{"id":"minecraft:wild_armor_trim_smithing_template"},{"id":"minecraft:coast_armor_trim_smithing_template"},{"id":"minecraft:dune_armor_trim_smithing_template"},{"id":"minecraft:wayfinder_armor_trim_smithing_template"},{"id":"minecraft:shaper_armor_trim_smithing_template"},{"id":"minecraft:raiser_armor_trim_smithing_template"},{"id":"minecraft:host_armor_trim_smithing_template"},{"id":"minecraft:ward_armor_trim_smithing_template"},{"id":"minecraft:silence_armor_trim_smithing_template"},{"id":"minecraft:tide_armor_trim_smithing_template"},{"id":"minecraft:snout_armor_trim_smithing_template"},{"id":"minecraft:rib_armor_trim_smithing_template"},{"id":"minecraft:eye_armor_trim_smithing_template"},{"id":"minecraft:spire_armor_trim_smithing_template"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_star","nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA="},{"id":"minecraft:firework_star","damage":8,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA="},{"id":"minecraft:firework_star","damage":7,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA="},{"id":"minecraft:firework_star","damage":15,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA="},{"id":"minecraft:firework_star","damage":12,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA="},{"id":"minecraft:firework_star","damage":14,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA="},{"id":"minecraft:firework_star","damage":1,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA="},{"id":"minecraft:firework_star","damage":4,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA="},{"id":"minecraft:firework_star","damage":5,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA="},{"id":"minecraft:firework_star","damage":13,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA="},{"id":"minecraft:firework_star","damage":9,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA="},{"id":"minecraft:firework_star","damage":3,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA="},{"id":"minecraft:firework_star","damage":11,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA="},{"id":"minecraft:firework_star","damage":10,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA="},{"id":"minecraft:firework_star","damage":2,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA="},{"id":"minecraft:firework_star","damage":6,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA="},{"id":"minecraft:chain"},{"id":"minecraft:target","blockRuntimeId":11542},{"id":"minecraft:decorated_pot","blockRuntimeId":11929},{"id":"minecraft:lodestone_compass"},{"id":"minecraft:wither_spawn_egg"},{"id":"minecraft:ender_dragon_spawn_egg"}]} \ No newline at end of file diff --git a/src/main/resources/entity_identifiers.dat b/src/main/resources/entity_identifiers.dat index 4e8d836c6df13ee61cc445e7edf97d83af9a75b7..297d30537148f5cb80ecd63810b352f3e4c7ab03 100644 GIT binary patch delta 86 zcmX?Sd&!2Ei-D6ZGbJapxPAjQ4; z5|0BDi2V-8R^spDfvGtl{eh9MI4?6TEwzZ5xhOMb8q?&3Jc^SS$mUI)AUyd1GdE9i SVs2^6Nva%~=k5H7o74FU6yT}ejvO?KABgu%eGp;?az1{me z=jqe;_s8$oAMeh2UhmiQp3igL=Nwof&*Ol9*trZ3-^-mtA;lxA8ZHCQ1H!6lcgFI5 zyb%$P+cX@TEA3NxUz_ZxwC@nrq??;bBW06!ZbU3ndP+2vdt)vn?-W8e9uiHp-k6)oJ46@z zSTw-{EL*lnR4Y8n6RBfF~bFn%Fd38N3ZttaPPcpbmYUzg7JzEZ`IYmt%e#XZv&eQKd7(o<6;Xb{K^53B+yCQ+f;cEQjA!BK=At2H=F!MryirnYv z@6~G5Y%I4p=BxSXi*_A-^zC|O`|x)Snh@E2qq?Kws@{^our8m`BQA6CahmVKzO4$M(4NZ)J8x7N+3xABH2`ddi}Qj|-k7i5{NgMi{I^J1U9@6sbx8_6%V= z#jLQ-!^h)?50%d-m%~ zK$>!WO0B2!V3^kfF0YIsn^mEFB)#zp>hyD&>11uRO}Uxi6;-j|)p|wNNm2Eqjl;Lx z@wJsz{eF(U9wt4U8x0<&J)Dlkd+>Go9(0YMYVa={Z4<~A<9*{d{5ge7dt&bFbS*xk zVzu6#ohp-BH6*v}uC9c~-Q=v*+HY=NCY@Px0Y1wK<&~827ehzGxHNb&?RhCGZmGXP z6fC&&$bT9Q;?R03Nypb)&P|y*WWYM}=@*O3 z2i#%9SI29$p-|_S-i#)m5 zRy*?BkHpe+Z9$fxjNg+|Hdx+#Z3e#Hch3qip!ccKmM@BQmX8nkSa`Qo_vA_l;MF_*Ut+r$M(qiuJ??GA3V%0GvG=5M*buG#fgBWy)4}y-J5f* z+YQfR8df+^iMwvHo~CIr!y=V_Z)G!kulK#MaO0tg?P$GU%7Gnox~I;Y8S?^hV4omB z?lSno_i0$2XnS%pIX$DnBwac6ommYc-I3f*@xxVn(iBH~VckW?Hu$rj$m z+qb!Iy8Bx+?uSV&UC6uM#^ioo=OT(iG78csV(NwT17og-mG6a4@k?#B8i!v`(XFlF zr%O-1(iI16Bh?D8=g|gBr|aC+p-z`85@CBT(kc{|e(C)pD%k$n zrgg-^C%bJ4Wv_(~+A2ni(hS!RVwJPFq-pykZiPv-?a35a^b(#^W^&!D@Y{Cl@Hm=N z>75%WX37{(j|tWx-a|i8Qzu-Qc&l)CEYt6TYQNp7wSGnTj%@pHdrQ8om1m-52yHi! zH1A8!wa|)eM20}JBDmKb8Y<~0gH&xuGUlI&Zb@fXKnpjM!#xI4a0}}7fD}UdpO-wL z>7$gK`fZOIm8Y*x)sz-nY7H1QJ7dJkG_=~H8b%~4F0*#Mm|CQZN8>Cpb20D~#8_9v z*M>--ZGzN}`z|T29n(vwU0M0kMlYo{kv~jrj=Jvhw3MUq*L_sEnYwREZkwME-_|@{ zEvxnGLlRumN6Ck3K=J_j3`j;Gjc`qWcJgd_;MKPBZ|wm|4rDZtnn3p8)#m)-+2R8? z#Xvd$sR8Ej1d;;R^!}6Ahg#rntb* z(R!#XaAOVtb8tNcuBX7Y9KL(|?ZP%Ba1aR`M*{z_z&|V)$3o*hRZEdsS7>>t`e{AG zK{;Gl;F?wI-qs)C;HQCyPathbFTCvxSir!(y41WZ`J`@g?d^>8Tp`SL@=_gIN3ZUi! z>QBHp1(E^qaB)qukLYCLy^FvET3`YJcmjdzK(fP9``={rbZ|8Ez$bcIa1G+)0!AXC z{D3IcL5kGDU?Q;m{;vd?ef%$g$KZAzK=a_57F^SU>(~F(UV~db0O^72-{AT;xEA@R zCIW810O$+2?gD{I0$KZCZns{5+hZUJfeZms8OV-*YMkIU56CA#E&xdfWYRyihv0Sy zNKqgwf%F7&|DPHKxQzi)3&>s|`GG9?r{)N5H-KaYG80HMSh^V8*8g$FcO177emT1V z1Q-T7=muP(VbG!X;i8IjzP+)4p=*%cx(NEv3@)l8R6@`2SumX}m@fBa$(s}7-QzZ5 zprs0HX@Qbu!qcWx!-NB<$Zp6;vn+8w*oqCvF6fmBUeFn!35=n&GBK4i%#{h5UZbu8 zvkU?k|2F&?xIXt!P4*saO!h92P*~8lB9LIo$UYM_Vm;z>|ATxix`~QB>Kp$xT`<%ITP_?udjs`0l z4OTQ76cY_rG@2eRKg)j$UwgtElnen%h5#i)fRZ6V$q-P!iPO@~WaLwNZ1S?X+wW zeTR+7>AQEAvpReHk}@eRrCEQMMZm%xEy4qCX6cr$=>b#=snjKthtl3uT!ANalxCkT zHh+}g5JgO%iG)jWCe=x-x~VvZe`cJz6-j&Lq*%v{*=0_XC#dUGS+*+J55*jCzND*#t0GhL?8Wg_+nm(-(% z!OX#dRg9f6^1WdAL|JPMTgTv~_P5|$JtuQ?aITqpdGQa|M4~TksO1lf!?@}}AbkeYlQ563EpoBFzYItDaM|{|q&tz`wmVA8OSHca{yFH?HZ|!c4_twn3%Giky z@l^iK?pEqR9Y17XbSdX!Z}*ver~1csx1U0inLxJ`gL+XoE%eQ#GL zgUBj;nCS*IUcX|{-ZFw{x6=2xS&7^l`5kcS@^+1rzSqs9YNugmAl-p<1=16iTo7@h zYE>LaPbOoDso%!MjV+q9+g71oTgi&=N^Y6Zt18pnnx7s}^tr!_J}e{i>n@>RQ$#tM z%8i7#Mc2xIq6znL|2mn;uw55(nLsMOI_5GFEYt9T^vp2$f*Hw6las;vp&BB_6y12m zZe~P0j?O?<1XytmrD6kS?Ce!_HT=d;LUaadkah=^W*@9=r7zp992U;#1}5)~nM($# z5qcdRXvXWFH1YLTJ^yrcN2aGr>C@5iuJBY+%fgNIr;Wt{J^Dj~zQ#mprB2#A4s6R9 zj^pRXss{q`PEtVn07(R-3XoixCt}uP0#Z8@O&v#`Gd0NQo%Qc-SjNTi=tYc3Z;owZ@M`j*Pf$E@4f7!oudwq1-oZ4o~NQR zSN2lvXv&;9sS5O-`Th6plE)~Q|FEE~M!8O_IL2vr*^p>`&l9l{)?08yDg0hWpzzqw zZ0n~Lfr{LERKMe}mUqfw`y(EvK4GPS8f??-FTobS7ZxOL>?p=xLMp72{m(376zgYc zy=c2m2hW{Ay*KoL4el|HH*8dP1qD|JghJamGklMJ_`V~~h&N&#wEX!=jE^~5yYDQy zcEP^7SN;8~CLQj~S8I|_Z!dgUYBzm1^F_$iNDarYDzFj6??BWQgmnu zFsJb|)b*{szLCak^u{~8T-&W&wugoN2e8BO-Jqz5kuej zlS@h@elqk5znd*n?m_e3SY_>U&Z{Zl_Oc6AJ63WPJNm_Nzek5DiesgU<5StSKciZ1 zHJyT zO};30R^0W%>Z~_JJ{E6Lq6ox@$FmWY$!b9d0Y$Ni5-|9xM1s%>Ijr9#jUD1e?fFVe zw3CYE=TJpf-O=I`-`_wpxt@f~(qHYpMH4yael7QfTtWRi&I6j6eHLOHW1X@D%+HZm zcimWxrMEx7Resp}Tw8ASeMEjH^X@OBSn**YlDyti+x;S)(&u7w9%VICHzGCFpQi*= zeSfiU953`olgn}T62V|oo%Vpo_gQ5}@mIo4cKeG=^ zZYCV&*d-=+&GA;9b?dELMRBE@-h8x8leJ&B7TZlMkHykg)?zWgC2*rzCmSWj-rjGk z(_f^GN-dv%RDtZKV4E~}8u6<$VKi*rm}-&!3g(@kmO;sBo_yP4h|ZS%cpyQm$5ZVu z^<#E54lTo+^|c+R`YtPjX6lvxlQ#s;yLx}KISlUYO|LE%aP?eh+lJYN zMb`9dG=--0HoqGTbi+XJAT6ZJfOuaa#5K8B`yIl+mU=>PRa=P*tYrslDScT@&ycuv zdK@q#aCWrQpz@B7} z9jJVq_0+HEjk2)9GIEM5x}*;14bt8z5m z#?ab`U0_jJ-y%}VBD21*^<-1WpNw^q1W~*CcBbx6BZ^YWse;($Y%G&vbnEEx>^>rI zy5DE5X*018?W&3CH)ZBeLnccM_NUw0?>uly-HNgyeIl&oXR{!~q7;5uaM#qz5gj?4 zf=!yK2~f};pU+5%EgduWTgqGbu$7%9AildyStMdU8$6YmmHN@?TgamHTF!der5dF0 zu{6(n?=jLAO*W(D_JF5V^@4L{1IZZdS}%VSjl;=a?79G*8ISmWpp?D$yn`ICeEry) z9W@&@xAMkO--bpL=iO0KZCOWubm6v&`KNAz1L`deg_(1>KHJ^R`X)a(N`4iZF2;nJ zyLBZA#MHd1e()te&q!dng^s0-+t=xa?t{Vo06W=kjqrrqD6F3PJ&r z`3Rvf=WH`0Hz#mlUj+{ArQkF74O0~{#Sc<;8#3_O`#~do_I`33KA-nnS_)YjNDMRUDZcdKp_ zQWYK^1jr{k@=cXav6apfMK52`9lp@lHiqf6bnSkBYw&`)#(hjcL+zPLYf4dajFOb2 zUDM?=$^-86bV%rseWDf#osCcAB8jqEEzTWw!l&q`tMJJ=#w{zy+w@fuepXH_y7oGG zjrRR|ezlWo@Spl0=rCT%aFbi@Y*@)0K8^p`2UB;w;N$lBmi+5G=Jd4iS$)m`Jgy54 z;8&(~cK6Kb&%r18O40DKJ;M@gijW{HCktEq224Tb2~VK_PW$^I!ht#cS8(GDo4M5r zPZvcdt5k!nWtfNA%60JJ{t@g^`Ksx{!eIw){Km-7_0Qd@kOTH0DkPRbX z!vThGjRJtiH=+e%m;y#7U`)Y){RQ8MYY?CSfOr@f836#bZ$vf(=l~!E022V9@{M=| z0qOvF4S+EKKsII|z!(4-0GNb$7Yc?%m5{{}~P<%(}aFjS#yFdcOdz;6I7eRz=u z0;m9R7XYsSAbEI^6tZCjx~mMp;sEyD3!ZQZ0l=^&|IJbpe=k@kAWfygqd{iSPTC+dT!GqUIoMk7 z@34>3pi44As{07DEIF_qTj{VX-k|sEr{U1e$Yk5ULlJ_NyaiUWH(1H_&`Q>Ws%{5d z@qj(u3IS{=kU}VsD&WdEfSCa7xXJwx3Iq)TIRJqW0r=~*PSHjazbpuZ1q7lE*hLU9 z0|B5sS%B9Ec=S+&gGkdKpz7wJtfhc43<2FpQ{P$G`9{E-0z5PndL_2@5bRH9JOo&oP4w^X+WW1=bn2(aY2V5gQu?mm=Jqvr#+EnfJG<+Ss}<{( zyD}B0uHYw$*WoHwGarT%$c8&AYJLV9S#G)iB0VWekgFD=vRlyJ&}bt$j+CM)%fxsl zaa-4CVrF_3i}H~*LR72EavK`$s|7{xt`07*#)dhK?!J&64C~b>E>mo^Bi8OZ%Q7&E z&plkqZq3CWcOKrS+=!DqUh2)|Le*q-2@7*ES}mJUY6~&<=UX&a*-akIbD{0kUis#6 zC)^Ek!wQb~r*?(drR9E4U=ikcK(9S?!h2l)KrAsb+0!s3t*f21x~Md*t4(ly@rkS9 z)*Qx>^MEhYWYS~%Lqu%p^Rp}kr|;U#qY@GcQXRkUZiuLgGlg@#(y<%Ee0J6gRU>pi z?Y=8I&`3@j`r*8!;@4_kSw@aj?!!v*)SM#5{)(x;ddX(`({au4S`&!9%PJ>)DRh0Wf{Y^b`DssoJ#E)Xra-Lhq6)g*GbCSpby;mZ{2G{Y`s-STPNJFU z!?ev6#^|X*v&z-@MM|csMYS_A$6l72yq#~O3)~;g_2^jKYXSmeVj4d!(EDiiu(*5E z-+eAkT-ta-`sipnkBZbH7?Sh|=!e|ICfUWY!Y97*S(VOJ1*9668b8lJnh%v3#pRON=PnZY`3^iDlLObYR?^lAEOmT%FD zzHIiwYo(`nhbF{R>w~7^3gcHks99F45Bpa=Wp?rA{W>>3&v437D=)rxyZo5 z*EK(jnP6jBy8D^R&6?T5X+I$#d4ddlsY2~_5YiwkIOoMDM&ixx!Y4)nBr%YD45K$^ zzLzUeK7V-_CjW80Ld>Bqbof=oyK#iayL72GyWTw4G>yC((X^Ofj~DB|8VfC&FFv@` zm|vUn%D!!7#6(Th@{+?fj>~0}mF|grzcThJUqir)aU%$5a9{(#d@ViMBz8&*3<e9k{qh!}cmG08I(jSvQI+F4J~(T{+MWKVUg{sx`ED>Z!(SnE#^P zz<2XcEwk_1xA!YkX?th9D7IgG@$NiclY9D|H!D)S?2 z+EAn2?ItL%XOEzfsq%Hxn+LnYPkcQcv$Dd|I1<&whDRrP<4cHG^lJ}u1k7&#TfPOPZP82Iv#JBB)? z>8E*LT9|VnZbdCO$XHoyMV;058hk?Qg|7P@EbP!U1v`9ax|R~X`@NL$fbs=rS<{sZyBd~F z5;vDp+AwbS(0#jS9} zMIP64X1I04!ZmZ6A&ldP>6DVi#&}$}B^8&)$LWxnR;0oa7xPb4yf8gMphYMD(&e(a z5(M~7>kgk-7?a75H$SEB&2pg^H*Gy~bHuZ`i_=^~l+KNdGfQ_80rXT9)ggCIHVc5}@o6CMa~bWXv)ftn!WPsVxaCQy9`4`--& zT8$Xrz$>7T9dh`5NfHFGR4WJ29W_Xe(8;`|tAE&fbkZQjFnweWMQmz<)FUtxz z@D|TG5Fba2@ug$)WsdZwM8daySWYVbwrYY2UOb#xW@qRv&-AqBV%GD{8+cXWSDUNJ ze_X_U>B3TN8bET(@toIPab$<-v=;GVUk&jD2QJPcvvUy~9My zqpI%O`~C;2IQ_G+5qmhzXVf_2hPWRod~f67a;e`|nwBTVhZ0~KNU4f5okw7cLm7GR zT5t3;6&1d?SAdGr1&co<%ryQlJp{Z>@bP+krio!7kc-2BYVpbDD}0=EpO!&toKKDf zeRr=m*V0Je@W)wH-b{yplja@j&mR`EXjkPa8?XA`?vvvkI)^6!Rp1HvhlouM;`30b zv;oN+SDk<1yVEza`oGNOd`n!<(;8_|6*#PT)#4AO8NL5YF9mM}{Bx5`Gn!On230{- zFhDbz^!9F%ajO*d`A-|?(P~$l>#o&v;o_nZn~nJRMdK|*FhIzn=KPKsnOa1ocNmY4 zBZCn)q{Q*%#eJDb`*x*8oBe`wIleCb6Vd+ibqWvK zv6;X>4q?Z!m^gR5ACL1AdXvVOXXfYK;%{nRh1FN!D)Q-WHG!t{=xahKg=Y z@ImrZ6F#0+(#we3HTZarMlVZRNd7L1w-16ypj7C=U5Nji^*mJOb8z}epDA9Qo**NP zfKqgsEWrZKgfGZMAO{ZAF(IHA$X?%m>Bh#u2{oteO_Kn+z0-oZKaMR_)V)oM+8pTp zqu5vQJ^`gc1@glZ_XAE;5>7J|J-K*O(2myt&hAkx)yy5Jr}0>EZf@XODd9{{;CDca zp@uYy9^M-a?!kM5#WiA{wrX;XIA=^$U2`kc|86t}ig-A^G8XrVaTuyzwzH?p%~ISf zn?B8Innj;H99e);A~5+JGx^zDSXPFPT%}=QOp(`QIK78l?Y-r)GlhCFp3;YRlB@4s z&4TN&lBz6qtR~)81#J)IY=q6KmRP& zBUVBW<|yULb~d>MsQQFIXG~&;N7HH6ss5-^bkaB{^Dtl+CDdhHPDd7ZW<$MV`mEjR zbl|roPx4(yreowN^9RZ_#$Ve?%?>2U>#%x2CwStF+FnIR3H#U|8P-oT+3^z}dnfF2 zYnAhACk&w2ySLc6Z)lZEYbP|et3Et0uKMk8(lDNj*acnGmKap+^>9J;&V*FqCi}IG z487+G7Y=%#nI`gnZY5-+_gQVI+fgO-P)*oQ*t<2~UHvge&Iw7Y_kdBeicZ@|CQOy@ z7Ap9dksz_sVEK#`MOmU^KYv8e({L*3pcy(FE`=-(*g{U6cAQ7Q3J&|CclqhZz52~p z2IX2xb%)b-UNVP4h>F}gN7+McDK7nzwD(4^yAC_mhCx$JVEf>ud17*Vt;eXJmC5({ zx_q~)z=KWws>Gv#3)92UOP9YkYJH!Et(f-7r^ap78fFQQmpO_~!!Ki!Ln^CJa!a1l znYj+OA4f@G3w=|mGM+NBF5B{sa%P0zEp$n9fX+}=qy6$^Yj=Jh<;E^Qc)jIrs<087 zu;(E4jMJ=iEQ(g(;{SbJ98HHYEa_((wSQOgsJC$Po+?%%Ff6z(Jgqs~jI_QWt=aDd z-{uoXyyShO>7GCM{HtG@jjM?#s)q5Lx~8bF6*e1hRRx(&7atk7@^6Zs@;##WHpV1< zBCTgas*ey%W;Ssg8xTzTH*u`tp}$R0d-{8!oQUh1rFLMJ=^@N2Ipw?mjMiHW(4+t@ z5a>e&JpB)V{v%A%^$L=DF&K^6gtI|wF(l9ed`_63J_lV0~W(q`IyVfowV;w7|2) z0(}*rp9}PFL8*C6duRX%S6~COP)67AvLFMV{@OsFYpY7}i{5l1AaDZ%x1=w|?Z zX#R6Wcv&E$HXyDFko!@fe+B6CBh`Ny5Xi;?LLY2EU>XjaejCnrB+zd^<-66-v&jKy z4*=~FY^D)}VGhD5h1}}V{}ZkfZ_*CWQVNnc46ND#FFgSL1tzKA_jks5-*aFFs5onQ zQ*AKA2{5Vv14{9o9X&7BO$V6d%?WftP*cDf8YICBO4>rrme$hvvj7q$$aE-RumQ$>m~jeK#P0 zPW^Kq4W_09&Zqze4Pd0e3~cLv?jr%I2{^p49nE>{u1Y|=GsDNN7r=7xU znpR3EO@KKfFoisfd&|O3Nxy-8p`7_oOB!U0au$yLD+u5`&`N-{Vp0FJz|?(UjdTJp z3Bc+A>r^_XiDLotu}*f=8>ZG@h5a%MgD21i>xmTXeM*PMmK&wDtLFs1!|A36CTW04 z6mX9iu-QSJUVhW6V2~ROg6+vH1fEi$8)hfZ{^wb}G_VGCzEZH?>Vy6B2C%93&t@sG zsSj)x0!Rb|kP3|OVUpgQ6mA?h+X1Kx3^D=B&>jH=1Gb1#D10~MZ&McUa{#&qLz5s* zrysCWVqhyy+pcPxF!vZHnv8<|b?SwoDv$_cP>f<=c?jBA4)p)*`BwqfIDpmxv=8hP z0V8bx(vbn6UI5Vp=mxMK4I4p2M*g}+2_P;2MZ(bE)jkRH#G$SQTGdG~G7P%*I;s)o@U#;kzclbbb@*Q`JTAC^pTMV*4$w4*HPgX{dDFI0Z7yv)2 z9NvOmIu!?uoU5DPjZ0)PDMZ6vL}qV=oH^5C@*mW&Z49OtX}7f!mzu>_|3AM+cWgba zkWWkL$FrF|7#S-2$76}rsUx`Hoi`q){& zMBvoi$k=;OleJmypkMpW8zaQONYlMKTCzj`$L4FWes#%nWd!Cee}7tihryWREO+L< z3H1AAZa%WrPyHhD8!k+I9zZ=6AQUSu@*Y!f(Ofl_UY{=+q`O?H&_0vc;qW?3^t0R8 ztBFUg9%hmW_cVBAcW)4cn(?|#&J2I1=wf+cF&xwN$Kw0Lg0*UFd2xY(>eN2gOnUjZ zS=O&6StiwvingNR7k&?W(hX-OX!pv=+u6*~XM>R)4Z_b~IPGGVds5y@H;DQ8SwCmL z6#e+yj!)rmDESXcoy_TR0wL|j=Tkq1Zjg2NT@gH3eaqbN#+wUz#YH{L>l~v}y|Z_z z#jmfCQ_sIKTN!WXDtp8{3wxJ4bF9doNSx35y<6Y+^nf%5tJU>RHCdqiKIZUiZMvEy zwYE{^G?8hnj7#F?_uhg-n*BrR z)GUc|wZwpSHG^tPc&Q7`@=io_&Oyw)|o^IrPHX>ZO#3s*INU* zWAxchCGpYd?Bb-bk!8HS0#}~gWG)JR9@BbT-WKFaJSg4y!PC!We1(q3OCNZ zZ@b5#S7HPZdq11Bhqu)8ye5kJJg0swbjEJ5rWVJas{2pKG5VPe)An(bTT-~lJ|=LB zkvhytVOzyw)7~7_)|1k!a-dZAq*Q*pA>PhrR6h_HVBlG1DTw7Rv7EZyvETh}pvU9Z z`W(VvV`CgMnd<3`wa}`W9rhpVns3pF`K7diqFSGexXqcY!d8QeP%j#2`OfM1fk*5{ z+8dK~>&G;KMAe3)ZF7w9fOP-w@0?F4dEWRPl}@o#4KT*e+r%zyb3TWEJG8atKJ;*{ z<`&uCubESYRqW$V@1gJ2d}ZE}D@Uhhby(jk66cu%C0s8yY4;<~tHhwZD32-0g;lYfM11ErknG|WH&e+7Fx2P#7YeXhEKmQFiv7RXL2aTcAd0DIo(ns z7Gp@<9Szla6Zx1WB}cN$&WpHXZint`zIckN8b9mAmlBy3(b1tl@BZv*Z)b!}%(|r8 zKxIXDBX)6*D9pT5SVU#<)Utvj;og~ORlT+Dcvj`v5c}w@FMdK4Rpw*m?~;Es5dL=_ zoBe*CBe$z*!2iP;`a{fEquSyrzF%BS;W&n^-9|R%;D>^*jVxlaX~9p3yaq{^bsVsR zQPR&izO#eT6X;&R6dnHH`f);U>asv8CAAZy$Ms>7{*kOl2g3Z~^v6OY$6GJEOC%4% zafVIryGNcg@oX;;m~r&7Bfa_ol{_co))A5=j5c@E>*5nZo4fxm5RgHednk4C2|;)5 zDFQdf#v~s;yUkIRZtFn0`zx8KdgWu1G46DEVXvRk%hi(WwXbpb>rlV+lhz&lMgQ=W zxAXP8nbXeH7JhUT2_h|(Jo-&lQlGOvbh~;p!p&Uga|?lyr(O8N>f73W4lfH!p4E2Y zB%69{tm2q>Dg7)absnY7Z_Km}wWDOZ*Hc9iSKAyry0j_R2A5}kZ%7HI|q9=eg$|(3cTaPg}tkyUU!h+pBT#d!uL&R>Isz0 zj|tb*3CAn^q!X#-R)^MF>sFSAID{BwfcY=!vO0s*H}&%@v^Woqy}`RS7je z*3UTM2YIZ+gY$^yoEQ(Vul*AoX&krMNYv_~ulFbOJiVF6w$C+_=St(jRQ&a*iGw5u zXZa*k#mZKK=PO@u^i6-edw`Jd0|yzPY15A{{-m(ke3Zj1Y}#NOPP#vF`9ewE#}yQq z?BpsunFy*#Ma%_O{phdl^J7%X&6h0oD5v-^b}u12?o}`NkFl z9O|QNv!RJ9&JUaFdG&yabU-DHF2G*phl@&L`!4UjT{BW5HGP{e%LFw`s-qlemNVRv zx|}5noa(g*9l3jzodjdujD)+#_#tjtj=Rc}XBN|DX9-snAZ{z*q~ zJIZv;2!$uJ$A(){`*Ar2uX^pHn;heG?Tv^H254gOJ*&r3=~$s;scHAz#hAomXWY!A ztsRVr{_6?izfBzuHMPF`gerWOe$>_4!`GZ3rBq!_;JuQMXX2sPok8e4s<()cZ1&K< z_~#OoN0$|w@B5WFK8_*w4y2nu`ce-{Yd+5PW*!LCuKemtIVi2oneEMh?odDaXdD~} zvp81}T^RyK&avwzZ`-#N7W{W?vAZ~`}sKmu2B-~^Ts!3k`V|NQlmv|G)%j90F0 zFzNOWr@t;b{&msmuZxapj!C17U^o`gJYEGG8s!mfVSEYAC zOte3T*wL7o=Au4F#-814DRaVZ6!r98cCp2bKZu_(=u2PO%Wp-KOn`<(g*vEA_w^$$cW|wS~ODv&yiS|}?C*Bo( zBk7GToX*#@u|BNLE`E`nGMA>S5$&^?B+e#Sl#4czcuBqYgb;2_;dSN%`yk`(msQceBg6$j*idkBJ9^6XsIZx zekqol9oL3>LjgzygT5vg7JCN0#9gP^9$!@hz6LReQ&uSwwj<@i?m{UHj$P#Jn#`_P7<29VzRs zvRjVb$8=rw-F@~wtFkCrktbJ=)q8JB(+i;v=B znKA3K?#8TEwpmZ|H5NroGdT>y(u@6?e|M=t8b{G>x=gXgKoPd~~P7Zh-}U#p5U$eiZ~`MXFQ(#MP=U z$gqUhW8ew-qP|$Mo%>w*+#LTgJ>J?iO2L1kf}~&8Ssl zGsdOcD8{6{?dG^Ck+sY4%|5C7RTuITBFVVwka>c0D)whBl;dViH@0e>d+U^REW0FU zt8RY3K|;ZdD4WYp>4W|r1O4Dd)uH@`f?Mi8c}jW35r3){J8i^DGvzR+8J)3Q`5i)A zV~QoeEq{J)&)4~0SgU1#Is1Y+b65+H#?Q>V>&KfC9b_!6y%(N?3e=~+a+)YbC$YA! ziNX56CvGP~uUC10ClV*WJz*}Qb?(B!`0ae$uoI`aL7a9*BiStIzb8hr)pjpw>3mPa zg^270ud`TK(o^okZ!0^xCwn<6$^NREbWGt&l7oI7{S~Sic@||_YQuDfOpRue4 z9SMEponXsq-iunP0lr)xeS>(Zp)1Z?3DJu8D9JWFpTG&QzO6K`AHh9u__v&^f=qOoJY5~<_#~MR#I?8yP_fQ#AgE2^vLcz zGkJWr7uC-v-s;t~R(-@P?iH`vYVgUCO`SNfr8btAkBo3pxjYJXsH?oZKFW>FP>YVF z7y3eGc-^2Xp4Pg|;*Zk9SM33AG={1WAY=X5&~j);<-13nn0~xc&SLDFRnc(0$MMc( zoVxF8Qh6`((*={Xuaw^te@ap-G#sbYs;bk^K6LSkRn&N=QR}^%8!UFD3N1DM8gv~2 zZbXJE45S@=)~1iHGaSV2S$8qa(v>&zQb$RBBP_q`?0EI^_d6{$NPBitg4zADG6<;a zdYGGZWPQH1zS~xqhkHbLX+STrEaj^uZ$Jx^p zDxL|!0Unn}4e3aSOs(k+dBw%zO<_R6Gm+zo3^v`2GS$t?koN^fzJdAEhyop{*y}-Gfy6Uuvp_D4ax0zJ1)c<84UezxW@P6he1_B7-Z8&6|!8PrkZh+vA#}Kmw|j} zot=}6a1|k_l=QC5k!!Eq;L`>tdAY%+Bd3euP49yQKkF8YKWdDRp?auNm-7}wf2n0v z{<&z^ou9L@+j}@lP>@`OcYdd5{%rvzHOLtUdHTi1@_W@!Ng0)l-lRgoUjiLabd#=V z{B8w^UH8#VC|o*tJmG2i52yj2z77s>V@csBg%gy;OIK9)4O4F5?8r*=4XQ=xmFwz1 zUSObK`@}80-0(b(ms$pEPP!x-9{67mJP-S?2eSX`0m{U`JwQPZdVmV_fC>rR16Mer z2o@<^Ki{N_GX8c82Cj8}Pq@{=Hz#h$&fpa{`#64j;Cz(Cq-I>-Bn7wYheU?{IDy;OvOZfp|z5Rfq^ZD`g}6Q3?XO0X#F>d$+z zX#86+Tn|?IF3Xmha1&k#V0I1Xy|sFTHM(8pv-7}^9c09TI=Ktp6h5f!xEfwJ;U%Gs zxB*!nxae+U#vK`ynD<2bCK zj6|2;y}5B!tcw*&)dXx6;}Xex5n4yZFu+_z8#mH6UTAsvJ$IX3**)P~hS5fo%GbT{ zyPUbwTa6xOt?J3U4o|~u!i`cdwS7WBM>s`Xd4HJOK4UcFj|~Jbb2EFXT$ts7&ln~c zRfTD|ylt+EgU{Rt((5Hx^ImhHG}}`CFnuf1l^!*aUd_DyaeKuE`CPO^XOs@tB{_9<_oF@8U9p>kC5-NReFx)1&yZb_O| zT-5gAt$vz~dKycwQMcGZzhQ)VW3w|zw~{_o#$D->p7}K;`u$+Mq(EBqb;9H%vQ+AV zNUqhdKfP;b+Z9w#kPDgEAm|Tg9my@bBC=3N+R)#}GLptVP2TB_Sd^Oue?Y6xGouML z;B@wC)Vnypfc5Eiy}d%UXhJx9c|>-*3bJH+X;9R3`NK!b;T{&hyNf@KU6C>wVO&Ph zv8TQ*@p5wLk2eaRI4RG?h_nyHE2cSndqj7rAhV~Juo|^nDSI0=a>(8LrXPfD9&FWC z52q8`uy%bow)~ZsHr~MXdfzoog;Qq$A-3=I&GgrV66=c@r7GgEC*EE10dYw!-HtRD z$jn^g-o_@i6oPBFxVO69(;ijC=LwzMH9rl=mKM1@Tu!rDpy3@#d3%j6WsK1={EO<7 zlPxzQC#}xb0!7Di!M^4R_4=5DJAzxs*6i|454;CG2m`(vmk3=bWtjBL@b~_`8M8{( zb|ciw)aI(0O#PyyLKBJ%UDw;+B>t7CMCcX0&0)VsNCb+ErKb0uVfu{K@THFtLcS%E%=t7IbWmt@^C9$C1A^pOfmZ;j9PWXqJY+P^>hvuvGK`Cg`HmA;~i zuJOF@^(~S|p3@59$@u1kvah_!H}#+!P3&rOD0SVbm}zK9{V%O4$ISzox`oh~jVnfr zzlS(2nY;oA`yL+4J<6Wt@X)oRLpiolaEw#>RBT^>wU4WXdytX#L`{Z5!%xE?$cE_tJlL?c9Kmmy)@)C?mRLDU?ik@el1;eAl73 zmy(3w6(qXk!}P)|$}jv>XJW0bE{8fb+W)z}t~||yk6ub5qV%tQ^vPZ>8(o@`(hWTZix5nqZMtJ4qZ$l2Y{iz~CO_tAqUMVpad ziuzusuM(1Tuptun^NsgG>bIA^^*(&oq7ZobQq)i#7hB@i&W1-EJZUY(k^8Bc*?_|G zE~m)-e$pmtrlgevW&SU5*Kl(QZ+m zo1NVoad*En`7&DBZqCJC6rt8gDaw3~%gLIH{jh9_*j}7dYceSM?)sdn^G!9bgHJ8C z>C(ZP%8PR+mbv#Oo-^&fA;$D379?V)A}>wzTRMB9O{ZmJ&4Adc(Hy#ei^MyqA71d<{|Y}sF% zRe>RXT;QDy;*7sMhJNH*EqgzZ6|<$bsnmB}lt;%`6aP_hZp_%88!gwaaqho8|KWS6 zg0alakf!rs=&jim{XeG`A@oesB@lWT?C`@LsvUtg5+nLUAvZevCAsbhJ%S7|uymFf zpoh>Quxui`Uu@lD{lcN>8w>BOh`gt!87N*VvwG$UQ zysWFFg-4+u^Rnx`S90aN4JpzyI)+Mo!qg;%3vG2bh{*}H)fFUbfq!-k$z&I5iE`CF zb>w-ea@XS<8KkuWx$oF+7qk0+O&KBej7*!aRv~aGeAg2l%USCMa*fzU7wRWsIjt{G zij++$ov=DZAkXN6eN)81zFE;=-(q0jQ#N4VGYi1JiFRP$PT0P6(AKn1Yoo0Rq(+I@ z{rF15_N|Av=2$BEOJns2B2V@RUvNzPrxlFz2%_yI1krp>GlJ;gv?zionJSDR%3>ji z4sbXKqBGNc2%@~R;=rD>{G#@G9Yh3C^9el!(PdmWfar{Zi3%-H+jJ#25HYRvjG*Dt zr;)R<4Yo%*scZe2J11Cg_XkMNh#AUViJTR7u=VYnUHcf3!>c9U>w<*~MAlX@cLp6I zc&|1WF3^dz>-Olpa_DUUcV*clJrfm8UzY&3_^~U~4-roV9ikpvze&76M^G?#_3$g9 zW2=0m3hq^FNUq>sZEeoe;a9xKPy@;Y6fPzwi&fvsl^9w3+53r|6~S16 zs7^>otb0)(eNy7o$SqrwOtO}-qJ$M43t1f>-ZS_k{YhZv% zU;z38U;sTKV1Rhc0P7LJWJrn#U~zi92w>Hz=c95ZhBqSTq)eAIlqZUk5ZfmsqteLmm)r6+|QU>-yo5i`Yuw^Ug72 z`ckARCDsDas&9gWy=G7P{#>T~1Gy7OOjm*+U6nNP4Pu%)PMEt z35#$xpZxTBzo8>t)L1Sv#Wz0D?psgm0S~6d}U zj)V_UbTMeJkw#`80Y2sqjpDoT!~z-}}P){x&iZxg#SM z0XdL2VlSdRRC@&vs*MbfL_8emf5P2=%y9P#B7b@!?J^+_fGOsI9FqReXpbs=zY(>E z;&(E+8^9wGg%lEM>yv8%mL3n8w==jqkLd(!5*LA{3CZA-oL<~QKS~rby)(JXkc)Gq ztOgfHtodg1qxE21uyvMuy;CH(rabJL2C!=q0ASJ<0x%5_hCI1C08F9+0F%ZCz-)>H zV2<9X_g=##BMiw?cmYF<3knxHq92j~>Xmv?5HoFlSfaI(yUklyUYH>wpU_yT7p;-| zXlMnB!&Y)P0XtBn43`pw8S?T;jg?4L)IgiBL-6L)sK$ zh$~#&UF|zhFxPN6y|BW2J6HPHYI7NeltSXBTHmVLoTB0DVCLL22t8I5L4=+QtXOdG zfygFZMoJ3-J!{$!dc3FI!M{@oC|pQki7m1l=n>l=!8DzW`0N?rIJ4MT{z?ilA<>rI zle{0)ZeIvi?g3UlV+t;w7}ikRR8Om}_2}qv@{?G?^&Zbd(U0t;0dh*~W^|BKN+t?6 z*yeUO*+ei~CWPO5ZeKm4ibBPB22#)Z8d8)-k4IO5brmyqA|EV#MjkAjsst9!s1V&S zRP})cl-%iywr0;nE7}_AnMG)8A{ju*oovJzyC$X(B}>onB1#sXU5qF>l8PwVtCN5z zdG~}SqGTPiG;Gc62lX4z3<}BV2OICqALEz3e~=Od{)uQ0{z>l!@=s*r)zXRQsIhNs z`&gu<5s2@bUv3@yJuQm7&g6`=+=b*2YV;esymppa1T&2z_R^DEJ!S7{D$qVHVM3sp zXvmgNd4a2+76w;8D+I2du@qcA8(jUgAh`P3Sa9{!rQqrr7s1sN!PQUefvca5-cnmv zn!o8n=R`=LYfO4_JV*UG$Mo4mmo+A1qfXG@8Hc?vk_%?Jx50-stp}EW0b8me5#{FJPTJoH8U2PRa^_ zH9k=L`H{{D6iw`$Aft_wwvvTdG};=R)K(PU3KIk%lB&u^a3%ADmI>JP+anjXYr-2s zQ5pe4(c~~l8-!2tof?sw9<)4n-$w`HC_*&EQQhGF$!o#=lNNyc*Hi)ZR)TuV7l3-* zB|*J{Q0@Y9@wKI? zw!Jx{gWoPx+%5|_>!}sYcITB#j14S^5qVix>bCB1?&#nZG--K=6q>ZXd^w)9VJR(% z^?Gx#R;GKITQvCoqsTesbJD=~mrH~1FAxRapORgFdn`l=alov*4B~)u1%ikJUNlET z<|K+ZK#6RFIA94~9Dv5UrgY)6J~90!V-*2P6>43nZ`)NPuhtB*0Pv65zZ5 z5?E8%d98oK{c{YNY>B;6hzIgYXMs{LH6QHOKRIH4W?eWTUbzMwxc;&AKAN<7Kmp_6 zlG@KfjPHjiT9YLRF}?zt&h+d&uDMkuOFM@douc{z&56N{`N!Z!pO^+j}>)Ab%Diq|G6C{a|U6H%fVP2fqab*d6O z4=55W&+%8R72B{}uK4>}__@uKA z@yV`**USA+dP9v@%MpjI1m%*#0%@-s><9VY zH`&;EbbakpKZ~cYe+pN7>CtC?C>hC<~|Z zCTm@oQ=A5r-SBgzQFbFhS%I>fa27Jy9~}k=JsT%}VHdLK1VT?7MH8%-A)I#MOC`Iu zH|GgT2ui$lP(pyLoFSH$@T!s>eJiJEND#9@1WICcxJywID@;Wh?x#vZ7-aIv>mZ4( zE9R{a75USRG9X3nLQiX-`P3!y@$X*H*45^S59M7f>E}+#d9GSMO_7$1Yp2tYdQ@3W zYJ4MpY?%u!{U4@mJy-1>r7XOjKnr3dh}69(C3W_yaVq)>a0b+jYb2+^zA1cQ->mgu z-$h{GQ^&!+DS}|%jCinbmJQf92kd)_5A1t-DcJYSOLZC|{7_O(_fI482;&#~lg-{I z?vwgR4Wl#skjr~v#pZ!xr{zGg)J0GuAz6cBk3o%OMje?Y->i^)l4AuUN+b95knasp zY!Ozh#R+Q)d)unr6C9jk3PLhJD-Hxo4Tof&hSD(|t_&pevr>@EQ*n`mfzq+UAtFl0 zbhu8yptHijptGWoj?H!o^zC#{QN51=@I~?8D@F-mTQh)RXg!w8E=8IKAJT(dN6nT}gba{$c1+at- z4ggjiGdI^HBtbx`_ONBbY;ps{xK?rL7t)hQ1N#mf|Coz|04@XpRsz5RGN=T_94hvvFaTq8ib$E!)XU5pFfCc#(RmWsrKuhis90k4)rX*3&;>4avhwVcG&P zaG7YJQguwF8-Yq`{166g#$mua2m|OM0CAEugaLYj5C*))VSo@018l+R(J$FpE9I$v zZ#fezN|Hl?Vst@x;)a!7`QZ`HX%jw(|CZCuQAi$N5SD1C-hC8GCKJ0Ms3ISSpvp=v zJ<0K9bA?^5(a7n-mI>|2V4UcLo>_N_kmp{`izr%?wHS12g5tD*kwiq%8Dt4W(V8qt zg~V6&CMZq|9BBYfos>otok89VN@WQFsTS29?(XYp-47gnyk+4+N^$V`nT~)h_sAh9 z?R-|Ucrz2;y$6-DM_WU-UR zyNN6b#U$Dd_WZ`W7jLV6#w;WHg!-byAxfFo>mTE>-2tmN*%a_;=IW1WnaXEo*Fm5k zDr72Cp4}YrUSEGq;G~^Zl(Jx5uEAL7ixzLua&a_Ww&sgTS=nF-3)KoScc+7>?&Obv zx4X#&0)4t3f>A~u1p1Z{iFP-8ccG}4eGptHg#Fum$jWtaPOWC!vh+pLrn zlEYHib&eZ`JU(-9Rw=k&ZJWhp7@E(KvKU}VNC2-Cy&qMcCbLoQC!JC5mBIA}Tv8HVM}I1+ksLo~S$>Hij6n15=Zj60J}!KLU{w zQ5^Z>TBmT0+)KkLD9YK#l|+WWoU$Alz64Pekm-aX_VFPDjLhp8nF1y%6=Da7!jM$b zM4*aq(bl)S8RbIEVFi?WPfR326^|l; zg7S;3goawDdfy6g%0;InE^%*+w>|`*Z2=$8P*O~ISI>SSkn1;Mfwm`us|!w^p#V;v zjjf!6@-V-VDU^q0a49Gc)1>mCpdp1A19=#sOJmst^ShYMV}1P*dz5oWa-C7mO`wXT z#oN^pqEXI0tcmlbjo|31LXa;JMIm3Jg+adLg>vpSTra1Nl%7cp7_GA7M z&cTz!4o|~_GkVA{ug?lL+GY3hZF*7S{grIJAR{R(L#&^#af5Fk-=>OT?=iBqoT)-4 zO{`ydQJwu;!7;wRZr^^sKmKZ`MwRV2BB9o1rb>J^gsr@d8;DqiGac24PHw3t>lmvN z^a#-o_IcmBQC@zZycQ^cz8Dltd*SessX1Hvtw$Iq`?iqmlbs>iCy79^uX!+qX*Ssf zH7GhiI48YWw7d112LQG=0k-5L09%p(z*a9FV4Lc{;db|S&o!z}df#3nPAeRddLeh` zDcKfLpB`ypMuOcNH>3fBQCFmZ4`kPqy1^zSQM45q1&bPA)-^Ws)aAY(HG-lEc_k`y zgwu^p34AuRH%S|Y)V>JbmlYuYazN^P_T6KKg?P%-g|B(6T?QL0-w(Da@Z4@`sMRmm#VVy{vm@&=C}HDQmu0 z?&-0^NRM$=QW?o%*+mc4E^)m%N6E~;%yyL|HhJr+(`hAfqcDn-aU zB?}1VJ{k2umfC|VMF^azQiQ;nTvwZ8GAe;AHD)pvS!x-o6q%c^B+4O6edSbfxLfmE z9!{Fx7cbmMc{nuy1v;(>Fe*_37}X0GPIf`zgklVZ6IuimPWq5v^cxw)<TL+=CH@iKa=AfC%OF70$`aGH51iALAuYcQ4Ihb z#Q@a@vsvMec1QY)prH5XII0guSxIChzIy8`*q386X7OU+n(UuUu=Z(Puy%$*M&hTp zY~JW@uYOKAR0yYv#Wzy+PEDhwbGf`|>1(Kq!O{tp0=XAQqrum6#i6K2m4u=mp$eRR zvIi9PG?7(4;ckSY9#sg6dc;JqN?NDJvPs@wO;|~NxTtp)?3~IAc22^z=2TqNI|M~N z>U=2bG2)=8N2-FNUNbK0i9!}SqXFbWeXXATte|=i{iuH5hw$KY37?O52Wh5^>*&hWk!>ZRaw| z9GDH!&!^+SOHr%9OR=z*62o3f0=(30C3q=DVPXGqE&)Xd<}-STL`y4uINwlfUw`z^{_f$xk+f|m);YF6>CZ}OzI>nxIhi#2=a|cm?(fAE6JDV~9udwK zXC2IppW9x%83*^p0RAq$v1ddjVfltLSXh;1dPL2A_ z3|iLSKfDU*LX9xbVZ%z4AfT9vEDQP-!=V6V=&MotIIg|M9(6aIhnXm)+(M?Hlu{hE z?d*PZ3Zr7w`=LS{QYo6Km=OBdS0j_7q2?#N4r+eupyp?VOZqZVP|~->C4FZUUI>#! zAo_AT4_SY_4P^aGAnT_ugRGy%54lw8sRfAn*RN@HEa=<}PHN~70-7y(7Xq3DX^}#L zT}^5o%65jO5YRlywg_knq=g7*Nd*ZAXmxHC&?s?5KuaVWK^TBi0|Ycu1Zrv4-D$pu zeNS_x-9KkWp5i{qYKT+|A|O)z0X{F|TTY!%HqyTf*%a*9HzKQwBm+Kw0B2%Ot4lLrPk5Rt>RV zn&!j8{)RG8<26kP_e-#^*9HP$$p8W5K^gjNROW?_x`$r4wqyWRXeS&6(xO0x z_i?L938GUDB9yM4DMToKBYZeIwFWm&TmZ2A+rAR>n|%A{*|i+y3wdrMHaH7C09h!- z6iU$qUMNM|uY*#wGXRrf2&HHOAC#h#BcT*sw-!p#!%&K*%!5)iQ5=_!s!=K0=Ccz} zNoO9Sl06f50G1SaC`Hp2K@RGLsASi~b;v=j_;6A%A4R>DO;ONk@<&v%lDijCi8MtS zsDwyUxX@GgfOHN_UauNVJ|1b(fqM{4p1}_$uU7*m{~D?CIQJBoJY_zZJYxx@p9P2y zj7IWt`e_8|CwKRWb(aWFC`te{2HKl1^}ak1mMTE@$<=_cl!5%&E65E{Sh@#ipT}|b zsR2DE7Eb8{aogSkcM0~jo#!X+o<4yhZaaz?>R(=9#W&avbaU4FGy5l|A)TM$LH)}F z;sOU-c9$5o@Mv7sQ-iABj22Y&hyqa6Bj6(?PAB=6a953M;;NntRP|;Ip{hsZgQ{K= z+L~Px{!rCJxdE!`F?b;9Ct>^6LtC?J;x*X!j1bs2RSI_rdY5c%b!$D&y462nUMBE1 zBZd7*V*iu4NpmPYQ~BgB?5<#)hr=aBs87t#Jr0LUQ0WMr*I?O3@R;52Q=u|v8QLbOn_w_OEDd)XB_n{pXi==M`Qj-#F zyT6iD{F!Gt$5640J+lNAyGj}BpcY6%#jfE&UsUWWo7#hlUF?}fsMr-qO@?9@_3yEr z6IMyT?2&$ZrzMbngJ%oGK)(3Y!=n}vA*~a5$RWUf`s%d zG-HDF8!^j=^jkR_302csKfY7Ly0#o0^8ST}3aH~jINPU0uq}z~)LFXNXPxlkIMJUn zh4E6QF`~}mlFbc+m+rOv)Y-FuS7G^Y_lF+rxX(51A@+rwb_?SVrRfhn1N+_-`Cpi% zb2t_pLb+_O62-ww_vjQ-Hm@zO(pRso?cS``3YEsYeHu zb$K;goxJHUrsA+zO0xOUlOu1aq>y1Zt~bR^YM=IXZ^?8wzU6}PP!SH(PgiNr)yPL*?F zheE8Qomqshj(OTPn+c7a_kQ}3zCM;JpWRSLLBm|Z#AQN@xzNn1GyQdAjh_%}U4U+! zHhD9%;`Xhf`|CBk4I_P5noEsK(i7n|I&*C`w;vQ@?FcZvt4+3G)@$Vr{j#-p@eyLJ z4KUZeHKdd%cz#NC>~5xKbob#!tX*f;H%3h8Rz~<`RM)*+WTzj;UTz`xQO9b>8AHda zi4%`JoSnK$UoWa_Fr`QTjM?<2JLHw4z0n1>x`q5V)UZBdeA8~E{7_x@K@Ebh<oV#a$;;Tk3Bg9*KRF>z7uqS)XLPCWWAa$A&#p zTdtqtbTu={M}xTGjNz;6ZF&~z+YBZv9(DWarwAr!o}aojX3lIWzWy>IN8*(7OPfdj zei?ODIsVQG-7f0|bJJ>n*dkRVn8Y~Ru8Fr_@6~-+lDIx#ow~-b9kcVc!0>GpV*3ZB z^m}hg?9^ut6kn@c+0ENYU%f^bYj``>@WPf7Q^(GY%o2p^%l-QiU{SHm(PhGrdFOWC z&|!FfWnR4F?P|$(RddCUl2$eWt6yo8Z=qQoI-}zp>uMw^*BUvmz4R}(AInp&6iZc1 zOqb&>K)_4xu#Zcs@ta3eN5JEuTt>hf)bO! zEK?q1ERSL=GZ2N`)#mQNSZ-YpSZ3_QShm=Luq;1jp>E%OSb(wlly#%p#Mc}Tzl;|$ z8;~;2vDA<FoNyVqgR%kHcm31>WZ&{-go|L`Q_k-v zJLnT!{d#8c^-@Br;)T(+>GStQudKWZdDntDany*Q=A%1tfS>U-q|XdQ9a zGGE%qoN`4FY-}%xZM4-V>n%}A@cBd8hcMX9QOI#|ZesbyHP$xp%v|k1ziCT{Z~P*A zqi}|rx!gCAnFk1jqMhtm0O1UM;M^w5o2$$ssVZ3M(OBukOQ7_bLacO6taMr&D1CNI z!1C|fTz?F3#^TJavD)0t$ZT#66|C3H&8TEZD>>ZJ<|<&jVHhc0xLPOqdQ#{a8*|94 zJLV8E%pugvN(l!gXE&Z%eRmhvtA%MQ+Rb>0Og(XSIWqO`iGwZTSFY7jQ;?~v%5DNpNunk`I3Io}%} zJ=R8dxV=is8{6XkwI*O)Fuf7Eoum1K%gq&JB+i~TK?Ja<=i}EyeO-phZk)xL@dp*z z+UBw!4c*ldeIF>>ja0O&EEVB0p{pElxrD8TI6$S{4RL^384WXw5e7WRMCn3S!Khxd zq?L7m@rheQVOae~(cGqyVoT7}LIfQ&wWQU0OfOq8y<7np2$6On(z-R|x;{ELt$EiiKNQLeLN&yUxQw?1s3 zhy4uN`=&=h-f02n^!JYn^@#De&92vreV1YqzlurRSKUJC;}-hUpWc@ZCXJEWjZ%`Y!p@TSbT8y(>@5$r=o~1%{;EhO z>*DB6dc>HgV&8~Tq-L({o4_T?i9V}YyMl}zb0>8x{hgb7Jc8Ndq%Ex|bZ#fcK@xkk8DtPQnrNMIjY$r+OWS@1kWtIxx&Qs0}o!sQm zFDTShS99K~C20K%J$K}jOi%RsGmtjwMk6ATHZlc01#@K^e|T!1Fo|7 z079+Yo4l7vx2yNM>mgn2Y*#^^a=Y@yOouz!+{G=!#n1VrN$WyNOia|WF-pVClT_fLQHg=X`5`byq-hfqik8l^DkWIAPJ`TKP z(~``@bZ0IKGOk>!PL@_m@>xu@15lHnVNloob!-^}=gyHTDP@?MH6hp$B03}g;4-lh z`G+l?n(-*uiKM9_YtiOz$B=%HAx+$eA#H#m-6RQ+CT_-%er%i9eNc#47hwIeU>^-t_HHCs|*e+F#lo4kq9oLUcPrj%eb zyIpf!Flqb4?G=ETas`7qAGR-rqy}K7+{chbgq0gTLLL#5rc%3Q9C5K9+W|S8({rH&xN`H6GybIW{+hg zQPo1GPMhnDdISWGfG(( zan!L72i@#g6#EQPNT+n-N~ZYgOK$fLim3~B8wOIBnMsbfO&6ko{>@j~hDEol+otdI z97935*QE&O#;>%^j<-IvO-J^e-23J$DXr1I&Oi1WS*g_xmVB1F68lUA>@!($;4`V3 zh*-y_%lkc%%O{|WB#S|nMEK_AY68ArPyyfkv#1mBN{;J>vJv@8nRx6o(+AE4Sl-;( zsLc(;esFd%+EvM!JhZEMLkzU5*D6yXo{yYei6KtF5U1LDp%j4{Qw!K1H=Z)oJv`)|sV>-K8qT>ZsTBX7 zzdb*`XJ>}sko!J$^gKq$=@i8TgrEGw5N$74hb{%ZDNcJFked3~PXYWqq&)!sq0@*7w z!{8@11zA5Vuwol+!{{H!==ViAXPq|L2BSX_qu)dt&`*!W=)Z;0@2i2)uZ5$Q23v?) ziWWoEf-)tD_KQ|4FCL748*+RlW*KA5GWNn^wSG%jJ5YAwOtygRWcb%1dz417RV<{( z4e7avb`!cS>VakGk1@;WVwUmnM7+C<<$A_a_wYzztwd$$%HV>p5DjUt_MvFVU{V?L zIR~O44c2B94P7T^{PfN^47pNHR(&00R9ooDkWuwaK}MCr)UrfR*Q-N8A7hzYf=Er&nP{iU; zd>9iAAzW2ePOi35l#_U7QoMFqdysW;!4}`eXj*McRkVNF$T30clZctz8RwQ7$VE7y zTnVK@=rB%v z-b2P6R)iz+rVA);p2^ZSlg%5R+KP~u$F5pt-+g$(YA+@hwPipo$(MmxveeD4Z;RkK zU#jxc4H!Mm%QiYWk^nM!*rqt zPC^nnCZLlQm`)17NeD4E;UMrM+CHbwX7JmRl>1oI&9P|zB1w8EzK2u^l1!7ozBpyz z+(}Y|l@r!-I${vZ@Fp3h_-=j*;txr_iTjuQof}w18yD3c;nMd(&17m3N@e8eY|I@~ zy7eu$?bxO2g1H%GG8>RGIzp4NGB|0Cf}EN@4v>9YC<7H^`UVBpL&b!s1a;s=&#a!b*+5(-nv(mT z%lT`H%g^k)ai{SS_qs)?^|A@p8IDx)<|QE|4{!85NO76wlzX0e;OXFA8TN+ta9-lf zZCCc^onN~ydK+FC61-)9=@4ByqJ z_gfyVOLBegIeWDA+Z{`r%Vw1N?W#+Xy&h~o?sR9f9ruq=ZU5y2&36?;r!JHghHqT{ zT~+S*O2L=uE6gw1yes_EKeB%0Q=G;^euE`qI)e4BE3W;sI&jHK!Pge;G&3t1S+COQl07@pZ1#E+jr|*zZm`ln)v(ms$_UE_ z_|xQc!_uAfRVN#kuA&>l75ZuzL&xLk`uK_w9%F#U)ZS+|?Db!8iI9LkFA%UydjnT! z4*UEUTqTJhIe~<)*VsKUxhXwr_{&XIr?p9)RV_gejV%0 zJ2>8A-&(&R^bPO*vU^{Cb4$l#k1_t7zmgQWd-g;p_4P@?ZFj669vF~gulP~+Wbv>T zN6(>}E@fA?UcxV0%8py4(KUkpN!I8}T9Gh8a;TPC5my{2cWwn)=OE$SilAyGp1l=n zx`HR?Cl{;1cPX9+9pZuWPTC4e`+4!W4aKbBMFR%sbqFc@xxx2R-r{ikHl-I*-*mc^ zVK9duZWekWW!dEe148fsg%?svyQ)KB_CaB|nFzlf#oxuy_hB9$x9tyO1m~gI_QHT+ z6&N7MhrV0#!(`b!5BjTB-4}v<1zy5^K8UVZ6b11B8u3O|c@fqry#Lh@TSNy%zSy27Zjj z^B_3Dh+@1T0r*i3--%&@apcV7rnQ49QSAWN>)>^t!ABC_Uny7v|G5>C`TYyKmcawG zVUG7O)`<_`gmp5&^I+?}9L<1}mIcg+;-Wkq4#LtRA0A^7VcoLJAHO6*8YpIBFHGHppF##v4?krc7U#{qvOY}h5Bj8Iz;DK)2u${x z_wOr{@S?APUPu^qAF+t>{B%w`1^6k!Ftsm!iX9f`p=AXr&%JUw-Zc#Jl`sQK2zLKC z77@7cD=&iE&|f@5+R()!s4IvUUXsI8oD(Ef?lf7O7=m9CThSDV6drhAK`btKAyt## z|FK#OHkrEs?o5Uq;mTqWUq$;A=f=#BrQrwRZKvUVk%MQ_@Xng?xJee|xJeN43!^{P z)gg}lMi+YF%~-@%xq|0`mb-0%hCBWtAO9c#V^m=t64=9p=-fPvh48~3JVq2m3oK#- z3z?gTLV$IM!6wcu1~D0nvLHI~>~$`Sb9dls!#ZdH2Cf;1B!CE49gDnJMQIa9;rR?+ zc>W$N;_V3mk;-$@c6fQP(Qq<6e-;^|@^BLt!ES~NK-3eM8{>=)-~!^1uUFZc=0Ej|_0-iGN$2wuVh(=)cOO>;v6;cbgoz%stVst4hz zeqt~>WZvIp{Tshqc3Hvm5#Qid{Tst0LjMoJBaBTcnEVmJxLgI#vVmtQVCmVW#;3`Y1!N#^fUk zz->pswd_j7BDO>aEFw#ca~ZS#SScGrSQo@((0vf{te7o5@C?|weZ?XlOtuD#h}Q7yn8JfO*f=fWsZ*Hb z^{~iWysj`VOC<}F`wYzQgkN?E{3?hEO=WK7_3$eJFj$D89{~?RfA|l%W284=1XFln zKdc+PUoNnw2RKyV{rfI{SX+1>Pr!3bVKef6E0TsUe(=h9fP@TSC{uv4A99zl z9zV>{N8oMHA8|zcj6w^`E@`;KOL8&QYP4pbbaJq&z2(Eq&qBt_DrdA8=3B|%@!eqd zcE{Dz@%A}P>q^2k<&9S-cmJI46}ZPop}roNT^XhPTDrLuW2moxTyUp>A`yVm9ADs)yCLiK_2P z`cQf4$vfShhmQpHb;&oam0F$WgfHicX0AO?NY+0kAg=P*A#*MN>%v=&D&gNu zZM+tTohr*Vh_N=y<~j6j(BDz1XlA3?W!_)uO~;mAe{}G`-*7+7XZP%5mcV;6rK1Ul z9Mvpd#0K;17E37qvoYQXLl{Y)!&^(l9Ieadvh-ZE3y2J#)_!;SeaAK#=NK^H91 zg%Wh(2fE+}BdpN{_46iiq@LS{=|YtXmc98)%PKtR0w5m6;vx`#EG={9ZnHNU6 z1i}kY@p8mKoW=X&1S_jYEAxUEpu%z{-9VJF0W~VX!x*q(a99ol{P$3It%Ct6=(Q zc%w3NxaTOmFFGXocS%pNLKNV((^!-NaTeaj5^WExX*3wtkbE9#;%q2PW&)r-YlFq` zp{Rquh!tgcr_=G&c&F3x40u-?=3d7a1L`VF=8G-l6Ba=SquvXAwLZ;AylF+dP*wS2!0(+EaKO}=8iOvwUZ26 zdzJvVaRAF_i6Cm?dyxL|hR4GEPS|dE;8!uMgw-&kAcAtxFNV{2;{`ee{|2^U#sk}h z-+wJ!v_Uv^pDH-K2o__B-#HkrD}b((;8|p}k#Ws4`PUU-SxK3#A%r&Y5W0J$?#xU^@cxR5`AF#2TVljGd%t9DL!`8SFi}=~vSQJ8h@pRUw7rj@7 zhZUws11w@Ae2hhextgQ1AK&zW7Ayr}q5hx(D=`p9L8SA{jgf(slf_}F*uX6@N;kti zbji6fsj$UlGZ^EB6^pnZiTJ9~oCXUpQS63w6ku}4o8gN|+79to!L*wJ3sayajPZpn zJ!=K?6yYgX%+2GAZ$1NK*!X5gEMlOXn49O{s2>f-T*>i(#?Run>wYi1t`S zYeJN-@)xK?yjK#SSR&pl7cBmV_qX1g*1Sf;8)OD=LBmg&4{lUhKcL58O0)8g66ZB{^UbFX=cI!~WV9R~KergI>8znqU5j098b;m!Aj8D_DFi1*>OsVi%P@H_v86}9ogE1rTem~{4n0r{h+Vs^sJ z^8j9?L>QE?57amaEbAXQH)IXG!XP$JDH!q*p3DI)mYB?uiWR;D8&-lHjS>Ei*@$Ou zNElpdhJ{%!fETKO?_%)MXT5nxf5;@Sc{*4ndg={d>-8VGJNyp|@7T9`NgRKM_perx zHgzhU&FCqmw`P$2PE7HADI%plO!Jtz74T^3fn5#{Oc8Mq2I2x8EaGhF96%dc?gRlR_brA4gyN z>U(Yd{^}zmG{)5L{=jt!<2~lyHQk-(e;6E14FAP?c5?8#Z*t?!ik2K` zPIot;zTcBII@n(m`n>IO>)6|}9@W_W5yY$onkfV8KNFG<8~4fXaaC2^bnD*E;*T+5 zTA`H>Zr(BUd-UKw`pkKdi#{(s$VZ>yGfpl?x_T6|?*wRu42&CI89b)9W}C~vCiKPV z%HTo0@~~U)_WinP@?rb)^zt#gv`v9UsqH&m29$rlOZQ&>^}Tzri^I+QpEpfbXszkX z2~n1PenZ#oIVohobJeOtaZ;j(2Hh1`oT=w--nUGC<*Mx@)f*ZZ!p-sO)dp?_ZGk4Qau<@?KZ(h>dB?K;``;8pUcpmqt9 zH?2OF?AVdkBlzY|7WmoR({HmC=Y~7AKg2y~WvKQa^>9Zj6T3?;~x~08SU(4)J zD1POuX!hdrN6||qbKHI#o?kci%6-08o$`~JVPX<3-A3**XP1qtV$x)U+zoHj(PX>4 z$r`sES;Ml-h256g>lORTg@rPWe7g2DRqLr(4R5@)&jG#}KnaJOk0`2A?gTg#%#{R}Jok#O?VnMN1krp7BuZSKYfuXU$* zHtu>{b}!K8xm5f-iGXI@dB`$#kxt#vzIDL?h&u7mf%oCja34Z1g?%VLRQZ(Y-a ze8@M@C6DfUbg3y(>>avv@@F&pJVkzpe#q@Uhdv*7o9}xX8(p^+edg6IMW1zbo!l>b zlFd#~em;Lhj4e2~EMfqq3C227G0&E{Bo!T$lGDkLE#BcYoU_Pp-vhnd1{C(ovmE88 zM*5k8_n$xaE*{VNGH%~3I=_{2X6;0`y9~{V{Jnj@`f8I0J5(2+E+5!Xaf@~T^Jre8 z(>~(`J)s9~s4FJ!Zn&OU7aset+e`5GFTy3p^zj{>@;|+_28ZoBE5m-S2~<@+^QxWx zt2X>NduAxBe9@7T$Qbojy36XZB$ofnwiC~$1F3h_m38xD_6(c)MJSEmcGp}Xe(dEA zE#1#Ot`*jX@*7QBgljFAf4*6<$!m|*l84Pt%I;p@WUuB&?Ja0ITD#_MOZol7y&?%8 zqkkRB3*P>0&z_dvs10$q(}r&QG(WpLyx|_x-EiPz$q@&!qQw^1M|?C5%oCqZhn0JO z$;~Oxz33g!O6zhmIzRn>wdT=l51CKGyEwmt8L!L4%q3Wcn~itvxLZ-sS$OR6F4e9a z%da}(`s<3WsjCY7kV}hcBRYl6A0D{tpvcY*_^6@$Qt5ua@v!xvEc#XO)Z^3mQa-+9 ziZ9h6pjck8K%nUgYl{V>^f&@6R01C%DDaiI;8`+vZ=EV%cslxl+nHSte!n{VeQrX@#iu5bm9x|yHS)B>^LNlNRx{MjMfaDQZBh65Sgb*&Fd8`)LQc8@OU z+9MyfUqyD|L^>a{|J1#nxZuTxvNc>>(5BGYZ!D#YH@0z4InIDH${*WJ>5XkA%V>Vci&E9-2 z7vtZ#`m0FwS-+AK0tcUztux`%Kj?lZJxtc9;9Y}tLWUc<$FAvUfWcz5{-t>-xt+`ARTbTt?J zUUmO5`OvP0cZ4_G8t+`<$HfbcYgb2?-t*`sd7LSmR0)#0KkL(1TC;*aSbyv1$}y_b z&<@3Qd5e8VMfBx2c;21~i7Dz{Y#tdD-Dpr#=Q!lJ*fMWo@EtjANTN(3`mJ{L{4^&C zN(`q1#`yAfb-5-p^nZVoH>*xNQ+;Q^TGc4`L)x|dHp}Faq^%B}DO}ZW)ugh#b;ilG zC%lW&q_sTfI^*Zz$Pav3OLaoEeua0g{iU?_UQ6%>s{LX~v7>_v=5>mHRa(2s@@~0W z%(sB#0rrCMF24q)wJMg`rD_YJG)Lv0u}w}^xHr!ysmofB@XWnb*+E9`WCpKDj!BluxwT`p!e~%?kUqhquDOr3rn8ZHwVZ6oKE6Nuq-oY0 ztK`vU_0>i9XMYJR>Ya7JY&xhXBRL&9Q_smdRr~1B=IPdWV=p(!$TF9n6&j{7meOe* zDvsyf98NCwsyv!?s<^pFRYGw+wd_QAM1Qi$mXnL8hYwrTISLMnE0tEgCOGG>OIL02 z5*we{ena>B%);3{lj#HlQ@^2N{l2fZIwC$HA2&QcR6=?{Zhtlsjg8; z(Y1#=w$oi64yT&Cv6MsZM((_Sed(e@&y*kO<@)T}T2Hf3yPSH2L}{2a3CWc4nNm58F>a!SYi4Bz?VZPBYvu5SwE=d&Mc ztHWZ>!1ganfMOA4@9`#F;3_PmcC8KxFI zXd!Ew(?^d6QP4w%BD030CZfpnCsw==U&Ao~ks(_;RN7xcf_wLfz{I}rjOi0UE7>`H zGw0w|Czxd>9Em43@}mE{ls&^N;PazHSI#Cobzj{6aH-2mWzp&k7CO~fP~O4Hl3o_v zP2+}_&W!ySJIi@PP1%Y58U|{MOL3(yQcr(;#twA2{@}~+s|CXYcaO9C(-uAVX-%bk zy!_+tangwEduO-fQ`-vCg!VoCwYkj49E~A;aecoFUA^=mZ4+EA@p;~lu2#CfcSTp< z-A_9KGaUOE(Cpeu-=2Rf8qKc0+h~ojkKVKI53EjH$vgFQv|TfOa`W}6$PK?+RpNZ# zwiJ=BFs2J1AO0D%VbY8tp?CEq{mP~B3Ll5l6;}&(KKc>-_0O;MdryZ%ZRs4+pY(%E{~S68qCjwcSNG)2Pm;n+y@)M^i>FtA2GJ&jR&o zs|t=Tsq-&xHi{M0%@5g^67=>_6nxP+8jQX?d~_AQEI$grTzHfOU$lO}FX*oupgYC3 z*ae|4TOVb?FU!=*A8fRg-9=>do_Oq5-k0ax%Dt9f;xl8y%8Ry{85%3T@`n3(`gGm{ zg5j~PTl;(L#mN0b(W&J&&%8piz4z~a;YWLJujcap;sv5)k{9Xe#(fzkV<>uA+x{}% zyxKO%L+|@ynRbV=+ctZTnT(?!!oI$NA5LGEIUOTB(ChwTsczPuAm`}f{U-)&mAX_* zuD}o7pWuh6R``J@={LHM+4>3H7kQ#I+`P|mqUUjez?S-t^&t^o-UVJW;YSA&=I`Kg zq*GJJ^PP4M{A|rOpcV}=CSN_rClSU)kKb>4XRfz${oeX#&Fg>eIZ$oktvdLyGA8iD z`n`1Y&%5<)ev#trd$Gj_(Lbp1MTid)fhIvIZrqhZD5g)nt07SYKQB=X@lRgld) zeA7%+$;ApVA8xD;;?YiX4RAMj z-@Sl6KRuWr1SV(`1e>AHoevnqO?BMnod(4z;})wmj6TZy_jrIMR%-<;Mhtg#wcv3p zVD2)sulU%58~(u+ZgayuX?!#S88dwvcKYH8v zStej8L2B?U6WlJAz#{Ie8_cad89*B34?h%P+T4sqd=+gsK8E3oyXxNf2Yj;y7I8!T zKc@`-M{^s`lZ`?K0X$N)c$u2R*El-se?@@!w{H)4K73H)$qDBu_pfS zQ%%alyr6f&g1JrqpWW@QM1_Aj)TI1hj>jhm)bN~O>wf<^)Px8dxIbx*dH&^4Q&2TF zf0sGl`IkdY9pXi>1WVN1$F}z`hnnUmPvCqSCMzY?Da2) znvg2+xe3mH{0Ke?AqX2x!H0mNv4{@=*$c(6L(u=6^?FhQK=(lQe!I0{=->ObMGA-qUsSfu4hpsmdq^gVlhYcZ-u_9v`QVGeJ z=@J<;B_tW5GQ}nHEM-bag^(daWz0MerDUGx;hN_mszxl#_NiCYC2O4Zh{cbK|0ut_*D0q7TZCq6$_UZOhWVo9h%l zU9x(#-;_^B#f**8y|-?EW%xmCs&-U(!w+&_qn;C@{D-NDgEh6eSM+!XtvB`p6>Fy}dIFHeWfcSr-dHNYS(^kLHb}-- zP}q06vP1FnfWr>Ozx@>_2b?4*eio7p9aTQ7ALMaQ9K6EqqFl<;WwT@+s8iskLhP@_ zYCObq$H_hQ;xLFh9&|5>t5`;ZN@1aT|AvWk3c9*SKVd#JcT1Ni9M`}9)wNLnNYP~b zgy%MTTX*eYE^XPM~hrZ`_(w6Sp<6L|}_2TGCuWzS* zRZ!J_?*3P7p}t90u18eWK6c#a5zSV*<5SP$yRDrWi*3ZLkr-Bg(X1IY3weYU zksmG@`Bi!Y&5ZB36IW`ntApH=4xA8BPYbCC;Eq$JFFE0Bb6I_a=K$Qee)}gky=zDE z1>vJO)nPO>>8@~8F@~9PKKV=G;V)C!GUQ-|KOw6K!lk49+;L~f2X!_1Ey9g|MwkG& zw zg1>`hGQXeW+|MfaTdw2fz3j5T6v#5@S6OzMb)%n~qfQatGCQ{L?;W#IJAwY@A5RsO zw%P+)XhJ0PmSPWc({gS5o|Y9>5O?3cBqk%+J72VZnuJKaIU^{cdpwS2YVs(iVLvg*F-L@~K>)p%O;#I^~HNG-) zPt;1T9L&;%S3X}Uer-R_y5p_Z{q~hgFZz+koO-#B4Km}Mo#FRmo~gy`wjOKhi#~?H z2<-5KcE>~}wCD1(wr+2X+;<1puF-Hhs><%RHLi)@k^^=Y+(m&lVRd|035*KjwMiDBFHagKCV{t(uX;SOpN;9eeo#lDfZqHp~|IRb9 zh$UT;z$4oF@^e+`yj%iARHHV@OD;89lc~ z#(o*7TDwXcH1=SQx*>5*)%Vf@igE4u@;b5@sM~Nct|0NZnu~a2Kh65YwGf)u@IKMz z)>oh6g#@vMeRK7-O6@@;#%p0ic(-m(^ldE9`!V;o)hJre^vxbOo(B<5iL1Cqy6}p( zs~VB^;|VVKJLF1ZbuJky;(w@cJ`%eM`uompwhLE+mfHx}=cPurg-BSOdAbw0$MYmt zaDznQ70>6}Xzd4BUA)5=w0|W}w&p0VWD=TK z@9Gy*tDh;2!ECj~ZygXL`IJ1*E{sG%{fK7g)Lhy!+b_1s%8_lQV<39cmtpkGVf5G- zK=ckn{Ebjg9G;G`d7=`Bw{?eIs~S}=7+#Cd$qWq~ru)781i|F}uGQCz3g_Ycn1@1; zi}$F}?gq||;%|-{;I+6qf9dz_*O-0cImx-VtgLxfanV%CW9aJbGS-E&M#?h%Ra;pP zxKd&ggh3=i_gY4D? zW`-8}MTZV@40}zNRJi4Y}c5Hz67v1I;>X{qnAyEXL zr#S>CwW>)Y83~Jj-!)_H3EFUmd}7EaNBBF@-|CxsgEnj-pA7OT0H2L;$44daWK|8j zrwQ@E)6ocTf=EV(i>MdMuLy96GHyFh8PVvw5UD+E#&Z@0`ru<6$9p(m?qMx@ufy2wAHuQ;?cTq7FfJ%h#z2YkXwh2rpWul& zD^YH`J7^wwf`PJTUPd6;aZ`VEj-)Q{F4kS{>OnfL@*Ja2zqc_Br7v>ycp!>`kZW@ zU+kN!rr;mu;$I^|vt^C*(r8?dAPtv^RX@i33tep%xj%2b??3;h#k#F4Ea-kr98>68^BKWIjNf zCZ#%hg?f1sxtaLhwKcb(J&=dpem{-*NRjJ9h20DI=GovU)aj7G1iIq>1wE~~H|Y;b zuyOYM4ynEG&eqWP73S4|x4!Yz?w2TMZL5uD^Hk<$b-fI7 zboDh--8v$8E?$3Du;Uz+LwFBU{rR>yIVK0MG*62>IhGXHK*sv>9u}Vgskgz{(6HIo zMCuScX7KYIRi|VoBJN=!5+=@HDbxZOKTb~4R27s1>Av3$gXkb%lQ>uBj3$;gYxkvY zB6a8J8Fc=eXUZMy`CZPj&!mNv`>#_oUE)pLBj>&8`$5!RT{5R^_*)bBpX7|LiODtZadl$0MHxGXN$of zohbHwy3xq2_`%)W4rfEB*|l-zPO(hf5xQ04hPiu+d4ID&jc-y6kEWaHe1629$1H3A z``%@Vu1FH*QB(XhIl84RXvp&1>qf@?Z69$LyYIl#y7Z!(ax)YF6uqYELei><{Rdl~--_ zpZ$4bLAhyrD)sP0amD@wgD$({*!YM`=Ee>WlhxCs59k|sJ)W@Q>JRR2yQ?F5xLN7b zcHa4Ciu}XETL(mt=MNHBH+^qB%I*(%ll`qm|L3SzgLR7jPs!4YGL4y&GyE^vlULr; zXUses3cs%Hm0xVGaSEk8EXeZ`SH$XgBZSOGa#ms853qB)U0OmW>-;ArsSkXTD7>da zcz?Z9s9k?K@uyYJ!~mmwd1M-Wn2C3L9+e;ML!Rn7mLx_pNtLwrLP~z~P`@zG!jcuw zJ9MhOqv?nj`QHYXtjM3|7M7=vPV*5x4Wy&)qawU6PhP0_TIii@sQ-tL$L`VpKC7Di zQ?^ZusI|){aDSwUGv|=fIZRX+cXpA9?<1h2dwc@R71gXz@Io4v$}j{ z(WuJgzAX1=zKLV$ukKC;@{oVcmDP*ZARsXr~mHcopVl?b;BCT=># z#ZhtH7E(=Z*}+e@_1GNe8F}oOr9Dg&^LnafFfjT0Sg`H!*YvlB0>`g+zOp_3<}D-# zU)vskr}JtkkU*k{V1DmsC>!R9T?C8P*ivfBGdHd;2k+{uSGrFWAMR!I9O63|a%YQ2 zC6BsyWqZ)vBfj=N9{51&f%(k9CTX!M-ja!;O;MJx7v`6IIiy4qu7@71Tb^UI%S-wA zL@G=AdTR2xc4M5{Y_%xzRz|vMtk3ZJ_;PD32{USL*M=#mCjV;5dR_6)2fSv|Lk4Uy zTudv=2XDw+&TC&m*h98oE>TBo^2_|Iv$@h$JVhgU<5l8Ee2v$UCwD1Q zVl}gJN+K_qMXM(%%?=HgJkdG}<;!%13bhNm!8gNT)cpn{5GNYU81gKhn_DM?|AUb1;04a=X&#}BM-I814}>f^$G$pl+<){GA41G9Jsw;V zC{kF@Or$|=ZRF)R%_gu!i(xD4Z}zAcQ(oo$Tv8WZurHeZ)Fo#uZ^xsDx9E-W;nP>R zFnQX!W#V5SgB`c!rnKs>@4uG0h^@Ie^mYrB}7Jg`z~P*tL9eH*5KKr z+2p_yd66LE!3dvgcj}y)*bDCoTu%&HblAM3pfCL(@ug)ZPpb~C_x3x*%h-I6A*s`y z`t9#CIQAC}5D61$ec7V%wdt%*Z_Q@yldU)jb)B8Cx{nH6Y$wUmFQT$8B%XT5_B^q_ zZo-Vkvrd6cfx5<$gW7+Jo8FzqqP_XT#yN3g!ALTPF@E7lvbuF^870$DgGhM>)<5>) zoNN6j`HXIA#b`WQtzjYbC_(U8QnVTuV(@L8{WXVkBJvmO4GMnKoaoHUWxjFVW9Fp% zMb?#zXasvVhQmhpUzF*$=ok*v3Kp7YMMTeT<(xk?N$Jr}bHahN3Vl34{JG3U**!B^ zqv`dC_VarAB>u6jB>3SIRn%2qZ%UaT{?5Etzu6nHY14Z$v-IJ_9GSF=SGEjRN-XMl z{q>~DQxR2Zt3ObJjq|VJTPkpCgXVBEO@%GRxB6P6{8oE^ z;`(lMGYa#5`uzT!;ae$%BQJ|LLGwv#&KsqF+Nm$N2jllTxu`o)s8}E|N0c~q0eMP4 zUl8$o{mxwOoGNLGMsAg}!_jUi@}StW!KCW5{D>HTTP{752Zr9zr@S#g;DtMHLQ3TF zyn6R{c~s5=VwyFJTKJ+y$$L>0}OHY)|x9+&+=#J8Whf zbU-xG^R*)Udg@yK>B_wb|J#wK=AkGy6$#Xdr?{;A;*rgior=#x;fV}In9IF-ZpJ-6 zs5`@r2-o6eQG&F9wXerrLj$d?nddxDqDK}OCo8EM^3l@AMPIztC{fGtED3Gk4ryCi zbWlk_SGeDQGe5M76grRMAcP&hVxYA#*Ewpp=)7}rGK^e(LW^s3y8XcF>qqX`GJINs zk6`dpTXxr-xcPBw{iW`j@ z%@R66wm0Fu7S=`h+io-HCr_Fx|`FZsqunr;bF}^V=srHH} zb3JL~&1ZH?l~hRa56_QppC+;IcG`|E`AQWuUL9?fugX>3+E5+S=az7v@&Br$xV$;_ z9jD0=z5RkikK@gTot65^WkuQGsl2bWG?N`!kB_~`YrBe;yYGvubSVWdm+6Utm&=$C z88(9gg6Y=K4DYS6J;&hOoLee)xA709I+mSm^UxmI{_4IZou8?FJ&)YY#_#QCGiIY@ zGL4u?!!5g=O~-@#j2IAd?#P|6#DC07Pm&0f({1EHA~USY`=BbjQ|PXcf=X?B9F$l^ zNMkR4(jhPB_Oqt`kPksOSj@WD6)pg_kwf@HzdzOR8Te)h4sA?>al^S zpkh#}?L0xLb^!n(J0)1&OX$I=lCV@$0MdM8f=RDtwK%Gu39n4WzvjyO5M%#MihXqT zUfv=3NnaMvT^=kgyBd#9h_Vz;Ww8rea~Si;x5ykx$0I7&6+0wZeLq&XVEt40qV%cV z&G#qjIj2V$Hl^=(np6Z`I&T&GX^-QZ12e*}^vHH0}u;lQST#k6d$TIYff#&$6G_F{J%oyx<@^M`q8 zy5md^(X0giiXWWF{F7P*K|rYrqqe9q@A z6>>@BI)aTUo&k0r!q(4KCzRv#$Yw)MDI%p2PU+_e=13!08Lic3M|*oIO-_}Q z@}99Hzo+h8X>!L{lG7I#*;?}<4?AlIvc#$I@Puc3A7kq=6Fi~CV4vXu^Y;l1Nk;6A4?)Tg=X7ZZq~F_$l6VWKN9bvm83FFyW11LxwhL| z4^mKW`gEFK?4W~a(vS9+pU0hj!TIf0z@33TZw1}vF_n04l*&2PlBvOaguy$KO1uP} zc8-NaG%LCH?y6{#ah}Pn%p8$3+OuA}9FxgwGG#OqzSz|7pE*o%!}6yb=!GgDw}#6)@dRC6yyg2zMG7Gci&qyZoYX%Vy;xL$wZOmxM-j}zQx4u zw@;EF?k6j@W^6IU|8m2t($tjZfnWEd4lt)9t3q4w8(H>J9|qrSo_y3kqPZ(SVVRs} z`~Bi}$jsaQOTqO?U7B(&yqZfH=h9;>Dl_cYRdxyxezlthilmdF9pUd!k|2#j->rr+ zZ0pc?ufOp*ks@H+rzyAflXpz#UCe4|0F>7S^0^C$yqPxP&oK7o;ClQ6??GE%RAU(~k-D_R>-+@M zhp>v*ECI_^4;Sgj+@Fn(wAyomhGM%>Q|zR z1j|gYy9rhoQj)60S{1gssncVFefGckEyNOTYAk(gH>@vm8e}4F&)Rx>mM+EBM^yfX zQ;Wc3G$x1pM~Kx# z$Az4=Pk7{EcQOf&20za6xI=FG6xE^t0>@5OL$<)T*YQ~Pc90-yI{A#R@{!$p2w!PYdpXt~$9u$S3STtNQ+9P4;}1KA~*9 zB78VypcXCddu7+heJQtj{g2oH<)YQ`fYM_9vp3H3uk<3n{CPbE3U~>g>k2!j0N>+3xH-1TAx`u zEl}hQ#r%ps_}Y_PhumRw0yiEh1$)j>JqU>so_vSDx0)OU?=HTC_aa4t<+s|b*`{{g z#?C&b37SusfVU+_!Cgoj%X){21Q9454ev#Y0%-{nar)Vs@`c}F)xRgE594N*i8*|w zKAccUNR*C=ac&|Iv#8;lGZXAgc9y=hoFaWRr!vvie4aGy&nNqZwPEUe?Qg%csxh!F zs7$=<3h2jvvd`!W5JL{mA%A+FveA2tDmS~|IyH6|jtX$R1IJrnWJ{Zox^M!W1ILfZ zxT;nRP75gMf@2>XJHe=Ab3G#D`>-_}t>6ea%=@f`9~u-USc|}z0OnK=TN7w2rR`+s5acl_AS{x zZ8S>!<<7Z*ye%OO6N9<5fM69M^b( z4Lx@6)0!S_| z2zm;Kz5(Z~CK$O&f0j)*`^$-~5MFb=uzW09sTFbu$YXYR%E`--= zLg46s0j*9Jv`G*-bsc!x4#AoKZP>JBN^1fEegQzj1a|8A35B6%1f{t3kk$mYu6YC{ zV4t44IsPYBp<2Wxr20@k4&%zXftM1bfoc-03sZi@lA;scWnhO!36%;q0x z7Xxtm0A>mrtn~o|J)$8O0X;%Ank#U?2l|4AB*bX_pGHj({-p$b@rAzdKoZVk0m*;c zTTuXCYGA~8Aqlc{ml={I|6p=quQLqvg&4Snf*!(tHmJXt<#TxGiwOj5!U_M|r|ECw zAvg;HY5Wr$1Kkn>%8nq3;LQ-XR_dQO17~)Ct0NGc{a++2Scy_NaRQhs2WTv%0BVfD z=T%aObCl_279)jN%2G0LOd84pz+Wlss$={wcojlhk@j$Zf+3W01m+nN{ESo%q_tO^ z8v#&23>83}CafjT!Dlw2e?XNVK|piiW@v&WL_vRr-+!Ppbs`H`@d9pq^|y3;6a{_mC}NhE`=KvmShMc|F>Ba1abaf%DmL%zs;Kd z_L_pt!eD9&0sj*rnjfYLI|B5ECB+^re+2xxQU;_2NPDkSAmfz&*;g_!w1Xfz>@YeG zkc0_9{!h8kg!>A9_xPfHXmBBQzPdkwcoXA$g2ou90^Z!1{Z^25>R|qDA5R9lXHlYC7;`oe4>}^N>cD zu<4fxoaMh|A3>_Z3atK*TXQfP5G}t6vb7qP7(+-Rt-xS~TLH#lAX+{Fk`OpM3P=Hh zztzhNLZz|LX#^^bbnSzM4DM^{e~U7Ns0RVnKJe`o_%-hX-!N(T2a}6}v%CeWQE(QB zU`9a@cmYX$)tl#7Psg3e>yvdW^^pVb?5{-)nq6cG=Se*)Og7$>Z?~>2%%r?a+|Ai| z%_OvH*3r2CgI(@kWxt5UfK9cQmg7hyMf;-T?JN2PXwTM9QQ`YKdK?4ecQezsQ8?S| z4$u(GpjFUwouNu#?AvwvsHzP$rprOZcV=R$a_`d?(0w8GT+kPO*LGm^b?e5OT1-Y( zY1N-hl7I-KOQk<%Ois3)&ao3 z-Gl*on3*G1ebINS`vT>>@PkzS$Friw!`17STr-+4-~* zH-3t!jEJ|0;qEMh?`67^IHCAZt~XS=UXOGa+^*4`z{$rtO7m{t8eE*U=c3I&vG#je z3XR-+=NCuEO)r?vxi@oZmL<&D@=3|`A8v+&8}(x%;6}YCxE1eSeNm)YZ*MY*THhK^E!KM z>iPXyb8q45F7M5d>r5$V84)G;? z(<%l<`VcK*7^nqD6sNG3`j}1Hr-H{iM^wxTqwscL*33;qjJ;7 z$rX-k?lHF7a7Rc!9bMp^yBdh#M?01Cw^huAX`Vb`j#;9MY^f=y!q55NrWd$wmXT8( z5i*B>sVPa#K7MvIiBd8g?FM%Sw#x9KG26ixeYf-39IE+ukWmp|Y__zg%36b|%2Hyf zK~=&L(lr}P>gZ~R@y#_hiMY@GIcNGacHa1_qxSIr!`LKL^lN9X$TBvjf)Rso7mSZRXeGI6aC9}M7VT&EPdmDcg@T{N& z|N0uEIL`fA{T%WJ>d&o_*Hl;ScM_RzgJ*vCmB3Bj%fbmX)iVoa8sHwVbKo;hj~qI1 z`B*AT8=~qU(YA6<_E_xtz8EE(lZX_=~f_INSzzyPlGH?v(R;3f}?EX}&DHPeWSkg*sFl9?>1v^*Diy)PDZ zOQeuX2Vd?KTym}6*j*PvpiGs-B{tOyvZiNTbNCRn^kt;e*xgw=ig!7x|97rb>hKvf zmAL8dpZha%?l*4nOhua3tMVLfhtwA4b&I_f3Rt3B=%W^!LVEqtY83mNOWSu&mhbC9 z-et$#yT|C&#M0N-Dm$GQekPC#p?eYf4~ma+rin#y{_(k|cXRz7%#q>0v6WXTg+DOG zi}BK}x47tp$bN84H9qiOyX{)diCoz(WT9z-N}cRXkulNkGi}4qp2A3Qtt$$zbuWOc z;lUnW7R*z33!&%Y3Tv;ffotGd$iKlEPOQtjZ(zIh2|JJjT< z_~B;aMTP9yWw+iBUlxz)823pt#m0p{?7!Ji5UTZMm2t&NZgLmBgp9}0bI8bOw`L`0 z=7p*2_OI`$PR$^Z9HdyCwyJB}@t5?xm)B)%&!9=+@{NII%Bn7B$G^hkUS53F_+5lo z!Z6a>?JvDd;)*Ra)%2{Bmg2knNTAe0uI^i^%UXeo#Ui=q(gw+x4N2g#Zr)1yN3qq7 zwL+tiH7&7Ybmx7&gMmUz2E(I+ykWdb`A0z8zrw8wXf2$# zL!+occLJsU4XsO=QRymG&vpH%f}#-WkPv$3>+8#k<`v0XY%;Mw@1 zftzed?E;0W&zvN;)4)j0Q*PX;?Dsho;u@4Yd>I=hQUyL61I-x`?(w@P6oqeNUT^f@ z?0(>T6(@JI$vMDDE+8==eZk3~GZWp5>5WS#*5CeR#b#GP zb-#Bh-MzHidHm~`Nb3*V&S#k(ox;?vfye7K%~Zlyu7qQul#`|ii% z6KoHLD5XX?)#IN9Nj4XrmB_P?xMoYrw%VEATKhx#7mzLh2R*Z_g#Oq^2-yZcJ+rzF z57vQ$JaIr37f_Y>1gO%v75|Zw0(%E?9uH9-@vKSJ#upEXKB1@kKIVBLi&Ww+Ye&_> zSbU%E6K+9=E1bhGI=ZS1Ef@T6mowO2;v4nssKO?D=H{k#5RM+W_%jcCr0tR{RkqI@ z)!>(SIX}$FNaWfrC`-^$V~K!KbE?3;kH8uRmH3bE13bS0Iz}0=zW{Rc6XHyQ0PyQk z*W-&1Ciq_itDl0i8Q|FgKujN!QH5uR^6Yb?fdmtf$c+M|1SDesNopnZF5e#g4$v2% z4izv}0nkwbXU#-(;sRF~B6;CqyFOKIsjx)C#bZA^~Xt z$+v)Xw-RzPo2WhqViVehtYS46Ae$k{0m#g*$MY@Uz14wb{lGGBc|eLm5}IC;);gYN z`N1u1SQ4Jb3r_O{BtJCB8%&3KE&BXNiUSc=9)5x*nL`qqqyx#f|HwFkPc?9n)F27P zZ6F!>kBlMw1fB2%Irbb7o6;x2I7_pVt;4=>^J!Yjjlb^ z1)nf*QTTucxaPjlaB3?d@heO`_D6831>h_&FW4ao0`Hn#c$Vc%n4triJ5aF=)@WE2 zsN?`LhG3x#ulzN~E(F+M3*`8~xh2TCZlx>Wvn`UGz+yWxaF!(0q5`zE0e@UTh)R&8 zLS~{b9R(+>@FTX`AqM`j_k_}LCC0iQ+us$|7~g>>ZzdgE$|s`iJ*T)AZr4MgoCwkMbgQwlo@1};rV|ed=cnW0$x_YT^A4XzXI;M zc$I%06+C z6ehAXBw_ffAc^cj#HIuXEL>j{a1aaA0mhFS=E6BEp@#xYuD_)30^j6;FVc4a`2&)O zTS!s;)cU&0a^x&l8K{VWS@;rI3d8033Xn4Y>d`~axDWT3Dv&+|){+8Uy=i~7l)$92 z1_x~b|0D>4Buw6W|0p>LR|aeYJ7Cs*=pIre+)BIuF{K|EF9zaa2iLX~9t2)z$B4@B zq&QfNoCPzBf>o5%0bWc&hl~Mf@XsoE`h(MWfB~LwAPH8%6Z)3%?p^#J%nj|2d<#z@|>twuqT8<4$_k_X#4xFmVQ3>`2l!zzQ~^f)pOO#j*Z-~R|FJiL_4VdI zHO>T<*hk=G6IhXTAqiDF|09EeXQ+b%7$|NFNhogguMFJcWgz}*U@36V0Lc!L1sC~G z1!IQ?U^PsF2jFrgK`v4SV3jk02VfyEfd}B$$3QN!^`Q&~9)LU47n-I7?9m4qgM0gr z3@=y(Be>S>AV_%lrIriH=x|T3em$}z+?4Heol@yIho!C8v^zJ=Jy`q6xyd{B?}jyW zZwO z{i4Tt>w{A<>X5Au*wT=#={jXtl83?G2CS;nK$0CwLPN1SfPDzqhXjC)>KNUfJhOw7 z1M~XifqB_ez`V^+TNz|P`+_b3`zFq=b5d2<^-SvrW=!IQ8*r_rM#6+N_ypHKQ1 z?a`iX4Vuz-itGC(+q=p_BI2C4+Dh!C z*CIT1w29+V>5gR9m;7=iKHW^M4nEUo%b_a$(}(ln`^3rpIMZ7t7wo3*nT0&Cp0MV~ z|6cT4)cIZ!TewwUi+0YVVhWN>8?)*rtq=2yme^vu=WHj=Zuk8TGYWmedZLfkd!)Xq zR{83Eo0B6wRkhYSHhH9Wvtt((pMR{UzRTHA+OOpM{7f-vxN~kqWa(LQ+5n-pDw!_N zDL3r$y;yN6T0>`l&ye2q9Qu&>is-xV#M2Eni|rla1BFrFaOl`Xx9@5?4Co5`KD96P zs|!I>T9(pm^WRAAW~|RSTtId9%1$ExMl17K{osCI4k_K-+;AzA@`p%tJZ-7hO1Eah zV%fFR14Y;Ett<6%g&$9Q1(q6lRPYpEf3zYfkhROxyPCXp*h+apLz>>=W5`?Wf;xRP zBjK=gjyD(M39EF?c-mJUmG!jto>(1=yE?A+DCcw$=Z053JC3Uf9=|fi?pVhZMpEc0 z6?v=@pGZrO+$A*ZU!FEDE&2H;VUz;hYqrb38o*G*Se@xPk!?tcX|4J*dFF?!zTQgx zGrRnJYo6~|Exi?m-NdG^3CA&N21WD}p1heZ9A4~?dNn>gTFJ~B3i)&RPFIq)x&}y7 zL+NKg8l{X#XY7&z@dFw}`~~}0ApHYK`;-Fdh~LLejQUsJ$L#Z|24JnJa)n#`?Dj9q z2iEktM0b=paLkK+pSVC|l65`>=fuaPCT#h_IXA<6N>kyL<(0DWTAjrOCH&jZv=P0+ zhs+gvOWSucUBX_R6zUBw!YHZk4DKe|62+xgHyTQA5Ocu;wFFgrxl4f3ZE9+tuq&Q&zCHe<}A8J zzas1MhKV_Ry9Cw4=z9O8A!n@wKMYIUfpN=W26)H$)a#pcDKmH{)lQoxj zm$t;wuyj0VBxg>sE}|@KjDrLA~1|Ysr)DPiG%DXbJ%9 zhR-4vxk#f5#v4Wy(H5r=cx>n5WmgwbnlQ|N1u<=D?cL?C@CMz+0mWNSFB zO~licavy_7P0X9^PB| zyCx>Of6VPtOEQrl*h=`E{3FE|q~s@Bi|!}V>={|va}Gewy7*UA{~;UASrR*i|2!v#=xocdgQ@BQuJ=f8DC^gZ!f6A@(z<81t3z4ti-P>Z7Bo`s0X>2nc;?LGH( zN2Y4&Mf4@{3Q%7(Sds06Mt^ok<|=93Kk|0n>lu-AR(v&8rB>ZpL8FOsiRonnYZesV zl`B0v_dimX8Z{&dY|)ko4pYEY@}c!`nN1lo_D}p!Hn1K;;ZwgdvZGo@U1rp7!nZ3} z(m8AclhT*f-EKBzbWAoe4$&v}SDzl#hvA3%B>(DTaC;p6Lr8Whw;1b}@i@oD%N;KH z7|6z@Ju>JyE)ROne!jgG@x0;pTOq`A73jGPAN1S;dXB4vo|_P~cOjm~)OHIap6f!- ztNEcmvA_C?p*{>@d-qc}&vlRC70+?2xcEc7$;XJ2gdv}cTZAP`yAc60lcJ9ewdaZL zy4R9kTUgkAwdF=OS!;OiWti;kFxijNV6xYofypiglil#7l!#*i#s;YwNEkh#+ea6n z+ey%EJZ}|-=j46nh!1DUKkbhelwmrVy00$Ys9TJCuU?XEdz8@W@}y;Yc;~&d_tzD? z)$pCnOkqlsD7KxY>O}=ZQF3EVoasRSK*PgtZqjLr3e;lw^55N}G{W&7D^>?JPu~01 zn^x+*_x)*l&)`Ad%eqqrrh#tn>f8*oV$T~3GTO9KT5*$_Bkc`unq5_BEp$2kt9hg4 zwV5De%45}f)8IHOPEz@vTVJldXOZgGqYV5U>jLE%o}af)mnUr*G%q*u^=x0i^q#i$ zDn8Ei4)wyb`aPqIjM*v&Dc3&j*`$7}XW{LCn{l31=zDaK1V++zrr=9nb?XB{zI6f0 z?7kSwi)LuR`WeUkrO8KY|Cvb*xA?)k1 zdvB|&F+}WL6v;T|>hVWa6&U7Z++{_~Y<}Ns8G=K7!2_4WHHr>jZcmhZl#MdVZBie2XOSh_nlOU>q@Wp*rO~4k zet6!qzLJMwL<0n>Cu;48Uy%+FSX0U}+nf90GX)7okJM=%quvDhsUtH^3z`bOIC;*4 zhVgkGi9!UCIc>4u^aE3t@1(D#s9ur1Kd!%7@;NcDs_ji2x&6Tm*D%d(guzFIp8oU%Zofh1!^KP&L@PO zskP^i8_I56wZ8~s!9seK_Kg%J3sr}2V`?oe!6Ae6aTan!Jc3Y(@CiYqeC;Veyc!!} zIO%(G+g%+~=Hyy)0@2z)6lpObb1hjOVb2}E1mXyi0U~SFT006$0z=tI63df%L{A8A zo=Fz-V}Ed&%j4YfbI8n`&%*Oa9#WI&9*>vZxvLeHN3K9kDuNiZWl3}F{pk=k%5>zc zPIEcFTGH3jr(O^g`z=Q%Q<+i(vmO7A__rl)N^mPwboctVlX<4cl8=}Vk_Sk)e3@jA zqF&V`5UKwY3)xeq#GeHTY-<^1k9ryXR()9>@N>Bze;=Ml$Q41jjjX}exvRtv5UnIk zG%zbJ{EYNizMtN7UC-RyFENdXsp_%A8YC#Hzc3oUF-RP_NlZ%!XOSgD(&3~y zd-4&oRj^0bUJOP|pEo6hX>lJ336d5R$ljRO9zXGd@0>ehDcu8NWj3-LBDh6zNat&& zwa1xYFmgy>FqQ~lFg#!|>oZ~d9QW>)EEE+j;DqABvqx5% zeM`nfdbj*$(+@@JKC^WZS09Nww9!&ORQTZB&lfS$;jnZ^0aF<4cIBG^2kqzI= zA*_c){VU~3o>%E9g|pK0j}!ajuv#8tm+0p-FV{Rg=>AffXyqRGCBQ4{{0|cw!*?t3 zKEKf~D7(AMubtAobwiD~{7l}$(vO@+O8yh~=9ZO4n)pNNTa`ZtB%&7eJR-l4eMn_@ z%$_>waq(e~WCkkd>qziz-y6DRw1dq(QOuF}Pbp71Q<84$+bQgKpIw-{KUHy1oGGf3 zMs26je-MZIyvLEw7WPYhl)*t<@szs+j|XcQw}!KKr>XN@HI_{&g)gYo%klHrg0K08 z1_envQAQ72eT;QoY;1NYzUGH!4+?y3;{W!z=F{bhFcI7%-NCfJW&JykN{SxWe7#(e zAc7Oo9bCyC%*||CHEs2g*L6{{*@^#}FKJMKxg9k+7 zwxx%I@W)kK3wihrn;7RIoiG(?c{LeFr0Ctve%gJA)c!x zG%DQmO1%)vc!2KZqMtfQh%MiC*eh8xo<}3;m$nPl@u|P!jS@NJGm8R`K3}NhViKJ4 znPkJA5ZU_~CD(0Y9(JUnjm`M=qZhfR;ypbhrv5a0FSBPlhO|(D!@v(!adW)&)$=)jy;=9o79@GhVC6#<-U<*R`u-8Y^(3fZ9Q#+%vbM1-ZIz~9*hX- zTuO-7Nhxv|p38ZfpdNL)?9Ag)&!&47$e)m=mqru?t7?z9a{>%pzvPSN1kifwSt$<4 zP|RK#$-?{H8F)RLrzS|v6XAkYFF!Xe#50P=P7mFNNmne@{UiMDdNSL>^ zVv($fSAJ#AfE;yLUbN#73?IEl$L_r`O^gZ6I+x>cVW=auu|r`=PRN>B^*;UD?Od`} zzs{})-zq4{1y`jXDhGSaUy{GkqayJ4#}gX-c-sFBemv=eA5T6DTp1X*l_GOyjUA|#l$mf9{VbPZ==}qmY8}g zy54LkkgcHPPK?H#UwVsOAvfM57!^h=^4?p>)NUU#KHn2PyP)ISeMUXukKj6s?4<8u z(E^&|k8YftZ}zC2<;fvYPD`KGrZQZTvC@YeVQJpU%&s3kqx!Zrry7>5*eS&ldW5C< zF$eQrq0|g9wmhWr%O1m_bu^xnRAyJ}!^={yy^m&B78q6mKhqO_B0t^-ZvxWDC}4d< zsvw?`Qd_73krxp%0Bq@Zb<^Xq(ojwfFsdNQ!#bOFUek_w)OE#evbs-#F(zg=dW9G9 zztsl`#muq*2nFE@ho@KDo`Kk~Z~;agBqz_bdGM|CN!|gG`*92@dDrg)lJnH$^mqa% zD16sd0pk)BPk1IYo0X)oB95aX;_1BAqR;-SclS+u<(!ZA{QFlsSlsSZTeiM*yftdY zIo>BV0Gn+Pzmq2B?b6vOf;uU2;GD9MuEb#A>K4&zS$^bbuBwXliWKM0?r#ZZv)}s~ zeE|uPdK40z>S^SHW#^M0av8&t1wx?14K8bnKuAP#zF*C?CIQaJUIyH3z&-!y>UFnt z4G@<+@Hr3+k(BRqjdS7$>k8g@oN4>4R-Iy0A~||xV{3O!$xFBHn3{4@PB;1kU;M1c zMQ!5rrH}PUpATy8U&6>|2Yty^WEAo&N7&cKJ^JNCbnArfQx{cVFU~s8xiwdnI4^43 zBle|(b|F13((86@u>J*ov)Q=-l1D;O0rdIe{G12;)SMa^kBmyew!`B?U2OU({(dxssmGdb^n*x4ZBTVM|DeAZ%}q zX4k;&!=6VObCN&y*%tGKHWD6qW1I$K_uBHR^~;yDye#fw*o|CVs)e%FLii;t!)HUKAGJ54Nv=X?{Jaq|m?NF|c7>P}=2K&ibThG41@E(k8!I${B-e zT4rv88iPWc*P43ME-HUyO(ltbXbv3nwLB{nwQpKIn`Ht8 zl5vvc3+c9tA55Jr`K=fW7jkV^RF2xS$o==~Bzdq4$VReay#~lGWE;?K`46jb6YV#> zvVCFWXLdXtE3Kw`hsrdzqwv)kWA2hL^JtSZ<-!b)HXQttsI~0eS!tJjdKv`-Z92*s z7^Vi?LMVPs7HyO8sZsFJEh-kIlC$fMK3*xxckNuu^oCeINp!f)}@|lJ1UlJAOP=ndE5k zSxG;)Elh1K8d*;A`@?=_V@9+6`*w5y8#ymi$p8HC22heaJiH?2f6GDtpYA&^Cw`H+vMW{qWMoY%#RpxJD52L z_DoK5Q`=Ru7?}x8iOpHg}6)+|L&f#_gjcjlV$1r2Fe!!EVOg!LNBZ z#NB@CTKMz^r}Hb%Nywekv0?OoLsq?Vmx$}UV?!kE+Nk=&>vlOP9i>~ZuKsamD-UP+pWd`$7Vj@gZ67n(}m;6k!14bBWZHelHV0X1d43B0AKQIt$lGl zk5h2@t#a9skngK4{2z||ud3_c;<~0$cU>~;yRu1wpX6KoBCXKq*_C1Sb5s(MuI%-< zB0lp0(9Jd5o53$NWc-dL(dJqW4z422@#`P7Kc{fdQ{`&ph0-wc`|mxVtH0ILq5#Qr zRO%Ibtle^6K^jAF4NAm49Doc{`lEBXH4~v*+pb-rOtx0@S&5cX(QCbrv*h9J2vbJD2QCYKk->X|!=scsMN5H8p` z^UqQW8pw5QQytL_aoCu;n0A;Q%NA>|3~ZIh59`Pl3-=T(uT_+4uh0!q&kk%ok00JT zXGZ8?>ard`Y%W-P*j$-^k^?{;=lkh`mY5axm5K80%bVU_dDw)o|h)n_P)^}G_p*;K1^?lE+2Q2>;KDs#$m9bn>C*S7P0Yl%2CNyu$5qDa{+ zI(4+s$Td>GmZpkxHRalv^Oo|M>f`Fo8|l??!0@&!=i~qkZi0(5z(}UKS{7~SYT>fg zTSx6OyZp|av9DS}-{9ISSH*$KZQtG-62>>G9DF04horre!wmL{$?G#(h99*rr_wvT z;3+G6ITF)162xiPS(|*6@MFAASY9Yg*-%a)gktet@eDalLt^)ykDy_D&C^qp3-6}C zy1q_W>z`Mw!u7I_QWNbL$5-8S5RM?~!2lrp8C&6qur68vo_Ok7>f9l}?lCkSQ!g`fmph{`!!r^exg! zs%9CNfnRL`gaSWnO1L%Xt5le)6sACp39mv zzc|O0z-z$q?_BU2jS-=s^4e6J^^5~ z`+6TyjMl2VElwss_@QfR!%&Lr5rVea- z)yRAC_)BT5ar|Th+N!)(yw@Irr^pAtEYr!LM+%^tK)nvAR^(j+s?D9)ht*DR!D^IC z>7bh6fUaukW-jdvZboZqE#>OTGbY-oyl4?61%J(Dclo#>3*beTWoH2E46uho>HXn zr&#S=R+slYRZ^;3QhiHrvz7=7?o)N$w!G>QjY61DbSJ7w~g_4Nw&rbSg#G#_fim$`7UuY#p&2qO^ z$Yt|qNh{Wut(XO;zP6GrSz4~ipGmiqlia$aNvcc})U4rNVN=$YRCL&UcQ=OBI$%gx zR+2;Uqha=E#;hHx^A!?TlCtTeGv?M+rq3xnZBq-E6$*q)BXqfLse%WhEdE>VtMrJbLpP3OWx@?0@ajy zS72oa#D@%lrxMRi-*?%@fSfrYW*ZZNbw94$I$N@h0WpM-e47Y?f5<~2`8Es0ghKM@ zwM%;y4JOpbJ4?hG1o67DaLQJ9zsOlhv$lIZ8zDUoN;CyzvV8@{jQRSWh#5krtV7Y@Ag1G+jrg@mf{@^iED~Gr>aU9 z24}x}eN_&&H!&|(>UVpbPL^k2ocX!Cwi$2nOl_EmHepwab6=#91xHtB#ko<|==nMA z0FGA1y%Uo9_m+Y=St(5D_ZR^TGXEIm1k9_G2J=|qxv6$NAnD4#0t@X>VKOLO59d1{ z%tL$_l~TZzGOl)`PhvyxWpk{f!7pt@lMWfo0F1x8E~wx6%>6CI5TZ)qu##wpzX;1g z^J@!v9q~3nwyDTSy3skl+iN%XofL;XGgbL&$=>jvIcQ8}nY=pP9hAY%nzuXm>fIbM zWlw&uWY)R3Hr`Yp5r*9zS)q%$UDyI68+&qhu60dyM&gh#W9qWtGlj_LR-TJ*@XgDQ zz#-8;D0%frFQ`uOoLrDqv%@^6#)sof(cBe&j`Jg;%PIG!=0xK-tR%%XQb(hA?h0%7 zyIr}lCLSCu>^dcMiCcs|)-#Aaypix?L33c4TE?o1Sfs?H!Q`=K+OE!td(r;nL4D5* zQh1V{HD;S=%&H(GLy3210vJqwW5nr~ z?{n*jE+9L4|C}xJg@oz7-&oG(*cD1z4;#bvBjXG4Vv~p?BrOlI-d@F8RL6NrtW|f5 zB@jb1`0Z71lQZ6hczNUad20fe!JP>v)#*ivuIJs9PgTTh5T1V^oI&`dtZgfupfrz| zp~o?8N!D|r>(N!*4|OV@k+U7^Wdw=5UT+`IK3@+di03#C*6h$aW*w9RKUq5P4ReRu zBiQ=y6MAOmU|sqt$#uTIkKmIE&V%sxnRtnP-f8rfFNx(OEhF(_Uw(Y%^N7_uJK{%d z7bvld z1#|E3kHt|81|vxana$}M38HM$wVoXdaXA6R-}JcJlZ4!Ph0j_XzghW~_|m_|iD4zx zzg%_9--V5yqm|N%MzS$?U1~&~*ve|me;>2_(^vWxYt_c?tG^D8e}pBDBH=!%b0R3TUVJUAu%&@I;r zIWRxQP`5Q8ri2xwA;Ux{?*iBZD^fU4FnLsJT_me+FCjZ2lx7P>0zhd%Mv&h-NJ+vE zTtxlHYfaH$EuoXTuSm5r8x?BbaP>u3~ z3`0@tpjG}KXH7rV4V*P4$l*V(`T&Glu|tip!0{XnO9Z_kpLXAL7@}*oL`05&VOkB_rz0w_^`qn`H0WIc<0l* zoUiF$3#RNQ&aH@1jWPU^@UH61p;m`jh+=2c{!5W1Xpn>&+sh1RL zLmG^t6R5$ipoj(|V0S_^Amdd%0|y*yIm#TB3Q5Y~6qW-`EmZFkBq@W#C`Zu-Ez3>U7B4 z6p_JcK&c7;WF=hqhX?uupwHh)D78QPG@7m!>_3~zpVU(%aBf6UU8f;9q{6uCGY3HFxl(3FK~|7T9#X@vc9*lkx`Wi z-8FfY5d17Md1ScmM$MsYbxp^>La*Cb<>hU2bJ2o3az*uI-%A$0u*;UNY}&rOe9TYL z-B2*SweBdqJQ*%Fxk}+-crPg(n3g{7ny0yF{@i~GY)iGApJm4Pd?wRNHSD!mYjOl? z;N2^?UOF>_6jy!xbL|$Fw}{5-J19KB{p*zNQ|Ft@3Y&G4uho90@JRi%)7Q6?P1;PR zIhEhxFZyJtj7-O>y+`FBV|K^cOp3n$4e@E5*BD2RZF79|0i$ivc&B5xs`Ta@FH@YJ zuk}=MY8g{ZIcG8?1B+mj!9(f37!_@bz!(cfB-9PTOYBhY`Ds>2=XDc$$-3x{hTO01#4@+P0tZ-U>?i1z~8g-hA>r zbCnd7&2vDq6?xoB+n!?0t?`BdpUR7*+&%&Cj6bIDlFS{&az0TR{uuE;4(QI*v}}GO z@B8MWO_=cPObsW)#xLBYSgKu1EmqqnLynY@k;xlh)dIR-IZi6L28^o=C4RE?2R~yJ zE^@`4qS?-tWyU!YldAOSNy`@9eWb%$qrOKTA2Twd>O7FN-&>5!O+k39(*5(5^l{wK z)}2L--yXYUi`1<~M?dQuqE{FePtj_X`DQ*_wy%a=m2pNvmtVyB0DO%*8{Ucb1rPO1tFZy=aP@ zi>p_fSk4#)JSvomt~x#0{#evW_lnQ5`)@C$(^ZGl;w6*~=AyeY>@P;$e-|j7_B)(@ zeLCz^WqjAL?xqRB6H9KHgbVUiscBUkS|(d$x zJZ+2Ty0|1B!R_jbk+6xIwvNAm-0XHgDhGTXQkwgW*}&jYbwMj&d84>cy{$gpq1)zNl*8+4%h!Y9hBvlG;? z$r{M0%o{MO!23iws2gHsV15R)V8aFLF``m0SW5bdQfRi6XzcUR%OGwWL0ETK955Tf zgasjx&;haGhW%lgY8yEL3-VBb59EOQCrk^*wSo`F^_8D{8`VHvbVCKFVAGJhE5PY;vVlD*L!c}I z;Xs_|;n0<)+Jx|7e{$gzj-7;{83NBjaG9=p;=|VXo>cRMAe^ge2H1umoU3XeI>)vu zgk^iqN7Z*k(pH5Ekmz0`CfJaPFal3~qC9JIj6Yk0-f&@o8v+R-cnCoejK4J4=nYJ< z(5w#vkpRUJc=|6*1A4=PGz1frF-h(Mr@gC@rbF&58(fOeT6cQUw!T+!kxL;VDctFZq_ked^3AiKPfCK?r> zb>;xUYM}3k&nn`>0clndIodW>;S}zQ0R789bTFu;fgMB>?~c0d2fOWl5n`qPbUS8O z5wf6x>bYwL!6c+T*=K)8Vqc^J-i!g@zYZ8#B#)FvN+V2+R!>yW(Au2~0H?yd*y z&@oz;p?^D6jaVc>(dppBakIi^Jdn`I{!04jJe27;qCsLpisgY=68=Mjw4e^s98p4Y z4+NqWS%TKIg`44LJVH2T9oU2>6r&c}A5GwUAljmm9&wUa0E8! z)M)RbM$!@ct6{jXf)H9#bdWk4b^0un)BV3BeuxBp3y2{}C|tTu46r$%M`%x=MoYj5 zqWI9)lLUdVWhd^xY+B$_5<=UP6=DOW>x2dS6({c4@OW9Yz8ZZ;G||TteC!?DNZ{}i z(bm%UciXT)dj&-SA5GMi=mgaAlk?5SzcXJ!91q?_-h*TGRG|?7Ch!zqNDnP688UhhH-d`Fdv0WBe zkPfBbdIJs)31msuF!8vzDlpVZzh3RitWJAdWSSz))^3Ra`sxN7m+3#cU z@v~5+X1MY5aR0k_1PsKf*HteDWTa}S%8bRET)7-1(W8`b?VO_QV~|fSD#-Tz*Vdc% zF@@9Ps#o6UwaYk9Ml{~QswVg4;h5B;QpE1o3SwyPx8(}CSd|fIm78nDMK<*^*J>@O z+zR(#Z~bc%CrOJ@9eGY5#Y7`VO8?-KsQ>Ys;9lGc@Mt&Dr~#29a-VTu|89S^>RI$N z*}1gudpFWm<0=#Rhq*qvD~ApytUidd_-u5fa@en1nP~Y=+3>bTYVMc6m#-g8(RESq zc9&ecP1Ex9`RKvC*S_wSjU8)_gUy2)1F3Vb3u9l%1RIo%T4&TxX$LX9w2TV0k~p6j zMqTzOE>Lu2vB%~pPI9}fk@4vD#i+1}{PqeWg*KUi!&RDEUB{6xna}Ck@sl6f-D^`f zGY-%<> zl6E5cq%i6ApCm;~jqWsGoj8RAYc_o^9ao#pW_z7d%H1|I7k+Dk??7(w`=fzza{RC; z^Si@n&ZVmPtOaeny*K1RJWRzB_RdkP1jeX1I1tc`t8aG?%h91(H-#^ z&vp$HSfyM0i>Ucm0aVHDkp#@OBkbX;!Y+PZSNsc$+4?jlbuVh{KSOn+zHY%I{wSH8 zv(FT(NO(=AI-lNfKinSg;CGAG&+NshH`b-?N)O9Z9kfq10Vf7}_4luK-A}Qte(0h6 z&cusq<+JeWbzx#;{T2J)~UF1nQ%HjfT4T0L=o__3V4JRoxWI&J-S0b-3G zfrC z4zs+_w0HA$?eS_ZEq3lH`ccR3sHeSm)-~Pzo%S7G`X`JPH8C9N5Epc7mVyYc$`VT( zA?(y^gs|iTC{Y9@`z4NzAV}onhXRu_2PmW~$bJq?1t_*+Cp;rPMplD-j691VpyNRv zc#779j(dR>?2Y6I3bu(16T|Rya0HznZb1SE3`<~YD@WF|1b9HV0;Akop(E8QL<>4- z_MmYTj$s9cinIdV`sMEej~YkN5g!O~%JP(W8y$0_K$M4&mKxcW{yBqAVGndf=au5H z4Gw5{DM!b_C~zntwxy)tg|&YC<;dqJ9MbXUmBiXR&&A8#i7Gn6rU5!8aUzx zL%j5$5t-GfQ9gM`*iB|cCID+_0#PsoI?6(H85GmM*%!M4B&*pW^)O`mZ}!C$?m$O$ zT223h7o4F6-$8kj?LV!MrMqH}vfH0MZQ zglNhi{iPv9vwj~o1O#;=bO*^P6p;fvEaDT28bCHVBqUc9u^E|}z==FEIz2!_hX?O^ z=%5=Jo>0tQ5XTf*((;f|0j-sPu{J{E)_)9+zx>a@{-8+G3Xt)@UyB_-48?4P>;d3> z+_AA6u7x~w8vl=N@C;mcoyg(^LZO2|v8WWqZ3WYX3{tMh^vewAz!e?hr=!DnWTpa# z+<$1$iMb&vV1i87u>Zt=MaSx$aN+JCqai1fyB_Qm zClWeP86@669FU3jD_p=jXmKM0`F~861h85@8WyrZcqvLlA zM9u`U;L!UYJKKM3j*gB0VsQLpxhX?-NfZTI7K*qQoxNAV&eejEB6HL#8iNkxzKRx( zI|OOa!nyU=7U%xIM~MAni$ih2k>%*$ERM)%wN?aYRTOF7It1&GhyoFa^Vi(O2|Im& zR%t%mTMj7TWaT3)SRKf+RflYNny4PK6$l~_#npAe$os4#xhhwUwvKH$?jsZcoe+U& zD53ut5J})*MbYGPBM^!GpRnM6jDz`5UPtJ1fdvB5<$_fNg3$a(ipOY7gs>OKD2^|h zXb+@tjDI_wj}{Kv7Cey9(YD|Lh3*J8V>GaT+up%Wk7|OQ;g4k$7k&*q;AnKfh%c;3 zQNym+z{bIz^{3PH5Tyx)riJ!ud?-XS*k_JE>uiydkJlIq%nDi0h9T*npyMHskptGB z4WS&}lhB40SCH27LB>^x8d!*+-D)xsRzSN~ISOj-hNuy5(Z9L@E=hpx7fpi~q(+<9 ze>QuxvHZ(^4+Mku2Zq0U4>g*0Dab$_sy*8A4nAI&z89aMS`+K6hKy1=i$mh z|9kYmC(#{rI4GIQ%W@Z#9I1)nCzU&pt9ks~ls}gpms=ymL&$2am&E*D$w+?1OzVBE zZTN#V;y@wSoaDzQsjoLzD34(lr9fj;R{Q7OrxPC}*VXcwtW!2Csn=D1rVakc4w5!8 z#Vx(e<-J)z5!xcY!8)x&(N%VH`@6=&?oTVJ=O1>T2C!x~J%Ba4(<-9X^zLzw%9`lk z3rtJbE?F!aUfTTlnO*7GO#3rp2GStjN)8zQfQun#KZP#$yZ!MB5jTB6(lxz1g6FDq zz+JimvSx1YZQSOJ>GUzTbYt8u^Wf{T4U)B{Vy-5<|Yc!h6v45Ldg;kDuG0JgqJb zc5*%JGO~RIFvLS=#AwBp5MK z8LhFtIAq(%!LR}5sj36tyO?;VwBD0?5;ZgdP+cp9)bm?QDgfWL0n3$)L9G(Tr0d~z zVAsMw!~#$9;~8Mxx>A1|!0{6V;Q4;?E_milv4Usu^v~0tKHFM*pXYbg(=krozy@hx z^LI`JIX*SQ=B(GA0@sM!>UbG`e-LL%+^fz*%%q&T z&rKKLeVfwRpU$kiUBf#wS1`O9c6X&GUMrF%Y_ojKQJgxCdETN;f4xy);GUCdB7cKM zM9XZzdV1XYfRL5Ftt=?-a5HZQ1TpQCLc9v`Wg}65py(ANq@F7m4s9nfBz2fBaGG>| z{0vf`T!HKgj`9FC)t6Oyi=#cuC!tlH2j)d8>%w0GNdE;XKUriMBmFb_q8RrUY&kCm_ci&M8JKeE&zz!F{ZgQ_u^fTSTf*jUlU0G!O625S{0( zp62VAr&D+n+dh^z3GU{FXN$-@J}qCzsHXnxVy%Cs^gAo%ZN~TKi$qU4$jf#22@}71 z^Vo#0k~UE(@}5e4H@JbH>>;Jf%Qe=S)yl;5v@^4n$-uNTyOk-;h7(-LpLGTv<4jE6 zoDd(t3Gre15Fecn@k#j*p9cKJnbLACAimhb;0dSu@(8!Ey6c|iyc`E+TFiyF7-UjmZGT%A^=7W_HF3?2+EuHs zb#ZsDT4i)+ZCc6=hcWMu+g*9dPwC7l{4-5BMB_kjcb8f(ivnB;s2o-4U0fi&b7XZR z>f^Kc*#Ei^p#F2g=s8xc1f{CHI)7B)hXI`vPP#ZPD|f%(UZ-qXF>duEu&fs7YnP_K zT57jmfES`Y>PlY(oQY?Z)h(+RXW}{~9+)o9+7MG!Rqoz4m>TG9k0!9R-fUM#6-rPA z22^4AbbJFJYxbTMAI+MWq*y;g*Yv}R{F{S?k6q%vP^@W7HBJdu-?Z+}=xAm%>e#-hf@E{PGOr&eeYlhLI_LKbPDA~d4KAJuFyq%Q ztGK%@%J-_K;_~qiDeG*lS01(u6)e%y=Z%|V-e4cRH;50Ijs&obPAUYGdV{#FHh0N* zrsOUkXF<$(PY`!_wMIKXiTYlD2Du^qt^+DehJ~V#Tz!TUVR9kH1Ck%)$67J1Jjzw& z?&zB)2n8>%YG51Yk%`sw-xMu6a$Od5Cm$Brg(l#hZdiH9m7m_@KR;pDFMG3vGD*|C zvS(yNI^osvfI7)(^|_shdy>Sb<2T#VQh2~0OD)CI*oML{R3geT=h~IqOT4@ojPpoO zhGbNfg1G4ICCbI(j{WJgLm+lKPd_Hahd{iYQn7fBdOM8CZkz{lZYtVnKmOq#yU1k! z!(#W#z{?d#v79b}3>>fz>$){?z8Rv;Y5^NA_ zJ(d#W4(%DCEx`6T%fojOlF&g;ksw<%k9s1e!=!4RaGn0l)Rpq$=q^>iVxta+LADnL z#d|9fHe=s^({|15J^HPRBhu|Zd*CV7^kJjaVx@R%YieKNz(U~B5I5dlkW9+pMj6#K z6_*f0%GY2MKAn=KGL^>p8?N9!iKlz=ZMjpiTZB(8H*-*&zKmNXQoYhvC0j#%16%5p zMM>60uTN4-FBlXD{A(;3NO|>`bMAM{wpWZvC4V5^HZeS?VGmB zGoMB0o4J`&qjF{m2Q0okHs0AW;g1IPu9`N@<{7BPY54HioKly*L2;VV{bOupYPRqu zpV4EJS+%^a8|U~K8o7(klV~qrsfm`i?)?~PFzK&eLCIAF2{4{_=LQ?}I>ogzSM?cc|n7cipmf$YuF?drN^= z{0hTeGcuDKCh67E&YBco6F^C`HCZ%6K*31wjEvq@hp+yw=Js=0K8d-^^l>`o(_>G) zMuFR)V$xOMW%!92x(zNZuu89V@6=od-h>01-aZ+jZV2Q{n2Iv%Jop{nxf&ZbDdWPv zTIoRDL$1g^@1Vf25B$M;3+rglD&D}w1Fm5SS>*7{i^+|mJywr59CTg4IEuR>XEw1>GUKJ&y+na4|756=Q<9h9Hx3AtM z(pr5J@7x0XF88@5=!uJ*RaBf(yI?gLxz@Czd9=Hp{lmlauHj`6r|tMn`y@duGU1K!?SSsuqygJ%xmaG# zXiaefy@0MPWg^-5vwr6r&!yMAU@KtPaSvwkXKc2Q^?smddhbL*&7vGww^bT#K?-82pORuZi{oqt{+>jqiYX<;ax5J3 z-dw@bbL+KfvZAN$+ZIu$;smewwoO-wi54l7+2n~Z3u>t=CndmZRSKK@Iq=F~Ir#*< zYQATaCkC%dmGo1Bzsp2^?`2<7Gjk`iYAV~kpzJQ{sbv)l{+~LSvIYMuIkk|3|D@J~ zxxqhTbL56sPSAF6cBm1E#jD4^BlYA@;#a?dVMT3~2rE1;1<&foA@HBE30GM8(+ODF zrvg5`PeB&gWU1I!-G8-RaVXQ#PQa4cS5o~Qd+e-TUd_+&Bh#Ji!sh6Dii+5HabWf0 z%IqG!l-o!9hgyLSTAWtQ9b5K`sVmci<0c7n@^7bt`*u0ZRH~|SqAOk-+63Bp$v)dS z?ER{J_nk!^hwZcJrIj3=38mHf;Vkva9>FS?EcJ>WLFfFcAx4{_k$W}Gsr+%8nf;X{ z(&N%K9l7`Jj7zldY#o^S9huY~9cl`aSYc|P+IclDXfS5cwzG5a%D$eCGogf5Oo(}g z&&N)YVQK7j4BtynqSt$I5@P;e4(W-B^j>)sRnb<6dN~E+;VCkdfl{8r2RP3&!iTSm zHEWmm+l@xG$$Pzbdw!LI+b7&V@r~jQ_oPO$>se;K10v^6``+kIZ^x4o)|rfW60n%e zos(QvMSZiI!dcpRetB4Df3RVVO_QWEUSiTNeCUyN{N~N!JKE7%ji;}1t$%ecvAv_0 zrw&X!4mIF^V}I=xc`yfg_R&fhdSq3aV4iX}sR5>>&jrjxzs4%O^kq^rZiZoTirP`^ z3dV#NE-0XOl!2fh0wo|QjX*gFwv3)M>h;D=yiXwJ_$WpR;}f<5>j_$+Ul9mogJ2K@ zI}tX17(L?vw$nA_P?tXhZqd4s=8tw!;T5s!=;CL$C~i zp!*YE4hRG*Zo+GO1ygoQizAsC9l2M|<1;0^3YtD@WA%XXcnNI!-Z z`_&s5(`m2sxao|lpTGX|HRnP}jcCMXf}KLMLmxh+E0gCf{s|k#i(^rx&1B$T*1nzV z*i$8|x5?s5+s|AHnpoib;C(c7Qup#3ErK82Z8wv~@qE4SV2$yZv3*j1N$j8dt3(fH zQf4g}JRhv(f~V110(d&Ext;tJE1GUrrFOhJD*234HoL!V`pl@MILpPd^^%g!qy2`tc-6Ny9&2cE z>vOwB8t9qK9<{F1aBFzX=H1F)akP5lSk6x!a$}H{i{Fm8y+`jX&HlW3qkcHJ*%GYY zEZ3f7?UZikK}FDPeQ;r%fa>O_8=@26h|g&Rh)eH0i)6jIy4N$>$!{w$dgv`K-7}hx zK&CSF>0h~7z3?TsKAGX?iUZZnl$E2fVK(cGDp9L>|H}PSejyoEbqHkkSy9!WcwHtU z$|o7Ub(Aw*7+qa`=BKVYcEHaEi^gy5K1w@johc5PV6jNV)^uL%&QQxcsnKGo5)R_u zUM#ie3A@-Z8T64*N_TBA(XEy~$_4mckI=~0eo0E=O77X_4qJeuuPxwd1 zIi3kcvr4;w?-{(tpTu7nX!meusoZf47{Augs8ptC7TH*a|03NanYQcD-ncU9KrfZN zsJTobRCnmzvHWmJP5H|Q{5gKu_xjnjG<|Pm`4AD%RfXJUd45t5`1^Y=E%Dy%$PSmX zBfd>%oSF@t|3{zS_t#iR^s+XZQEaG-yTR%EJ(Zj>EvDD`YQIVHJLe}iC{5}V%EmqT zJu5X#FM3V41WgsZS!OiOPuJ`o=c|dmvRj&#RzsJ*&+^WhAeo49Ko-xJrGD%-iv<1_ z@$G^!L-JmAye8+mrz)aliq!(Ew+?;p`wJU+5)l;Oo*}(WxApecZI(vr8xQb&2Yg;U zdD|xNbYNm;cChUM2Axo-T`t>lp6WZk9XJ*S&Xb_tRRuc~B55 z_9up|Vr$?}x=Km#TzVmM*#6% z2>-{CbQlL>?m^em;nNW00*<8Y;}@xUF2FY{NE(d$iNV;y8+nYD2|%Y%O~|Z_5rO!~ z!IT7Yh{J)dsC|e%gX7&W@ny495Mz%pgb+Nt;OLMjwA_Iyj5{KiL+(ExOaA@c(ezLf zYj=jZ>t^Ay+isLj2A{F?iGn`$pTR?SZslyII={2;@E3OYcy4My*0lxM04QcQ=rxn5rc3c4>1oX-j)l zvb`!^FQeYccXqKcMm>fUYd>S7*6fx2`vuN$zKtni?_TC8UZb!mEDPd$NT<_(lYzqQ z*KY7Mu9D3N-PZkz-4weQ#Mu)|`7T+FvXz&^bzTd#P4iZ|Bxi*1D-URD+2Qnvh-ESS z+G?dCJVYrI)uS*#3Z(^;A(e@HM;u1Rw&W7-bI8?7Cc6>=6=qm|}^Cwi3 z>DM0}xUL{fsOUeiS(i`Bw6kG!Eed-)nPB&>P0LL=I<+{lZ`W*6Zfen~MjxN#Hmqh) zF)ub&J~by{_x`igSMpUBss`et1S|u7ERdb7f!MVL<6_9i-(M(0-rl_M*%Bv_$%5ZI z>l}oVJ`S@xRO6SD8FTmE{UI-tP>?Rr*-Az$ z&{JREs>c4%IJ&9?7bT3y-wPb#-~tK5>+tX)bza6Z3m!tUpm%;B>N zV#BN)=52FM=4q$GGl4vg8iG^FMAEY2AQf^h1<*zFWOmjvb;??|&DmC|}94yP-$1_MpQ;gNldy z-7dFrv(UBGxD=K3!?-ww;tSt?ulKZA0nc*mvvlgyV_S71ID;#mv%um}sJ9UciFido3ChoHJZ^$u5Jgt73y3mlE8Cu$h zLQRmJnz?3_eg&WDzyu2)WNy z#YhK@pD>>#p5p6o7t%Z6kn5~+kPH3FCt~oBhMx01^DApd`nsO(iZ$u(j$A>W+D_^I z6fWHwYQ`}F`|$nKxv$syrUTDk?sltfJe;01(z~goA6%<)%XZ*e=DZU@7O-3Mr0ttl zijKRzIE#PM&$+J6&6^{5KYq&oL6|PV>fX|BbXDi0qB%~LFuCnrg6-GKTL*0CC5D%F z)vsifMr|Erj~W_1?yPHe^QPK5FyUq~u&pjtcWkw5G<6weEIm^xVx6G*u(4ujll&o> zcJaEh$HEpV;&nj?%%XD=O8fVpqavvz*0C2u1b{H`BmDcypIBRDprz5GdU8<&Fs~s_7=kX zooOBli$v;#yK{L+(Ut9JLTBr+Y`>KY6wZhYmO}%m|9IuJ?{IqueEG4IlgDI>B+O@&ZjSn z0!gM=(^ztsylo@$$09AR(jjYoLt>)(sx$~z#%6t^@$!MH0EM2?Q7l&M2o0WxcF2o?^s%sa zbX%ExGe-VyKiNTPlCvg4e|a0Txw>DLRJ}>k`0gjUY=S8#o~``R=Sp8ScCDXI{i8p& zc$}~8lG=;Ee^LJaFtKVv@`%4{YG&odkdCvHsPMLz_Hgz0+2sM1$%hkmhEl4|{A+c+ z0i?s$CP^%9&g`TbeC#HPDJ8Dw_Ps8irfVkzmzWIQFP{`=S=R@##^hW@h~Z5pWq6ZG zgam>%>W{01I$i1DEv0C9VaXK+Eof1gm*=(dfu797~ zeaHQyKx~Uk;V}pp9|KBj2mU1z!&?%StTq zv7INLIl4T^`j9>)#660U{^vXy0`D<9e=zLY%)X$#b@^6L@z#x1!smx+*Ag?rU~&uV z2k)*WUfdrC2_X7Aj_zMn(7L+M&2ZGIANpYT_nl~>&i5y5g4h-o1Z(sBp+n8{>YB~_9>dZr<`P$@=ip#-(J#D70|7(P?VaD4#XwRa)7{61kqcF=sx-hu#R{xcwnw z6GOPNq&yWbL+_3(u@ECh6#a)Ge~p(ItS3H@diARHDd8vz;>Z$fEpwNyO_8rXIJymWK9kGhDN&i8S34 z8r2U3D&NIE(R*#L^x8DxAZR6rY4GMM`6y%l7wwc_3*1vYJJ!@IdEf_^;%XYH=f#5+ z08-3{t9zaes51!iPYJi`rIR;YcWAF8b+VHibO+@VH^D3JXi^D(NjnVpC6VdiNrHS^GEKubp1E3CvX; zElShI&y5*&?#C~X;4!aOWo!)E3GXEnf9zad?4s$!+97uqKKbJzhRBVA7BTFP1%@I= z;|kqhrhiVbMp5N@ec{H&X%3?`=N|+$Nsd)iEE>Nlgb#%hYz-|P??^f_EW&zXj

    rdAJkm_vY5#nEpeVF>~7=itCWnU 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 851db6943d1..286e25c3c75 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -11,8 +11,18 @@ + + + + + + + + + + diff --git a/src/main/resources/report_template.md b/src/main/resources/report_template.md deleted file mode 100644 index c31bcbd60eb..00000000000 --- a/src/main/resources/report_template.md +++ /dev/null @@ -1,40 +0,0 @@ - - -## Generated Bug Report - - -PLUGIN ERROR: ${PLUGIN_ERROR} - -### Expected Behavior - - - -### Actual Behavior - - - -### Steps to Reproduce - - - -### OS and Versions - -* Nukkit Version: ${NUKKIT_VERSION} -* Java Version: ${JAVA_VERSION} - -* Host Configuration: - -| Item | Value | -|:----:|:-----:| -| Host OS | ${HOSTOS} | -| Memory(RAM) | ${MEMORY} | -| Storage Size | ${STORAGE_SIZE} | -| Storage Type | ${STORAGE_TYPE} | -| CPU Type | ${CPU_TYPE} | -| CPU Core Count | ${AVAILABLE_CORE} | - -### Crashdump, Backtrace or Other Files - -``` -${STACKTRACE} -``` diff --git a/src/main/resources/runtime_block_states.dat b/src/main/resources/runtime_block_states.dat index e9a55547410be1aeae9989611a73fab91f865f62..1b3bdf5aeb415f42dc750e2b0a1736304bb08d56 100644 GIT binary patch literal 52833 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

    K5fv3c3jQYcGZwx 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 OXD&E-T=KgtHv<5I7a2JK literal 40547 zcmXtf1yCGK*Yy)1A$V9cxGe5&AxLm{cXxMpcXxujySpzE++BhPcla0H@285I)90Qm z-7~ed+jAQ~{Of-oe{Z`TFfJOZj+L54UlNU4EXU#sFh zO7%-@N=jS5l&wvD6TxatJpl6ZLjXZQcu}TbDW*mM{{4%6qls*HCIG}LO!N}UBp1yiuTyj|}#?hUVy;bF= z(E|33TT4Pn0iEldT8@TY6c70l{&C6NTC1{nf0TyFtAZ(UI-Np?M#sZl&HkIMp0M+q zYNOnZ84F#uCf%%ljR3XQFx%Vhz=aCq`TW{^_O&8*ft&7_yU7)ul9hwrFRrfpKaw?i zOO~&xO7y~LpikAMy4B5;DmdL{K|O#{9hm>hu)m0x`jLDp*TtkRfK7s^7GLh(h3apk z^t-`yr~2hrK(i9KI)sBF z(1WF~Tp{I3v4L>&>u)Q8tuiynV-}ysc-i~XWqEGc;y`i_#nTcz$k`&CZ`z5!@p!&I zlwuu`|NeREb6*N|)6X}oJSQ{s-%SuWdmNz-j?5S|n>M$r%jH~XUwoAmUX}-oJ$O(8 z9%#9Pf$5Ehqa1I?W}=;zQ{KajX3H^ePQsc~_<+>d8+lDu>!Wq!6pl8t3XkTCv$}~b z7}06IAgP=*6zn-VhyOn+TjNFdf7Eb;%KyL+3mK7i{O=yO z9km}H;Bi~cPbeLI%E@UHTaiwkV%jBiQDkmx;b<=|xdM$<%&JD;__aa6jrEG03m7E= zv9t)vJB{fV1c-DR;pqnh3FCMxS*}#LfTFQba%HJQON64Kz!=f-AM1#s(IOC`Ga!_c zn0!INaJ*5LNpQY0(6`=XU(`wDQ2;RPXO!aqgH!Yo9L&uq#SLAy9sG_r5CHl<8w4%MeM17UdRNO-{1K*W z>fmS59<(1KjufkixvPD+bSklDy+0m>fm%MXowaQOseXv{tq3f(x1Y|HOI;Dz1DS~P-#)+QSPY+ zr>IAX^hR1Kj77?M9R!E$OyCzq92wJWvlAh5&Q_q?ZWAG@&A?G_0|jud^D)%=MZ?%l zgql?QMMK&D@;9mXA0pdMd1)CSlhJJdK5t(nlBx=UjIRnzR_Q-PvDNv~_)jjJ{e=io zq7Plm2-%po-?wG4cZ_BmK8&57P~9a^FoO^o+Q<;Dsy>8?db@kpVoMR-R!9IRC7Y%_ zZIFXvbGFQ?;J8k~Anv@~Z`jL-M;|&Wz2dZwkCypKmCI%|-x42@F>h&d$#-Yp>d7XC zY~1T-ZHm1B`*503*Edy==q5pM+KZk61DUUXgxRfl>_7$-;$_(q*Lb(6lL4C6*WpH^ zE(>U>@dJXpw3jekc9v{^35(k@r_EUp#LL>HDnWGnI1Gr|`lTvibbA2%@RCs%@<&+z z^cSO#upU;o>ddPF|25f9_(;+`s6S>C0GevpQj2T^V27s$y_S9mYUKMUSez(>Uz;z7>H`>7k*OI8@m>=&gBN+)+9X6!39!yT6JGHTesWn=bW z)7MBQp4?j<@fJVjQsb+2w+3F^&Spw!Jo3OrVK(Ln_#?%#qg|Sisha6AOpb+C>_6A7`^wbGIOb ztPP<30rl2OzAfGxD7@`0H6A0LJls>nHXrZYw2DACzd(NaTIFF)UH2AkZ^fcb-g25J zTdHI=%XU}(oH%Q9u_^L5Zbhqtw%0e2h8vhBtW2qF7>x*Td6@I(2j^rWH>XWQL9l56 zmmC#3E9>#V*iuXnGH-UVlxUYi$Itzq_9trl4VSYCEYy5))u`fO! zPyP9da!s|g!D{QO@ic|gk(z+QCfA7p|sfy8cdN}0d!gWG)BUbt4t z%A!Ba3&1{k3Ka|4k-kb>?+hKw-9(_h=h-W?#lqwyXM`A`@If53*GK%+<-NA1MwcvF zPu%96P2+YJXi;Hf&m>NM`n*su?DLEAbvX4DJoZoqGW~m|io=3VeKL&WHGi++W|nA1d=Sd60*d80 zE5G;?!>-aD%duI0ahN|3H@8|S;?IJeiZYIck0|JGh?PnV;v_yujhq&ey;RgS722Pw zofyussm39;mry;JS>ooV*w%p=+^D&7AjhF@yKhsWqTCyY`H!4>J_I$x*F{zeBdg9`^(KcIzpMA_{fVSMt`2=ZEkCn$)XXFZhwq z-SIpOgIwA9?ry#E(ud4ZeYb|c_zwvNnzixi7xpXRR(GYNw?Q8d5N9#&EZXuuH~{i# z{3R6OEyhNNsfB0mas9Cot}}j^&!L@8(^dzYJ!mIQD$4J`*X# zvV7g6EWP9Cw`!UWmfs0H$0yU3SDNo2GwI0|SE`zNJ-uFN}a)oR?GD>h(9#$KdO zZXa99bsnKQBYpI|1ic}JK-;wD-~ZP>-b>PY-^w_I55R$YDMiZHqDel4p01Onv0h-~ zq7{F|cI=B1t!Je45F}1WO)yO2mk@k!k_UY$PVn_reQw0t2 zE7GRG(`{f1R_=vp2>X;_309J9wSH+fligJVROWRLB#bg7n~OF_eZr_?FrZ$D)4_$N zHnJX8sU+#a`b=%){((3@serZvYc$0DSiY!$9)nUf#Cw?$B|wkC@5lj=l5&I0)RGin z3IRqaALyM!vj=ljkQyMYh!(Jp<+DcXf@y*s4a`DA_kaav>R1C%V+M?{GEH{OG)spo z*^V49Bfh$No&7!ISvl2rGla8>J|^jJoyV$U$nhVO-Scy-U@_X^hgQ}|gqv@ePl<2k zYzCBSR97|(W~|QjBr5Fr1y-=^1FH}#6DTptEh-qu-2H7wK0QkfcLb;`Pv89v+7(;5 zfobvPAi^lO2Ag5WACd#$=9|H>diI~idp{p*n1e)h?O#c@{2D4*_IIl}-r{U})E2Pp z;}$c#MVoah%p*Be%X`Q?r0+WeRE|GlV#ywGMrml?sbX8bjGY*vSUba56-^iolo>6A z@2w6@}dX!A6}k#F1DQ(NS;K!Y-WtK@T_#v6UHb`OMZS1wmZrWrE`>^@FFe6Kn$5B9T_}k=< ze>(Ri0jPF^OcD*;>0XqCfz+4q@|H`5*ww#_1t(ET1>)MKN<_A3ZLr`g2gPOaaa*GsGbQX zV=xg$u&5YR;!uQ~FAdcb#Bczn!U+~(_Oq0DzX*Ig`DaH*Mc_~Lwb81j2`z>Y8A`pD zK601E4<0PC+WV=*D+Cth_h#s!OnjQZZfuS2h@srRP_2{E$bRx9FW*`lO#U2vM7c}fz;hhlXO^F&^cV^qzFVqyQdeNkW(>@pjQHyz^Vaz)G zHRdOO@&&MLvKM9DnYoD&7Nt&7xYO!IAiYqytfC8*^L<5?EN1Z}vqd18Di&efnO}$y zB%1W}Z%w`kV5Qg(44(dh4`Zgk2+OP&W&%!we?hV8MjNT*`%*{NORMVc?3+AUEXw$B zxa+g7b&0Rm_5ZZ*5xMk^@_dVg(X0Wi7VI|u$gCr0rM+!Fk_W1;1)w7$YW``-y~nYesziw)0IC8DgO$ zb!Rh;K#mBi(w>JptO;_VVY~|$@Ks~Th(M}=4|Q#I;6&(8$I_Vbu~x&;{^(8X3a-Qn z{@1SNtQL>F?}Ny?-E5UjcLv);@b^PE5uv6*q9OE~#0yMDh2KdjdVR7rCR5UkQWCyG zcG_|4TkUTYW(}{C!bqs$?z4BF;9wD4SHTWMf5WSNtg4^VG~gf4FRjtKu5HL|lM1;V z9K3xqyn;Ro%uokewGP15WZmrdQCgqs%8t|OORSB>%TaUxAcM*JX+&cpH<5Q&E%=O? z$m~V-H{L8V(m;*7p2<#WwBa$z!`edc0LgAo&NP`xy-VYww!p5Tb4kJUwR3$@^f21R zs>17rY_}_4$?Ei_!}z*!EtJ24(KRyJlsE4?JxrL8dZ!1Ps}JJ&b{jY`4PG&mMj%v zn;aCN`RFWa8HR1EhUI2KVl0?C=0@2zY?+Faz6SlvQ6)W?8QTt_-sG*57%fUBp{@0~ z12N91bx^~^e%3HfYRN56CZVYH*#iCSMHd${_*9i@IJ$}H9qpFu77e5}bxg|ZmLnLq znjCyAIk}vi-6yPQw>wDVdSH0E(kgiPl>YYTQDt14QHBCF+=}IRU7>g&W6VAK{thNV zw9t_k6IJ-rX9t#F=kRxL&W##EzN*a&^!_i26k})I76UjUcO45+kJbnTalzE=m*g@5KmW)N(De& z#L4o=DNu?1-L3U+*YZy!pFgxQJQgRr{?xyo@BTuwL%;P;$@jt=B&%F{$mqgokeiza1PluJ?DRShW38!=W1S0ZXiF{UPQOVW4v4?|J#uxwD+8xxFY+BjmFbl zJG^>bcl8W;xx){%r=Wj4pbN#vM-T^Ms=TT5s82a*JgklShuGI2zA{n*2$c39}sP=hilj2$wz(t zoYa2r`2P6^k*)b0bdwUt>+4GEHRC2B`nww$qpQFvB!BravUNV>E$Lqo!~SxP5vFiJ z52q}s;@3F6E%Fz0L55^RmOh{WW;w>dD#t8*JgRQB5vu*343mUdKu@bIDDu~pHOhQc z?H#MAPPG>*BvTZ=cAi7O`;#=cD+&9(kDWy*1@b^bAyW%;TCMjVU!m07nQ=OWd2(Ec) z6v?6{Ul5(XE)joO?+{H@hs}h=D7--0!1wgG;t;xc*D%BrUzx2sH6#(7#n%c_iyHVa z>T2TpwE6)?U~PBOl-VfySoJW3JAWB>ql%CK`XZ{DWEo8z%e*@Div6CRe)w6^N^(^z zHWyRx%L>0WQC#8-$jj=ht}tp4XqUOp-97)sMZa?WxA6dsbKkMZD4uvG;1MCxE{Z%@ z8FiybmX>yO%dr2>JV89f&gJwUB-UPBE{+!c^KYS+1h53gTRu-Cnmjo9i??{5hFU`3 zv9Gg9!ZF$&fvW0jj8Mx@S;Wg&LnGv$)f!#o&=fQIdtFKB^mQ*WQZMoFP(Q>61G)xo5;cjuEjn|1z+=ndWYFi^&PX` zBA5J3)lo?4B4THyHVbX3p7~vn*IYnxnB_9vp|WIH>7t#ddY74)!ockz+oAF)vSJO| zqaju4;(UheX>~A7l^9LtS?II2SxL-y+-Ro|1zOZnhr1ZQ^Fp_r`ak5etQ{WIn>2OS zGV+l7GI<=$6L7{Gzm1TZgfBNa>6ASE>2qi3!ghNcH6otA=qeX!TlkD0vTk?JNp%5X z*@?EhpQO5&rJMk1tG~j8|ZvOR8(PC#fhDVGF|f5;r|H^p8RsC@dA$#=xV~Fs4SJfDoG- zeKyY|wL+t3?W$oDssc>gsX~HY^>ndPm@hHp{)M@<92d#rK>BC-dvvPysrvN!GBa*H?n=ZTfd3LPFYgc@~S*~>dCB|a!Ju%stfARRl&p=K4Dg~QK& zS&{q7OE|qN#>aFdgH;@byo$E&e6H! zuY1=sT)(($*5U1s^&ICCQ|rV*r9D00Wg<9sX*X_1iV%*|eC!f?r)#%fA*EC!^ksax zgN(m70psPRnLIOBF`P#HRb(0_i5%L?4C3afS96TxjCMC6_;`U{Iz34Wmj z%@Ao>t}PHd%jeuB4OjCr$C(746S5ABEoOZAmIS)`*S1t*CylEocEwU(j+WBcaap~- zTVar>Qly0~39E&7e}=`}aFWdbNaaWi(f^SiU{X%6PteWCw}pxoCSW76Fd8K_5?!o*k=0gsCDjE-evy4h4?tMKM-bS1|H|ggjQKx7teXA- zJ>E9C``1RE>6fgacP;#i3RiklgF4rSy=RlxPr-_C?x=ZA7_|nsL~R`@UY@G;Mceye zF2M>t;WVWB+L=`*Wy{*+GWV1>30&v|SnQ^ozA{du6;dbE{8Jzfe0B&d*)*W$pA#We*VG;5 z{0=9_A~&q5oT4T@xLsfMxAOe8X&Cz97*_svM8(0*Bc`vC9_@F;0B{J}^#oQPepCer zCdVr29dkI*c{{KY`@XCIqciiA1XBn5gGB%Ruyg>rL=QuV4sk?#0@~I4>sRIlrfu8x z@u8})89Evqee3aa7NL8vMI`FX`egl@;&Q{4+e`XmRtqxoWy}$i9xO)6tQ#qRHkD4u z3fJB?$`_j@Q%Owvw228&?QKsK_?5fybfW}ZJ66eRz>q6rH=G3Oxvu27F^j`~^@x=j zr?(VpRdf_kv>5Vq@M}i=BCgd_mSf_pdqM z_iJ!J=P1o}tD69VS0VxA5A`kWq(P!w7`_HycD`eLV` zO~g53Kacusj`$)n;>8!?;B!7c*tN+F(}h%IocW>8ZCo#)PtG*a=GC4EdotVGJ&MeyX#0x!jZEDMNgORO310r2a;0?FdE+SKW-EAfeamJgPXVqb`MUR=3MJl#_KtuT(Dh zWu4?`5h0vH4)@kQxmbUNs?f-B@tm8sv@gOSmO}S~{gu=;Tz>H(jrl>x__?yr>m1-H zB8qWd{n&vd1pXHU{|i5ML>GIXE}VA)rs!Gum0e zm(-b^`sQNd>lAr#UoHN5m|vs~JN2htP*G&f2rgo{FTMTg*L68+1i*m0*ol+>U*yC+d)F#S zN7Oj7e`w3g1^y-JA!2+4L^O_|4S8D^-LnB51u}5=J4D+sx@3{Nx2hOY(WLZIw_;P{HYVsFLn9$ zJN-hvrBeZ;!V6sZUx|$;@D`sNRzf8#PODCz5(8l4svF)cH>!XNi=aL0lXr%(s-dgl zTp2()+%o?<(Wwo7Pr9L|zdRD)^koCqpb5n_Y0#M-elCP_t_HDmo`Poo=fOn>{>bRD zGgp`Rn!+j!k7_wy5w#FTYr&- zW|5bdi8M`z)M)ogwbt%N-*^D2JuO%DD(ZxlWeBSNrMe}L3luZ~rws-Ol);K}`z9@o zW-X_Zs{WMQue17zcn5Qh<$QBGxnNaza-wYP7O?{TD@lUxzr;=Y`vmR`;1| z@K2ukf^m|ndQ$|-FZw$W_6Tn6+yu^84PVgwWo^!cO==p=esEQ&`HO7@q*4@3y?%2} z2iy7{H=DxrX3tw;86+c}m&9DTHn6;$S>8f1*QMtC&DR5QZpmda_a;QqJ6{B(|K#Bp zg!!*o_9z-J@x=gw80ehdWF|2O`NdF?)J4!)Uj(X1f&|0>Lfv(5m}0g12Khi;f{bSc z@{y>mu(i%bR*@j^r>B?gFx;Ew7o#2!&VtZJG*1zmZ@j z&wxn6Z|AQg=Wh;uJCM8JieaJ7e-G$1|B?EEC^OWXMTN~zM+V@@WDWQ~EjgS}pd>*TmIPKJ$F z(DKrw?Jr=3LnK)bLS;7nuj#sUqkq)RGdH|U)S>&|M*KIu7X0D46V_BAC;hOsk)o(g zY~RKdrXJ%rSydc>&Dchv{m7y=@+X8&)Pnqe?)pd#h7_K}nZKAm z;U|6{QE1ngiNyu zAgUZ8`sG(Ig^^YhAQg=S6iZSqJpi7u9y6X5tC1B3C*PET)U*DD6n@Da#V;2D zU!y)Y#w6V;lvbp=YvNo((Cp>eYUF&atrPrOmzr%8;~R`dPzz^K_B8mF4Ek>n$;!6` zsbS;L1ISeIrbmjzY?4MyL)xw7Fmd7h{-k_Rfrh#*MNU6@A&5lt10@x`ee={1Bd-xG z-ThZ|+8hy{5QAp)O7Ym7Aj9vM@^J8XM~)vRRd4?-{3k6*LJW)1E6HQCL`Xu+Por1q zYjCgt3C1h$yl8P45@2GgcjSjk+vrvDa9fsaFRTXYDp0_WqX(Gsh5n^B7X$EpA1Yoq zsON-nz=JSWPl}$R@RP(|5ex^I)>jLc6z&fI+WUzzRtywEq11w4NXP2Y(*Y79Ok^p{ zGinxM^n{ZcI+qgWHQzB92>)d003^+8D0%`U?EOZVKYZlIVMymbe1=)bQZ&FmqyiEm ztYj$$|8rl-(5d~Oj{`Ul+lSBPc&OFoxT5~UzNJFv!?_Z}3uFFDu}6Ff2Ktw}F^?-& z%8h5~vXoJ2lq<=?vy_HK2LTBXTk5jGyq>&Y=mV*I7wEk-h7AL=4ApQ+7}EW830&rn z8sE8s|KmVwg73xldt3!W6R?t`#8^uWBJ#f1H_S$s3Hc2?PcYc||29&v4LskC42kI! zON{6AH|sMZ2iK~H)z(Nl*|sgva1()<(&zxc<&61Ui{)#N6`3uI4+Dgb;Kh{Q@tft` zNJZPR;D#OF9ch1Mm#A!cwIWq=?Qx{r6$(JlAoD!0?TuQ!#H}H1!BvqV>vtz~A#*QG zEVVYRydgyTg=(cs0oA!Uem6qXtD#ff6eVB%F*LZ%r~plfYkZxnQt_3l$L$iybe`h7 zd7k3aZzYF?5qomW#P&2E8M?5}D`rac%EWUn0AWt01kwh3)nL)H|n1VGLB zSfXUI+t-d7U$Gn&hceZ>LVlWkjrFHu=c3Y3dQA-AQNh>%zy8>H-YWvhphkLPh+>%4 z$O1~{+pmyJz1GonL85uaG@&{3k~qG3ocK~LuZ0@(z{t22**&f-BAE$%m}@P?vAx#w zp;vi+^F-5Lf(u^mpg2;U{WFZeaMrH|Ibxod`!Y4BXr&LeQ-ECA60}RL8KNff%=ITv z2yDGtWKX+FzlDRq{P&uMm~sds-F`Z>~jWrMqI~80P;DCQGXRW);?3 z0~&N$%+IvI2)#i!)a-1%{x+akv#K?*^6E1u|6As$fsje7Xsng7iGE)EY0Rcabax0{ z$=>taSjC2>mbKR?glmWCB+VIrG0jUxntl`Ps3q!Jx;k!4yBSl7Ra)%I8d>xUi(+Tc zp?YER0J3Ve&$zI~`~DlX&XuhC``)klq6Y|FT+W~eixt0VAP%5f9X3uz3jx(!^GgxgF8<54##a8p8Q6 z^vFCC#XtX8cav=E6DYCPgA6F&J`2S&-52oFkwi!wV`Bjjtbe7kA+=O-5WSnR2r+M1 zV8E#Sql2#<&Q<@tZ?A7GW-i1-(l1Dv&>Yhfgbf+lzH9;u4I z)KPZnS6xjO{u54YPo`0r%8|b#l!`D6x?SrGk0~VU`d)k>^*M8$SveBxA24n|zm}Ki z1~+4(Gxp3#3y~F2=YXs;)V_al(#?cvxobO{l zseA)*uCAF7BPPXk2#lIa!L(wD2Fbyv$<$WGVMj-hWT>@78$k zFVzx`7k{socA$x~75#BPt2AUqAn(lNk46>vp7eXL*@|<@a?O;I9>7GZC)9IUmVWOktTTdbvzy7 zI0h^h4{(qX=YNrTGc#t#clSIV>_!XjgWKLz&NC9~hZ=XmuwC!LXV^muA{c`zHOVH( z6O0xpQk|kdP%?Sy>;I54S!z~H5zq#!62#6UtSRnNMuRSF&|?k@k|gS&wvGKG#LUPO~rKMdhIyV_<^a~(z< z!#duvf-ast452!WV}Ghd_r7N=`e)VMvIahcItFr_VZ8jN7QAa5a|wD0qDdkIhs$WW zThjZEu1b)T>fS|@gdF?%GPwl_ifdG5#(jm7*+E_io$#*T>Ore^2vH|cBL-UY?(w|^?K8(8N9+Gl_?mY@1>#-q z8bN2kuCP+-f0XJj8R@4xzUdX6%jkHS8_8L}{}3`AduAw7PlbK5)`VdQo( z>w!d?P&W95WyGxr0uRKX?9I@-7~hUXf2#3P@Ks;h51Q%E9Gf7fJK#r3M&+wr0635T z!_iR{`JB*VPniN-15J~f$#@8hvS^t61D<`c5*^;LyDjC0er4d{fW9Z4)Sf%2N43MH z{CuFqBbtNK{KqX0GSU_IzD@bXV*I9V=MYXX3m!N;=q?4adJvp37#t?hycOkq^jcl| zp+Q=1Zy`lX{?QdD7COT+(3G+un~e6-C$WNlE${jYD1i5`60J}&!7D3&7}BVr_u7AY zG9KXCovD;EUHa(iCV*F+hgR4Z@0C?Q4Eb8uTW>c#IYV{X4P_sA2!6d@B)#kVeGhgf zLzG9lYZy{~7_ATvto;-Vy&pj((MIy}!=DIA0-&7IDfZ|ZB!HKKR!NVLaD4?I{(~Mt zLzk1}@ZdYLtIm2;Z@oYos%tO$-7{+JEr?z?v|g8;=!;U5ffTZ7Ur0En%3>S`Z$1Js zp`MPU`2I-@YeasC?;drk{;y?vIk2$SKngz9L%uqsAd*ec&-aE(k%1Ni4m?qqW2-{BCr||SgF!me8KPhlSxjYa|J@?KY zfGGgQzofC5arx^IRF}-alH$B@A{x2P4zzu<8rm?eSO~gedgmZV+3QeL7nA`O=|6OG zn~5musf;htcz7fP%s^RRm$k1}ppo5P_;9)+-?52=-R9k}Y zBOX0OQeyqS<-jY!aCUAY#C)<;I&gPzID1Ty!C8IC4AnMoZ`?zzV8%~mXvSoaGWGT~ z*w$;%Y4*_#tU-Fk63*^Vga~9wty1s*4zML?8}aBPny3zeG?m&<|7Zptc4pkjwT)`r zacm}qd9#|AY#v)+4Vk-(cxudFL^Eq1;3 z;HKI94kgA}O4(>lB4J=JT3)?oZnH>0nQ(_wD1ZpL5_q#jA{HjlTsmu=OilvWf}P>r zJ;$nKRrOi`3N&9DL=DRVdlifM+5`YRy=WzbAJ@4O-H&S`Wg$W?3{fclR%xOdm9piB zrrey!d%=cf?6QrNus4xLFxRh5k#^77eDh&X?~aHu``+24LRPj+4Ot$NXZ`bJKaIPLW5_DIA%i?_z2Pr`s4m^1lU^GH(sI7g)b}Sy z@R#{u%6;-x1}>W5WWmrSf?iYdT>0*YY4~~DCQsBbB-4dGL;5T6mapqQ$57#$h>H%o z7p>KdH}D5NG*5E%ArAmA#W!1g`5GSv3Dh-N5Yc|_rO9;X8Wh>Cy>=B*e3fCcL%}v5 z&Slsm=J4n!`6PW>sO#AvbjV-ReyZ4_MA#(-{8bk(v^Cu@Hn)#rpEyN8mo| zq$UXi56EAl$kQ2t(`v}?{~S7f(qPn5x|dee{~2cHq<#srtXZo?K_;AGIMe9dE@pik zuk752xB1%F7u;PgQez}7xN8Pr2Dgt_h4;?^bz+>xCj_?40JI`qM#gzIEHD`Px^+dw zcHr+p1_pJ`+#=`u)pO2F1gjxDI3+*r_PQniXl~5mm0Ko`-He}1WZyMfm)A?3quN<9 zv>l?;^}EfF-MG1$pX+A>e%_v0uYb0Fc`aML62NM5V1;+q9;ex(;r->;Hv24)LqA8OTA0zR95-%M^{!vKyDr z6wm228Hv!O*0n^<089~&bCUeDTDd#NrhaSoD*EfJ1$eldzfpj4V@Cqdyqhl`hbWy! z!%5AMUn(BgDE;`%K~0@sB_78rvpmj0&6M9L9(N{#mB~R(18fJ!l3lh0vj)B5akO%r z+#J-jyMy9!opO%|9MsIaAn`aad51y7sink48FSx;bogsd?d!y{lP=NS@$fioZ@xu$ z%1E=TH>{wbc2`>_oa@-eIOjP#ef=5wpZR~n18we!E$=JBP=XE#)3p+Ca{u0LL62v& z_5`oG$=YEOe|`Pm{&-_siv-(qi~$|`ub;1FBiSTfz7g)ScG4u@yvC^7Ky7V3!Ts*w zb5TcJLB&?xgb!NO>9-G4{V^VNN++ovCQicx7}oXs|8o8il4zGN*WFE>#Qdh4r*?BU zaT%V)IKGYGX6DL24M%XV#7o1iy&DkMT~yX5)++m=qbzRqdR?Y0J^hN6*kv7@$Kyp} z29o6KkM>!5mfZT;%aUkUBeP=R7)W3Z_i9zzrJ4yn{>rZ=yR|Z=_+;0l@SZ@(po2nNv7fP-t7BJNQLXs5mlbN0LgpTFp zGRK!e)H{_V90?&R^>K{m5xkh7C2iXjEh&lRU!56FTMV8&3jb2N#5b#6ALF08I_LOz zO8X}su(x*}5QUW&UcF<7hlYS-w#u%IPhIUwC9HN?gg}wHXzmyxGb*S~t>S%F1Y|yd zr<7g2)@4lA61gR+BscIxbM%_b{FmMPsW&WsKZiT!s}9YT+kK5wW{WSdRA`KMI_2%w z+(+l>Xju--Ke&EUcJofKld2lB83@_oiF}Uc9!^3Tv8|@`+pUct#E!et*>EW&%P~TV zyW$mdN_%Ln$BxPPdJJHkI|OxU_8nO#yQUyJrtDa5D9){sC)chI25I|tr-wor2Oe&- z{-ISckpj}X4TQ<<{W=g-Ff7?9+N(58rfNDPIV!bxgyXB@G>Ql+v2%orU95=0&eJv1 zSmOsemXSsNp+~E<%DHTT(Px(MEK?6ruu5!M!jB6ou>x!RDi{@c6HK90yVPi#zJ1R~ zaxQhpIw7_`Lohy40|&4vlTbxn^CHgiy|zrG2+Cct1~SKVR4|grI6OrLe6eRMJPNhy zik;%!ZyZI1nmT0zU=1%Tr9R?lc5QVwdrPI2RQA?@Oj1}>lgHzq5Qsvylc?-~Z z3SbMa=Y7P=yu{e##JaF#Ovj~5#3&dlA_~NeUlPm`05e(|r4AF)B~la&xcvoU2QLX& zU=j!>rFja(697#936yJN(x&NhdTO3ypy9g#+yYR@p(N3h$ZMlB%1taLelq2QSK(L! zU}sm3M4Kr$3Dsx=MP_6nB1@2>gX?!()pzc6xkp~!ciTMBXaoPl_KTu}?!%TW_ZaCT z)@gJ-lQ0eKuNq(-ll`08&lHUm2So zZZdMhLWFnJ`OY~DIFY?rwa~Uo{7zWQQ;1%4)f=Nu8Ha#BjftRWi^0Z+V zS`U51fWLF+gqftj9APxT10Q#e$u3g0GZyB7bBaPNBy|Q%;{N3n3sH6W;NZbRmJu)) zl2%y7AWi~aZfH(P7zQeVchpKn(ofw#_U_^)7P90W!yaSpfFz69ze^g2RDX2L>zwy< z%7h-QOv?+9f1?XYzz2`8A1#n_X3@`X5fhWkF z`+>j{JkEJX6Egguq^Ngv#s|ajR0Dv%L}=9=^-XCh*oCYBmk)d6n*z!BUY1%xKnkSq z2g(bW2WR7)QWY~{0q>yEXXFJaeGslk->Rb$VQ+!kB@opa(+%}yw9nXj>O^aUFX0fX7ckr#0xn|{kQiL zy&?d5rMKXP$vb;Z_-22rLb`j*i}X7nML+1htVdh1zgF+r|GwW!l)!fvJP{a%3KAgV z?h_hr4$faTNYCH);1URqAQ^&n4BxBK5ewlsqkCV(U>SZSn2(SJ0eCnc-a;^sfA{+#_E+{JA_U3x-}i|1;6NV_ zfTbLNG{XDBd=~p_`yok9nA-VxA_T?$!n`xZA~4)Pz)6`bWcj0i2QFK+vKp3JF;qDS zGdoe`%uO1^096eBQM_;zRqG1>R5# ztPtVp_x**h+F@@E%OX>#%Oz?YdYXzgLIp?dGp&iAdm< z@hkS9JU}e$&Yx#C(`j=~(XzcC+&^pCI=lFqcX8}>sPdbM!~8GC8HT3$!-fQHt7|#- z|7nU`1dt=1F%Q8n5)Q=Yvfq#6*vByDyNIDe!==WbG{yoJPUhFC-w)e;oW)`FzN)4U zU4DtBLUAVe&zSv*O=L5LROAU|7+s*M(ePqyzF54DtSc|g$8FBSs*VHVo7gArhX*L> zAO>uz9}}N;LWKurQd53p+myiD-zri`8{b}d^fobqQnfqSad&R7V93Vk^O6()9&+gX z$xu4rXr;61sjxy@18`5^tWjc^-3T?<*q2F8E47qN#V*SfIg+K!v~Jzt%HppY`f>DQ zYw^$?`KL?J8@u-%lL>g*M8oV4Tf4mE7b7`=`+#|nsRXpqSk14LK^p^53`3*~W4Z2{YG!J4&4#Ym`voNWf|x=I5`a_v<-}e}R_o z&D{m5EZpW)DnKHVem*yqo8FKoax9p#rl)WrnPfN^7`!XEHM{?U;(vDzUx$oA`RX>i zLzf_>rdebe>nyBq%$KS$_LoMveLAr>!Lyo$5xgVcw5C^6=R($?h$GL{f*Le(Vd$64 zTT<4c1|8`~&*8fR15Z@wG)f5{@BPC!x_>q*B~bS= zn(SXj%JM@iy~OJU)GO*3_@tJwfnQuhjq%4JF4W4%mc|#+UZ_2D{o@ZHXra&XerU^2 zuCb>5{d^Z6-%5({`{^y2c~+;MMjp|W-_4d0BiTVKDX_bt<@*^6YyUXW@r$t_uJGqc z_e1oWeV>sn^=xI~n|ydmC8-I+wrJ0W>O)Bc8CHW|nptZS$b-~NvZqMriNm&x{*pC> z7Jx02&k3wGTC(t8?jJVhN$1JJdWN~*OfB_ZaxvjYuCo7-vJ!;#sBpchTI%_OsobmV z8B$hYSkGVfH&;u&J}~8er9DN;N)*=P!ulqtR$j3)8JMd)X<3@}>ewj)Z?aIfrrah& zZS6j1Q6OPS%U<&;R6w!mC`GBoKTJ9+ruI4BQ9u<_76nu?ijrAdL8k6N*RqSjsY1QF zNWNR=S|zt}B7*l=;=cwup)GGEF6}ec*4?+&o!iOf%*UVtMbMPH@hb#-=m(~z2P#U3 z9mV*R)7Mp3A5qBSGRC19b)qSC2rC4m8V07Sg6-i)F?Hqi_Qlml9l4x91}cyRMY&sc zF{0{t*z&8W^3e-8DhE~i{w%bBD3PWBPFH!ZDV;}zjWiefUQ5p>+h|!mAjw(46?3a7H47tWZzuN${Kbp}am3Um zj4a;Zy=mbozsW;BIgfS%7`ktPiIc#n1nK{$lp8@`Zy93ObGa84p zd%Q&H_3x`4^vUZVSU^@&sc%F!uKLm@K6I>o+xUw>&6DX1)tN39X57U};b=n5Qb!82 zH$S7(mD)MXPs{*yF281+LFbl?l}6;bA+E}Yd5`-ccSO$!0UfI-5l^Lal-W9vFznvu zEKLcCl0$o}sWoE?e|XL@xn{mrD}@f?S16()KM(9{H89WQLX7A8ZLk~g0VO@({|&1F zcE$tmSh~pYgJMVEH9~pek`?s)Y|tPFt{*2Mxa3@J+Ar#(&{%5Z;^3?o$*<0SMz{2D z8wHppJM=({%RZ~?T<^!m&67b!8iG?HZq}4LSdy^!oeN)Ef9MVo{X(%H)YvHa~x4Skw9+Zg7ls1 zmu1bSX;%^-gc_oC$9=ISVy}$T4z8ee!wto?b=FvSy3Kq$UzE#j%4Fn@rhSmMUu*N= z&`kHi(wnRO4r%u8UYlbG`mSzdCsLF%ak71bUd?znsK;#|o!<+G8uN8Ik5l7F3a%Ge zJJ&3_{oto!lqhH8kr2agq`hM-g;zeiCvQ|!Aq0qkfgUfv!UQ6eJ z0N#Trm0P?4r{FA=+sHZFE2Yb9@u^0~Hsy%nJ)lg+AuYDtXQ48iW$EYwhP~5c{hbEI zzq@9A(jY3gdk)1o)Fl2U&T?1JVqI zNilejdTkx97{$5CtqelpKX=knvv5j*r?0`7OG?92@^b8hV=oa1knj3lJmJtRoZj!9 zEulRsaBy`tqo(x@REte64@EkMiqnX?Sdx$1+{XQ(P4((_okU-b{B%)jw3@gLHmi{$ z*?PMZc$%ZNwueR_#*0BO2%^CE>v_8aj_-4OH$xyYvKXsx5p;R$I?i9w*-fPnu6fK^ zHTjXK@wTp8_(2FFVf=0wi?vQJApW4Z(4~6S7WXJjNuw9e?&9Y>ad{|Nl{LA3k%WM) zWp$w!IPP%`g~c3i;<ogd#L?76z(@WqSZ$<3U#L;ACoI z%<2_QafURHlHNA^g@@AUGk-*=qdOIi(bxFJT!eKt&mtmf$Bf>Of#R}g?=XAyVm1X@ zAEpx_mpp!-{+hkJ0`LbO8Lf$GDisskpMCdh93|MU&fZU^7uc4lw=S13FXC9F52(9a zp_hy3ChbTfd!ky*izjaQXek=ui`HHv9ECtU0S5@j_{XX9ZdXD4dJ+zL9Ff zSiz9HY+>E2V|BIVKo$Q(%Dm$;rUxs-}+R%n|JA5+=6RlWz29!eO*MyC~ zm#`MUaqX&l+*cYjDdBob>ECSAEND6{(Cd-*|>asO&@2%4Gl3n zb4EjZ6>>2jdTT|1bO4^!$Nq_bsgMyj-_vjQ)$|eRa*FH9hd!&3;JcvOPb?&4!|_+bTyvf$~X#2M^>v7!?XX zo5ZWN@t;vdk*7(`eQ;y~;I}XxaI8eVK|!%#vw>h#@~G0%CiWIX!xx2jWTat;#V5MT(X@1$jh~CLy1tJtD2pMaRe_wp>UovujAx_C7>MEI&sm5{E7% z=GirrXoG!ApUga>yk)3|?QSU?GZ@)$r2OZeklSQBsIhm29ugXr=NWLMI_I7k+hiU9 zlh{K-vobFwj@0Aa6JeW72oUu+7hPnZNmPKpQGePw#z6<}M{vm#vrZo=2aK9<`zhb; z#I51R?#9^Rv~H9_1A%X{DwoQJX>Zi%$ksLhRBo9mJubhxz>HT)UUGr_<)u*FaNR(J zxvC?N(!jrv31=t){;YkE0ZJWpmn7-ma&MtUiz0zDe3T9qFWXW`_NPspV&hp?=}RiU zSD;KV0wV2|$!fW(=gJm+MbW z)lUNJ6us(o#9 zbR&_%Bh%t2CTMPITQl=yLs^;-Jw_8oD^C&Y8>R<>0$eMlikw8|ntzIZ8HvJelu9@2 zzdzZ_f7LF>TQvHSn^D`m{&z}Kk;Rp}+<~e=oefC7VK?{zK6Uk)hdBz+6&~rRD$*1? z{XNeYuzyS7pYQt()CF(M@^fpkYrNTT?gXP%#GN$DX`bEN3g-;W9At&s@e9TsUR@pq z_sijHEo3!J>-I4QR7IfJ6|o43cV_QRSdM64>f{lI_AGg86$2`hPdF1{5mMuK!mwEJ zKHTqfaH|fn(!HO|qDOzhobbz;i6@$kP8nQi$is&nNQW~WJI=_{<$sLM6UwlDQO9{U z;@}sh`DtW~^|l-yx0D&WM2<|67T61-Ket2b1v+rfjBp}Rusyf#OF>({Uu*F7Nxs1%zqe>Iv@7ROH^M(!tGi71Sk4^r&2KA(9r z^Rvh{-~1IblMi7~g3{{%Qc0-r$jp8b1E#XLu#5iL%t&8y0t5rNa0f2zo~<=`3FHXs zVZW(q=Dj&wcR#$gIMivGZL&-mfSNneOece8#Qdy{(@y+&^=(zS2S_doT+*7aXTUCe zBe=VB5raD4H{?V1=67vn>h~k9>jJZkV;C1{t?MkZL`gKN6{o>Tyk6{u`T#`gi*>Tl z?t)mBMW)_IbTAd~$lBY5h|+z#x(jG~20Aa_yO4p&-42KRiW_29CweH3w4Axh8F6GRf5SKi{@k9>RF&!x-^6!# z$-Ur|D%$7mcKzwZRM(o%&%p`)f*Y3giYL5>REN{Kd3lN$F{UawXlfvn+uE=jfU#|Q z*Aoyv)7XNL22HJ>acqe3gl#G$nK|84sit!OiUwP_Y4umXm<;=kWA}g^iOexrh%dNM z2*+8?$02o60DG;coqLc}j`x>24riv$;CwX0!PRp+lll1m=jV2_BsK_-UWCcN-1;9# zB>Y#p;qW9HNF;dO)uerouagScI32d!3J^tiZ;7a_jKKIQ?!blaA0p})Be3nSLuvGn z)0aFlrY+s9{GKtyXaB~mB0)}D-sk1(gUq}BXyfl2+2-!yCBS5}$U_6ENme~`*Nok^hW>-H ziI{*fc=Vr}cdO7aBYX80!e0rNc65_^aQ*~nY%HYnrkQboHIJEe!4!<~_8Hbzh8odC zkXcHo@v~|OQ@XP<92KdV;J1gQuTHS{Hsx-WZMn0x&(?3Gem%OkuSmZG-Hc>%Yx6`! zZw~vnM)Vjk)jG3;fQxzHqE-wLjozKYO!@yI47QRH)IWNXF{Xa`?pVKUb3;MDsAVN? zi8k?9Hso@Ss5G(3wDBtkadpNdXIjl=A)W6`ofnSQp)hGyKbhY&Sj;WF!mozl8N~MB z8ZMMn_#!)8!w*LDU!tlms}hZ8 z5ZmqUYuEhQ@><|`CH5g)2y8RI1pL+>1^Y<9@*X~O-JEjFFu=smSz*;2lfJpntTtX= zD`)>ag{i?zEqh@_(j@pV0J~s0OEvkc0_?~Vp{uG2gQ7HTkg;8?kU&8SEmN|jE#F{H z)vRJNjv{3Wu{nn30qp@WHMkI)tEwK*D#p+zGQ@Gom$)TgMAIhX$8lK|0TRHtA;fW| zu>ulsxQOK{VRlP?9Z8$G6U)^!14zJkHnw9FVf0Cn#7G)4*@)X}+5lWO3|#GrxV@agh1^WgORiUV31|vLkNTDE)^;W^~7(H%U?- zE6*NrlX|^?z7x$E)Up&mzTzv8zicnljoZnE2b3PzEB(R0bD=BfMMMB8l4w0E2tt>V zmdXE%%SIP#n}_0hA{ZYDhru`1-{O(;H4<)-cM87QBPTQxE{}IgrqLs(I1&!R>%^0) zMUC-wT2#wfQA9N%67GuUi9bfr1{=xODvv3zn5;~%KWJo0bPi5QMD~F1GbfVe-O&by$|d5>nK4^mh6C9*&H)$q*BOvmm{{7On=bL z%DvOHAOiv^&*glMo|edC*Z&7-+sb=7T@Biycer!@3goQ^a5wQetMdIZJc;(d*}QSb~E9)yW)m459Xh_h_kJDMA@sy zevF)~sV$*Ei8!De2Yep;JQ3@(*V$H_OAzX(idfuaGC0kmL4Xw ziUHHXK#mLlzj$yLJKAYvC~US17jEAjEpbMKwt z_bR=5J?+quA=bIYMv?(>4QvQ*!-F^8ovdso{W!)i9a1bjs_Rc=^HqY3%+k^lBE@z|#dgcnVr;r>>yIKhR0^S#kPPH>^HIiwN z433|Vle|$CVYlu3<(22<*=xff<3K1s#p9$Qcv4)BPIKtB?}u8J3uLj{i%qo~tnRcA z>Eq^Pvh6>z_bCryh$+l>gSMVuF zNclF7V9qKHsVf%1O*0lWq-zl)ME|DCh)wT#zcnaF^=ZNmu|dg}(^ zP-DgJRS{If(#cX1?&}Fu!v2|{w-h?bONq79^nr)HeUH?Dp=k}~mUG1Pp-+g!Fc7>SMC zi3uP%B(8x>typwvSwG^ZeWS!?)+O!A4-2KA$icxRVd-Q|9Jj~=v+DY4P$obvh+9JA z@ZRBzz8zgNogrjN&JWwAn;>0XB4NS7S1{&QYkcNB3>(lxm3Ck5VI={rl67EpoSeWj zmPo&1#3OS`*wc$lEqSt0im*6$ynL@q^)Y02vt#c^?4ObTg-@|A>7iu!@?i)#8INVz z(S&rw?KSsC*y2#~O9t~CI)@0^SW|h6po@|xL?3dRf*z4kPnmxfw=W%GL|ETnDCtv1 z9ZapB6QZDRfF9LD)gby0BZO8&8df$E)H{32G~94$XU%#YvjQyw={E1AD)-UoT9!R@ z8Fg3$4zqofrMpy$`NpPN64GDdDz9VYxzPRN#2PifG5c8SOGO_bJ02H!eLgNQ!(%F? z`n z;TTi7PJySkdrzL!mt(0k`HRw726otVlH1+V4Xa-kcGzf=d%)Zci&h3!L4T4P?>!&^ ztM(+fCD;wC81My6ayOZ}VO>PRC&uJhqPG5`REU93^vbcUZ2m?03lD{IPH-$0G9xw3 zfFl|2l3vkO7?S^vHt=}+_#Kpv5DSH}M)0m{9Ord3e4<*;gIxpsTWXkHOsl6BgJ83y zSKQ$Z0p{so8QQ@6&Ep@l@Sb=m!~g~N*T(T)gL|!^yX&5JZs$VxkE?qg>UWuzjxL#$ zA7rSjReIO{a7y74MAdnqxZd@_Kfk%J(kC=2r{YG{U7@%l&%y(*?!Bvw;~FB$r^Jh@ z_4l6@BkP(`-@1w6IQY~&)kYN+SQ*ALJBtjaS_??+M#?OU#8u2X1~anbm|^mjs<)vO zhAM=1%x4;&&_^ayV_#{Tk2YmfDn-dG8^u*DI|i4_)Ao|vfo443$k-$y`_yn*4XR*W zM$0U_#tA{~gUi)udpGRHZ}i?gZwg$2%~cJuD2cJ6>Ig`*`qtrR@)bhGRyM3MtQ7!1 z`_mR>URE5lgl5cTI%4doIz1Atu08e>8Kc_|OV8nIMisnQZA)P#ir@S!>~l^IR~&68FX}CB!9oLx|JmU-xxF_9_fp|bxl0~ zL32j;VDpG>`L}qjCMl4at5@AQ%eQAV-O1DL__y9#O&XRQiqc&>^d?T`G@vcGpq_jW z<;nf_Gbpk$502=_#=UYzl{r_m{tjEb>1+=BTo5UifgDfm=RxEh_UFbkQf1NHi1^>~ z|7J#Afp?6YfmVtw8J4LSL2qviecrOR{R-Y@W&$)fp;*b8d+lDo^iV^p@Q=bpHx;2b ztSBWyAhEp3+N0--b?c+QZtW&&bnR9Zm7;~e*>G{!*($BC@-`ZxehqWBKY(&>F?en+d;k1&oauC`hIZDv~#35|gqTlJ^K)_H$b{h%@XT-2P9@;^~7 z=322n9F?Jq&GY{J_GWU2-3!ucN!6;I#*7yBpB-co=Pm+2)sRJmrg#%<+WH$^C7Rl$ z{P+LQ;u)R=yK+3e{bS?rbDf5pG_l--i?nmQohLn}Oh2*Z2&Vmw1N21~S> ze2RNIcd}&+jOi>@8NzSk=|;h9DNg)nWXhpEGP9tz3eF$(GDI073y!;pckxd6mS&g& zlQ~bX&&iZi&1yZ)*yK9Rqd4tHN}-7F%mB%4-!Vw1mz}1v&AdG`30`K;I1gsGuggm& zg&?Wf~n61X`?%$Ri87TS5g#5!n#73nk)hW+C_LGh(@4{zuG9YIS68CxOv3WI1u;vZMbcwK}%;v}C5! zKn%On04JwWcv1mr5}&T4Oo~iYGEtRLn zZVx@%P~5q3gU;s#b}IvB=avzT9qpm4E^5zuXmOn}*e!)2)|G6^0pHei2J`jx=^(Yw zR$WTW4M9A$VpR&LIi<0%;e_mpr-Nr6L(DbgQdq9`W@b$~jd^K}_s*eKeIf&>X^VPE z*4P;PJ&$0=fpOaY=~WJRl=A@5E%j9oDMKc+N-NBcQs8F#8xS_bdLRm&B0nA2 zp_`1P^EApnVN4id>Z{-sRZc5T*8Y5Wtk)jFLXu!AM7qGuwl^R@CYU;h#JS%gr*yu~ zzkgr7GNAC~*a~qy{~v|+KPo0^FrNlC7;}738nerI5?rXu!~?c8WkI_>`bZ%NIVP_4eTddzDdBFCsiqPDj-=Iubb zZnnYQi{z?{exEwk}{R}69}G9G6!hKW(riXSkBAt9It=)B)F%E)^+B=6Khm$x~X z*@R56zk5`AYbXI@!lWAXjPSmt12TWhkZ zJn7Q3JY;}|WA;{QSo5iBsmEYq+wN#78A-R<*6`VOC0tqn0cHxD0xqdxSJ%rgr{Plp zIEGacmHWPXhv`P+Sq4}4uX@s%_07z!ZYd~D59WDl>grSOF|lg>4-4`5J^Y_wdZ{%B z%C{%5S15gy9Auv{d&7B}CFR>Hy@)GX(G_OkXMkR!SV?9u;?U~KVcx8|Tk@))l`Z94 z39t2(sfPLH#nWt=_dj#2P?lnO{49HE>a`{sLJbC?ay(l}%S8JIy)-+ZpF}kE0IU0B zPnwOaIc++vf(>acwXaTFa2j%m6jzxel4`Oi!3hjgU_c)U>#EaKET z)=JI4uDUzrzs@mgtqfr4w`*Y6cNfS(Q8~iv@^L4xk&B3HRv3<|%`U`xYvqov%&UvC zX0DN!h%Mv$vtW+Oz z#X35)*}Bcx;O`h6`-ki6q~K_EUUoFsw}c+cAq~jE8gAy=&?faRV}qmPbq#6!eGP55 zTq^<)GG^DAa8%nTpLa{ODVc2^GX30Ig6@Y&ufnJ);Rusy2_7# zH3qW!4*5q>Uik(Min3wuo`Q(79Z8Y$iIz5AXgiaG{QO`tQXbRE*Ydba zboR_y)yP%-ad2O%hvxj?C_;8=qgEk*IurP6v1v$Xk(LwxCyoQFB;^bSqYK`pq0B_< zLXG`rV^(~_t|R6V_j$!Cq;Oi7R{EZld`whDoYO4$DL-*RTE#*;9MQd-#6;SW&x(IQ zWyShnNuFQn@M_LV(JKbto`2dQ%bsOG9aSz96;<;+vuumo%xSd+t1Q|4?bq3YIu-0>Lj;ReH6wo@G@08!fS< zAh)XwB0b)7%LMhEnK03=_=jD#Z))Bp%fj9et;hbfI~I2d5!YmCJ=!(5R%UvsLtcCD zF~?>ad0Cxtba}Q&ZW*2r;&E5}T`FCH1YNuAs!jdSPPOaaIPXnaDh7||?R5{Yt`E#M31{AirOOi2 zsji|Wx;}y`e=hGuCR)1B-G7|;Kh>|NbsK_J;YQ`!_*uoK?9yLlXI$WF#0syg^1U^% z?KZALPx{Nq=2$qe1mCjeL+weEH;m(2@+WmSY8P#_Vz0qx&>@xjjb_~LkdK5K@hFjhi-H)LgHmZd0G0)+xJnQn?_d9U~n?AUy9$hf1{ zi=@?1vIM{Ly}Xs~&Eqw@ZNIQhT*76SC-$R&klFvJ|FJ=%xF1QAFwxq$?U43??h%(J zP&&x@Uxux|%Gx=N!B3%>c0Jse3c^<~mF^jn0Z1NC{+_bT>x?F>O`jJ0QTbr*!#z%# zuME_vNbOFtZ*`pTb?S|WTUf=dSV?+Ce-@prPEauUxJ@_lX)MqA61hu04!PM07t;GS zjNBb9#>Qm*>7b{`H{zkP?Y)NHyR2s^YP=Z?+Ex3t2#U(uJW+(4c8N@oYrMG^W)iA; z1Jr@p1|u=hm3{bZ3t8p!tR3AQHNtB-oRzi7S$6)WQOkR{q8sy9mw3BB-sC35(7eOc z-BoW%Go-OJp(EaV3EJS8bmqD7=uKux0;+&oQw01B>sr#l?@uj?y87$6=Hf%P2LTNb zr~FfGnMs%&)^3VQ{S@iXYf$<}r7_`I*_R5cHYU6yfB5wPdLU){7Y{j&I9gHM-}qR3 zFLTky_nTHLo_cjiO-91_N|jrdrO@yG>{kTImLh~>jMjf?1CwAV1q2hTa1-?_;%HB? z6eRaD?$@w93w9s*yMKE#n4SwMAoM5{?5;zd_Wdc%KT*qn7CsH{cNF?~UE_M>3w8ww zZ~g5URurMRMWoJ{x>wuH$7&P6GVs~IOG@#OJ3yE5&$|h-na+3gg-eRE@+TBx!Y)a4 zRU4p2Dk;SMa>T^&L6cgEDj_dA8!^t4q$H6`jgFdut`PUbPKCv8zILiKzGzx>PHLp) zuxFV!D1^Q>PIK74Y&IzcM*~&pSlj8vr&)M`;AP+p)ypb8k_voVBgZC5@;9e(alfC& z9UcCyi>Dp5d-gcsUMp^>k}+BQ{%K96*vuXoMVP2F$5V#kQNPCZTGnUzHv%8zSc*s0Raa<`B$T+4-_Z6J zm#Qv*WF_MnV8H&&%U7yByo;*D6Dd|XU^{dm-LOyIazu^SS@HrOF zrQr|E8$lFH(#YH}%&cxj=ACZN=0Dl=&YGiwoE~hP0>9dZ4m1XXVC=Rq-U!-Qk`k8S zix=~=S!=V%`VDwJMOIRuOQciJY-w8b z?j4mXM)B(JhkYY3+sf_DV#oi%go2LmY-X+MHp^RSPcXl+Ea}*wejc5BRAZr$v7Avp z#xs5kVJzNQfBJKV3i{X-ZZP|LAeEW_iOb{$LV@hG&Z(SP5a?}dwD-YJD+u{?o6{~` zj{1Ne8^zu_&Y*<%jUo;`XAvL7Y*vz1F|a#fF*8izO%g2?teT;@E_s~H>;yFqse``x z>Oy7|Ey|mm_ke68iKIorBGuLRiC&2zKhCia$*zN@C94NE#j57mkD zo5V`_F?@~KTQVr_ggorI>jSr?ZvL*0R8b&G0jy^Ju8|D*psdA@bX7^TFtG24+vXf; z6(hS-20QJAk{$7rz@`GF&G#i-$+RHOS`zd0Z|E|~@W}h_=Hp5&QO_T;-7(hSq?zyk z@+oDgOST8b=II|LSaMxD3w1ui$qD4w@S$%nysst?o=xKq!L-0S5>PQR7*J%#DM=%%5qs6ERhJjPgsjm820A zw|E=yY+sCosyuYN;nx+FjzP?nORYTf_eSr}&tXDoRMz@JKhqor{{(T0CA2NY#k6iR zzX6d{{dRv-$o_8-ey*b3XT7)_ZIZL+g7(x@fbOujO`^w*;?%T7em<elYU3GI)3jjcK5uN zu6K{h$V)wWd`L5RUt(NwtkzTBOkoZxsKGiE+p~2I&m`D$LCIuF#gp4$!$*r_(;z4C zg$nCY?cVW^#p(Ja1he35!g4gwkcH=OFyb?>>`S~saTd#13BZuz{Hm<}yx3I>?M5($ zObNmFceD3jK?>M`7iJ?!h7kPQC-%2M4j3c#CN-NYggKTV)#;2~nf%q~A?~uGUeQ3~ zZ-%Q6aM&;z@1W=ER9Nx&Xuwf3XJPh!b7Q8qSs@HAa>gGmF{Fw9SQ=aK?Tn=ge=b|q z8A{V?JNyc=)g#N0A-X(JbvG$Xsd$V}8HYDviKZz8i(o+}KjYD{7lMCcLWpbe=%@<8 zDVPulpqL855}1%LfTAk|A7MZqoo>;4*~ywWC{a-PcfsnaZiV)Iw%H3GpshAd46>L0 z*@mSh+#i@cT9~j!T343;tonaduXXqRCCc~BtC9>yp0>wpa_;!~7_FQ$(RBr%AZ%1cH& z=E|b$QpEA8>6jBS)u?Oar-cz>*wWBM2+*$mz7gKHQ}x0kQ549Y3uL`n36Z~ViG{ZS4d{GI?!OAN0UA@vgz^dtJhTC^3Y<^dH_AHkyvS>!I?997q)vhzy# z!w2z<&Ie3K%o)g@VfTYMM^pW$hi5ff|6+$;CveMBT+~Y{j>%)0O5D!rf`!N8#e@G` z&HP*mRGc5f!$!#Qy||g_R2I`{SLRhUB$J2ae@B>I0P@>9D60NDcJ0TZsO6T`IF2AS zjQ*%#gGqS^B|A=FQY??rqcAcKHO$u`!G^io5K1PTK+9Mjl6zrfO==jy0l|ja+K_yC z+E=cGR?X+`SjDoie?nX!FnUN*wF_GN?rDrxZ4>Scrfq3AC~35wh*N>*34 zw(3_BQ+f73AE^m41A@f)`PUjtN=Wz;&uhLshJ{f9iht&H#%HGav z781wKopUUfEz(r>WQsoF_}SWMpb!4I$vD(H!dn1W=LSHdv8JJce#tX1f+>KQ30l&8@(ef68r`5cs9V?YQhFgEjLTdj*QK#GR^zT z-_90w?T{iRK2wRc9QMlrjQY;$Tc?)gZ^2Jo?`4e5H;5*D<8df-!4?}|p7hqg4ap~) zTy|95ip?rCR6*0^W~)6sj9!oas{b?-e?}Dw);A~Rr^kv^!Qt;H(T$tW)75Kh_2J8E z$G0c{7B!br0LOE*t zBhrYW)=8Oauee$99zt*~!4~)Ny%7r}Cj$gh_pXc->3z)vpAi14bw`xcKPhHCi?lC4 zk|6hQ{+O-Xm-ZFj{A1Ycqh1All0AQJL}-FK#t`p}O+T7Yq?QZO?0DjeS2s`mQIWk_ z_5at{)x>oybFF4^9ZIezdS%nn-T_&#TLk!j-_MjoB=l-dK5lK|$A~b&Fm=)*MQQv0 z`iZWfmOF|hq#m)NHbJ>|Nz5=M!7ei6U!>cx^2_qcC5k`SCuO!}At_RTrY&_KG+_oS zRPiHEvH;hFOw!BGId7H(TDY9w~9cWc2L^?MO1mZK6#HwPnr zHl1MOGb$P0wsMqW6pw%*s)u|GVWBl{uSQ5g3gwBG{$a5HfLeL0J% zW8u%yvkGSFnBa_Hb?nwvKKMTVfzt(!Uq=Z<9Q8s#Fj`C|c;Zn4Mdzz;7vVulfdN#cyR^UkyO7r1E>~fZ}f|!_lHe2_rst1T7dck`s zO9nAKu!OAsXA6tlv9A!1KDBQc=3sY&QBozG*d~#|b*Q6D`Y$e_XQ54kG=V*ai~&6_ zE<&jS>Vzvv@yN5+mFTrAfBW|9KitAZ=^I!H?--Nb$JKbBc)V>sIJt1EI&~l;JNs12 z*Lsq7W6Smn8cqIB@CzhzX&!Yw9(7wCOC7W{Kl&^+vDB59=A!|bTtbAzh|=xrDc@Kf5%LC{40^^vpu_%Kiv-?Ip4ZpAk(Wu&zIs*_yD9gnVQRc9;x5B;?Y$9wb9=H%&1Y8F`cHaE>{ zn`v4hZ5z!$jz31R7GC=rJN_-uib48R)4KAD-Usw2v*}dByY|k%2>Y0h8~I!z9OXyD zOxc>sl`p5!?ta?%Wg9jvHE`sP!< zH_(uUlhuec(Z(OM?MdU7V1zdRuGw=s@}N376Udn=b&?y;@uyb|^+;pWe2_Lf_QS%? zPvhj(yn3DQ98b@>5e8%y!AwG3Wmo(prfe&LLt|8+52t-eL@gV?9;_U6CM1}lbR1j_ zUmS@eX2E3m`|}C{amqGzIB2dO#o6?L!bNlS2b8=`9X^`tgpPnp-Yi%Mf1kKIj4NzG zpEaHd1|8qr%$i#7$7)L(x|b3E#>IEKo;_>`R4CadB5Ux>f1GGXq142dIOduQ`icfR zsX3>gH8tNdsWGSjF#%;AuxQQc>n23awTh9(>_g9-gKV_V@KrKqNtX651~0z>fBN1# z-l82Ye&QW2Vo4jDSs+Mj*AFgx^s~a_?bohySl2%<2_Ve>qJZ0@Lsy%0% zn_F-FiKJzdqn$b6Duqih`{c?<3e0g?XM|Tm zFr>Qk;K{+)cR8;x#%y>s&jHKg>~dNk8;)R69C=O#?bhesHF*B+VY!YBT8-LXV*)@U zc4g4^)b^4d6M`+wHu#jI2u|IN+!rCPyFp{s zu0%L7FqmOk^^V?0iid)P;9YwEfDJu^3ZUNry59yOA_HhlK=;`|-p;l^38iA5R&utb z?>_)D%#vPajmr5WFzb-8jX#!oJ-zv}MMnThxqulR(v}wDx)fJrZ2Mr~$ZY6XMs$q+ zJ@a08Wsp`YdUHEJ%C_%&h9kEiwBB&~4)f84+TCpEw2T-1xZA&mRVeM-zfzRm0<6s3 zQR9zu@LQV5AWjSJ0A48zCiv2FZh|#lsa;0+P>l>lvpM*rGAt|>lYQxI`bgNYz`$SJ z1S`B!xJ>XeVj17M(@A@Ya2PfT=PlVk+XQ3vqX=TTL(%NADB)dT#xXP#V}ms zScXx0wC{VI=H{eFrY_hTTkv1~6@muGF}%Caw(t-q>{C_G$ldtVKA<*y>?=9Zap!T- zXyiIX%ZSEfTlS$Q{~H@eh@i*69|?ya?ZfUEYQ2@-3v`wI_?`Eoz(b1or(ctlpZeFb z)|HbBe6hdDygB0!5dk%;s1F`@3WYiU`Wuo7xIb~d-Y*aCA<6c99rCRbT-TlHi5_C9 z(*vI2`o#A-T?-*3Y}j{_Q{NQtH9o_+mjn2HFcpeU8TezL(vNcB_-Sh)bO;VDDTdD!_3%$Fc7Rjm z$lQL?-Kl(q=eFOwPZU)JtE~=XLx;S2@iTM_S}|)Nes)wP$V~BhF?zcPjZ6v<=q;26 zq6OZT@@`kI_Ip$0GpGNZC(&EjID81a)tim1vUPGtRmW2fEG^LE$V2F;4EIJ+#~X_P zE-3`jymipsiPfQR-T@!>W zWl}qdJ#XaSaNYI2dI-xaoK0p6=~P-!k)*2Ls=v1-|D=}93~wW zXlx?5=vVT36r5r7k@0V*!4huntN5$V??N7?bm-yaIbP(X4t*EsKRL0mlDeJsb_Oir z>ds#Bm0mLBVN9n~yk_P_jxMk;4e-GbAFF*9&(t)2li%aNdnm2sq7sOBmr#XSg1ZE7 zUH9XwH{X9f2NojstkK|4HsL!-t{w}hhQ=WDZ=e1_4E)*-_z+e1yNsx(Ad}yzTo_Tx z>!BzjwCr>+fBAT9B50KrCG94R>)s|8(NfkxemA^LE~CvahGc543M-h8t-9qJ;uuszEaG;8ketkJwU^ZakuNM?~#1 zqgfJMln`3!&OQ3HjWuGmn5Q|R0YKt2OBi7Gra#Z|>-xZm*PL)OboHPli6G2y-tO0h zqaL(B(hmMn3$L9YzzrITMUvH?@b4Q6iDV3GYa~Nl-(T#(RGiHXkijWm6gS5nDiFjf zSQNLz9x4&U%3AdQL9Lp;4Q^pB&9C;zAsq+;Hx6p2EySmk31WpU;>MKQ_AsfFB1_nT zN{mhfWuxI-tWM7HT>!)Y{yVz>d+aihOWoKa0qb^7kzn0xqq{dTg66nLi|-S-J?|QK zVzo&16U9c2UoFPF=52RZ9&7Bc)_UZiM8hdJ$V10ES6%3tFsi4>EkFM=r5DVdwxgRY zNnUF6F)j4h6}Lo+XM_A}mqDw!&m$9TPTo4lv1~lE5plLB>ibD2Y!imv6z(@!;GCbO zOBiLh{1e_ezd)C;=RZ}hOL+62s?{ar5=G6~l+iVl3|uudE3Q`+Y!XCTwF9sw!dvs( zq>S}=a{Nc3S~0HqW4eUc4!}rou-dDrG!^L2(`VZkZrJa2Qz)C9B!Nla0UYP6h?`fj z|MKXZk-QMA8+V-!p2}su)=0e1>n*^|pI&6yXEIiT(X{uQ5tU8X4BR(-qCUqJQi9pG zeaP~P7A<*&*Yr405t|hJz(t+USgBcN!V^PaEBjHmE6(1inIPax-(y?yvhyWU z8h*Fbugml06lj04#dt8y2h!Ffhr5aRjA{DUcO*e;kYX;UqIafWyq9alk_-rc#*7oa z-#z(#7bjPjF-?nc_{1O}MBz8PUOW}~(*Q(tAK9bZ;U!WaMOf(?s~b(YhSBj5ida8= z3#<+Eu*Z9aX|Mg1rVPGcXQ80?0_RA@DQ&DChS$hx{tUwF6WV82-6fJIOsYfGw-6SW zr9Or%FMS+g4Q}M%j45Q%@VtB|#+Ry{dibgtd@Qw;E^}39$?U4ZcdH644sDQMnRN6U z#|LEQGE}iSS(Aj0x6!5Si8hd2B1;$yHpGy@Ja(=*S`Nn&O>w4szNb&I>nof1*lhMY zhfL3VrSKb1o=NrfQ|8)l9Y@$WD?FV)45`(#2E2JXnMxE7ICNERsxel0YIfY~#D+U{)+dM!!d9wm2c74deZ;rzPi1nV zP?po1IYX5+LToIi;>kX^|p?ZgJA9bF}lOzGgp2=(ovYz;a=+K76ugZNgo2qD>&^0 z{7C6WVQh=fBpjSm0k@>l+SN@#c_B$)8)5=+h;Z)}ndZ$qe!)Q|r1Hek+V7i!YC@7B z+T!*h=D{BVVJm$55r=5^UI9lCp^swo@ckzA;fgk?UEio}K70@+(BW9CSqzpd+IUJE zigN3s#ikyk(DUWOMjQEX!>wmA0vj;O8T=NTbMAMm8GHscID5*n57{6(5Q&^69Qy+< zjFIPOV!2^0c>CFJkN_F5i0T}TbYNhd08!`&qcrDz;(XHUdstVK?arXL1rQU%%?QJu zGhWaaQwyYaJYI~fbiJLd5SN@gBqhuy9n4-q-MaYLRg{sDw*!1geP^o~BNOWZwx0#O zZchXu;9q!|+48QzWzi0pt}6kW2ZVQZ-t!QB2_@`lgF_oVZvIgad9yfg^Tym~3&}Ji z{^{jHwYKCQJ^vVV-sUsF*q`2)>N`(K4`h%#bKE^}DF8sA$tO3CoYvv5}p#y{6Wy~IiAhbc0#>CAzN(6%Tl`?$~ z+Ad3GydAy*%Z_Z}RZ39@cy$3@GWDW!AU5(0LU87AKnQK<0Z9VQ2fUL(6qoxCj0DH6 z`_ReZ{q2lEG!L`$0JgY)beq8@E24Af%f|OO&OPi7k0ZPC%H0TUU%g84 za>50-7>A-zw02x%J}bI^MoLnC>l!kCL>p|EZo`z1-CsbNUk8E4!xBpwqrxWJrrR*) zW78^2{aM7HQCZg%6!#ol^ojL4yNlp}EblAAsJCP)l|m8+f83%6v-*R2;a^AQN(AhCyjupj_fP zF8ur`tLxocnqhbZ7%%QIUhHXPZbd1{gmKiYctzG!?iBg1&MgOBkvldCo{bIk;7QfX z%_-#>xbh8-U-NdRMXfu0|Es98jEeG$+CDAPl2Qu%iJ^p{hY&_WVrXd?>F$>95=2TF z1VILd4go0{X^^g=+mV!#lo)tNpZ8hMx9i?#ul?y>>#TMC_TIMw2q>`mV(C-A{vqUZ zLE0uktQ_jq$-NKNajL%$RxVOc?f?7G6Bh_}8M*HsP;o-PGI`cDYN1#f=%=F-$g6VX ze3h3MCS(L*}Urj2|O2T)A3rLyQf6^p$cZmG*GyX z5l5tOZieWpR9slTQvVZ@Ck%a&PfmqA0~GZe3Tzk%8K}_XP{-J_^J<23;&Q9;Oa@!9IqKInFVpvh;MAM%2MuZ{Nc| zuRfn#(Ts$*-5S9o-$PCctKawpvaJa|vUn#P``TN2bSDz$0uOpD+?(PoJ!%!TE&kY| z0T}z5LweK%?>dLv2cGc8r%u?k$S}_WAUe1$RYJ?f*P^N3p5Asxf zSdh_OnES?O<*k4;kApHD>sE(V&v-5ild^B_NxZEW2*mHaCDl*)AJN9o@zOjriwf0t z|C?FEh3ONU{xzXNluvH-ecV5n@LHDZQ%QQU!A+?WTD8)L1(DKT74(g#X5<#RqqSqnXYcB20r` zS5&p!_Mxe(WHIu?&pu~KK6>xCzLSrAEp%on(7flI#3u})%=d`h=$H~It#&-mNjj^s{n-S->YmD$;LD}`B8Rbm0t{=07N?1d4+ z^~W-s0FwA0c_IogOfHwjmP9v42n#5WUA$$?kWW=TYc*TAP&V=J zC=6hyWpJc75^F>4sUfFIVID;5Wirz8T%SxGa)wNby5j5{q6anKm$UjRzo0_R5Q;mK z>usFh_{Tr9YC5=cV;H*dE8lY{jJNbvkVoLnCVc zy5fn%$J9o~)`oJ@#0NTfM=@OXiSX#PCtNtsjjd-y*59au0MfTb}%qoz=>;1EiEzf*4v=7#tjdz?3@@Z;WKB8f#<$R?3 zC(|lMh8Fkc(-HPbsRKQNqqwe;{qTg!tN3g>HqNu&I;=NB!@a-y>D){3`myFhyT(4c z{7l{??5_E#-Aem+!o{juGmXtCP$H=S>9Numj-^k;YlK5lMcP%w0laupU3F|(Q>XFY z%fl1m35bKCb9e1AczTA_rN_9v&$Ody6Hn2H-wP+xo#JFNasQ*45>uk=wit|~?U`8c2HTEKDxjf&!tv?B$? zno>z%-gd3wj)F*qdxK7#YIGUem}CbT>wi1n$W*cI={%I3WYqRxTkm|670Id6iDo6T zQ;9kD)mcqXhmhYcJy5-StgR4cLT$eJtxrmIrXoHQ|MjZeCj`+&NW1>>X}4#4>%7~c zYU(f2MXTD``NVgU{hjX=G=C-qGtflA->8^+aUN_y;s_oktRan@ZFOFEIF7(eBXtrQT zb+6YOR~ges)@~Xh0-{`qvFFKRcJ#T!#@h2@QG$1D%Hsp(dvdO|M*9*?H=biMQk%&L zifVNQ!*&b)1ULnrd(#!G;YxxcxZ}>SeQJ>TK}5uV)wdQP)goRy9%7feHKRdU&!hZ` zqR)z-7u%MZ$xFI_$Mt~K$C!#9evgw<#+d~mmpPw_U98jM<`!}S+)Bu;giF>kvA-E7 zz_=vUip^{@6IV=4O(#T)xf}dOjvdENj~bO$FU&`RoxIB}EXA(Jf3a{MIb%rmUGFd# z!s~cB0)J#^w=qPZJwQPN6Do>LLTVmmawarKNr#!1&HW3fv$e`H`Zu!I@;_d5EIR zY@1RqrmtCZFXJzDD#=n8gm%j58R^t(@n@N~MSU%-E&${?wNnSmED`z<8f-zXv2_oE zTB8cR5r2J&0FVW^Me1MSuQ}BP^h?0$Q?_Yv*qoM!IGn3S>Lhn>FKcJrJ@GrpN69@% zDY^o&#TiuI^|RKIo5@^Po+nL@j=M}*a=hc}3Y3^sezS!_mU?d9v`XcwG7n{A%v6ckC9gY$^|y+qM~P8t@kV2Bs;2 z!)Q(<2}0kGv}yA^;@J!&zWVavRXz{|)Ylwuw@8@+{>l$*+11RE^1J3)X`((2kUV*6 z6Bf`G$+dmqz8Tv0vq#1x>S?cO-&fHe0r6+stp)8Z?|wh!0kY<2jBEgUm_B<+JA z9&~hZ7H;9kLu_l}BsIG2Kh$w8pVsG*W#QT8LP~BCUAR8d-)==Z!kQFk!~u&DK|0QV zatt&|ACP1w*R1^_NkIM+O`Y)u!Qj?h6KQR3T{nGNNPZzsm8by``2cNL|3=j6nK{{>GMs59j0d8wv%;_~8No;7V;pq}L3LcY+S3fLxLqI8krf_9^` z6fW5Ylwnf(?nKkk@}QB(kP@Ya(d}R=Ps!=Q zE-ns%Ws+7aSB1A;lGA*BJAUBG@&NUF>IVo@K@gGxP=XH($Y}ZFHjeojQn2kVGJ7%t z%4eZ`v8=hd`wckDym)r-qBw@_z&(tc@B)1wj9$KR<$Lm7G-~ea<+W2Ydn$3^;pXGq z&xwII$=W?zV>e2&UNbyD)Xoy6yu<72jXVOb2EJ=w+^Rxt|J-)wR|GAFof5&X3zU8f zib!t$C;63wflG`_XJ}AIGZe;J&{;`lB3E7}?~rKE#-(VO?Yuj*Kdx}zT|vzYI%Cq1 zisqH8QExz_jOIESn`wTREAeTC+AW~V-;Oq?V}nb_pWYRxOZt>Q3n_{8Nto{wwCYevS!+x!`Bi=MrI4 zj~>K)AuAdhS3;avAX-z_LI5l3pPK3nVK8SM(>FOppOsnZ*y)Bhpmi)GVB zEHqy2>&I=7X2oLJc|Q86BV>15dtrG*OS|yVt=F*c0s|*@WvT8WuY9K^Di}23x+WyvIR|*8?J|`T<-}3$4 z(eTqqTiTA>z2mLPpoktH+`Y9YcN4Hsya7Oj;L65W{`|eSDR^^u&|9?VH9@*Aj#eh)pbUl%Uj4bj!R|qM@*5@FQaU3{B!89 z0!n#a-7`7_LnLxmdaX)Z{WN&vanAD;=Dhq&z}a9x9w9>BWU8U6H3g<}S#& zrKWswA~|mO1J|>L8?L@&8I+WvGe)#8`7285ofAgSrmr3)HRFh>?@Qh)^?K`Lw<+*S z3H{2Mg}@Xx@PKh5Fp}wBuYtYZGZistQ6E?_3r6tvLCX75FDhj_?qwS#w4F1Htrkq} z@ZrQCkTZ*~#-*1{Uw^5W4#&nUlju{el;{RPcoq=7Rpv!Sx-rWr`V^efz4!9g*`|-D z%qx3rsOCL$Y6I=+FNTYi(;Q6|A9J>npA#TVyq!rB9UO;(Fyd9H*90I!1J~01#<8$+aftm2TK2G))1B}SXUIpB_3!uWGoyJAr6j-dfbBo=Xs?jzN zqFkBh+Z0C>$(jdWIAW#7h#~=2$}`W0yvvFI9iGK|PqOZkMVu0Tw;K+0e5yUtJ=cDv z?=RPGIdMX``FeM=1inexy5r_AlgGv#mC06Pny z_M=ltyUT*lj~b?ra+>15p|l4iZk)Z%NKuCJ-%b~Y&yv+K{2ySl1DO4O{h9|U8P%1F zO+&-fz6!D6amK6h_9x6=%bow!c9y-9;&3r&FEq@!ESfJjqTFDZf3kOZ=E>i&12&CZ zx{TUu?XTgr3(wUPZ@QYImI;L}P(-`?{923e@hLq4&A;e%)g^gqoIcw2I4v}vfgJWt zQPXB|_JT>ch_*>abZoY;Gn3wTx zu0hs^lD48$^nX%vJE^$g+gmu(i^fS4 z4cnKQ4BzfB34egw1Jkyg@a<1f0^A$IK@y+m@x)(WiU*Ko3+mNp^VfTHxF+WGHZX9@ za!HsW?18bdyp*uHJ&-efy>9koYU0Qn-TG{ku1r6OtAB1zFXsb#;}q)r3`*d(Hi1ZY5 zh=NrfU5zYn`rDx5;{Y#ioVcc-Ra@072?+Mh%1~r_#L98k;`9WD%o?(p%XQahlf%8C766 z+f)S3t3@2Z_A0$GSEa)E!etax%e~w4d->u{WAFi4 zei`;$iNExE{c=6oO4x{wOq)T7nP>>RsH@Ky_77g;6V{11JQS!QA=3))a)!@E>lL_Ca`J^Kj@gx5Vo6oW!e1 zbil7iGe=d;=?bEx^X#3AO;fPcdb~n(7?jk~TrNRq9?>+IDKCyC)Z8=II9V+fwoj_d zk<-_>S!IsS|0J)1opelb)&GHM7)?yW*)F~VxTgG*7Czt9s}5&#ItZ?)Frn>OH53_g zxIH}|{Jds#8Ox$?u~6FRfE9VQhQ-tjsIy%>@U*9>%t_F|`3_ONQ%y$CC(b!&%6##x z+7CTy*NXE5)^q``PIqUB1$!PRNIgmul`Z{JTJ1QK;A;txu-!ME7I422V#61{!^x{h zy_eBPD~IJ3g`Bl+&7CN`slVu=mcO}E>}dKgyXZiy_W8+h+GG-(pGv&jf19jNJy*AA z{8`caV|vG%rM`03zeJbWo!P}-m7DkeUWuD*dG6!N;wbgaJnB~>{pK3*#6LME3I&yz zXUW4MBY*;(CwGwC7oj5p1-kar=g`#20P*?=b(1^KHrucfv4R)g^4T*0zS2~VdN$=D zHhfILD(?iqXLwrQ0<27iM{T#ykn)@sc#k-9NZ#dXZUR^#fA|e7mCKsyEzhsVpC739 z01C9dcLV%<^&q9GjM20iyR>PzK7r(QB^gZJgM6|a6RfErkbwu$ilxv;uIw`ldqE&w zX!UZoHo?iIA7VkfN2_9`jdhFyGAjzv^6eS*-`?-9$FKi9ukaxs!gJ=CQ)P8;(qgg_R!X^Py27%M#&Ji|EcyyRtWm#1 zUR;`-5p=v)Acmy#3}3 zLD_SY2Bd4<)H%nJi*PRhxj!su@)5T0%<~%tJ9|@?Np!7aus`t=KrVr>R!MfxkuC1j fGJuxbkeC^nu;}A4?b&e^T;5G*)l#=c-^2NTf~O#; diff --git a/src/main/resources/runtime_block_states_594.dat b/src/main/resources/runtime_block_states_594.dat new file mode 100644 index 0000000000000000000000000000000000000000..eec1d33e5e486bf4769857e2c0de4b6ef7e974c4 GIT binary patch literal 52862 zcmY(KWmp|e6Q*&OAc5c{xVr`k5Fof5+}$052X_zd!QBb&!QlYG-Qf`20tDZI_uIX$ z<=5R$)l_x&%weXdddMP?p#J;Y=|E03ky5?>!E;m9&{S46vQ`#RRAO6{Y_=edmGn&? zo~uLEqm6#Q0!*1wwf|jd(IiAp1ip_Xjs*9OCN81~TZYAE zTPs6e6kBG(&!^`eow;qTYx-*fH+@7{H1fm|{Rl?2`F7mNjBU$$M}4lfN47I7sL@`{ zL?;rHW7RxF(b9ZaqdFH>S4M?L#AK<2VTFh2O`EegwY~US!m4Xv{0J`l{K>m}jl*ZY z+I+8PM*T{Ut5)@)YJ8$vjSqM%cv`;qneL354n%hM3K+BLyAqDeUY`zGk`Xf(&Q4f- zG-N7N9JPIm-h!3jouO=gvufzPWyCNL3+cfwGeCwS;zmnWftP_<2s5lnq}M>{mT2$~ zN2WZbhnFGpVh{^3Wh_Q@Bz30M!0X14j30zE2!%TOLg^t3#SLX3Nil2;QzHWP5l@N~ z#}6HP;rz=*DwNH;?g4NSswAIo)Vr4m(Pe?rST&-B$_YlM464=MRHdW=D%?{~DYun) zKbr1w_9!Jht@Z?n1`$`Qc`jMByir+d@5N+`EfWn3uq)-c}H1|qS)gBvHO$IPX7s>CH=luXt*3k z#i6*R?OICjZBBsx*s5?udl@$zF~UrnZ}gU=N##KP)|XLJLDzCY)Y*H|;L)Yf5|!yB z65+6rA;l(_3Xs0Jm=5BmlYhL+>GAwC65aUw!5Do`SelXxC3$lXBQ`Fg^Aw9lpOiAOti(iJ zSVA6eYOq~xSV9qRs*hbmSVAsus#~@#7LAOf!3EbdaU^djEe5z7^1~X-+;6L!)8@$o z&k=zR1Kbb!VUJ}Vw$&YE^Muc|g$5!{O&WWS7th3sfCbgx;%oE~6!NkPXZYq4o5yc( zTG??qFeT0GeC(oh6y`8L?slwifaN_Cu7;2bt`&Bn#?oMCt!R5;wCUX^Myv$1YuSChATNDkT@ZgsH5$$Q;iW8g)9 z(5Ul9pIItLBmd^i1@XT+0!U)OW-3nmn~f>6ROg=|#_~{z*|s5k;Rd`fp*PG@>k(f? zAG;7f+c*k`wyy{k4Lb_^&!v6M(H%nq!D4jXxzQa90^*s<)?4vaci@F2UD`aQO&{^Z4bGnG3Eajd)1i=Jh&cnO{9> zd1#F+EY3)nL5g&^!jv-fg9x=0Xc|(#=I_Zi+bgZ5!8G;ftAOdY|QVj5;- zNoKBS;Mq2WcAC3AfCyjI5D5+=p)QW(3;g~**l+*OmK0y#CT!SOH?I!m?}{eIp*K%L`WGvx zx1Q%kb7$&nkNPP~myjs0!Yes$Zl7I5?dKX7w)F*iI~_)^D^JG7yNPcad!}`|J?p2a z3o|Fa%33Re53n3R#jcL8_2mB*N8U%-AK>dP!VWME^AN!>w>01x|3i$*NU`64Rlki3 zA4T5CUAQ=Y{MnTNYnMSNjqyRNcux{_-0rCI8#II0PlKm{ry(oS!^iyqF=8+6uCozR z)_+5_P1<9|9l{1RnZ~DoUP2*w@n4U3y023zxb37%W8@zU8^Ty#er?jCPv$B7C`z*3 z^E>Is7!R|0heqBOY0VJE_iBLwxXAp00CXfW>)n=u!&xoo-hAE#morB-(r8>nB6Oj( zk!h()d}|`@LZw$wyLiGE(}NP{&nflb_Ww0aV_#1;ngK2yF=;F>(Hz6 z_!t{AZ~Gf^pZ|Ok8?j_7UqrTkc=_eWx(LN0CFS|z%C^D~modv4Eyko3t+kF?jaa^v z8`Wp=JpS9Sk7Yc{st&9P>%Xf#aa7^E*l*>yR3}#B^j6xtZXaxhVWT+)=R!Y{~n2|wUuHS7jg?#!r#kyC3wpTYGfEAxBSlabbM`1!9T%>Qzz zisn})<#k!~uMgNcGQ-gAR7ci!y@%U&lCG(_Li_47C!|Qb(VRuXB;JGu7+~^2eBMNp zJE8{ zuL1)34-T04i`ehmP!}FA3IS{!iv~1S-w>~#_aq9CS{`ahpJBYXy>g4{_ECCdI(=fa zQT1sSAykadK#k+Zo||_o5tf?E`PnXzLWIvip5um$ns*13J|R=Y>HKUNDE5YoH9zg1 zg@`!dpC9tritqBqn?6LaIi%AnKujp>w%vrERe@Z_mx5QCY#ad9jNN&?vFCjlX6g|5+aN+HxsU-V$Bixy@r5OLk7_!# z<+=9N+7JFV@oL59xOsj(62j`d&eQY$$1iU^_TaR8{nyL{;Fagamww3_K#aM0;m)Bi zQ#>TUewZy$rN0PQ@vK){w6ys;AbLa;-!g)mzz|@`uYz+LYtNON3@Yf{6>~T zgzLP@4e|}MD6Jsfz5L^)9pbdCl3aUNmPc%$Wl7lwEfwK{8^a#YPw5@aCw#J4mtp%FlO$tsN>P=eOXf=#uKdh4o zvxd=02TETO(h&TuQFzORw4}FbJZ6{L=VWV0{=Uei3h1tKl!Qfee(q-Dh1 zJcXrGrV-bcoJwd&Qk!JMbNSae;A;#OM1q0DzpxL^np6fXuEw7y9gaLFoSb@J?h=%U zjZ+cK$TqKLNr2jvQ7BQXb+H@J@SO@WK+j~HOOIcX#K@MS3K&&e43160TZ*MeHA+U{ zt?6V&mE4&^oqCo=rlfK8fLpIMlT0}vLE~3+Fjk6hHo)oB;MKbb!BkUq2Nb3iew5s3 zfYYc^u6GfG)06RoN9B=m3-7i@t}!4n->+)yNcOXYNljg51Op+aDOuUf`(6lyk($b3 zWY(VSxBs8!!2~@|w_!9Mo_47PQ#IY)pZS(!56ETi7suWP0GT~hmxnoa7F4gRY8Z){+VJ z4?_Ty0&qdgbO1m)02cu~SZnTs@{vQ%X3$)kx)BQ58Ce*^*?QPjWY$>MGJS|{d7#<| z;Hpv`*iIbDaSv`oEYUd0AL-1|>F{QpIZA)HP*RUqQwYeYiTJ^mOSE4PJ&JX4zg zEltZn#H>$9kGGrRuK_!etZ{GBf)Cw%68K@2wCiQrSc^>$P7&UjB_pHl3X&yEz5tcZ6urO23KKHrFdHn8>SJd_X5GZk`{WnO_DM zV^H;z{O;P1^`Zd-fbZP`)^-UyVL~SAZ{Qo*2n| zTa)%emv^!MN%*6gJgmtIv+Tkr<33aY-euhBTRu$AHFFw5s$NCSfulM^c8ovD1VJm) zrZEtSBL&9{9u(g)iHMI~;LWwLM>;zM$xVgXFOI(dWL_esk-0^u;pAc6gH~it^nD{f zf*%hR9M@ZXAL&j$-&70tckDl16*p_Ic0J+VR1^57i%p^GdGwCjH~R1@s*x}zUw%Bk zhiYSgT&6lK+y)mOY7GEep!zr`nY}G^v zC$8sihu0akeb}3tJn(AB5n|<@a4Y)>;V^c)IB(b-hF!y+SeQia)p6@zn#Dz$AKDzw z;DkiN1e{5Y5AS%Uf?%LY8_~g7dP;Tjj!XE0#Tdg3QBqw>H9pU%U8!~cqd4|Q?g3)n zX{0U1HxBy7U^lAhkW z%&ObrUIehHwfo!<4K#|85Ic z9N})%Eaw~zUL}iHq3aTh&oR>U;>0NQ2jhM;iKUjeB&X^}IGUuSMc>I=$PrAZEcBKsWUu!DSud*Fx8|VV>zGiv!y}NTVvM?muWOb z!c@Ed)YXrxoPmi55FIwNL3y6BH--M;>QRe@({dcLmIl{=E5v0w3UCboTzOYH^8r^w zHZw#!7)wH~Cc4Uv2{4XoxAL@DXzDH*yk<@Go4uDU>B)ILIT)RVV2srL32p60A zmY|8&tieZdsnI;4b(@a^VAAkKcO1ZZP?k)g{+!yN_dX^~FWYQe4mgOE<557Y2H92p z?HYXhbE@S*>32D%u1&!qS#yPG8>?INQ!%q8FDnz!sJ%N2`}Fp-O*=B+NtU+g$wg!j z!`)7kwix{%v7{~L{6{=#i}hKb8&{p6=cFz|_txpOdCZdwIYoGJVdjU^W@+W#IHLF; zZh=1}Qis6>cYk#-JC?%v951@j%2U5nfc!!Hy+c-prX~B0>qcwiZIc+3|KS2!p9|vW z;nn8Ub;cGRk6`cK^4XPW>HJNE!0^WmO8rc)Qm%KQzdo(;-W<*uY~dnY47RM-ASC3l z#z~Gp7ICFg2K#$EoitVS6wspWBsq7H>@Fw99NA)bo6H!-z=G{2{XRdJDL&LthE~k% zih@4mCYM@OrL=o@>{Q8&Eh^?K+dh6SoZP+M=3Dexq1t|f5!`LtOww?^3-w`$A~jJU z0KA?7ax3BYzD9HYb5x+AYtJP0kP60mu7hW{v-15EB5|wE80Uy{56kW{(PiZyl`%&a zms{wg#OVl8WdFO!5<@)>XJXN*k1dypJoNkfvlgc#HMv;EINFabo26{lFZKIpCoXwf zrKePvHjGX8h(t+F9<(|x*32HWmr zuF3iwrS38f<|Gtc1jFXc+kRIht)hDbe-JkHtOgr4hhPy$K$Uu$uCj_|e)uG>x5ye5 zmxC1Nt&S3c6^{#DcNAg%?!7n`c~+H9&9g)SQx_IYP!zvt%C?>75`J7w5@%A%1Y zEviYxe#){@I0)|x!E|*^e|!#O+{Yr{2|Stjx=+|?K7~xlxTgL#?2lzg>lE#hGfQ5S z20i!U1j+f`%86!8kDT1(86@nHFqNp^t)=O_`1M-GJOvRIKpekFSRLv6kNfLN9Yrcr61wBDI4^KSZvU$ajp z^Cw&CN1*s(hveI>k9&(;_U298?YflA-_US$g&Uiiw)FPD(A>45Lr&+~FpaoP8O$+j zdrtps51KZGUSD3i(F#{B3Re91(d9>m|3FH5b(!MLt@*HFwMmiZ^CWn&t-MWfe|w9% zg*Z<~Hajt)-yIv78Jm0=`}G2U`Is+F#6f?bU#duG%p}551*HagpwW<4DhDt1N{mF& zhA1T7L{`H95Buwp8RHl~A_aMNhO z?{Hx}hVQPBfLhA1rjP_QkwZW&Zdg-E;@mv?upCr*eAFQa^UlV;*d5LV->gk@y-byU z*#~kcUnlS@Ggy!U=TdW@uXT3iFTYRoLW$eAGj!xp_GzpyERkZGRUiG_s%SOYBcwfv$yDTE9 zWHb$UEN)Y5YKik0Ye;@Rz-TkVCc<9_ChJ>7n#R?2hx}#-)m5dKhShb)09XKEYF&3S zfXM&`)pchB7zSWsU3U?Ho@*`OIURd4kHwP2^yQ1ucFea{Cy7a>Lb=-t-c37C-J&C`I{MrQ3BJ&g&7Qb2i5A|Z-f2>WIogyQ zC-AB?L=f%NtAcUL?-L=>f<+&SkGEc- zWS2OyS=lShkV3Sd*hX7@NoI8$eoek|SE*NNy>O2_T;oPE4a-qdCZ=lmw=HJ>D}f4^ zysF`w_LzN307X;{83EJh#uSW5A*AuN{a6Xre2h$GiG9h=qspoG zSQSn0l6RV{WP8W6w7F$==#*6XKnZhgZyNlwZd*j}TDovVzFO;SYMfnL(KzdeZQGtg z@R&-PF(ZletkJDewX{a~K%aN$l-XFvN;!7BuA^DH_y>wGQtSQnRd}?(PoS%!-m18<-cW;bg6I?Y|9ps{Gk(Cl~ZvFt>C)P?F$ep<8Lv zTG`m~L4TgjG9A&!*((;5{yOc05>@GA657^?zshM7rt;;764EE61|L3awDtbY{rwbo z9b<<@$+&4aGTb0su$pfagoNsms&!F?tXZ#8qPoMFukQOnpI)eCF<$og=TlyC7`hH^ zvh;1d+wfs)BA=@io}_*Q%8nvAT7I=Ad<2Lb?Zbc=0hF{Nc~q{w2E4Q~c?Hsak!lVu z{I9widrf$05P1dqz3a>;4RSLST1_XG78FaQ$i7bF1j)GI8pWeFZVxyqq{#lKLF!m? zM8lqi8=DIx|sx`qNXr$;q2WX zNv?Wi@&}C19Wq;*4PC{C3ulTYS;QZP>@iGX8QRWjOD;0}&~1)?4d#yk&uul$s6WE! zyO0;UPnf;g%O0o=U=Q2QO8;{@`&Ash_ePR0H0qs?LT^oUS$)socI`_yN;UK9T5pnexo zgRKjyX+LU4GXXeiW-IoOgobQ5?^zv7FWHp(Ag*3=l?$GgR(iQDo5-w=(0^-{`ig|9 znInamBNqM+_Vp_lVdl5I5S2=i>W|t50Z39i@M^^3=AF%>uCv%byv;58>0gr??q%P% z>KH)Tri`M%I#49pQq+d=3qYmG`${&9@|a00R~^rCw+{QsmO3_!{Cq+>Bc`ncM3GW4 zm?^Glc!L8UzHj#y>J^VMzQbR#?lOy7(J~illhTy)}8U z4TUNzXUcsld9etff+_cd+vZr>5JS)0vEu=elI1U+~}62Mqj;N@>88u z^$w;>BIrk_sj=>A-EVCJ4rHogtY4_w4*i*WVj0`c0tXXdpn>|N^b50Ro zIMvOGDJD)`X87fE3iDN9uzN)v>)H#yWLpXz5)x6f?v+`#tqBK_2e9oEi2@O;^@CxT zn?HXxjloG4(tQW&%y}P_^K_8^Dai!f!sA-)!TQNf%-2WPF1Lym^UfP)3YD^-(N6iw z%L^mb>V=Xh7{^aLZ&NrrZ?+3`D5e?p8JkWOnx${v5C7|`!-m#XzfXnbZ1+fOd0r;g zGM`Qc8Y&)4Fh5W5co1#e4KHqK7g{}WfmNsJkXwu&(`1PX9f6*+x0rrM;Q~K?-{3NY7{`BqC1T9=9-4_0fF3{LTIdp-xTb}+xxrb) zZQ7-HG?PUIRDSGTWxjL}#)Z~a4{6hdY@ST3+tYbA(*l7=Jb8^>I(F8VO-WBeDRL4_ zc>g-wM8n8sZvO6l`~qiDhZahDBAIXSY7oYQ&fx9M4QpiS01fyyG$EJWSypnypvDBL?2L=ZSv`}=B z;#yAT@H`HteZhIaYy=3MpQmdii5Zaxqd4tiYCFX+u0 z(2_CG->!Cy8hFkA@}ULWPG;dpo#-Jhm*b(05Bh+ltWN{x^cre-c4a)nz8gfa{waDE zw@t3|qO+;iTA+$AMcNM=ET8{%U4{U=zY{-2gL++LSS?Xxl}{m~*FxB+&i0OIFRiKp zCkwj8i_fOoh!tg={kAKgXyW9mrbBvj0M2>eV^3_Qjr_%R9uJ;@U1W@r$JpRQ9>yP4d)bAxka-{O^cmWMiD#rhb=8?|`ZwcO^O#}$D1J%r6$&!MCCS<@E0 zcA=>&ZPc+m{pnA8Jd}Yql+H~el9F8#&K<`L)>TI}e>;Cqn}{QhI_1{NgIe4srC6jl zkf!vN**y-&u9Z$m3myKNn}w4ooQl@YmA^r|u!LykVdPdySI&oqUam2LejpWF4G2x< ze)>WCDQJ6+0cUeF$bn}z^^ILzSx0>v;Zd@F<0@b7qZmnG9?mG9K+|v4zD)t$c)_Bl zg*Q^m7#EarxREm5Cn={6%aI1l5!ypx_W%N^h2=>0t2QAr?Og z)a8&g;{JT1r1&PrUMBB^CdJ$gew+)U!1~zuJkeJuHO@g9vrFkb=7v<}>a-t>vN9?K zAwAnrQOA~J(b+JOXIBqK+_R#|(#z?d%`|CU4RzP>LN38)cHpJq_=a^x#{j*~pxVk( z%03C=OGF`_9Ikjw1b*~xE*FZpbU-OYet*aBDv`<$X5Ph#H6oE}0|xPHd`shyQ8%VG z@;|*&iMx0!l`JdKPf7Mfyd8^w)3D3m8kcqV=F#0%noB>ZMe$K$W6Q0&2!f1Sjli1U z;~XN!57V&s-Wrcb27R*`xds~dZ;fN37kqG(uh)X~CZ`@FllO5rL-YKpBH2Ryql&np z3IEkC1b&dQh2dp2uSJwhSb{Uz_-%w4&e(vw(OuL}x+HA>tEPh|SN-SrCvhV5@W$Qb z-7zeTx!bJrFULRDt%W79I#?VvgN#03H&}PL=V545Hng{qPkSDW6ZI7%Z|u?`;H zFGt8gQ@!gW2H_eoFvBNq;5y5;_*DYO23UjHymT73*hT1igdnNWHrkGKIdC*UrLfty zzt9g`)--1Ex$!0BSqh(-xwo=Jiy7EJNXCejM#$*x^u$|1nJoaV5IA!By_5Tlv3%#}LSb^DMghZ_1}KJ={Y6kXI*>k^dvb30R09qgp;^RQx^B21eCPW@VPm}g zaQS@a>fhdEz{onJAL6q!pKEG&omibCJ6g4>LU0g;aS*%|m3M*GvH0nA&AoCJZ-1EQ^`&o|?nG}cV);s% z52&&I6w3W9yX5@vXH9ri-CD2XHP>`^@583&=tqB+v=ux3x9IJ*=JIkU;J*H^8RqaNR{|g7IH{t0*(dfy@HsFpw zf~Z3?G|%G5Ud69I&>O%%dj>2jv#09M4g~aekIb+UfUB*HkFX3LKT+PQ9=DLfs}Y~}xO10?JJKU@dNs{RkxK+lUHs{UXzkNSOexN zEv>sFq@vv18cfO3c1)*h8Wn??qxWQAMe>A77qb6Ec-}gdF05SNm*{^FAG@JcV`?#7 zM={we$F8k+ON^95?pylGJeEGMmV}`Bfm`bm#p`PFzrCunT?Lf1d%i>15n@W+MnAe@ zsSD6emP$^k8~um5C%Luw zup%yN*Iru8pq5e{E`0lmM^S&RTA~;(e8sUxQ7(Yc5is)C0@KyX3?t;?(A%Eb`}XB; zO~dm16a*(+>SrU^WtF;B5DB~*!zp4Aq6D#=xgTJMcGH@hcuB~ixl#G7^m+2TDbv(^b#4fp0yjh zs7YB)Hz#{HR0<|fH?qcj(i&$!A^Mht3{^qF^ShlRoNA)TsKh%DASc~z6{R7Q z7q?130Sc&I3CU(P^69*{i`Pn{wB`;3s&+z3K;zxCc2AlkhOL zLi)ugLak1V1R609@@K{rn+p>XuYb19fAS@R#d&J zr5ysm^j6QOPA=PU1om=sH(f3pYXml7YqwP{TWJKAV|!P^9yAa_vz!}u63gq$kWs}g zXSx2R!LieGIPqFeg-8H=6r=M|vVrKOWXPy4w0u`&LlI6#KPf#pp4mlryGET>hh6D% z1b3GaZ|@pU$M|B%@1i>|OOUk;-*0xNCE=ZyDr)X?Swa5OJt)t!LTOd4E;t1#%CLvpB3VF8f^nP#1_nLns0MH?%R`P=06{Ay#vvsTXr}48V z*h}*V@QC|;R~r0t)S}h4^0b4%g~IxJ!$F-K+V5?1KoVh3YOaf9H5

    Y64qvmuZ=TBjZPY^z3RTDj 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 write, int size) throw write.accept(os); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); assertEquals(size, is.available()); - }; + } } diff --git a/src/test/java/cn/nukkit/test/ZlibTest.java b/src/test/java/cn/nukkit/test/ZlibTest.java index fedde89f415..ba29f6b9c49 100644 --- a/src/test/java/cn/nukkit/test/ZlibTest.java +++ b/src/test/java/cn/nukkit/test/ZlibTest.java @@ -17,5 +17,4 @@ void testAll() throws Exception { byte[] out = Zlib.inflate(compressed); assertArrayEquals(in, out); } - } diff --git a/src/test/resources/cn/nukkit/test/chain.dat b/src/test/resources/cn/nukkit/test/chain.dat deleted file mode 100644 index 9c69e4f9094b32c11db6924e3fc1af06ffcf59a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32353 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 From 36dd01d4b3842a13e0981b32d762157de4c05377 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:03:32 +0200 Subject: [PATCH 02/21] Get rid of lang submodule --- .gitmodules | 3 - nukkit.yml.default | 2 - src/main/java/cn/nukkit/Player.java | 2 +- src/main/java/cn/nukkit/Server.java | 7 +- src/main/java/cn/nukkit/level/Level.java | 2 +- src/main/resources/lang | 1 - src/main/resources/lang/ara/lang.ini | 344 ++++++++++++++++++++ src/main/resources/lang/bra/lang.ini | 383 ++++++++++++++++++++++ src/main/resources/lang/chs/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/cht/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/cze/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/deu/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/eng/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/fin/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/idn/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/jpn/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/kor/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/language.list | 17 + src/main/resources/lang/ltu/lang.ini | 382 ++++++++++++++++++++++ src/main/resources/lang/pol/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/rus/lang.ini | 384 ++++++++++++++++++++++ src/main/resources/lang/spa/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/tur/lang.ini | 381 ++++++++++++++++++++++ src/main/resources/lang/ukr/lang.ini | 385 +++++++++++++++++++++++ 24 files changed, 6472 insertions(+), 12 deletions(-) delete mode 160000 src/main/resources/lang create mode 100644 src/main/resources/lang/ara/lang.ini create mode 100644 src/main/resources/lang/bra/lang.ini create mode 100644 src/main/resources/lang/chs/lang.ini create mode 100644 src/main/resources/lang/cht/lang.ini create mode 100644 src/main/resources/lang/cze/lang.ini create mode 100644 src/main/resources/lang/deu/lang.ini create mode 100644 src/main/resources/lang/eng/lang.ini create mode 100644 src/main/resources/lang/fin/lang.ini create mode 100644 src/main/resources/lang/idn/lang.ini create mode 100644 src/main/resources/lang/jpn/lang.ini create mode 100644 src/main/resources/lang/kor/lang.ini create mode 100644 src/main/resources/lang/language.list create mode 100644 src/main/resources/lang/ltu/lang.ini create mode 100644 src/main/resources/lang/pol/lang.ini create mode 100644 src/main/resources/lang/rus/lang.ini create mode 100644 src/main/resources/lang/spa/lang.ini create mode 100644 src/main/resources/lang/tur/lang.ini create mode 100644 src/main/resources/lang/ukr/lang.ini diff --git a/.gitmodules b/.gitmodules index febb0db9cb2..e69de29bb2d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "src/main/resources/lang"] - path = src/main/resources/lang - url = https://github.com/NukkitX/Languages.git diff --git a/nukkit.yml.default b/nukkit.yml.default index 3d4d2aae20f..1d1e15fcf88 100644 --- a/nukkit.yml.default +++ b/nukkit.yml.default @@ -1,4 +1,2 @@ settings: - # Multi-language setting - # Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu language: "eng" diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 38860c36293..efad3ecca47 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -1223,7 +1223,7 @@ protected boolean orderChunks() { int centerX = this.getChunkX(); int centerZ = this.getChunkZ(); - int radius = spawned ? this.chunkRadius : server.c_s_spawnThreshold; + int radius = spawned ? this.chunkRadius : server.spawnThresholdRadius; int radiusSqr = radius * radius; long index; diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 169c7d69534..0407ae42f77 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -148,7 +148,7 @@ public class Server { private int maxPlayers; // setMaxPlayers private boolean autoSave = true; // setAutoSave private int difficulty; // setDifficulty - int c_s_spawnThreshold; + int spawnThresholdRadius; private String ip; private int port; private final UUID serverID; @@ -2810,7 +2810,7 @@ private void loadSettings() { /* nukkit.yml */ this.forceLanguage = this.getConfig("settings.force-language", false); - this.queryPlugins = this.getConfig("settings.query-plugins", false); + this.queryPlugins = this.getConfig("settings.query-plugins", true); this.networkCompressionThreshold = this.getConfig("network.batch-threshold", 256); this.networkCompressionLevel = Math.max(Math.min(this.getConfig("network.compression-level", 4), 9), 0); @@ -2834,7 +2834,7 @@ private void loadSettings() { this.spawnThreshold = this.getConfig("spawn-threshold", 56); this.cacheChunks = this.getConfig("cache-chunks", false); - this.c_s_spawnThreshold = (int) Math.ceil(Math.sqrt(this.spawnThreshold)); + this.spawnThresholdRadius = (int) Math.ceil(Math.sqrt(this.spawnThreshold)); /* server.properties */ @@ -2845,7 +2845,6 @@ private void loadSettings() { this.achievementsEnabled = this.getPropertyBoolean("achievements", true); this.pvpEnabled = this.getPropertyBoolean("pvp", true); this.announceAchievements = this.getPropertyBoolean("announce-player-achievements", true); - this.queryPlugins = this.getPropertyBoolean("query-plugins", false); this.allowFlight = this.getPropertyBoolean("allow-flight", false); this.isHardcore = this.getPropertyBoolean("hardcore", false); this.forceResources = this.getPropertyBoolean("force-resources", false); diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index aecc770e5a0..e689b90f720 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -342,7 +342,7 @@ public Level(Server server, String name, String path, Class {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} لقد فقط تلقى انجاز! {%1} + +disconnectionScreen.outdatedClient=نوع خارج الاصدار! +disconnectionScreen.outdatedServer=الخادم خارج الاصدار! +disconnectionScreen.serverFull=الخادم ممتلئ! +disconnectionScreen.noReason=قطع اتصاله من الخادم! +disconnectionScreen.invalidSkin=جلد غير معروف! +disconnectionScreen.invalidName=اسم غير معروف! +disconnectionScreen.resourcePack=مشكله حدثت اثناء تحميل ملفات اللعبه او الخادم. + +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=Haste +potion.digSlowDown=Mining Fatigue +potion.damageBoost=القوه +potion.heal=مزود الصحه +potion.harm=منقص الصحه +potion.jump=مقوي القفزات +potion.confusion=Nausea +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=Absorption +potion.saturation=مفقد الوعي + +commands.generic.exception=خطا غير معروف حدث عندما تم تشغيل هذا الامر +commands.generic.permission=لا توجد اي صلاحيه لك تمكنك من فعل هذا الامر +commands.generic.ingame=تستطيع فعل هذا الامر كلاعب فقط +commands.generic.unknown=امر غير معروف. جرب كتابة /help لرؤية الاوامر الموجوده +commands.generic.player.notFound=هذا اللاعب غير قابل للوجود +commands.generic.usage=الاستعمال:{%0} + +commands.time.added=اضاف {%0} الى الوقت +commands.time.set=وضع الوقت الى{%0} +commands.time.query=الوقت الان{%0} + +commands.me.usage=/me <الفعل ...> + +commands.give.item.notFound=لا يوجد وقت بهذا الشكل{%0} +commands.give.success=Given {%0} * {%1} الى {%2} +commands.give.tagError=فشل وضع الداتا: {%0} + +commands.effect.usage=/effect <اللاعب> <التاثير> [الثواني] [التعويذه] [اخفاء الـ بارتيكيلز] او /effect <الاعب> مسح +commands.effect.notFound=لا يوجد كائن بهذا المعرف {%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} from {%1} لانه لا يملك اي نوع من هذه التعويذات +commands.effect.failure.notActive.all=لم يستطع اخذ {%0} لانه لا يملك اي واحد منها + +commands.enchant.noItem=هذا المحدد لايحمل اي شيء +commands.enchant.notFound=لا يوجد اي سحر بهذا المعرف {%0} +commands.enchant.success=تم السحر +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.kill.all.successful=قتل جميع الاعبين +commands.kill.entities.successful=قتل جميع الاشياء +commands.banlist.ips=هنالك {%0} محظورون بمعارفهم: +commands.banlist.players=هنلك {%0} لاعبون محظورون: +commands.banlist.usage=/banlist [المعارف|اللاعبون] +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.ban.success=حظر الاعب{%0} +commands.ban.usage=/ban <الاسم> [السبب ...] + +commands.unban.success=فك الحظر {%0} +commands.unban.usage=/pardon <الاسم> + +commands.banip.invalid=لقد ادخلت معرف غير معروف, او هو غير موجود بالاصل +commands.banip.offline.invalid=لا يوجد ايـ IP بهذا الاسم, او انه غير موجود بالاصل +commands.banip.success=حظر IP ADDRESS{%0} +commands.banip.success.players=حظر الـ IP {%0} لـ {%1} +commands.banip.usage=/ban-ip <الاسم|الاسم> [السبب ...] + +commands.unbanip.invalid=لقد ادخلت IP غير معروف +commands.unbanip.success=فك حظر IP ADDRESS {%0} +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=حافظ العالم الاوتوماتيكي شغل +commands.save.disabled=حافظ العالم الاوتوماتيكي اطفئ +commands.save.start=قيد الحفظ... +commands.save.success=تم حفظ العالم + +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 [اللاعب المحدد] <بعد الاعب> OR /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 ضيف <الاعب> +commands.whitelist.remove.success=ازال {%0} من اللائحه البيضاء +commands.whitelist.remove.usage=/whitelist ازال <اللاعب> +commands.whitelist.usage=/whitelist + +commands.gamemode.success.self=جعلت طور لعبك الى {%0} +commands.gamemode.success.other=وضع {%0} طور لعبه الى{%1} +commands.gamemode.usage=/gamemode <الطور> [اللاعب] +commands.help.header=--- اظهار لائحه المساعده {%0} من {%1} (/help <صفحه>) --- +commands.help.usage=/help [page|command name] +commands.message.usage=/tell <اللاعب <الرساله الخاصه...> +commands.message.sameTarget=لا تستطيع ارسال رساله خاصه لنفسك! + +commands.difficulty.usage=/difficulty <الصعوبه> +commands.difficulty.success=وضع الصعوبه الى {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [ ] +commands.spawnpoint.success=Set {%0}'s spawn point to ({%1}, {%2}, {%3}) +commands.setworldspawn.usage=/setworldspawn [ ] +commands.setworldspawn.success=Set the world spawn point to ({%0}, {%1}, {%2}) +commands.weather.clear=وضع الطقس الى صافي +commands.weather.rain=وضع الطقس الى ممطر +commands.weather.thunder=وضع الطقس الى ممطر و مرعد +commands.weather.usage=/weather [duration in seconds] +commands.xp.success=اعطى {%0} نقاط الخبره الى {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp [player] OR /xp L [player] +# -------------------- Nukkit language files, only for console -------------------- +nukkit.data.playerNotFound=Player data not found for "{%0}", creating new profile +nukkit.data.playerCorrupted=Corrupted data found for "{%0}", creating new profile +nukkit.data.playerOld=Old Player data found for "{%0}", upgrading profile +nukkit.data.saveError=Could not save player "{%0}": {%1} +nukkit.level.notFound=Level "{%0}" not found +nukkit.level.loadError=Could not load level "{%0}": {%1} +nukkit.level.generationError=Could not generate level "{%0}": {%1} +nukkit.level.tickError=Could not tick level "{%0}": {%1} +nukkit.level.chunkUnloadError=Error while unloading a chunk: {%0} +nukkit.level.backgroundGeneration=Spawn terrain for level "{%0}" is being generated in the background +nukkit.level.defaultError=No default level has been loaded +nukkit.level.preparing=Preparing level "{%0}" +nukkit.level.unloading=Unloading level "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format +nukkit.server.start=Starting Minecraft: PE server version (ترجم من قبل : بايت - BITE){%0} +nukkit.server.networkError=[Network] Stopped interface {%0} due to {%1} +nukkit.server.networkStart=Opening server on {%0}:{%1} +nukkit.server.info=This server is running {%0} version {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: PE {%4} (protocol version {%5}) +nukkit.server.license={%0} is distributed under the LGPL License +nukkit.server.tickOverload=Can't keep up! Is the server overloaded? +nukkit.server.startFinished=Done ({%0}s)! For help, type "help" or "?" +nukkit.server.defaultGameMode=Default game type: {%0} +nukkit.server.query.start=Starting GS4 status listener +nukkit.server.query.info=Setting query port to {%0} +nukkit.server.query.running=Query is running on {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Could not register alias {%0} because it contains illegal characters +nukkit.command.alias.notFound=Could not register alias {%0} because it contains commands that do not exist: {%1} +nukkit.command.exception=Unhandled exception executing command '{%0}' in {%1}: {%2} + +nukkit.command.plugins.description=Gets a list of plugins running on the server +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Reloads the server configuration and plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reloading server... +nukkit.command.reload.reloaded=Reload complete. + +nukkit.command.status.description=Reads back the server's performance. +nukkit.command.status.usage=/status +nukkit.command.gc.description=Fires garbage collection tasks +nukkit.command.gc.usage=/gc +nukkit.command.timings.description=Records timings to see performance of the server. +nukkit.command.timings.usage=/timings +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title OR /title OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values +nukkit.command.version.description=Gets the version of this server including any plugins in use +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=This server is not running any plugin by that name. Use /plugins to get a list of plugins. +nukkit.command.give.description=Gives the specified player a certain amount of items +nukkit.command.give.usage=/give <player> <item[:damage]> [amount] [tags...] +nukkit.command.kill.description=Commit suicide or kill other players +nukkit.command.kill.usage=/kill [player] +nukkit.command.particle.description=Adds particles to a world +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [count] [data] +nukkit.command.time.description=Changes the time on each world +nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> +nukkit.command.ban.player.description=Prevents the specified player from using this server +nukkit.command.ban.ip.description=Prevents the specified IP address from using this server +nukkit.command.banlist.description=View all players banned from this server +nukkit.command.defaultgamemode.description=Set the default gamemode +nukkit.command.deop.description=Takes the specified player's operator status +nukkit.command.difficulty.description=Sets the game difficulty +nukkit.command.enchant.description=Adds enchantments on items +nukkit.command.effect.description=Adds/Removes effects on players +nukkit.command.gamemode.description=Changes the player to a specific game mode +nukkit.command.help.description=Shows the help menu +nukkit.command.kick.description=Removes the specified player from the server +nukkit.command.list.description=Lists all online players +nukkit.command.me.description=Performs the specified action in chat +nukkit.command.op.description=Gives the specified player operator status +nukkit.command.unban.player.description=Allows the specified player to use this server +nukkit.command.unban.ip.description=Allows the specified IP address to use this server +nukkit.command.save.description=Saves the server to disk +nukkit.command.saveoff.description=Disables server autosaving +nukkit.command.saveon.description=Enables server autosaving +nukkit.command.say.description=Broadcasts the given message as the sender +nukkit.command.seed.description=Shows the world seed +nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. +nukkit.command.spawnpoint.description=Sets a player's spawn point +nukkit.command.stop.description=Stops the server +nukkit.command.tp.description=Teleports the given player (or yourself) to another player or coordinates +nukkit.command.tell.description=Sends a private message to the given player +nukkit.command.weather.description=Sets the weather in current world +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Manages the list of players allowed to use this server +nukkit.crash.create=An unrecoverable error has occurred and the server has crashed. Creating a crash dump +nukkit.crash.error=Could not create crash dump: {%0} +nukkit.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. +nukkit.crash.archive=The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. +nukkit.debug.enable=LevelDB support enabled +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} +nukkit.player.invalidMove={%0} moved wrongly! +nukkit.player.logIn={%0}[/{%1}:{%2}] logged in with entity id {%3} at ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] logged out due to {%3} +nukkit.player.invalidEntity={%0} tried to attack an invalid entity +nukkit.plugin.load=Loading {%0} +nukkit.plugin.enable=Enabling {%0} +nukkit.plugin.disable=Disabling {%0} +nukkit.plugin.restrictedName=Restricted name +nukkit.plugin.incompatibleAPI=Incompatible API version +nukkit.plugin.unknownDependency=Unknown dependency +nukkit.plugin.circularDependency=Circular dependency detected +nukkit.plugin.genericLoadError=Could not load plugin '{%0}' +nukkit.plugin.spacesDiscouraged=Plugin '{%0}' uses spaces in its name, this is discouraged +nukkit.plugin.loadError=Could not load plugin '{%0}': {%1} +nukkit.plugin.duplicateError=Could not load plugin '{%0}': plugin exists +nukkit.plugin.fileError=Could not load '{%0}' in folder '{%1}': {%2} +nukkit.plugin.commandError=Could not load command {%0} for plugin {%1} +nukkit.plugin.aliasError=Could not load alias {%0} for plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin '{%0}' has registered a listener for '{%1}' on method '{%2}', but the event is Deprecated. +nukkit.plugin.eventError=Could not pass event '{%0}' to '{%1}': {%2} on {%3} +nukkit.resources.invalid-path=Resource packs path '{%0}' exists and is not a directory +nukkit.resources.unknown-format=Could not load '{%0}' due to format not recognized +nukkit.resources.fail=Could not load '{%0}': {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File '{%0}' is not found +nukkit.resources.zip.no-manifest='manifest.json' is not found +nukkit.resources.zip.invalid-manifest='manifest.json' is invalid or incomplete diff --git a/src/main/resources/lang/bra/lang.ini b/src/main/resources/lang/bra/lang.ini new file mode 100644 index 00000000000..2e5ea3f6fdf --- /dev/null +++ b/src/main/resources/lang/bra/lang.ini @@ -0,0 +1,383 @@ +# Arquivo de linguagem compatível com os identificadores do Minecraft: Bedrock Edition +# +# A mensagem não precisa estar aqui para ser mostrada corretamente no cliente. +# Somente as mensagens mostradas no Nukkit devem estar aqui +# Usando o sistema de linguagens do PocketMine-MP +# +# Traduzido pelo MrPowerGamerBR (mrpowergamerbr.com) + +language.name=Português +language.selected=Selecionado {%0} ({%1}) como a linguagem base + +multiplayer.player.joined={%0} entrou no jogo +multiplayer.player.left={%0} saiu do jogo + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} adquiriu a conquista {%1} + +disconnectionScreen.outdatedClient=Cliente desatualizado! +disconnectionScreen.outdatedServer=Servidor desatualizado! +disconnectionScreen.serverFull=O servidor está cheio! +disconnectionScreen.noReason=Você foi desconectado +disconnectionScreen.invalidSkin=Skin inválida! +disconnectionScreen.invalidName=Nome inválido! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} caiu de um lugar alto +death.attack.inFire={%0} pegou fogo +death.attack.onFire={%0} queimou até a morte +death.attack.lava={%0} tentou nadar na lava +death.attack.lava.magma={%0} discovered floor was lava +death.attack.inWall={%0} sufocou-se em uma parede +death.attack.drown={%0} morreu afogado(a) +death.attack.cactus={%0} foi espetado(a) até a morte +death.attack.generic={%0} morreu +death.attack.explosion={%0} explodiu +death.attack.explosion.player={%0} foi explodido(a) por {%1} +death.attack.magic={%0} foi morto(a) por magia +death.attack.wither={%0} apodreceu +death.attack.mob={%0} foi morto(a) por {%1} +death.attack.player={%0} foi morto(a) por {%1} +death.attack.player.item={%0} foi morto(a) por {%1} usando {%2} +death.attack.arrow={%0} foi atingido(a) por {%1} +death.attack.arrow.item={%0} foi atingido(a) por {%1} usando {%2} +death.attack.fall={%0} caiu no chão com muita força +death.attack.outOfWorld={%0} caiu para fora do mundo +death.attack.starve={%0} starved to death + +gameMode.survival=Modo Sobrevivência +gameMode.creative=Modo Criativo +gameMode.adventure=Modo Aventura +gameMode.spectator=Modo Espectador +gameMode.changed=Seu modo de jogo foi atualizado + +potion.moveSpeed=Velocidade +potion.moveSlowdown=Lentidão +potion.digSpeed=Pressa +potion.digSlowDown=Cansaço +potion.damageBoost=Força +potion.heal=Vida Instantânea +potion.harm=Dano Instantâneo +potion.jump=Super Pulo +potion.confusion=Náusea +potion.regeneration=Regeneração +potion.resistance=Resistência +potion.fireResistance=Resistência ao Fogo +potion.waterBreathing=Respiração Aquática +potion.invisibility=Invisibilidade +potion.blindness=Cegueira +potion.nightVision=Visão Noturna +potion.hunger=Fome +potion.weakness=Fraqueza +potion.poison=Veneno +potion.wither=Wither +potion.healthBoost=Vida Extra +potion.absorption=Absorção +potion.saturation=Saturação + +commands.generic.exception=Um erro ocorreu ao tentar realizar este comando +commands.generic.permission=Você não tem permissão para usar este comando +commands.generic.ingame=Você só pode usar este comando como um player +commands.generic.unknown=Comando desconhecido. Tente /help para uma lista de comandos +commands.generic.player.notFound=Esse(a) jogador(a) não pôde ser encontrado(a) +commands.generic.usage=Uso: {%0} + +commands.time.added=Adicionado {%0} ao tempo +commands.time.set=Tempo mudado para {%0} +commands.time.query=O tempo é {%0} + +commands.me.usage=/me <ação ...> + +commands.give.item.notFound=Não há nenhum item com o nome: {%0} +commands.give.success=Dado {%0} * {%1} para {%2} +commands.give.tagError=Análise da tag de dados falhou: {%0} + +commands.effect.usage=/effect <jogador> <efeito> [segundos] [amplificador] [esconderPartículas] OU /effect <jogador> clear +commands.effect.notFound=Não há nenhum efeito de monstro com o ID {%0} +commands.effect.success=Dado {%0} (ID {%1}) * {%2} para {%3} por {%4} segundos +commands.effect.success.removed=Retirado {%0} de {%1} +commands.effect.success.removed.all=Todos os efeitos de {%0} foram removidos +commands.effect.failure.notActive=Não foi possível retirar {%0} de {%1} pois este não possui o efeito +commands.effect.failure.notActive.all=Nenhum efeito pôde ser removido de {%0} já que ele(a) não tem nenhum + +commands.enchant.noItem=O alvo não está segurando um item +commands.enchant.notFound=Não há um encantamento com o ID {%0} +commands.enchant.success=Encantado com sucesso +commands.enchant.usage=/enchant <jogador> <ID do encantamento> [nível] + +commands.particle.success=Reproduzindo efeito {%0} por {%1} vezes +commands.particle.notFound=Nome do efeito desconhecido {%0} + +commands.players.usage=/list +commands.players.list=Existem {%0}/{%1} jogadores(as) online: + +commands.kill.successful=Eliminado(a) {%0} +commands.kill.all.successful=Eliminou todos os jogadores +commands.kill.entities.successful=Eliminou todas as entidades + +commands.banlist.ips=Existem {%0} endereços de IP banidos no total: +commands.banlist.players=Existem {%0} jogadores(as) banidos(as) no total: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <modo> +commands.defaultgamemode.success=O modo de jogo padrão do mundo agora é {%0} + +commands.op.success={%0} virou operador(a) +commands.op.usage=/op <jogador> + +commands.deop.success={%0} não é mais operador(a) +commands.deop.usage=/deop <jogador> + +commands.say.usage=/say <mensagem ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success={%0} foi banido(a) +commands.ban.usage=/ban <nome> [razão ...] + +commands.unban.success={%0} foi desbanido(a) +commands.unban.usage=/pardon <nome> + +commands.banip.invalid=Você digitou um IP inválido ou um(a) jogador(a) que não está online. +commands.banip.offline.invalid=Não existe este IP na data dos players ou o IP é inválido +commands.banip.success=Endereço IP banido {%0} +commands.banip.success.players=Endereço IP banido {%0} pertencente a {%1} +commands.banip.usage=/ban-ip <address|nome> [razão ...] + +commands.unbanip.invalid=Você digitou um IP inválido +commands.unbanip.success=IP desbanido: {%0} +commands.unbanip.usage=/pardon-ip <ip> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Salvamento automático ligado +commands.save.disabled=Salvamento automático desligado +commands.save.start=Salvando... +commands.save.success=Mundo salvo + +commands.stop.usage=/stop +commands.stop.start=Parando o servidor + +commands.kick.success={%0} foi expulso(a) do jogo +commands.kick.success.reason={%0} foi expulso(a) do jogo. Motivo: '{%1}' +commands.kick.usage=/kick <jogador> [motivo ...] + +commands.tp.success={%0} teletransportado(a) para {%1} +commands.tp.success.coordinates=Teletransportado(a) {%0} para {%1}, {%2}, {%3} +commands.tp.usage=/tp [jogador alvo] <jogador de destino> OU /tp [jogador alvo] <x> <y> <z> [<rot-x> <rot-y>] + +commands.whitelist.list=Existem {%0} (de {%1} vistos) jogadores(as) na whitelist: +commands.whitelist.enabled=Whitelist ligada +commands.whitelist.disabled=Whitelist desligada +commands.whitelist.reloaded=Whitelist recarregada +commands.whitelist.add.success={%0} foi adicionado(a) à whitelist +commands.whitelist.add.usage=/whitelist add <jogador> +commands.whitelist.remove.success={%0} foi removido(a) da whitelist +commands.whitelist.remove.usage=/whitelist remove <jogador> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Mudado o próprio modo de jogo para {%0} +commands.gamemode.success.other=Mudado o modo de jogo de {%0} para {%1} +commands.gamemode.usage=/gamemode <modo> [jogador] + +commands.help.header=--- Mostrando página {%0} de {%1} (/help <página>) --- +commands.help.usage=/help [página|nome do comando] + +commands.message.usage=/tell <jogador> <mensagem privada ...> +commands.message.sameTarget=Você não pode enviar uma mensagem privada para si mesmo! + +commands.difficulty.usage=/difficulty <nova dificuldade> +commands.difficulty.success=Dificuldade do jogo modificada para %s + +commands.spawnpoint.usage=/spawnpoint [jogador] [<x> <y> <z>] +commands.spawnpoint.success=Local de renascimento de {%0} definido para ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Ponto de renascimento do mundo definido para ({%0}, {%1}, {%2}) + +commands.weather.clear=Mudando para tempo limpo +commands.weather.rain=Mudando para tempo chuvoso +commands.weather.thunder=Mudando para chuva e trovões +commands.weather.usage=/weather <clear|rain|thunder> [duração em segundos] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Data não encontrada do player "{%0}", criando novo perfil +nukkit.data.playerCorrupted=Data corrompida do player "{%0}" encontrada, criando novo perfil +nukkit.data.playerOld=Data antiga do player "{%0}" encontrada, atualizando perfil +nukkit.data.saveError=Não foi possível salvar o player "{%0}": {%1} + +nukkit.level.notFound=Mundo "{%0}" não encontrado +nukkit.level.loadError=Não foi possível carregar o mundo "{%0}": {%1} +nukkit.level.generationError=Não foi possível gerar o mundo "{%0}": {%1} +nukkit.level.tickError=Não foi possível fazer tick no mundo "{%0}": {%1} +nukkit.level.chunkUnloadError=Erro ao descarregar o chunk: {%0} +nukkit.level.backgroundGeneration=Spawn do terreno para o nível "{%0}" está sendo gerado em segundo plano +nukkit.level.defaultError=Nenhum mundo padrão foi carregado +nukkit.level.preparing=Preparando mundo "{%0}" +nukkit.level.unloading=Descarregando mundo "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Iniciando Servidor de Minecraft: BE, versão {%0} +nukkit.server.networkError=[Rede] Interface finalizada em {%0} devido a {%1} +nukkit.server.networkStart=Abrindo servidor em {%0}:{%1} +nukkit.server.info=Este servidor está rodando {%0} versão {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Este servidor está rodando {%0} {%1} 「{%2}」 implementando versão da API {%3} para Minecraft: BE {%4} (versão do protocolo {%5}) +nukkit.server.license={%0} é distribuido com a licença GPL +nukkit.server.tickOverload=Não foi possível aguentar! Será que o servidor está sobrecarregado? +nukkit.server.startFinished=Finalizado ({%0}s)! Para ver a ajuda, escreva "help" ou "?" +nukkit.server.defaultGameMode=Modo de jogo padrão: {%0} +nukkit.server.query.start=Iniciando listener de status GS4 +nukkit.server.query.info=Porta de query está ativada em {%0} +nukkit.server.query.running=Query rodando em {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Não foi possível registrar a alias {%0} porque ela contém caracteres ilegais. +nukkit.command.alias.notFound=Não foi possível registrar a alias {%0} porque ela contém comandos que não existem: {%1} +nukkit.command.exception=Exceção não tratada ao executando comando "{%0}" em {%1}: {%2} + +nukkit.command.plugins.description=Pega uma lista de plugins rodando no servidor +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Recarrega a configuração do servidor e os plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Recarregando server... +nukkit.command.reload.reloaded=Recarregamento completo. + +nukkit.command.status.description=Mostra a performance do servidor. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Ativa o garbage collection +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Grava timings para ver a performance do servidor. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Pega a versão deste servidor incluindo todos os plugins em uso +nukkit.command.version.usage=/version [nome do plugin] +nukkit.command.version.noSuchPlugin=Este servidor não está usando nenhum plugin com este nome. Use /plugins para ver a lista de plugins. + +nukkit.command.give.description=Dá ao player especificado uma certa quantidade de itens +nukkit.command.give.usage=/give <jogador> <item[:damage]> [quantidade] [tags ...] + +nukkit.command.kill.description=Comite suicídio ou mata outros players +nukkit.command.kill.usage=/kill [jogador] + +nukkit.command.particle.description=Adiciona partículas a um mundo +nukkit.command.particle.usage=/particle <nome> <x> <y> <z> <xd> <yd> <zd> [quantidade] [data] + +nukkit.command.time.description=Altera o tempo em todos os mundos +nukkit.command.time.usage=/time <alterar|adicionar> <value> OU /time <iniciar|parar|ver> + +nukkit.command.gamerule.description=Define ou questiona um valor da regra de jogo +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Bloqueia o player especificado de usar este servidor +nukkit.command.ban.ip.description=Bloqueia o IP especificado de usar este servidor +nukkit.command.banlist.description=Ver todos os jogadores banidos deste servidor +nukkit.command.defaultgamemode.description=Altera o modo de jogo padrão +nukkit.command.deop.description=Remove o status de operador do player especificado +nukkit.command.difficulty.description=Altera a dificuldade do jogo +nukkit.command.enchant.description=Adiciona encantamento nos itens +nukkit.command.effect.description=Adiciona/Remove efeitos nos players +nukkit.command.gamemode.description=Altera o player para um tipo de jogo específico +nukkit.command.help.description=Mostra o menu de ajuda +nukkit.command.kick.description=Remove o player especificado do servidor +nukkit.command.list.description=Mostra todos os players online +nukkit.command.me.description=Performa a ação específica no chat +nukkit.command.op.description=Dá o player especificado o status de operador +nukkit.command.unban.player.description=Permite o jogador especificado usar este servidor +nukkit.command.unban.ip.description=Permite o IP especificado usar este servidor +nukkit.command.save.description=Salva o servidor para o disco +nukkit.command.saveoff.description=Desativa o auto-salvamento do servidor +nukkit.command.saveon.description=Ativa o auto-salvamento do servidor +nukkit.command.say.description=Envia uma mensagem específica utilizando o enviador +nukkit.command.seed.description=Mostra a seed do mundo +nukkit.command.setworldspawn.description=Marca o spawn de um mundo. Se nenhuma coordenada está especificada, as coordenadas do player serão utilizadas. +nukkit.command.spawnpoint.description=Marca o spawn de um player +nukkit.command.stop.description=Fecha o servidor +nukkit.command.tp.description=Teletransporta o player especificado (ou você) para outro player ou coordenadas +nukkit.command.tell.description=Envia uma mensagem privada para o player +nukkit.command.weather.description=Altera o tempo no mundo atual +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Administra a lista de players que podem usar este servidor + +nukkit.crash.create=Um erro irrecuperável ocorreu e o servidor travou. Criando um arquivo de erro +nukkit.crash.error=Não foi possível criar o arquivo de erros: {%0} +nukkit.crash.submit=Por favor envie o arquivo "{%0}" para o arquivo de erros e envie o link para a página de bug report. Dê o máximo de informações possíveis +nukkit.crash.archive=O erro já foi automaticamente enviado para os arquivos de erros. Você pode ver no {%0} ou usando o ID #{%1} + +nukkit.debug.enable=Suporte a mundos LevelDB ativado + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} moveu erradamente! +nukkit.player.logIn={%0}[/{%1}:{%2}] entrou com o id de entidade {%3} em ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] saiu devido a {%3} +nukkit.player.invalidEntity={%0} tentou atacar uma entidade inválida + +nukkit.plugin.load=Carregando {%0} +nukkit.plugin.enable=Ativando {%0} +nukkit.plugin.disable=Desativando {%0} +nukkit.plugin.restrictedName=Nome restrito +nukkit.plugin.incompatibleAPI=Versão da API incompatível +nukkit.plugin.unknownDependency=Dependência desconhecida +nukkit.plugin.circularDependency=Dependência circular detectada +nukkit.plugin.genericLoadError=Não foi possível carregar o plugin "{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" usa espaço nos nome, isto é desencorajado +nukkit.plugin.loadError=Não foi possível carregar o plugin "{%0}": {%1} +nukkit.plugin.duplicateError=Não foi possível carregar o plugin "{%0}": plugin já existe +nukkit.plugin.fileError=Não foi possível carregar "{%0}" na pasta "{%1}": {%2} +nukkit.plugin.commandError=Não foi possível carregar o comando {%0} para o plugin {%1} +nukkit.plugin.aliasError=Não foi possível carregar a alias {%0} para o plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" registrou um listener para "{%1}" no método "{%2}", mas o evento é obsoleto. +nukkit.plugin.eventError=Não foi possível passar o evento "{%0}" para "{%1}": {%2} em {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/chs/lang.ini b/src/main/resources/lang/chs/lang.ini new file mode 100644 index 00000000000..ba406e071c9 --- /dev/null +++ b/src/main/resources/lang/chs/lang.ini @@ -0,0 +1,381 @@ +# 与 Minecraft: Bedrock Edition 标识符兼容的语言文件 +# +# 并不是所有消息都需要写在这个文件里才能在客户端上正确显示。 +# 只有 Nukkit 自己的信息需要写在这里 +# 使用 PocketMine-MP 中的语言属性 + +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.outdatedClient=客户端已过期! +disconnectionScreen.outdatedServer=服务器已过期! +disconnectionScreen.serverFull=服务器已满! +disconnectionScreen.noReason=已断开与服务器的连接 +disconnectionScreen.invalidSkin=无效的皮肤! +disconnectionScreen.invalidName=无效的名称! +disconnectionScreen.resourcePack=下载或应用资源包时出现问题。 + +death.fell.accident.generic={%0} 从高处摔了下来 +death.attack.inFire={%0} 浴火焚身 +death.attack.onFire={%0} 被烧死了 +death.attack.lava={%0} 试图在熔岩里游泳 +death.attack.lava.magma={%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} 掉出了这个世界 +death.attack.starve={%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.ingame=您只能作为玩家使用该命令 +commands.generic.notFound=未知的命令。使用 /help 显示命令列表。 +commands.generic.player.notFound=未找到玩家 +commands.generic.usage=用法:{%0} + +commands.time.added=将时间增加了 {%0} +commands.time.set=将时间设为 {%0} +commands.time.query=目前时间为 {%0} + +commands.me.usage=/me <动作 ...> + +commands.give.item.notFound=名为 {%0} 的物品不存在 +commands.give.success=已将 {%1} 个 {%0} 给予 {%2} +commands.give.tagError=物品数据格式错误: {%0} + +commands.effect.usage=/effect <玩家名称> <效果> [秒数] [强度] [隐藏粒子] 或 /effect <玩家名称> clear +commands.effect.notFound=ID 为 {%0} 的效果不存在 +commands.effect.success=已将 {%4} 秒的 {%0} (ID {%1}) * {%2} 效果应用于 {%3} +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.noItem=目标未手持任何物品 +commands.enchant.notFound=ID 为 {%0} 的附魔不存在 +commands.enchant.success=已将魔咒应用于物品上 +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.kill.all.successful=已清除所有玩家 +commands.kill.entities.successful=已清除所有实体 + +commands.banlist.ips=共有 {%0} 个被封禁的 IP 地址: +commands.banlist.players=共有 {%0} 个被封禁的玩家: +commands.banlist.usage=/banlist [IP 地址|玩家] + +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.ban.success=已封禁玩家 {%0} +commands.ban.usage=/ban <玩家名称> [原因 ...] + +commands.unban.success=已解封玩家 {%0} +commands.unban.usage=/pardon <玩家名称> + +commands.banip.invalid=IP 地址无效或玩家不在线 +commands.banip.offline.invalid=玩家数据中无 IP 地址或 IP 地址无效 +commands.banip.success=已封禁 IP 地址 {%0} +commands.banip.success.players=已封禁 {%1} 的 IP 地址 {%0} +commands.banip.usage=/ban-ip <IP 地址|玩家名称> [原因 ...] + +commands.unbanip.invalid=无效的 IP 地址 +commands.unbanip.success=已解封 IP 地址 {%0} +commands.unbanip.usage=/pardon-ip <IP 地址> + +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.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 [玩家名称] <x> <y> <z> [<y偏转> <x偏转>] + +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=您的游戏模式已被设置为 {%0} +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 [玩家名称] [<x> <y> <z>] +commands.spawnpoint.success=已将 {%0} 的出生点设置为 ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=已将世界出生点设置为 ({%0}, {%1}, {%2}) + +commands.weather.clear=天气已设为晴天 +commands.weather.rain=天气已设为雨天 +commands.weather.thunder=天气已设为雷雨 +commands.weather.usage=/weather <clear|rain|thunder> [持续秒数] + +commands.xp.success=已给予 {%1} {%0} 点经验 +commands.xp.success.levels=已给予 {%1} {%0} 级经验 +commands.xp.success.levels.minus=已减少 {%1} {%0} 级经验 +commands.xp.usage=/xp <经验数量> [玩家] 或 /xp <等级数量>L [玩家] + +# -------------------- Nukkit 语言文件,仅用于 console -------------------- + +nukkit.data.playerNotFound=无法找到玩家数据 "{%0}",正在创建新的数据 +nukkit.data.playerCorrupted=发现损坏的玩家数据 "{%0}",正在创建新的数据 +nukkit.data.playerOld=发现旧的玩家数据 "{%0}",正在更新数据 +nukkit.data.saveError=无法保存 "{%0}" 的玩家数据:{%1} + +nukkit.level.notFound=无法找到世界 "{%0}" +nukkit.level.loadError=无法加载世界 "{%0}":{%1} +nukkit.level.generationError=无法生成世界 "{%0}":{%1} +nukkit.level.tickError=更新世界 "{%0}" 时出现错误:{%1} +nukkit.level.chunkUnloadError=卸载区块时发生错误:{%0} +nukkit.level.backgroundGeneration=正在后台生成世界 "{%0}" 地形 +nukkit.level.defaultError=没有读取预设的世界 +nukkit.level.preparing=准备世界 "{%0}" 中 +nukkit.level.unloading=正在卸载世界 "{%0}" +nukkit.level.updating=发现旧的世界 "{%0}",正在转换格式 + +nukkit.server.start=正在启动 Minecraft: BE {%0} +nukkit.server.networkError=[Network] 终止交互 {%0} 原因 {%1} +nukkit.server.networkStart=正在于 {%0}:{%1} 启动服务器 +nukkit.server.info=此服务器正在运行 {%0} {%1} 版本 "{%2}" (API {%3}) +nukkit.server.info.extended=此服务器正在运行 {%0} {%1} 「{%2}」API 版本 {%3} 的 Minecraft: BE {%4} (协议版本 {%5}) +nukkit.server.license={%0} 根据 GPL 许可发行 +nukkit.server.tickOverload=注意!服务器可能超载? +nukkit.server.startFinished=启动完成 ({%0}s)!如需帮助,请输入 "help" 或 "?" +nukkit.server.defaultGameMode=默认游戏模式:{%0} +nukkit.server.query.start=启动 GS4 状态监听器 +nukkit.server.query.info=将 Query 端口设置为 {%0} +nukkit.server.query.running=Query 运行于 {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=启动 RCON 失败:密码为空 +nukkit.server.rcon.startupError=启动 RCON 失败:{%0} +nukkit.server.rcon.running=RCON 运行于 {%0}:{%1} + +nukkit.command.alias.illegal=无法注册别名 {%0},因为它包含非法字符 +nukkit.command.alias.notFound=无法注册别名 {%0},因为它包含不存在的命令:{%1} +nukkit.command.exception=于 {%1} 执行命令 "{%0}" 时,出现了未被处理的错误:{%2} + +nukkit.command.plugins.description=获取服务器上运行的插件列表 +nukkit.command.plugins.success=插件 ({%0}):{%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=重新加载服务器配置和插件 +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=重新加载中... +nukkit.command.reload.reloaded=重新加载完成 + +nukkit.command.status.description=显示服务器的运行状态。 +nukkit.command.status.usage=/status + +nukkit.command.gc.description=启动垃圾清理任务 +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=收集时序信息,以检视服务器的性能。 +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=重置时序并启用计时 +nukkit.command.timings.disable=禁用计时 +nukkit.command.timings.timingsDisabled=请输入 /timings on 启用计时 +nukkit.command.timings.verboseEnable=启用详细时序 +nukkit.command.timings.verboseDisable=禁用详细时序 +nukkit.command.timings.reset=重置时序 +nukkit.command.timings.rcon=警告:通过 RCON 呈交的时序报告可能会导致延迟突发,您应当在游戏或控制台中使用 /timings report +nukkit.command.timings.uploadStart=准备时序报告中... +nukkit.command.timings.uploadError=上传出错:{%0}: {%1}, 请检查日志获取更多信息 +nukkit.command.timings.reportError=粘贴报告时发生错误,请检查日志获取更多信息 +nukkit.command.timings.timingsLocation=查看时序报告:{%0} +nukkit.command.timings.timingsResponse=时序响应:{%0} +nukkit.command.timings.timingsWrite=时序写入 {%0} + +nukkit.command.title.description=向一个玩家显示屏幕标题,或者更改该玩家显示的屏幕标题设置 +nukkit.command.title.usage=/title <玩家> <clear|reset> 或 /title <玩家> <title|subtitle|actionbar> <标题内容> 或 /title <玩家> <次数> <淡入时间> <持续时间> <淡出时间> +nukkit.command.title.clear=已清除 {%0} 的标题 +nukkit.command.title.reset=已重置 {%0} 的标题设置 +nukkit.command.title.title=正在向 {%1} 显示新的标题 "{%0}" +nukkit.command.title.subtitle=正在向 {%1} 显示新的副标题 "{%0}" +nukkit.command.title.actionbar=正在向 {%1} 显示新的快捷栏标题 "{%0}" +nukkit.command.title.times.success=已更改 {%3} 的标题显示时间为:{%0}, {%1}, {%2} (淡入时间, 持续时间, 淡出时间)。 +nukkit.command.title.times.fail=标题的时间必须为数字值 + +nukkit.command.version.description=查看此服务器及其使用的插件的版本 +nukkit.command.version.usage=/version [插件名称] +nukkit.command.version.noSuchPlugin=该服务器未在运行名为此的插件。使用 /plugins 获取插件列表。 + +nukkit.command.give.description=给指定玩家一定数量的物品 +nukkit.command.give.usage=/give <玩家名称> <物品[:特殊值]> [数量] [附加数据 ...] + +nukkit.command.kill.description=自杀或杀死其他玩家 +nukkit.command.kill.usage=/kill [玩家名称] + +nukkit.command.particle.description=向某个世界中添加粒子效果 +nukkit.command.particle.usage=/particle <玩家名称> <x> <y> <z> <xd> <yd> <zd> [数量] [数据值] + +nukkit.command.time.description=更改每个世界的时间 +nukkit.command.time.usage=/time <set|add> <数值> 或 /time <start|stop|query> + +nukkit.command.gamerule.description=设置或查询游戏规则的值 +nukkit.command.gamerule.usage=/gamerule <规则> [值] + +nukkit.command.debug.description=上传服务器信息至 Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=禁止指定的玩家加入此服务器 +nukkit.command.ban.ip.description=禁止指定的 IP 地址加入此服务器 +nukkit.command.banlist.description=查看该服务器封禁的所有玩家 +nukkit.command.defaultgamemode.description=设置默认的游戏模式 +nukkit.command.deop.description=取消指定玩家的管理员权限 +nukkit.command.difficulty.description=设置游戏的难度 +nukkit.command.enchant.description=为物品附魔 +nukkit.command.effect.description=增加/减少玩家身上的效果 +nukkit.command.gamemode.description=设置某个玩家的游戏模式 +nukkit.command.help.description=显示帮助列表 +nukkit.command.kick.description=从服务器中踢出指定玩家 +nukkit.command.list.description=显示在线玩家列表 +nukkit.command.me.description=在聊天中作出指定的动作 +nukkit.command.op.description=赋予指定玩家管理员权限 +nukkit.command.unban.player.description=允许指定玩家加入此服务器 +nukkit.command.unban.ip.description=允许指定 IP 地址加入此服务器 +nukkit.command.save.description=保存服务器数据到硬盘上 +nukkit.command.saveoff.description=停用自动保存服务器数据 +nukkit.command.saveon.description=启用自动保存服务器数据 +nukkit.command.say.description=以发送命令者身份广播指定的讯息 +nukkit.command.seed.description=显示世界种子 +nukkit.command.setworldspawn.description=设置一个世界重生点。未指定坐标则将使用玩家的坐标。 +nukkit.command.spawnpoint.description=设置玩家重生点 +nukkit.command.stop.description=关闭服务器 +nukkit.command.tp.description=传送指定玩家(或是自己)到另一位玩家或某坐标 +nukkit.command.tell.description=向指定玩家发送私密信息 +nukkit.command.weather.description=设置当前世界的天气状态 +nukkit.command.whitelist.description=管理员允许加入此服务器的玩家列表 +nukkit.command.xp.description=为玩家添加一定数量或等级的经验 + +nukkit.crash.create=发生了一个无法恢复的错误,导致服务器崩溃。正在生成错误报告。 +nukkit.crash.error=未能保存错误报告:{%0} +nukkit.crash.submit=请上传错误报告「{%0}」,并把链接提交至错误报告反馈页。请尽量提供更多资料。 +nukkit.crash.archive=错误报告已被自动上传。您可以在 {%0} 查看,或使用 ID #{%1}。 + +nukkit.debug.enable=启用 LevelDB 支持 + +nukkit.bugreport.create=侦测到一个错误,正在生成错误报告。 +nukkit.bugreport.error=无法生成错误报告:{%0} +nukkit.bugreport.archive=创建错误报告:{%0} + +nukkit.player.invalidMove={%0} 行动可疑! +nukkit.player.logIn={%0}[/{%1}:{%2}] 登入游戏,实体 ID 为 {%3},坐标位于 ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] 登出游戏,原因:{%3} +nukkit.player.invalidEntity={%0} 尝试攻击一个无效的实体 + +nukkit.plugin.load=读取 {%0} 中 +nukkit.plugin.enable=开启 {%0} 中 +nukkit.plugin.disable=关闭 {%0} 中 +nukkit.plugin.restrictedName=受限的名称 +nukkit.plugin.incompatibleAPI=不兼容的 API 版本 +nukkit.plugin.unknownDependency=本插件无法单独使用 +nukkit.plugin.circularDependency=检测到循环依赖 +nukkit.plugin.genericLoadError=无法读取插件 "{%0}" +nukkit.plugin.spacesDiscouraged=插件 "{%0}" 在名称中使用了空格,不建议这样做 +nukkit.plugin.loadError=无法读取插件 "{%0}":{%1} +nukkit.plugin.duplicateError=无法读取插件 "{%0}":已有相同插件 +nukkit.plugin.fileError=无法读取 "{%1}" 目录中的 "{%0}":{%2} +nukkit.plugin.commandError=无法读取 {%1} 插件的 {%0} 命令 +nukkit.plugin.aliasError=无法读取 {%1} 插件的 {%0} 别名 +nukkit.plugin.deprecatedEvent=插件 "{%0}" 已在 "{%2}" 方法中注册了一个用于 "{%1}" 的监听器,但该事件已过时。 +nukkit.plugin.eventError=无法处理事件 "{%0}" 至 "{%1}":{%2} 于 {%3} + +nukkit.resources.invalid-path=资源包路径 "{%0}" 存在,但不是个目录 +nukkit.resources.unknown-format=无法加载 "{%0}" ,格式无法识别 +nukkit.resources.fail=无法加载 "{%0}":{%1} +nukkit.resources.success=成功加载 {%0} 个资源包 +nukkit.resources.zip.not-found=文件 "{%0}" 未找到 +nukkit.resources.zip.no-manifest="manifest.json" 未找到 +nukkit.resources.zip.invalid-manifest="manifest.json" 无效或不完整 diff --git a/src/main/resources/lang/cht/lang.ini b/src/main/resources/lang/cht/lang.ini new file mode 100644 index 00000000000..05deb64e4e6 --- /dev/null +++ b/src/main/resources/lang/cht/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +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.outdatedClient=用戶端過舊! +disconnectionScreen.outdatedServer=伺服器過舊! +disconnectionScreen.serverFull=伺服器已滿! +disconnectionScreen.noReason=與伺服器連線中斷 +disconnectionScreen.invalidSkin=無效的皮膚! +disconnectionScreen.invalidName=無效的玩家代號! +disconnectionScreen.resourcePack=下載或套用資源包時遇到問題。 + +death.fell.accident.generic={%0} 從高處跌落 +death.attack.inFire={%0} 在火焰中昇天 +death.attack.onFire={%0} 被燒死了 +death.attack.lava={%0} 試圖在熔岩中游泳 +death.attack.lava.magma={%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} 掉到世界外面了 +death.attack.starve={%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.ingame=您只能在遊戲內執行此指令 +commands.generic.unknown=未知的指令。請使用 /help 來顯示指令列表。 +commands.generic.player.notFound=找不到該玩家 +commands.generic.usage=用法:{%0} + +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=已將 {%4} 秒的 {%0} (ID {%1}) * {%2} 效果作用於 {%3} +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.noItem=對象手中沒有物品 +commands.enchant.notFound=沒有一個附魔ID為 {%0} +commands.enchant.success=已將附魔套用於物品 +commands.enchant.usage=/enchant <玩家代號> <附魔ID> [物品等級] + +commands.particle.success=顯示粒子 {%0} {%1} 次 +commands.particle.notFound=未知的效果名稱 {%0} + +commands.players.usage=/list +commands.players.list=至多 {%1} 個玩家之中的 {%0} 個玩家在線上: + +commands.kill.successful=已消滅 {%0} +commands.kill.all.successful=已消滅所有玩家 +commands.kill.entities.successful=已消滅所有實體 + +commands.banlist.ips=以下 {%0} 個 IP 位址被封鎖: +commands.banlist.players=以下 {%0} 個玩家被封鎖: +commands.banlist.usage=/banlist [IP 位址|玩家代號] + +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.ban.success={%0} 已被封鎖 +commands.ban.usage=/ban <玩家代號> [原因 ...] + +commands.unban.success=已解除封鎖 {%0} +commands.unban.usage=/pardon <玩家代號> + +commands.banip.invalid=無效的 IP 位址或該玩家不在線上 +commands.banip.offline.invalid=玩家數據中不包含有效的 IP 位址 +commands.banip.success=已封鎖 IP {%0} +commands.banip.success.players=封鎖 IP 位址 {%0} 來自 {%1} +commands.banip.usage=/ban-ip <IP 位址|玩家代號> [原因 ...] + +commands.unbanip.invalid=無效的 IP 位址 +commands.unbanip.success=已解除封鎖 IP {%0} +commands.unbanip.usage=/pardon-ip <IP 位址> + +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.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 [玩家代號] <x> <y> <z> [<y-rot> <x-rot>] + +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=已將自己的遊戲模式切換為 {%0} +commands.gamemode.success.other=已將 {%0} 的遊戲模式切換為 {%1} +commands.gamemode.usage=/gamemode <模式> [玩家代號] + +commands.help.header=--- 檢視幫助列表第 {%0} 頁共 {%1} 頁 (/help <page>) --- +commands.help.usage=/help [頁數|指令名稱] + +commands.message.usage=/tell <玩家代號> <訊息 ...> +commands.message.sameTarget=您不能傳送訊息給自己! + +commands.difficulty.usage=/difficulty <難度> +commands.difficulty.success=已將難度設為 {%0} + +commands.spawnpoint.usage=/spawnpoint [玩家代號] [<x> <y> <z>] +commands.spawnpoint.success=已將 {%0} 的重生點設為 ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=已將世界重生點設為 ({%0}, {%1}, {%2}) + +commands.weather.clear=已將天氣設為晴朗 +commands.weather.rain=已將天氣設為降雨 +commands.weather.thunder=已將天氣設為雷雨 +commands.weather.usage=/weather <clear|rain|thunder> [持續時間] + +commands.xp.success=已給予 {%1} {%0} 點經驗值 +commands.xp.success.levels=已給予 {%1} {%0} 等經驗等級 +commands.xp.success.levels.minus=已取走 {%1} {%0} 等經驗等級 +commands.xp.usage=/xp <經驗數量> [玩家] 或 /xp <等級數量>L [玩家] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=無法找到玩家資料 "{%0}",正在創建新的資料檔 +nukkit.data.playerCorrupted=發現損壞的資料 "{%0}",創建新的設定檔 +nukkit.data.playerOld=發現舊的玩家資料 "{%0}",更新設定檔 +nukkit.data.saveError=無法儲存 "{%0}" 的玩家資料:{%1} + +nukkit.level.notFound=無法找到 "{%0}" 地圖 +nukkit.level.loadError=無法讀取地圖 "{%0}":{%1} +nukkit.level.generationError=無法產生地圖 "{%0}":{%1} +nukkit.level.tickError=計算地圖「{%0}」時出現錯誤:{%1} +nukkit.level.chunkUnloadError=移除一個區塊時發生錯誤:{%0} +nukkit.level.backgroundGeneration=正在於背景生成世界 "{%0}" 的地形 +nukkit.level.defaultError=沒有讀取預設的地圖 +nukkit.level.preparing=準備地圖中... "{%0}" +nukkit.level.unloading=正在移除地圖 "{%0}" +nukkit.level.updating=發現舊的地圖 "{%0}",正在轉換格式 + +nukkit.server.start=正在啟動支援 Minecraft: BE {%0} 版本的伺服器 +nukkit.server.networkError=[Network] 停止介面 {%0} 由於 {%1} +nukkit.server.networkStart=正在啟動伺服器在 {%0}:{%1} +nukkit.server.info=此伺服器正在運作 {%0} {%1} 版本 "{%2}" (API {%3}) +nukkit.server.info.extended=此伺服器正在運作 {%0} {%1} 「{%2}」 執行 API 版本 {%3} 支援 Minecraft: BE {%4} (協定版本 {%5}) +nukkit.server.license={%0} 根據 GPL 許可發行 +nukkit.server.tickOverload=注意!伺服器有超載的可能 +nukkit.server.startFinished=讀取完成 ({%0}s)!如需幫助,請輸入 "help" 或 "?" +nukkit.server.defaultGameMode=預設的遊戲類型:{%0} +nukkit.server.query.start=啟動 GS4 狀態監聽器 +nukkit.server.query.info=設定 query 介面到 {%0} +nukkit.server.query.running=Query 運作在 {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=無法啟動 RCON:密碼為空 +nukkit.server.rcon.startupError=無法啟動 RCON:{%0} +nukkit.server.rcon.running=RCON 正在運行於 {%0}:{%1} + +nukkit.command.alias.illegal=不能註冊別名 {%0},因為它包含非法字元 +nukkit.command.alias.notFound=未能登記別稱 {%0} ,因為它包含不存在的指令:{%1} +nukkit.command.exception=於 {%1} 執行指令 「{%0}」 時,出現了未被處理的錯誤:{%2} + +nukkit.command.plugins.description=取得伺服器插件列表 +nukkit.command.plugins.success=插件 ({%0}):{%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=重新讀取伺服器設定和插件 +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=重新讀取伺服器... +nukkit.command.reload.reloaded=重新讀取完成 + +nukkit.command.status.description=顯示伺服器狀態。 +nukkit.command.status.usage=/status + +nukkit.command.gc.description=進行清理 +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=紀錄計時資料,以檢視伺服器的效能。 +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=啟用定時和重啟 +nukkit.command.timings.disable=停用定時 +nukkit.command.timings.timingsDisabled=啟用定時工具透過 /timings on +nukkit.command.timings.verboseEnable=啟用詳細的定時 +nukkit.command.timings.verboseDisable=停用詳細的定時 +nukkit.command.timings.reset=定時重啟 +nukkit.command.timings.rcon=警告:在 RCON 上使用容易導致延遲, 您必須在後台或是遊戲中使用 /timings report +nukkit.command.timings.uploadStart=準備定時報告中... +nukkit.command.timings.uploadError=上傳出錯:{%0}: {%1}, 檢查紀錄檔以了解更多訊息 +nukkit.command.timings.reportError=發生錯誤已建立錯誤報告, 請檢視紀錄檔以了解更多訊息 +nukkit.command.timings.timingsLocation=查看定時報告:{%0} +nukkit.command.timings.timingsResponse=計時響應:{%0} +nukkit.command.timings.timingsWrite=計時資料已被儲存至 {%0} + +nukkit.command.title.description=顯示螢幕標題給一個玩家,或者更改該玩家顯示的螢幕標題設置 +nukkit.command.title.usage=/title <玩家> <clear|reset> 或 /title <玩家> <title|subtitle|actionbar> <標題內容> 或 /title <玩家> <times> <淡入時間> <持續時間> <淡出時間> +nukkit.command.title.clear=已清除 {%0} 的標題 +nukkit.command.title.reset=已重設 {%0} 的標題設定 +nukkit.command.title.title=已對 {%1} 顯示了新的標題 "{%0}" +nukkit.command.title.subtitle=已對 {%1} 顯示了新的副標題 "{%0}" +nukkit.command.title.actionbar=已對 {%1} 顯示了新的動作欄標題 "{%0}" +nukkit.command.title.times.success=已變更 {%3} 的標題顯示時間為:{%0}, {%1}, {%2} (淡入時間, 持續時間, 淡出時間). +nukkit.command.title.times.fail=設置失敗, 標題的時間必須為數字值 + +nukkit.command.version.description=檢視此伺服器及其使用的插件的版本 +nukkit.command.version.usage=/version [插件名稱] +nukkit.command.version.noSuchPlugin=此伺服器沒有運行任何名稱相符的插件。使用 /plugins 來取得插件列表。 + +nukkit.command.give.description=給予指定玩家物品 +nukkit.command.give.usage=/give <玩家代號> <物品[:耐久度]> [數量] [附加資料值] + +nukkit.command.kill.description=自殺或殺死其他玩家 +nukkit.command.kill.usage=/kill [玩家代號] + +nukkit.command.particle.description=加入粒子效果至世界 +nukkit.command.particle.usage=/particle <玩家代號> <x> <y> <z> <xd> <yd> <zd> [數量] [資料值] + +nukkit.command.time.description=更改每個世界的時間 +nukkit.command.time.usage=/time <set|add> <數值> 或 /time <start|stop|query> + +nukkit.command.gamerule.description=可設定或查詢遊戲規則值 +nukkit.command.gamerule.usage=/gamerule <規則> [值] + +nukkit.command.debug.description=上傳伺服器資料至 Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=禁止指定的玩家進入伺服器 +nukkit.command.ban.ip.description=禁止指定的 IP 位址進入伺服器 +nukkit.command.banlist.description=檢視伺服器禁止列表 +nukkit.command.defaultgamemode.description=設定預設的遊戲模式 +nukkit.command.deop.description=移除指定玩家的管理員權限 +nukkit.command.difficulty.description=設定遊戲的難易度 +nukkit.command.enchant.description=附魔物品 +nukkit.command.effect.description=增加/減少玩家身上的效果 +nukkit.command.gamemode.description=改變玩家到一個特定的遊戲模式 +nukkit.command.help.description=顯示指令列表 +nukkit.command.kick.description=從伺服器中踢除指定玩家 +nukkit.command.list.description=顯示線上玩家列表 +nukkit.command.me.description=於聊天中作出指定的動作 +nukkit.command.op.description=賦予指定玩家管理員權限 +nukkit.command.unban.player.description=解禁玩家 +nukkit.command.unban.ip.description=解除封鎖 IP 位址 +nukkit.command.save.description=儲存伺服器到磁碟上 +nukkit.command.saveoff.description=停用自動儲存伺服器 +nukkit.command.saveon.description=啟用自動儲存伺服器 +nukkit.command.say.description=以傳送指令者身份廣播指定的訊息 +nukkit.command.seed.description=顯示世界種子碼 +nukkit.command.setworldspawn.description=設定一個世界重生點。未指定坐標,將使用玩家的坐標。 +nukkit.command.spawnpoint.description=設定玩家重生點 +nukkit.command.stop.description=關閉伺服器 +nukkit.command.tp.description=傳送指定玩家(或是自己)到另一位玩家或座標 +nukkit.command.tell.description=傳送私訊給指定玩家 +nukkit.command.weather.description=設定目前地圖的天氣狀態 +nukkit.command.whitelist.description=管理員允許使用此伺服器的玩家列表 +nukkit.command.xp.description=為玩家新增一定數量或等級的經驗 + +nukkit.crash.create=一個不能恢復的錯誤發生了,使伺服器崩潰。正在儲存錯誤報告。 +nukkit.crash.error=未能儲存錯誤報告:{%0} +nukkit.crash.submit=請上傳檔案「{%0}」至線上崩潰資料庫,並把所獲之連結提交至漏洞報告網頁。請盡量提供更多資料。 +nukkit.crash.archive=毀損傾印報告已經自動地被提交到毀損傾印存檔。您可以在{%0} 檢視到它或使用 ID #{%1}。 + +nukkit.debug.enable=啟用 LevelDB 支援 + +nukkit.bugreport.create=檢測到一個錯誤,正在創建錯誤報告。 +nukkit.bugreport.error=無法創建錯誤報告:{%0} +nukkit.bugreport.archive=已創建錯誤報告:{%0} + +nukkit.player.invalidMove={%0} 行動可疑! +nukkit.player.logIn={%0}[/{%1}:{%2}] 登入遊戲,ID 為 {%3} 座標位於 ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] 登出遊戲,原因:{%3} +nukkit.player.invalidEntity={%0} 嘗試攻擊一個無效的實體 + +nukkit.plugin.load=讀取中... {%0} +nukkit.plugin.enable=開啟中... {%0} +nukkit.plugin.disable=關閉中... {%0} +nukkit.plugin.restrictedName=受限的名稱 +nukkit.plugin.incompatibleAPI=不相容的 API 版本 +nukkit.plugin.unknownDependency=本插件無法單獨使用 +nukkit.plugin.circularDependency=檢測出迴圈依賴 +nukkit.plugin.genericLoadError=無法讀取插件 "{%0}" +nukkit.plugin.spacesDiscouraged=插件 "{%0}" 在名稱中使用了空格,不建議這樣做 +nukkit.plugin.loadError=無法讀取插件 "{%0}":{%1} +nukkit.plugin.duplicateError=無法讀取插件 "{%0}":已有相同插件 +nukkit.plugin.fileError=無法讀取在 "{%1}" 資料夾中的 "{%0}":{%2} +nukkit.plugin.commandError=無法讀取 {%1} 插件的 {%0} 指令 +nukkit.plugin.aliasError=無法讀取 {%1} 插件的 {%0} 別名 +nukkit.plugin.deprecatedEvent=插件 "{%0}" 已經使用 "{%2}" 方法註冊了一個在 "{%1}" 的監聽器,但是該事件已過時。 +nukkit.plugin.eventError=無法處理事件 "{%0}" 至 "{%1}":{%2} 在 {%3} 上 + +nukkit.resources.invalid-path=資源包路徑 "{%0}" 已存在且不是一個目錄 +nukkit.resources.unknown-format=由於資​​源包格式無法識別,未能加載 "{%0}" +nukkit.resources.fail=無法加載 "{%0}":{%1} +nukkit.resources.success=成功加載 {%0} 個資源包 +nukkit.resources.zip.not-found=找不到文件 "{%0}" +nukkit.resources.zip.no-manifest=找不到 "manifest.json" +nukkit.resources.zip.invalid-manifest="manifest.json" 無效或不完整 diff --git a/src/main/resources/lang/cze/lang.ini b/src/main/resources/lang/cze/lang.ini new file mode 100644 index 00000000000..a8e46c8c273 --- /dev/null +++ b/src/main/resources/lang/cze/lang.ini @@ -0,0 +1,381 @@ +#Jazykový soubor kompatibilní s identifikátory Minecraft: Bedrock Edition +# +#Zpráva nemusí být tam, aby se zobrazovala správně u klienta. +#Musí být zde pouze zprávy zobrazené v Nukkitu +#Použití vlastností jazyka od PocketMine-MP + +language.name=Czech +language.selected=Vybráno {%0} ({%1}) jako zakladní jazyk + +multiplayer.player.joined={%0} se připojil do hry +multiplayer.player.left={%0} opustil hru + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} získal úspěch {%1} + +disconnectionScreen.outdatedClient=Zastaralý client! +disconnectionScreen.outdatedServer=Zastaralý server! +disconnectionScreen.serverFull=Server je plný! +disconnectionScreen.noReason=Ospojen ze serveru +disconnectionScreen.invalidSkin=Neplatný skin! +disconnectionScreen.invalidName=Neplatná přezdívka! +disconnectionScreen.resourcePack=Při stahování nebo použití resource packu došlo k problému. + +death.fell.accident.generic={%0} zpadl z výšky +death.attack.inFire={%0} vyšel v plamenech +death.attack.onFire={%0} schořel v plamenech +death.attack.lava={%0} zkusil plavat v lávě +death.attack.lava.magma={%0} discovered floor was lava +death.attack.inWall={%0} udusil se ve zdi +death.attack.drown={%0} utopil se +death.attack.cactus={%0} byl upíchán k smrti +death.attack.generic={%0} zemřel +death.attack.explosion={%0} vybuchl +death.attack.explosion.player={%0} byl odhozen {%1} +death.attack.magic={%0} byl zabit magií +death.attack.wither={%0} withered away +death.attack.mob={%0} byl zabit {%1} +death.attack.player={%0} byl zabit {%1} +death.attack.player.item={%0} byl zabit {%1} pouzitim {%2} +death.attack.arrow={%0} byl zastřelen {%1} +death.attack.arrow.item={%0} byl zastřelen {%1} pouzitim {%2} +death.attack.fall={%0} Narazil na zem příliš silně +death.attack.outOfWorld={%0} vypadl ze světa +death.attack.starve={%0} starved to death + +gameMode.survival=Mod preziti +gameMode.creative=Kreativni mod +gameMode.adventure=Dobrodruzny mod +gameMode.spectator=Mod divaka +gameMode.changed=Tvuj herní mod byl aktualizován + +potion.moveSpeed=Rychlost +potion.moveSlowdown=Pomalost +potion.digSpeed=Spěch +potion.digSlowDown=Miningová únava +potion.damageBoost=Síla +potion.heal=Okamžité zdraví +potion.harm=Okamžité poškození +potion.jump=Boost skoku +potion.confusion=Nevolnost +potion.regeneration=Regenerace +potion.resistance=Odpor +potion.fireResistance=Ohnivzdornost +potion.waterBreathing=Pod vodní dýchaní +potion.invisibility=Neviditelnost +potion.blindness=Slepota +potion.nightVision=Noční vydění +potion.hunger=Hlad +potion.weakness=Slabost +potion.poison=Jed +potion.wither=Wither +potion.healthBoost=Boost Zdraví +potion.absorption=Vstřebávání +potion.saturation=Nasycení + +commands.generic.exception=Při pokusu o provedení tohoto příkazu došlo k neznámé chybě +commands.generic.permission=Nemáš oprávnění na tento příkaz +commands.generic.ingame=Tento příkaz můžete provést pouze jako hráč +commands.generic.unknown=Neznámý příkaz. Vyzkoušejte / help pro seznam příkazů +commands.generic.player.notFound=Tento hráč nebyl nalezen +commands.generic.usage=Použij: {%0} + +commands.time.added=Přidáno {%0} k času +commands.time.set=Čas byl nastaven na {%0} +commands.time.query=Čas je {%0} + +commands.me.usage=/me <akce ...> + +commands.give.item.notFound=Neni zadna takova polozka s timto nazvem {%0} +commands.give.success=<Server>Dano {%0} * {%1} na {%2} +commands.give.tagError=Data tag parsovani selhalo: {%0} + +commands.effect.usage=/effect <hrac> <effect> [sekundy/ticky] [sila efektu] [hideParticles] OR /effect <hrac> clear +commands.effect.notFound=Neni zadny takovy effect s ID {%0} +commands.effect.success=Dal jsi {%0} (ID {%1}) * {%2} hraci {%3} na {%4} sekund/ticku +commands.effect.success.removed=Sebral jsi {%0} od {%1} +commands.effect.success.removed.all=Sebral jsi vsechny efekty od {%0} +commands.effect.failure.notActive=Nepodarilo se sebrat efekt {%0} od {%1} protoze nema zadny efekt +commands.effect.failure.notActive.all=Nesebral si efekt {%0} protoze zadny nema + +commands.enchant.noItem=Cil nedrzi zadny takovy item +commands.enchant.notFound=Neni zadny takovy enchant s ID {%0} +commands.enchant.success=Uspesne oenchatovano +commands.enchant.usage=/enchant <hrac> <enchantment ID> [level] + +commands.particle.success=Spustil jsi particle {%0} na dobu {%1} +commands.particle.notFound=Nezname jmeno efektu {%0} + +commands.players.usage=/list +commands.players.list=Zde je {%0}/{%1} hracu online: + +commands.kill.successful=Zabit {%0} +commands.kill.all.successful=Byli zabiti vsichni hraci +commands.kill.entities.successful=Byly zabyty vsechny entity + +commands.banlist.ips=Je zde dohromady {%0} zabanovanych IP adres: +commands.banlist.players=Je zde {%0} zabanovanych hracu: +commands.banlist.usage=/banlist [ips|hracu] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=Defaultni mod hry je {%0} + +commands.op.success=Tento hrac {%0} ziskal opravneni OP +commands.op.usage=/op <hrac> + +commands.deop.success=Tento hrac {%0} pozbyl opravneni OP +commands.deop.usage=/deop <hrac> + +commands.say.usage=/say <zprava ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Zabanovat hrace {%0} +commands.ban.usage=/ban <hrac> [duvod ...] + +commands.unban.success=Odbanovat hrace {%0} +commands.unban.usage=/pardon <hrac> + +commands.banip.invalid=Zadal jsi spatnou IP adresu nebo hrac neni online +commands.banip.offline.invalid=Neni takova IP adresa hrace nebo jsi ji zadal spatne +commands.banip.success=IP adresa zabanovana {%0} +commands.banip.success.players=Zabanovana IP adresa {%0} patrici {%1} +commands.banip.usage=/ban-ip <address|name> [reason ...] + +commands.unbanip.invalid=Zadal jsi nespravnoj IP adresu +commands.unbanip.success=IP adresa odbanovana {%0} +commands.unbanip.usage=/pardon-ip <address> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Zapinam autosave sveta +commands.save.disabled=Vypinam autosavd sveta +commands.save.start=Ukladam... +commands.save.success=Svet ulozen + +commands.stop.usage=/stop +commands.stop.start=Vypínám server + +commands.kick.success=Vykopnut {%0} ze serveru +commands.kick.success.reason=Vypkonut {%0} ze serveru: '{%1}' +commands.kick.usage=/kick <hrac> [duvod ...] + +commands.tp.success=Byl jsi teleportovan z {%0} na {%1} +commands.tp.success.coordinates=Byl jsi teleportovan z {%0} na {%1}, {%2}, {%3} +commands.tp.usage=/tp [target player] <destination player> OR /tp [target player] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=Je zde {%0} (out of {%1} seen) hracu na whitelistu: +commands.whitelist.enabled=Whitelist zapnut +commands.whitelist.disabled=Whitelist vypnut +commands.whitelist.reloaded=Znovu nacist whitelist +commands.whitelist.add.success=Hrac {%0} pridan na whitelist +commands.whitelist.add.usage=/whitelist add <player> +commands.whitelist.remove.success=Hrac {%0} odebran z whitelist +commands.whitelist.remove.usage=/whitelist remove <player> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Nastavil sis herni rezim na {%0} +commands.gamemode.success.other=Herni mod hrace {%0} nastaven na {%1} +commands.gamemode.usage=/gamemode <mode> [player] + +commands.help.header=HelpPage {%0} z {%1} (/help <page>) +commands.help.usage=/help [page|command name] + +commands.message.usage=/tell <player> < message ...> +commands.message.sameTarget=Nemuzes posilat zpravy sobe + +commands.difficulty.usage=/difficulty <new difficulty> +commands.difficulty.success=Obtiznost nastavena na {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [<x> <y> <z>] +commands.spawnpoint.success=Spawn point hrace {%0} nastaven na ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Spawn sveta nastaven na ({%0}, {%1}, {%2}) + +commands.weather.clear=Cistim oblohu +commands.weather.rain=Zapinam dest +commands.weather.thunder=Zapinam bourku +commands.weather.usage=/weather <clear|rain|thunder> [doba trvani v sekundach] + +commands.xp.success=Davam {%0} zkusenosti hraci {%1} +commands.xp.success.levels=Davam {%0} levely/u hraci {%1} +commands.xp.success.levels.minus=Beru {%0} levely/u od hrace {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Data hrace nenalezena "{%0}", vytvarim novy profil +nukkit.data.playerCorrupted=Narusena data hrace "{%0}", vytvarim novy profil +nukkit.data.playerOld=Byla nalezena Stara data hraci "{%0}", upgraduju profil +nukkit.data.saveError=Nepodarilo se ulozit data "{%0}": {%1} + +nukkit.level.notFound=Uroven "{%0}" nenalezena +nukkit.level.loadError=Nepodarilo se nacist uroven "{%0}": {%1} +nukkit.level.generationError=Nepodarilo se vygenerovat uroven "{%0}": {%1} +nukkit.level.tickError=Nepodarilo se tick uroven "{%0}": {%1} +nukkit.level.chunkUnloadError=Chyba pri nacitani chunku: {%0} +nukkit.level.backgroundGeneration=Teren spawnu pro "{%0}" je generovana na pozadi +nukkit.level.defaultError=Nenacetla se defaultni uroven +nukkit.level.preparing=Pripravuji uroven "{%0}" +nukkit.level.unloading=Odnacitavam uroven "{%0}" +nukkit.level.updating=Stara data nelezena prl "{%0}", konvertuji uroven + +nukkit.server.start=Zapinam Minecraft: BE server verze {%0} +nukkit.server.networkError=[Network] Stopuji rozhrani {%0} kvuli {%1} +nukkit.server.networkStart=Zapinam server {%0}:{%1} +nukkit.server.info=Server bezi na {%0} verzi {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Server bezi {%0} {%1} 「{%2}」 implementovano API verze {%3} pro Minecraft: BE {%4} (protocol version {%5}) +nukkit.server.license={%0} je poskytovano GPL License +nukkit.server.tickOverload=Neudrzim to! Neni server prenacteny? +nukkit.server.startFinished=Hotovo ({%0}s)! Pro pomoc, napis "help" nebo "?" +nukkit.server.defaultGameMode=Defaultni herni mod: {%0} +nukkit.server.query.start=Zapinam GS4 status listener +nukkit.server.query.info=Setting query port to {%0} +nukkit.server.query.running=Query is running on {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Nepodarilo se spustit RCON: heslo je prazdne +nukkit.server.rcon.startupError=Nepodarilo se spustit RCON: {%0} +nukkit.server.rcon.running=RCON bezi na {%0}:{%1} + +nukkit.command.alias.illegal=Nepodarilo se vytvorit alias {%0} protoze obsahuje nepovolene znaky +nukkit.command.alias.notFound=Nepodarilo se vytvorit alias {%0} protozr obsahuje prikaz, ktery neexistuje: {%1} +nukkit.command.exception=Nezadana vyjimka pri prevadeni prikazu "{%0}" v {%1}: {%2} + +nukkit.command.plugins.description=Vypise list prikazu bezicich na serveru +nukkit.command.plugins.success=Pluginy ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Reloaduji nastaveni server a pluginy +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reloaduji server... +nukkit.command.reload.reloaded=Reload hotovy. + +nukkit.command.status.description=Kontroluji vykon serveru. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Uvolnuji RAM, vyhazovanim bordelu +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Nahravam casovani, pro test vykonu serveru. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Casovani zapnuto a restartuji +nukkit.command.timings.disable=Vypinam casovani +nukkit.command.timings.timingsDisabled=Prosim zapni casovani pouzitim /timings on +nukkit.command.timings.verboseEnable=Zapnuto uzvanene casovani +nukkit.command.timings.verboseDisable=Vypinam uzvanene casovani +nukkit.command.timings.reset=Reset casovani +nukkit.command.timings.rcon=Upozorneni: Nahlaseni casovani uspesne RCON bude delat lag ostny, pouzijte /timings report ve hre nebo v konzoli +nukkit.command.timings.uploadStart=Pripravuji nahlaseni casovani... +nukkit.command.timings.uploadError=Chyba Uploadu: {%0}: {%1}, kontroluji logy pro vic informaci +nukkit.command.timings.reportError=Objevila se chybicka pri vypisovani reportu, zkontrolujte logy pro vic informaci +nukkit.command.timings.timingsLocation=Zobrazuji nahlaseni casovani: {%0} +nukkit.command.timings.timingsResponse=Ohlas casovani: {%0} +nukkit.command.timings.timingsWrite=Casovani napsano do {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Dostanes verzi serveru, obsahujici pouzivane pluginy +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=Na tomto serveru neni zadny plugin s timhle jmenem. Pouzij /plugins pro ziskani listu pluginu. + +nukkit.command.give.description=Da specifikovanemu hraci presny pocet daneho itemu +nukkit.command.give.usage=/give <hrac> <item[:damage]> [mnozstvi] [tags ...] + +nukkit.command.kill.description=Spacha sebevrazdu nebo zabije daneho hrace +nukkit.command.kill.usage=/kill [player] + +nukkit.command.particle.description=Prida particly do sveta +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [count] [data] + +nukkit.command.time.description=Zmeni cas pro jeden svet +nukkit.command.time.usage=/time <set|add> <value> NEBO /time <start|stop|query> + +nukkit.command.gamerule.description=Nastaví nebo vyšle dotaz na hodnotu herního pravidla +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Zabrani danemu hraci pouzit tento server +nukkit.command.ban.ip.description=Zabrani dane IP adrese pouzit tento server +nukkit.command.banlist.description=Zobrazi seznam zaBANovanych hracu +nukkit.command.defaultgamemode.description=Nastavi defaultni GM +nukkit.command.deop.description=Sebere danemu hraci operatorska prava (OP) +nukkit.command.difficulty.description=Nastavi herni obtiznost +nukkit.command.enchant.description=Prida ocarovani na item +nukkit.command.effect.description=Prida/Odebere efekt danemu hraci +nukkit.command.gamemode.description=Zmeni danemu hraci GM +nukkit.command.help.description=Zobrazi Help menu +nukkit.command.kick.description=Vyhodi daneho hrace ze serveru +nukkit.command.list.description=Seznam vsech hracu kteri jsou online +nukkit.command.me.description=Udela danou akci v chatu +nukkit.command.op.description=Da danemu hraci operatorka prava (OP) +nukkit.command.unban.player.description=Povoli danemu hraci pouzit tento server +nukkit.command.unban.ip.description=Povoli dane IP adrese pouzit tento srrver +nukkit.command.save.description=Ulozi server na disk +nukkit.command.saveoff.description=Vypne automaticke ukladani +nukkit.command.saveon.description=Zapne automaticke ukladani +nukkit.command.say.description=Rekni neco jako server +nukkit.command.seed.description=Ukaze ti seed sveta +nukkit.command.setworldspawn.description=Nastavi spawn pro svet. Pokud nebyli napsany zadne souradnice, budou pouzity souradnice hrace. +nukkit.command.spawnpoint.description=Nastavit hracuv spawn +nukkit.command.stop.description=Zastavi server +nukkit.command.tp.description=Teleportuje daneho hrace (nebo tebe) k jinemu hraci nebo na souradnice +nukkit.command.tell.description= Posle soukromou zpravu danemu hraci +nukkit.command.weather.description=Nastavi pocasi pro dany svet +nukkit.command.xp.description=Da danemu hraci presnou sumu zkusenosti +nukkit.command.whitelist.description=Upravi seznam hracu kteri jsou na whitelistu + +nukkit.crash.create=Neobnovitelna chyba se objevila a server crashnul. Vytvarim crash dump +nukkit.crash.error=Nepodarilo se vytvoritcrash dump: {%0} +nukkit.crash.submit=Prosim uploadujte "{%0}" soubor do Crash Archivu a pridejte odkaz na Bug Reporting stranku. Dej nam tolik informaci kolik jen muzes. +nukkit.crash.archive=Crash dump byl automaticky pridam do Crash Archivu. Muzes si ho zobrazi na {%0} nebo pouzi ID #{%1}. + +nukkit.debug.enable=LevelDB podpora zapnuta + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} se hnul spatne! +nukkit.player.logIn={%0}[/{%1}:{%2}] se prihlasil s entity id {%3} na ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] se prihlasil diky {%3} +nukkit.player.invalidEntity={%0} se pokusil zautocit na neznamou entitu + +nukkit.plugin.load=Nacitam {%0} +nukkit.plugin.enable=Zapinam {%0} +nukkit.plugin.disable=Vypinam {%0} +nukkit.plugin.restrictedName=Zakazane jmeno +nukkit.plugin.incompatibleAPI=Nekompatibilni API verze +nukkit.plugin.unknownDependency=Neznama zavislost +nukkit.plugin.circularDependency=Cirkularni zavislost nalezena +nukkit.plugin.genericLoadError=Nelze nacist plugin"{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" pouziva mezery ve svem jmene, je to nechutne +nukkit.plugin.loadError=Nepodarilo se nacist plugin "{%0}": {%1} +nukkit.plugin.duplicateError=Nepodarilo se nacist "{%0}": plugin existuje +nukkit.plugin.fileError=Nejde nacist "{%0}" ve slozce "{%1}": {%2} +nukkit.plugin.commandError=Nepodarilo se nacist prikaz {%0} pro plugin {%1} +nukkit.plugin.aliasError=Nejde nacist alias {%0} pro plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" registroval listener pro "{%1}" na metodu "{%2}", ale tato udalost je zastarala. +nukkit.plugin.eventError=Nepodarilo se provest udalost "{%0}" v "{%1}": {%2} na {%3} + +nukkit.resources.invalid-path=Resource pack cesta "{%0}" existuje a není adresářem +nukkit.resources.unknown-format=Nelze načíst "{%0}" protože formát není rozpoznán +nukkit.resources.fail=Nelze načíst "{%0}": {%1} +nukkit.resources.success=Úspěšne načteno {%0} resource packs +nukkit.resources.zip.not-found=Soubor "{%0}" nebyl nalezen +nukkit.resources.zip.no-manifest="manifest.json" nebyl nalezen +nukkit.resources.zip.invalid-manifest="manifest.json" je neplatný nebo neúplný diff --git a/src/main/resources/lang/deu/lang.ini b/src/main/resources/lang/deu/lang.ini new file mode 100644 index 00000000000..21d3454e088 --- /dev/null +++ b/src/main/resources/lang/deu/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Deutsch +language.selected={%0} ({%1}) als Standard eingestellt + +multiplayer.player.joined={%0} betrat das Spiel +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 die Errungenschaft {%1} erzielt + +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! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +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.lava.magma={%0} wurde der Boden zu heiß +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 +death.attack.starve={%0} starved to death + +gameMode.survival=Überlebensmodus +gameMode.creative=Kreativmodus +gameMode.adventure=Abenteuermodus +gameMode.spectator=Zuschauermodus +gameMode.changed=Dein Spielmodus wurde aktualisiert + +potion.moveSpeed=Schnelligkeit +potion.moveSlowdown=Langsamkeit +potion.digSpeed=Eile +potion.digSlowDown=Langsames Abbauen +potion.damageBoost=Stärke +potion.heal=Direktheilung +potion.harm=Schaden +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=Wither +potion.healthBoost=Lebenserweiterung +potion.absorption=Absorption +potion.saturation=Sättigung + +commands.generic.exception=Es trat ein unbekannter Fehler, beim Ausführen des Befehls, auf! +commands.generic.permission=Du hast nicht die Erlaubnis, diesen Befehl auszuführen! +commands.generic.ingame=Dieser Befehl kann nur als Spieler ausgeführt werden! +commands.generic.unknown=Unbekannter Befehl. Gebe /help ein, um eine Übersicht der Befehle zu sehen! +commands.generic.player.notFound=Der Spieler wurde nicht gefunden! +commands.generic.usage=Nutze: {%0} + +commands.time.added={%0} wurde als Zeit hinzugefügt! +commands.time.set=Zeit wurde auf {%0} gestellt! +commands.time.query=Die Zeit ist {%0} + +commands.me.usage=/me <aktion ...> + +commands.give.item.notFound=Es gibt kein Item mit dem Namen: {%0} +commands.give.success=Gebe {%0} * {%1} zu {%2} +commands.give.tagError=Data tag parsing failed: {%0} + +commands.effect.usage=/effect <Spieler> <Effekt> [Sekunden] [amplifier] [hideParticles] ODER /effect <Spieler> clear +commands.effect.notFound=Es gibt keinen Mob Effekt mit der ID {%0} +commands.effect.success=Gebe {%0} (ID {%1}) * {%2} zu {%3} für {%4} Sekunden +commands.effect.success.removed=Nehme {%0} vom {%1} +commands.effect.success.removed.all=Alle Effekte von {%0} entfernt! +commands.effect.failure.notActive=Der Effekt {%0} konnte nicht von {%1} entfernt werden, weil er den Effekt nicht besitzt! +commands.effect.failure.notActive.all=Es konnten keine Effekte von {%0} entfernt werden, da er keine besitzt! + +commands.enchant.noItem=The target doesn't hold an item +commands.enchant.notFound=Es gibt keine Verzauberung mit der ID {%0} +commands.enchant.success=Verzaubert! +commands.enchant.usage=/enchant <Spieler> <Verzauberungs-ID> [Level] + +commands.particle.success=Der Effekt {%0} bleibt {%1} Sekunden +commands.particle.notFound=Unbekannter Effektname {%0} + +commands.players.usage=/list +commands.players.list=Es sind {%0}/{%1} Spieler online: + +commands.kill.successful={%0} wurde umgebracht! +commands.kill.all.successful=Alle Spieler wurde umgebracht +commands.kill.entities.successful=Alle Entitäten wurde umgebracht + +commands.banlist.ips=Es gibt {%0} gebannte IP-Adressen: +commands.banlist.players=Es gibt {%0} gebannte Spieler: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=Der Welt Spielmodus wurde auf {%0} gesetzt! + +commands.op.success=Opped {%0} +commands.op.usage=/op <Spieler> + +commands.deop.success=De-opped {%0} +commands.deop.usage=/deop <Spieler> + +commands.say.usage=/say <Nachricht ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Der Spieler {%0} wurde gebannt! +commands.ban.usage=/ban <Spieler> [Grund ...] + +commands.unban.success=Der Spieler {%0} wurde entbannt! +commands.unban.usage=/pardon <Spieler> + +commands.banip.invalid=Du hast eine ungültige IP-Adresse angegeben oder die IP ist offline! +commands.banip.offline.invalid=Diese IP-Adresse ist nicht in den Spielerdaten oder die IP-Adresse ist ungültig! +commands.banip.success=IP-Adresse {%0} wurde gebannt! +commands.banip.success.players=Die IP-Adresse {%0} von {%1} wurde gebannt! +commands.banip.usage=/ban-ip <IP|Spieler> [Grund ...] + +commands.unbanip.invalid=Ungültige IP-Adresse! +commands.unbanip.success=Die IP-Adresse {%0} wurde entbannt! +commands.unbanip.usage=/pardon-ip <IP> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Automatisches Welt-Speichern wurde aktiviert! +commands.save.disabled=Automatisches Welt-Speichern wurde deaktiviert! +commands.save.start=Speichere... +commands.save.success=Die Welt wurde gespeichert. + +commands.stop.usage=/stop +commands.stop.start=Server wird gestoppt! + +commands.kick.success=Der Spieler {%0} wurde gekickt! +commands.kick.success.reason=Der Spieler {%0} wurde wegen '{%1}' vom Server gekickt! +commands.kick.usage=/kick <Spieler> [Grund ...] + +commands.tp.success={%0} zu {%1} teleportiert! +commands.tp.success.coordinates=Teleportiere {%0} zu {%1}, {%2}, {%3} +commands.tp.usage=/tp [teleportierender Spieler] <Ziel-Spieler> ODER /tp [teleportierender Spieler] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=Es sind {%0} (out of {%1} seen) Spieler gewhitelistet: +commands.whitelist.enabled=Whitelist aktiviert +commands.whitelist.disabled=Whitelist deaktiviert +commands.whitelist.reloaded=Whitelist neugeladen +commands.whitelist.add.success=Spieler {%0} wurde zur Whitelist hinzugefügt! +commands.whitelist.add.usage=/whitelist add <Spieler> +commands.whitelist.remove.success=Spieler {%0} wurde von der Whitelist entfernt! +commands.whitelist.remove.usage=/whitelist remove <Spieler> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Spielmodus zu {%0} gesetzt +commands.gamemode.success.other=Spielmodus von {%0} auf {%1} gesetzt +commands.gamemode.usage=/gamemode <Modus> [Spieler] + +commands.help.header=--- Befehlsseite {%0} von {%1} (/help <Seite>) --- +commands.help.usage=/help [Seite|Befehl] + +commands.message.usage=/tell <Spieler> <Nachricht ...> +commands.message.sameTarget=Du kannst keine Nachricht die selbst senden! + +commands.difficulty.usage=/difficulty <Schwierigkeit> +commands.difficulty.success=Der Schwierigkeitsgrad wurde auf {%0} gesetzt + +commands.spawnpoint.usage=/spawnpoint [Spieler] [<x> <y> <z>] +commands.spawnpoint.success=Der Spawnpoint von {%0} wurde zu ({%1}, {%2}, {%3}) gesetzt + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Der Weltspawn wurde zu ({%0}, {%1}, {%2}) gesetzt + +commands.weather.clear=Wetter auf Sonnenschein gesetzt +commands.weather.rain=Wetter auf Regen gesetzt +commands.weather.thunder=Wetter auf Sturm gesetzt +commands.weather.usage=/weather <clear|rain|thunder> [Dauer] + +commands.xp.success={%0} Erfahrung zu {%1} gegeben +commands.xp.success.levels={%0} Level zu {%1} gegeben +commands.xp.success.levels.minus={%0} Level von {%1} entfernt +commands.xp.usage=/xp <Anzahl> [Spieler] ODER /xp <Level>L [Spieler] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Keine Spielerdaten von "{%0}" vorhanden, neue Spielerdaten werden erstellt +nukkit.data.playerCorrupted=Fehlerhafte Spielerdaten von "{%0}" vorhanden, erstelle neue Spielerdate +nukkit.data.playerOld=Alte Spielerdaten von "{%0}" vorhanden, upgrade Spielerdaten +nukkit.data.saveError=Spieler "{%0}": {%1} konnte nicht gespeichert + +nukkit.level.notFound=Welt "{%0}" nicht gefunden +nukkit.level.loadError=Konnte Welt "{%0}" nicht laden: {%1} +nukkit.level.generationError=Konnte Welt "{%0}" nicht generieren: {%1} +nukkit.level.tickError=Welt "{%0}" konnte nicht getickt werden: {%1} +nukkit.level.chunkUnloadError=Fehler beim Entladen eines Chunks: {%0} +nukkit.level.backgroundGeneration=Spawn-Landschaft für Level "{%0}" wird im Hintergrund generiert +nukkit.level.defaultError=Keine Standart-Welt wurde geladen +nukkit.level.preparing=Vorbereiten der Welt "{%0}" +nukkit.level.unloading=Entladen der Welt "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Starte Minecraft: BE mit Version {%0} +nukkit.server.networkError=[Netzwerk] Interface {%0} wegen {%1} gestoppt +nukkit.server.networkStart=Starte Server auf {%0}:{%1} +nukkit.server.info=Dieser Server startet mit der Version {%0} {%1} "{%2}" (API: {%3}) +nukkit.server.info.extended=Dieser Server läuft unter der Version {%0} {%1} 「{%2}」und nutzt die Version {%3} der API für Minecraft:BE {%4} (Protokoll Version {%5}) +nukkit.server.license={%0} wird unter der GPL-Lizenz vertrieben +nukkit.server.tickOverload=Kann nicht mithalten! Ist der Server überladen? +nukkit.server.startFinished=Fertig! ({%0}s)! Für Hilfe, tippe "help" oder "?" +nukkit.server.defaultGameMode=Standard-Spielmodus: {%0} +nukkit.server.query.start=Starte GS4 Status-Listener +nukkit.server.query.info=Setze Query-Port auf {%0} +nukkit.server.query.running=Query startet auf {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Fehler beim Starten von RCON: Leeres Passwort +nukkit.server.rcon.startupError=Fehler beim Starten von RCON: {%0} +nukkit.server.rcon.running=RCON läuft auf {%0}:{%1} + +nukkit.command.alias.illegal=Konnte Alias {%0} nicht registrieren, da er unzulässige Zeichen enthält +nukkit.command.alias.notFound=Konnte Alias {%0} nicht registrieren, da er Kommandos enthält, die nicht existieren: {%1} +nukkit.command.exception=Unbehandelter Fehler beim Ausführen des Kommandos "{%0}" in {%1}: {%2} + +nukkit.command.plugins.description=Gibt eine Liste aller auf dem Server laufenden Plugins zurück +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Lädt die Servereinstellungen und Plugins neu +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Lade Server neu... +nukkit.command.reload.reloaded=Neuladen abgeschlossen. + +nukkit.command.status.description=Gibt die Serverauslastung aus. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Startet Automatische Speicherbereinigungs Aufgaben +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Nimmt Zeitmessungen auf, um die Severauslastung zu ermitteln. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Timings & Reset aktiviert +nukkit.command.timings.disable=Timings deaktiviert +nukkit.command.timings.timingsDisabled=Bitte aktiviere Timings, indem du '/timings on' eingibst +nukkit.command.timings.verboseEnable=Aktiviere dauerhafte Timingsanzeige +nukkit.command.timings.verboseDisable=Deaktiviere dauerhafte Timingsanzeige +nukkit.command.timings.reset=Timings Reset +nukkit.command.timings.rcon=Warnung: Timings Report von RCON aus treten vielleicht Lags auf, du solltest /timings report im Spiel oder Konsole verwenden +nukkit.command.timings.uploadStart=Bereite Timings Report vor... +nukkit.command.timings.uploadError=Beim Hochladen trat ein Fehler auf: {%0}: {%1}, Siehe in die Logs um mehr Informationen zu erhalten +nukkit.command.timings.reportError=Beim Ausgeben der Timings trat ein Fehler auf, siehe in die Logs um mehr Informationen zu erhalten +nukkit.command.timings.timingsLocation=Timings wurden hier hochgeladen: {%0} +nukkit.command.timings.timingsResponse=Du kannst die Ergebnisse hier lesen: {%0} +nukkit.command.timings.timingsWrite=Zeiten geschrieben zu {%0} + +nukkit.command.title.description=Sendet einen Titel an den angegebenen Spieler oder ändert die Titeleinstellungen für diesen Spieler. +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Titelanimationseinstellungen für {%0} +nukkit.command.title.title=Erfolgreich gezeigt "{%0}" Titel zu {%1} +nukkit.command.title.subtitle=Untertitel erfolgreich auf "{%0}" für {%1}'s nächster Titel. +nukkit.command.title.actionbar=Erfolgreich gezeigt "{%0}" actionbar Titel zu {%1} +nukkit.command.title.times.success=Erfolgreich festgelegt Titelanimationszeiten auf {%0}, {%1}, {%2} für {%3} +nukkit.command.title.times.fail=Zeiten von Titelanimationen müssen numerische Werte sein. + +nukkit.command.version.description=Gibt die Version eines Plugins wieder +nukkit.command.version.usage=/version [Plugin] +nukkit.command.version.noSuchPlugin=Dieses Plugin ist nicht installiert. Nutze /plugins um eine Liste mit allen Plugins zu erhalten. + +nukkit.command.give.description=Gibt einem Spieler eine bestimmte Anzahl an Items +nukkit.command.give.usage=/give <Spieler> <item[:damage]> [amount] [tags ...] + +nukkit.command.kill.description=Tötet einen Spieler (oder dich) +nukkit.command.kill.usage=/kill [Spieler] + +nukkit.command.particle.description=Setzt Partikel in einer Welt +nukkit.command.particle.usage=/particle <Weltname> <x> <y> <z> <xd> <yd> <zd> [Anzahl] [Partikelname] + +nukkit.command.time.description=Wechselt die Zeit in jeder Welt +nukkit.command.time.usage=/time <set|add> <Name/Zeit> ODER /time <start|stop|query> + +nukkit.command.gamerule.description=Setzt einen Spielregelwert fest oder fragt ihn ab +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Bannt einen Spieler und verhindert das er den Server betreten kann +nukkit.command.ban.ip.description=Bannt eine IP und verhindert das sie den Server betreten kann +nukkit.command.banlist.description=Listet alle gebannten Spieler auf +nukkit.command.defaultgamemode.description=Setzt den Standard-Spielmodus +nukkit.command.deop.description=Entfernt den Admin-/OP-Rang von einem Spieler +nukkit.command.difficulty.description=Setzt die Spielschwierigkeit +nukkit.command.enchant.description=Verzaubert Items +nukkit.command.effect.description=Gibt/Entfernt Effekte von Spielren +nukkit.command.gamemode.description=Wechselt von einem Spieler den SpielmodusCircular dependency detected +nukkit.command.help.description=Zeigt eine Befehlsliste +nukkit.command.kick.description=Entfernt/Kickt einen Spieler vom Server +nukkit.command.list.description=Listet alle Online-Spieler auf +nukkit.command.me.description=Gibt eine Aktion im Chat wieder +nukkit.command.op.description=Gibt einem Spieler OP-/Admin-Rechte +nukkit.command.unban.player.description=Entbannt einen Spieler +nukkit.command.unban.ip.description=Entbannt eine IP-Adresse +nukkit.command.save.description=Speichert den Server +nukkit.command.saveoff.description=Deaktiviert automatisches Server Speichern +nukkit.command.saveon.description=Aktiviert automatisches Server Speichern +nukkit.command.say.description=Gibt die Nachricht als Sender aus +nukkit.command.seed.description=Zeigt den Welt-Seed +nukkit.command.setworldspawn.description=Setzt einen Spawnpoin an bestimmten Koordinaten. Wenn keine Koordinaten angegeben werden, werden die Spieler Koordinaten verwendet. +nukkit.command.spawnpoint.description=Setzt den Spieler Spawnpunkt +nukkit.command.stop.description=Stoppt den Server +nukkit.command.tp.description=Teleportiert einen anderen Spieler (oder Du selbst) zu einem Spieler oder zu Koordinaten +nukkit.command.tell.description=Sendet eine private Nachricht an einen Spieler +nukkit.command.weather.description=Setzt das derzeitige Wetter in der Welt +nukkit.command.xp.description=Gibt einem Spieler eine bestimmte Anzahl an Erfahrung +nukkit.command.whitelist.description=Eine Liste der Spieler die den Server bei aktivierter Whitelist betreten darf + +nukkit.crash.create=Es trat ein unbekannter Fehler auf bei dem der Server gecrasht ist. Eine Crash-Datei wird erstellt... +nukkit.crash.error=Eine Crash-Datei konnte nicht erstellt werden: {%0} +nukkit.crash.submit=Bitte lade die "{%0}" Datei in das Crash Archive hoch und erstelle einen Bugreport auf der Bugreport-Seite. Gebe uns so viele Informationen wie du kannst. +nukkit.crash.archive=Eine Crash-Datei wurde in Archiv abgelegt. Du kannst es auf {%0} oder mit der ID #{%1} anschauen. + +nukkit.debug.enable=LevelDB support aktiviert + +nukkit.bugreport.create=Ein Fehler ist entstanden, erstelle ein Bug Report. +nukkit.bugreport.error=Konnte kein Bug Report machen aufgrund: {%0} +nukkit.bugreport.archive=Ein Bug Report wurde erfolgreich erstellt: {%0} + +nukkit.player.invalidMove={%0} bewegte sich fehlerhaft! +nukkit.player.logIn={%0}[/{%1}:{%2}] hat sich mit der ID {%3} bei ({%4}, {%5}, {%6}, {%7}) angemeldet +nukkit.player.logOut={%0}[/{%1}:{%2}] hat sich ausgeloggt {%3} +nukkit.player.invalidEntity={%0} versuchte ein fehlerhaftes Entity anzugreifen + +nukkit.plugin.load=Lade {%0} +nukkit.plugin.enable=Aktiviere {%0} +nukkit.plugin.disable=Deaktiviere {%0} +nukkit.plugin.restrictedName=Verbotener Name +nukkit.plugin.incompatibleAPI=Inkompatible API +nukkit.plugin.unknownDependency=Unbekannte Dependency +nukkit.plugin.circularDependency=Ringabhängigkeit erkannt +nukkit.plugin.genericLoadError=Das Plugin "{%0}" konnte nicht geladen werden. +nukkit.plugin.spacesDiscouraged=Das Plugin "{%0}" nutzt Leerzeichen in seinem Namen, dies ist nicht erlaubt +nukkit.plugin.loadError=Das Plugin "{%0}": {%1} konnte nicht geladen werden. +nukkit.plugin.duplicateError=Das Plugin "{%0}" ist doppelt vorhanden. +nukkit.plugin.fileError=Die Datei "{%0}" im Ordner "{%1}": {%2} konnte nicht geladen werden! +nukkit.plugin.commandError=Der Befehl {%0} vom Plugin {%1} konnte nicht geladen werden! +nukkit.plugin.aliasError=Der Alias {%0} vom Plugin {%1} konnte nicht geladen werden. +nukkit.plugin.deprecatedEvent=Das Plugin "{%0}" hat ein veraltetes Event "{%1}" in Methode "{%2}" benutzt. +nukkit.plugin.eventError=Das Event "{%0}" konnte nicht zu "{%1}": {%2} in Zeile {%3} weitergegeben werden + +nukkit.resources.invalid-path=Resource packs Pfad "{%0}" existiert nicht. +nukkit.resources.unknown-format=Konnte nicht "{%0}" laden weil das Format nicht erkannt wurde. +nukkit.resources.fail=Konnte nicht laden "{%0}": {%1} +nukkit.resources.success=Erfolgreich Resource Pack {%0} geladen. +nukkit.resources.zip.not-found=Datei "{%0}" wurde nicht gefunden. +nukkit.resources.zip.no-manifest="manifest.json" wurde nicht gefunden. +nukkit.resources.zip.invalid-manifest="manifest.json" ist fehlerhaft oder existiert nicht. diff --git a/src/main/resources/lang/eng/lang.ini b/src/main/resources/lang/eng/lang.ini new file mode 100644 index 00000000000..df31415f845 --- /dev/null +++ b/src/main/resources/lang/eng/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +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.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.resourcePack=Encountered a problem while downloading or applying resource pack. + +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.lava.magma={%0} discovered floor was 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 +death.attack.starve={%0} starved to death + +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.ingame=You can only perform this command as a player +commands.generic.unknown=Unknown command. Try /help for a list of commands +commands.generic.player.notFound=That player cannot be found +commands.generic.usage=Usage: {%0} + +commands.time.added=Added {%0} to the time +commands.time.set=Set the time to {%0} +commands.time.query=Time is {%0} + +commands.me.usage=/me <action ...> + +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.usage=/effect <player> <effect> [seconds] [amplifier] [hideParticles] OR /effect <player> clear +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.noItem=The target doesn't hold an item +commands.enchant.notFound=There is no such enchantment with ID {%0} +commands.enchant.success=Enchanting succeeded +commands.enchant.usage=/enchant <player> <enchantment ID> [level] + +commands.particle.success=Playing effect {%0} for {%1} times +commands.particle.notFound=Unknown effect name {%0} + +commands.players.usage=/list +commands.players.list=There are {%0}/{%1} players online: + +commands.kill.successful=Killed {%0} +commands.kill.all.successful=Killed all players +commands.kill.entities.successful=Killed all entities + +commands.banlist.ips=There are {%0} total banned IP addresses: +commands.banlist.players=There are {%0} total banned players: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=The world's default game mode is now {%0} + +commands.op.success=Opped {%0} +commands.op.usage=/op <player> + +commands.deop.success=De-opped {%0} +commands.deop.usage=/deop <player> + +commands.say.usage=/say <message ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Banned player {%0} +commands.ban.usage=/ban <name> [reason ...] + +commands.unban.success=Unbanned player {%0} +commands.unban.usage=/pardon <name> + +commands.banip.invalid=You have entered an invalid IP address or a player that is not online +commands.banip.offline.invalid=There is no IP address in player data or IP address is invalid +commands.banip.success=Banned IP address {%0} +commands.banip.success.players=Banned IP address {%0} belonging to {%1} +commands.banip.usage=/ban-ip <address|name> [reason ...] + +commands.unbanip.invalid=You have entered an invalid IP address +commands.unbanip.success=Unbanned IP address {%0} +commands.unbanip.usage=/pardon-ip <address> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +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 + +commands.stop.usage=/stop +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.kick.usage=/kick <player> [reason ...] + +commands.tp.success=Teleported {%0} to {%1} +commands.tp.success.coordinates=Teleported {%0} to {%1}, {%2}, {%3} +commands.tp.usage=/tp [target player] <destination player> OR /tp [target player] <x> <y> <z> [<y-rot> <x-rot>] + +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 <player> +commands.whitelist.remove.success=Removed {%0} from the whitelist +commands.whitelist.remove.usage=/whitelist remove <player> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Set own game mode to {%0} +commands.gamemode.success.other=Set {%0}'s game mode to {%1} +commands.gamemode.usage=/gamemode <mode> [player] + +commands.help.header=--- Showing help page {%0} of {%1} (/help <page>) --- +commands.help.usage=/help [page|command name] + +commands.message.usage=/tell <player> <private message ...> +commands.message.sameTarget=You can't send a private message to yourself! + +commands.difficulty.usage=/difficulty <new difficulty> +commands.difficulty.success=Set game difficulty to {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [<x> <y> <z>] +commands.spawnpoint.success=Set {%0}'s spawn point to ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Set the world spawn point to ({%0}, {%1}, {%2}) + +commands.weather.clear=Changing to clear weather +commands.weather.rain=Changing to rainy weather +commands.weather.thunder=Changing to rain and thunder +commands.weather.usage=/weather <clear|rain|thunder> [duration in seconds] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Player data not found for "{%0}", creating new profile +nukkit.data.playerCorrupted=Corrupted data found for "{%0}", creating new profile +nukkit.data.playerOld=Old Player data found for "{%0}", upgrading profile +nukkit.data.saveError=Could not save player "{%0}": {%1} + +nukkit.level.notFound=Level "{%0}" not found +nukkit.level.loadError=Could not load level "{%0}": {%1} +nukkit.level.generationError=Could not generate level "{%0}": {%1} +nukkit.level.tickError=Could not tick level "{%0}": {%1} +nukkit.level.chunkUnloadError=Error while unloading a chunk: {%0} +nukkit.level.backgroundGeneration=Spawn terrain for level "{%0}" is being generated in the background +nukkit.level.defaultError=No default level has been loaded +nukkit.level.preparing=Preparing level "{%0}" +nukkit.level.unloading=Unloading level "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Starting Minecraft: BE server version {%0} +nukkit.server.networkError=[Network] Stopped interface {%0} due to {%1} +nukkit.server.networkStart=Opening server on {%0}:{%1} +nukkit.server.info=This server is running {%0} version {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: BE {%4} (protocol version {%5}) +nukkit.server.license={%0} is distributed under the GPL License +nukkit.server.tickOverload=Can't keep up! Is the server overloaded? +nukkit.server.startFinished=Done ({%0}s)! For help, type "help" or "?" +nukkit.server.defaultGameMode=Default game type: {%0} +nukkit.server.query.start=Starting GS4 status listener +nukkit.server.query.info=Setting query port to {%0} +nukkit.server.query.running=Query is running on {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Could not register alias {%0} because it contains illegal characters +nukkit.command.alias.notFound=Could not register alias {%0} because it contains commands that do not exist: {%1} +nukkit.command.exception=Unhandled exception executing command "{%0}" in {%1}: {%2} + +nukkit.command.plugins.description=Gets a list of plugins running on the server +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Reloads the server configuration and plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reloading server... +nukkit.command.reload.reloaded=Reload complete. + +nukkit.command.status.description=Reads back the server's performance. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Fires garbage collection tasks +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Records timings to see performance of the server. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Gets the version of this server including any plugins in use +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=This server is not running any plugin by that name. Use /plugins to get a list of plugins. + +nukkit.command.give.description=Gives the specified player a certain amount of items +nukkit.command.give.usage=/give <player> <item[:damage]> [amount] [tags ...] + +nukkit.command.kill.description=Commit suicide or kill other players +nukkit.command.kill.usage=/kill [player] + +nukkit.command.particle.description=Adds particles to a world +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [count] [data] + +nukkit.command.time.description=Changes the time on each world +nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> + +nukkit.command.gamerule.description=Sets or queries a game rule value +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Prevents the specified player from using this server +nukkit.command.ban.ip.description=Prevents the specified IP address from using this server +nukkit.command.banlist.description=View all players banned from this server +nukkit.command.defaultgamemode.description=Set the default gamemode +nukkit.command.deop.description=Takes the specified player's operator status +nukkit.command.difficulty.description=Sets the game difficulty +nukkit.command.enchant.description=Adds enchantments on items +nukkit.command.effect.description=Adds/Removes effects on players +nukkit.command.gamemode.description=Changes the player to a specific game mode +nukkit.command.help.description=Shows the help menu +nukkit.command.kick.description=Removes the specified player from the server +nukkit.command.list.description=Lists all online players +nukkit.command.me.description=Performs the specified action in chat +nukkit.command.op.description=Gives the specified player operator status +nukkit.command.unban.player.description=Allows the specified player to use this server +nukkit.command.unban.ip.description=Allows the specified IP address to use this server +nukkit.command.save.description=Saves the server to disk +nukkit.command.saveoff.description=Disables server autosaving +nukkit.command.saveon.description=Enables server autosaving +nukkit.command.say.description=Broadcasts the given message as the sender +nukkit.command.seed.description=Shows the world seed +nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. +nukkit.command.spawnpoint.description=Sets a player's spawn point +nukkit.command.stop.description=Stops the server +nukkit.command.tp.description=Teleports the given player (or yourself) to another player or coordinates +nukkit.command.tell.description=Sends a private message to the given player +nukkit.command.weather.description=Sets the weather in current world +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Manages the list of players allowed to use this server + +nukkit.crash.create=An unrecoverable error has occurred and the server has crashed. Creating a crash dump +nukkit.crash.error=Could not create crash dump: {%0} +nukkit.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. +nukkit.crash.archive=The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. + +nukkit.debug.enable=LevelDB support enabled + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} moved wrongly! +nukkit.player.logIn={%0}[/{%1}:{%2}] logged in with entity id {%3} at ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] logged out due to {%3} +nukkit.player.invalidEntity={%0} tried to attack an invalid entity + +nukkit.plugin.load=Loading {%0} +nukkit.plugin.enable=Enabling {%0} +nukkit.plugin.disable=Disabling {%0} +nukkit.plugin.restrictedName=Restricted name +nukkit.plugin.incompatibleAPI=Incompatible API version +nukkit.plugin.unknownDependency=Unknown dependency +nukkit.plugin.circularDependency=Circular dependency detected +nukkit.plugin.genericLoadError=Could not load plugin "{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" uses spaces in its name, this is discouraged +nukkit.plugin.loadError=Could not load plugin "{%0}": {%1} +nukkit.plugin.duplicateError=Could not load plugin "{%0}": plugin exists +nukkit.plugin.fileError=Could not load "{%0}" in folder "{%1}": {%2} +nukkit.plugin.commandError=Could not load command {%0} for plugin {%1} +nukkit.plugin.aliasError=Could not load alias {%0} for plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" has registered a listener for "{%1}" on method "{%2}", but the event is Deprecated. +nukkit.plugin.eventError=Could not pass event "{%0}" to "{%1}": {%2} on {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/fin/lang.ini b/src/main/resources/lang/fin/lang.ini new file mode 100644 index 00000000000..96c9a8afca5 --- /dev/null +++ b/src/main/resources/lang/fin/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Suomi +language.selected=Valittiin kieleksi suomi (fin) + +multiplayer.player.joined={%0} liittyi peliin +multiplayer.player.left={%0} poistui pelistä + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} ansaitsi saavutuksen {%1} + +disconnectionScreen.outdatedClient=Vanhentunut asiakasohjelma! +disconnectionScreen.outdatedServer=Vanhentunut palvelin! +disconnectionScreen.serverFull=Palvelin on täynnä! +disconnectionScreen.noReason=Yhteys palvelimeen katkaistu +disconnectionScreen.invalidSkin=Virheellinen olemus! +disconnectionScreen.invalidName=Virheellinen nimi! +disconnectionScreen.resourcePack=Resurssipaketin kanssa esiintyi ongelma + +death.fell.accident.generic={%0} putosi korkealta +death.attack.inFire={%0} paloi poroksi +death.attack.onFire={%0} käristyi korpuksi +death.attack.lava={%0} yritti uida laavassa +death.attack.lava.magma={%0} huomasi lattian olevan laavaa +death.attack.inWall={%0} tukehtui seinään +death.attack.drown={%0} hukkui +death.attack.cactus={%0} tunsi piston sydämmessään ja kuoli +death.attack.generic={%0} kuoli +death.attack.explosion={%0} räjähti +death.attack.explosion.player={%0} joutui pelaajan {%1} räjäyttämäksi +death.attack.magic={%0} taiottiin hengiltä +death.attack.wither={%0} näivettyi hengiltä +death.attack.mob={%1} tappoi pelaajan {%0} +death.attack.player={%1} tappoi pelaajan {%0} +death.attack.player.item={%1} tappoi pelaajan {%0} käyttäen {%2} +death.attack.arrow={%1} ampui pelaajan {%0} +death.attack.arrow.item={%0} joutui pelaajan {%1} ampumaksi aseella {%2} +death.attack.fall={%0} iskeytyi maahan liian lujaa +death.attack.outOfWorld={%0} tipahti maailman laidalta +death.attack.starve={%0} nääntyi nälkään + +gameMode.survival=Selviytyjä +gameMode.creative=Nikkaroija +gameMode.adventure=Seikkailija +gameMode.spectator=Katsoja +gameMode.changed=Pelimuotosi on päivitetty + +potion.moveSpeed=Nopeutus +potion.moveSlowdown=Hidastus +potion.digSpeed=Pikakaivuu +potion.digSlowDown=Kaivuuhidastus +potion.damageBoost=Lisävoima +potion.heal=Pikaparannus +potion.harm=Pikavahinko +potion.jump=Tehohyppy +potion.confusion=Yökötys +potion.regeneration=Parantuminen +potion.resistance=Sietokyky +potion.fireResistance=Tulensieto +potion.waterBreathing=Kidukset +potion.invisibility=Näkymättömyys +potion.blindness=Sokeutus +potion.nightVision=Yönäkö +potion.hunger=Nälkä +potion.weakness=Heiveröitys +potion.poison=Myrkky +potion.wither=Näivetys +potion.healthBoost=Kuntotehoste +potion.absorption=Imeytyminen +potion.saturation=Kyllästyminen + +commands.generic.exception=Tämän komennon suorittamisen yhteydessä tapahtui tuntematon virhe +commands.generic.permission=Sinulla ei ole oikeutta käyttää tätä komentoa +commands.generic.ingame=Voit käyttää tätä komentoa vain ollessasi pelaaja +commands.generic.unknown=Tuntematon komento. Kokeile /help nähdäksesi listan komennoista. +commands.generic.player.notFound=Pelaajaa ei löydy +commands.generic.usage=Käyttö: {%0} + +commands.time.added=Lisätty {%0} aikaan +commands.time.set=Aseta ajaksi {%0} +commands.time.query=Aika on {%0} + +commands.me.usage=/me <toiminto ...> + +commands.give.item.notFound=Esinettä nimellä {%0} ei löytynyt +commands.give.success=Annettin {%0} * {%1} pelaajalle {%2} +commands.give.tagError=Tagin parsinta epäonnistui: {%0} + +commands.effect.usage=/effect <pelaaja> <vaikutus> [sekuntteja] [vahvuus] [piilota partikkelit] TAI /effect <pelaaja> clear +commands.effect.notFound=Vaikutusta tunnuksella {%0} ei löytynyt +commands.effect.success=Annettiin {%0} (ID {%1}) * {%2} pelaajalle {%3} {%4} sekunniksi +commands.effect.success.removed=Poistettiin {%0} pelaajalta {%1} +commands.effect.success.removed.all=Poistettiin kaikki vaikutukset pelaajalta {%0} +commands.effect.failure.notActive=Ei voitu poistaa vaikutusta {%0} pelaajalta {%1}, koska sitä ei ole +commands.effect.failure.notActive.all=Ei voitu poistaa vaikutuksia pelaajalta {%0}, koska niitä ei ole + +commands.enchant.noItem=Kohteella ei ole esinettä kädessään +commands.enchant.notFound=Lumousta tunnuksella {%0} ei ole +commands.enchant.success=Lumous onnistui +commands.enchant.usage=/enchant <pelaaja> <lumouksen tunnus> [taso] + +commands.particle.success=Näytetään vaikutus {%0} {%1} kertaa +commands.particle.notFound=Tuntematon vaikutus: {%0} + +commands.players.usage=/list +commands.players.list={%0}/{%1} pelaajaa: + +commands.kill.successful=Tapettu {%0} +commands.kill.all.successful=Tapettiin kaikki pelaajat +commands.kill.entities.successful=Tapettiin kaikki olennot + +commands.banlist.ips={%0} estettyä IP osoitetta: +commands.banlist.players={%0} estettyä pelaajaa: +commands.banlist.usage=/banlist [ip:t|pelaajat] + +commands.defaultgamemode.usage=/defaultgamemode <pelitila> +commands.defaultgamemode.success=Maailman oletuspelitila on nyt {%0} + +commands.op.success=Ylennettiin pelaaja {%0} +commands.op.usage=/op <pelaaja> + +commands.deop.success=Alennettiin pelaaja {%0} +commands.deop.usage=/deop <pelaaja> + +commands.say.usage=/say <viesti ...> + +commands.seed.usage=/seed +commands.seed.success=Siemen: {%0} + +commands.ban.success=Estettiin pelaaja {%0} +commands.ban.usage=/ban <nimi> [syy ...] + +commands.unban.success=Esto poistettu pelaajalta {%0} +commands.unban.usage=/pardon <nimi> + +commands.banip.invalid=Virheellinen IP osoite tai pelaaja ei ole paikalla +commands.banip.offline.invalid=IP osoitetta ei löytynyt pelaajatiedoista +commands.banip.success=Estettiin IP osoite {%0} +commands.banip.success.players=Estettiin IP osoite {%0} joka kuuluu pelaajalle {%1} +commands.banip.usage=/ban-ip <osoite|nimi> [syy ...] + +commands.unbanip.invalid=Virheellinen IP osoite +commands.unbanip.success=Poistettiin esto osoitteelta {%0} +commands.unbanip.usage=/pardon-ip <osoite> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Otettiin maailman käyttöön automaattinen tallennus +commands.save.disabled=Poistettiin maailman automaattinen tallennus käytöstä +commands.save.start=Tallennetaan... +commands.save.success=Maailma tallennettu + +commands.stop.usage=/stop +commands.stop.start=Pysäytetään palvelinta... + +commands.kick.success=Poistettiin pelaaja {%0} pelistä +commands.kick.success.reason=Poistettiin pelaaja {%0} pelistä: "{%1}" +commands.kick.usage=/kick <pelaaja> [syy ...] + +commands.tp.success=Kaukosiirrettiin {%0} paikkaan {%1} +commands.tp.success.coordinates=Kaukosiirrettiin {%0} paikkaan {%1}, {%2}, {%3} +commands.tp.usage=/tp [kohdepelaaja] <kohdepelaaja> TAI /tp [kohdepelaaja] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=Täällä on {%0}/{%1} valkoisella listalla olevaa pelaajaa: +commands.whitelist.enabled=Otettiin valkoinen lista käyttöön +commands.whitelist.disabled=Poistettiin valkoinen lista käytöstä +commands.whitelist.reloaded=Ladattiin valkoinen lista uudelleen +commands.whitelist.add.success=Lisättiin {%0} valkoiselle listalle +commands.whitelist.add.usage=/whitelist add <pelaaja> +commands.whitelist.remove.success=Poistettiin {%0} valkoiselta listalta +commands.whitelist.remove.usage=/whitelist remove <pelaaja> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Vaihdettiin pelitilaksi {%0} +commands.gamemode.success.other=Vaihdettiin pelaajan {%0} pelitilaksi {%1} +commands.gamemode.usage=/gamemode <pelitila> [pelaaja] + +commands.help.header=--- Näytetään sivu {%0}/{%1} (/help <sivu>) --- +commands.help.usage=/help [sivu|komennon nimi] + +commands.message.usage=/tell <pelaaja> <yksityinen viesti ...> +commands.message.sameTarget=Et voi lähettää yksityisviestiä itsellesi! + +commands.difficulty.usage=/difficulty <uusi vaikeustaso> +commands.difficulty.success=Asetettiin vaikeustasoksi {%0} + +commands.spawnpoint.usage=/spawnpoint [pelaaja] [<x> <y> <z>] +commands.spawnpoint.success=Asetettiin pelaajan {%0} synnyinpaikka ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Asetettiin maailman synnyinpaikka ({%0}, {%1}, {%2}) + +commands.weather.clear=Vaihdetaan selkeään säähän +commands.weather.rain=Vaihdetaan sateiseen säähän +commands.weather.thunder=Vaihdetaan sateeseen ja ukonilmaan +commands.weather.usage=/weather <clear|rain|thunder> [kesto sekuntteina] + +commands.xp.success=Annettiin {%0} kokemuspistettä pelaajalle {%1} +commands.xp.success.levels=Annettiin {%0} tasoa pelaajalle {%1} +commands.xp.success.levels.minus=Otettiin {%0} tasoa pelaajalta {%1} +commands.xp.usage=/xp <määrä> [pelaaja] TAI /xp <taso>L [pelaaja] + +# -------------------- Nukkitin kielitiedostot, vain konsolille -------------------- + +nukkit.data.playerNotFound=Pelaajatietoja pelaajalle "{%0}" ei löytynyt, luodaan uusi profiili +nukkit.data.playerCorrupted=Vioittuneet pelaajatiedot löytyivät pelaajalle "{%0}", luodaan uusi profiili +nukkit.data.playerOld=Vanhat pelaajatiedot löytyivät pelaajalle "{%0}", päivitetään profiili +nukkit.data.saveError=Ei voitu tallentaa pelaajan "{%0}" tietoja: {%1} + +nukkit.level.notFound=Maailmaa "{%0}" ei löytynyt +nukkit.level.loadError=Maailmaa "{%0}" ei voida ladata: {%1} +nukkit.level.generationError=Maailmaa "{%0}" ei voida luoda: {%1} +nukkit.level.tickError=Maailmaa "{%0}" ei voida tickata: {%1} +nukkit.level.chunkUnloadError=Virhe vapauttaessa lohkoa: {%0} +nukkit.level.backgroundGeneration=Synnyinpaikan maasto maailmalle "{%0}" luodaan taustalla +nukkit.level.defaultError=Oletusmaailmaa ei ladattu +nukkit.level.preparing=Valmistellaan maailmaa "{%0}" +nukkit.level.unloading=Vapautetaan maailmaa "{%0}" +nukkit.level.updating=Vanhat maailman tiedot löytyivät maailmalle "{%0}", muutetaan formaattia + +nukkit.server.start=Käynnistetään Minecraft BE palvelinta versiolle {%0} +nukkit.server.networkError=[Verkko] Pysäytettiin liitäntä {%0}: {%1} +nukkit.server.networkStart=Avataan palvelinta osoitteessa {%0}:{%1} +nukkit.server.info=Tämä palvelin käyttää {%0} versiota {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Tämä palvelin {%0} versiota {%1} 「{%2}」 API {%3} Minecraft BE:n versio {%4}:lle (protokollaversio {%5}) +nukkit.server.license={%0} jaetaan GPL-lisenssin alaisena +nukkit.server.tickOverload=Ei pysyä ylhäällä! Onko palvelin ylikuormitettu? +nukkit.server.startFinished=Valmis ({%0}s)! Kirjoita "help" tai "?" saadaksesi apua +nukkit.server.defaultGameMode=Oletuspelitila: {%0} +nukkit.server.query.start=Käynnistetään GS4-tilan kuuntelija +nukkit.server.query.info=Käynnistetään query portissa {%0} +nukkit.server.query.running=Query on käynnissä osoitteessa {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=RCON käynnistys epäonnistui: salasana on tyhjä +nukkit.server.rcon.startupError=RCON käynnistys epäonnistui: {%0} +nukkit.server.rcon.running=RCON on käynnissä osoitteessa {%0}:{%1} + +nukkit.command.alias.illegal=Aliasta {%0} ei voida rekisteröidä, koska se sisältää sopimattomia merkkejä +nukkit.command.alias.notFound=Aliasta {%0} ei voida rekisteröidä, koska se sisältää komentoja, joita ei ole: {%1} +nukkit.command.exception=Käsittelemätön poikkeustoiminnon komento "{%0}" {%1}: {%2} + +nukkit.command.plugins.description=Hanki lista palvelimen lisäosista +nukkit.command.plugins.success=Lisäosat ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Lataa uudelleen palvelimen asetukset ja lisäosat +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Uudelleenladataan... +nukkit.command.reload.reloaded=Uudelleenlataus valmis + +nukkit.command.status.description=Tietoa palvelimen suorituskyvystä +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Suorita garbage collection +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Tallenna ajoitukset nähdäksesi tietoa palvelimen suorituskyvystä +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Ajoitukset aktivoitu ja nollattu +nukkit.command.timings.disable=Ajoitukset poistettu käytöstä +nukkit.command.timings.timingsDisabled=Ota ajoitukset käyttöön kirjoittamalla /timings on +nukkit.command.timings.verboseEnable=Otettu käyttöön verbose timings +nukkit.command.timings.verboseDisable=Poistettu verbose timings käytöstä +nukkit.command.timings.reset=Ajoitukset nollattu +nukkit.command.timings.rcon=Varoitus: Ajoitusraportit käyttäen RCONia voivat vaikuttaa suorituskykyyn. On suositeltavaa käyttää /timings report -komentoa pelissä tai konsolissa. +nukkit.command.timings.uploadStart=Valmistellaan ajoitusten raporttia... +nukkit.command.timings.uploadError=Virhe lähettäessä raporttia: {%0}: {%1}, tarkista loki saadaksesi lisätietoja +nukkit.command.timings.reportError=Virhe liittäessä raporttia, tarkista loki saadaksesi lisätietoja +nukkit.command.timings.timingsLocation=Näytä ajoitusten raportti: {%0} +nukkit.command.timings.timingsResponse=Ajoitusten vastaus: {%0} +nukkit.command.timings.timingsWrite=Ajoitukset tallennettu sijaintiin {%0} + +nukkit.command.title.description=Lähetä otsikko pelaajalle tai muuta pelaajan otsikko asetuksia +nukkit.command.title.usage=/title <pelaaja> <clear|reset> TAI /title <pelaaja> <title|subtitle|actionbar> <otsikon teksti> TAI /title <pelaaja> <aika> <fade in aika> <kesto> <fade out aika> +nukkit.command.title.clear=Pyyhittiin pelaajan {%0} näyttö +nukkit.command.title.reset=Nollattiin pelaajan {%0} otsikko animaatio asetukset +nukkit.command.title.title=Näytettiin otsikko "{%0}" pelaajalle {%1} +nukkit.command.title.subtitle=Näytettiin alaotsikko "{%0}" pelaajalle {%1} +nukkit.command.title.actionbar=Näytettiin actionbar title "{%0}" pelaajalle {%1} +nukkit.command.title.times.success=Asetettiin otsikon animaation ajat {%0}, {%1}, {%2} pelaajalle {%3} +nukkit.command.title.times.fail=Animaation aikojen tulee olla numeroita + +nukkit.command.version.description=Näytä palvelimen tai lisäosan versio +nukkit.command.version.usage=/version [lisäosan nimi] +nukkit.command.version.noSuchPlugin=Lisäosaa ei löytynyt annetulla nimellä. Kirjoita /plugins nähdäksesi listan lisäosista. + +nukkit.command.give.description=Anna esineitä pelaajalle +nukkit.command.give.usage=/give <pelaaja> <esine[:vahinko]> [määrä] [tagit ...] + +nukkit.command.kill.description=Tee itsemurha tai tapa pelaaja +nukkit.command.kill.usage=/kill [pelaaja] + +nukkit.command.particle.description=Lisää maailmaan partikkeleja +nukkit.command.particle.usage=/particle <nimi> <x> <y> <z> <xd> <yd> <zd> [määrä] [data] + +nukkit.command.time.description=Muuta maailman aikaa +nukkit.command.time.usage=/time <set|add> <arvo> TAI /time <start|stop|query> + +nukkit.command.gamerule.description=Aseta pelisäännön arvo tai kysy sitä +nukkit.command.gamerule.usage=/gamerule <sääntö> [arvo] + +nukkit.command.debug.description=Lähetä palvelimen tiedot +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Estä pelaaja +nukkit.command.ban.ip.description=Estä IP osoite +nukkit.command.banlist.description=Näytä kaikki estetyt pelaajat +nukkit.command.defaultgamemode.description=Aseta oletuspelimuoto +nukkit.command.deop.description=Poista pelaajan OP oikeudet +nukkit.command.difficulty.description=Aseta vaikeustaso +nukkit.command.enchant.description=Lumoa esine +nukkit.command.effect.description=Lisää tai poista pelaajan vaikutuksia +nukkit.command.gamemode.description=Muuta pelaajan pelitila +nukkit.command.help.description=Näytä apuvalikko +nukkit.command.kick.description=Poista pelaaja pelistä +nukkit.command.list.description=Näytä pelaajalista +nukkit.command.me.description=Suorita tehtävä chatissa +nukkit.command.op.description=Anna pelaajalle OP oikeudet +nukkit.command.unban.player.description=Poista pelaajan esto +nukkit.command.unban.ip.description=Poista IP osoitteen esto +nukkit.command.save.description=Tallenna maailma +nukkit.command.saveoff.description=Poista automaattinen tallennus käytöstä +nukkit.command.saveon.description=Ota automaattinen tallennus käyttöön +nukkit.command.say.description=Lähetä viesti +nukkit.command.seed.description=Näytä maailman siemen +nukkit.command.setworldspawn.description=Aseta maailman syntypiste. Käytetään pelaajan sijaintia jos koordinaatteja ei anneta. +nukkit.command.spawnpoint.description=Aseta pelaajan syntypiste +nukkit.command.stop.description=Pysäytä palvelin +nukkit.command.tp.description=Kaukosiirrä pelaaja tai itsesi toisen pelaajan luo tai annettuun paikkaan +nukkit.command.tell.description=Lähetä yksityisviesti pelaajalle +nukkit.command.weather.description=Aseta maailman sää +nukkit.command.xp.description=Anna pelaajalle kokemuspisteitä +nukkit.command.whitelist.description=Muuta valkoista listaa + +nukkit.crash.create=Ohittamaton virhe on tapahtunut ja palvelin on kaatunut. Luodaan kaatumisvedos. +nukkit.crash.error=Ei voida luoda kaatumisvedosta: {%0} +nukkit.crash.submit=Lähetä tiedosto "{%0}" Crash Archiveen ja lähetä linkki Bug Reporting sivulle. Anna niin paljon tietoa kuin voit. +nukkit.crash.archive=Kaatumisvedos on lähetetty automaattisesti Crash Archiveen. Voit tarkastella sitä osoitteessa {%0} tai tunnuksella #{%1}. + +nukkit.debug.enable=LevelDB tuki otettu käyttöön + +nukkit.bugreport.create=Virhe tunnistettu, luodaan virheraporttia. +nukkit.bugreport.error=Virheraportin luonti epäonnistui: {%0} +nukkit.bugreport.archive=Virheraportti luotu: {%0} + +nukkit.player.invalidMove={%0} liikkui väärin! +nukkit.player.logIn={%0}[/{%1}:{%2}] kirjautui sisään (olion tunnus {%3}) sijainnissa ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] kirjautui ulos: {%3} +nukkit.player.invalidEntity={%0} koitti vahingoittaa virheellistä oliota + +nukkit.plugin.load=Ladataan {%0} +nukkit.plugin.enable=Otetaan käyttöön {%0} +nukkit.plugin.disable=Poistetaan käytöstä {%0} +nukkit.plugin.restrictedName=Rajoitettu nimi +nukkit.plugin.incompatibleAPI=Yhteensopimaton API versio +nukkit.plugin.unknownDependency=Tuntematon riippuvuus +nukkit.plugin.circularDependency=Kiertävä riippuvuus havaittu +nukkit.plugin.genericLoadError=Lisäosaa "{%0}" ei voitu ladata" +nukkit.plugin.spacesDiscouraged=Lisäosa "{%0}" käyttää nimessään välejä, mikä ei ole suositeltavaa +nukkit.plugin.loadError=Lisäosaa "{%0}" ei voitu ladata: {%1} +nukkit.plugin.duplicateError=Lisäosaa "{%0}" ei voitu ladata": samanniminen lisäosa on jo ladattu +nukkit.plugin.fileError=Ei voitu ladata "{%0}" kansiossa "{%1}": {%2} +nukkit.plugin.commandError=Ei voitu ladata komentoa {%0} lisäosalle {%1} +nukkit.plugin.aliasError=Ei voitu ladata aliasta {%0} lisäosalle {%1} +nukkit.plugin.deprecatedEvent=Lisäosa "{%0}" on rekisteröinyt kuuntelijan "{%1}" menetelmällä "{%2}", mutta tapahtuma on vanhentunut +nukkit.plugin.eventError=Ei voida suorittaa tapahtumaa "{%0}" lisäosalle "{%1}": {%2} @ {%3} + +nukkit.resources.invalid-path=Resurssipaketin polku "{%0}" on olemassa ja se ei ole hakemisto +nukkit.resources.unknown-format=Ei voida ladata "{%0}", koska formaatti ei ole tunnistettavissa +nukkit.resources.fail=Ei voida ladata "{%0}": {%1} +nukkit.resources.success=Ladattiin {%0} resurssipakettia +nukkit.resources.zip.not-found=Tiedostoa "{%0}" ei löydy +nukkit.resources.zip.no-manifest=Tiedostoa "manifest.json" ei löydy +nukkit.resources.zip.invalid-manifest=Tiedosto "manifest.json" on virheellinen diff --git a/src/main/resources/lang/idn/lang.ini b/src/main/resources/lang/idn/lang.ini new file mode 100644 index 00000000000..ae17103ed3e --- /dev/null +++ b/src/main/resources/lang/idn/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Indonesian +language.selected=Memilih {%0} ({%1}) sebagai bahasa utama + +multiplayer.player.joined={%0} masuk ke permainan +multiplayer.player.left={%0} keluar dari permainan + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} baru saja memperoleh prestasi {%1} + +disconnectionScreen.outdatedClient=Client terlalu lawas! +disconnectionScreen.outdatedServer=Server terlalu lawas! +disconnectionScreen.serverFull=Server sedang penuh! +disconnectionScreen.noReason=Terputus dari server +disconnectionScreen.invalidSkin=Skin tidak valid! +disconnectionScreen.invalidName=Nama tidak valid! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} Jatuh dari ketingian +death.attack.inFire={%0} terbakar habis +death.attack.onFire={%0} terbakar sampai mati +death.attack.lava={%0} mencoba berenang dilava +death.attack.lava.magma={%0} discovered floor was lava +death.attack.inWall={%0} terjebak didalam dinding +death.attack.drown={%0} tenggelam +death.attack.cactus={%0} ditusuk sampai mati +death.attack.generic={%0} koid +death.attack.explosion={%0} meledak +death.attack.explosion.player={%0} diledakkan oleh {%1} +death.attack.magic={%0} dibunuh oleh makhluk gaib +death.attack.wither={%0} layu +death.attack.mob={%0} ditikam oleh {%1} +death.attack.player={%0} ditikam oleh {%1} +death.attack.player.item={%0} ditikam oleh {%1} memakai {%2} +death.attack.arrow={%0} ditembak oleh {%1} +death.attack.arrow.item={%0} ditembak oleh {%1} memakai {%2} +death.attack.fall={%0} menyentuh tanah terlalu keras. +death.attack.outOfWorld={%0} jatuh keluar dari dunia. +death.attack.starve={%0} starved to death + +gameMode.survival=Mode bertahan +gameMode.creative=Mode kreatif +gameMode.adventure=Mode berpetualangan +gameMode.spectator=Mode menonton +gameMode.changed=Mode permainan kamu telah diubah + +potion.moveSpeed=Kecepatan +potion.moveSlowdown=Kelambatan +potion.digSpeed=Bergegas +potion.digSlowDown=Kelelahan bertambang +potion.damageBoost=Kekuatan +potion.heal=Darah instan +potion.harm=Kerusakan instan +potion.jump=Lompatan meningkat +potion.confusion=Maul +potion.regeneration=Regenerasi +potion.resistance=Daya tahan +potion.fireResistance=Daya Tahan Api +potion.waterBreathing=Pernapasan air +potion.invisibility=Tak terlihat +potion.blindness=Buto +potion.nightVision=Penglihatan malam +potion.hunger=Lapar +potion.weakness=Cacat +potion.poison=Racun +potion.wither=Melayu +potion.healthBoost=Darah meningkat +potion.absorption=Penyerapan +potion.saturation=Jenuh + +commands.generic.exception=Terjadi kesalahan yang tidak diketahui ketika mencoba untuk melakukan perintah ini +commands.generic.permission=nda tidak memiliki izin untuk menggunakan perintah ini +commands.generic.ingame=Anda hanya dapat melakukan perintah ini sebagai pemain +commands.generic.unknown=Perintah yang tidak diketahui. Coba /help untuk daftar perintah +commands.generic.player.notFound=Pemain tidak ditemukan +commands.generic.usage=Pemakaian: {%0} + +commands.time.added=Menambahkan {%0} ke waktu +commands.time.set=Waktu di stel menjadi {%0} +commands.time.query=Waktu sekarang{%0} + +commands.me.usage=/me <aksi ...> + +commands.give.item.notFound=Tidak ada barang dengan nama {%0} +commands.give.success=Memberi {%0} * {%1} kepada {%2} +commands.give.tagError=Penguraian data gagal: {%0} + +commands.effect.usage=/effect <pemain> <efek> [detik] [pengerasan] [hilangkanPartikel] atau /effect <pemain> clear +commands.effect.notFound=Tidak ada efek mob dengan id {%0} +commands.effect.success=Memberi {%0} (ID {%1}) * {%2} ke {%3} untuk {%4} detik +commands.effect.success.removed=Pengambil {%0} dari {%1} +commands.effect.success.removed.all=Mengambil semua efek dari {%0} +commands.effect.failure.notActive=Tidak bisa mengambil {%0} dari {%1} karna mereka tidak mempunyai efek +commands.effect.failure.notActive.all=Tidak bsa mengambil efek apapun dari {%0} karna mereka tidak mempunyai efek + +commands.enchant.noItem=Target tidak memegang barang apapun. +commands.enchant.notFound=Tidak ada pemikatan dengan ID {%0} +commands.enchant.success=Pemikatan sukses +commands.enchant.usage=/enchant <pemain> <ID pemikatan> [tingkat] + +commands.particle.success=Bermain efek {%0} untuk {%1} kali +commands.particle.notFound=Nama efek tidak diketahui {%0} + +commands.players.usage=/list +commands.players.list=Ada {%0}/{%1} pemain online sedang online : + +commands.kill.successful=Membunuh {%0} +commands.kill.all.successful=Killed all players +commands.kill.entities.successful=Killed all entities + +commands.banlist.ips=Ada {%0} total alamat IP yang dibanned: +commands.banlist.players=Ada {%0} total pemain yang dibanned: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=Mode permainin dunia ini sekarang adalah {%0} + +commands.op.success=Memberi OP {%0} +commands.op.usage=/op <player> + +commands.deop.success=Menurunkan OP {%0} +commands.deop.usage=/deop <player> + +commands.say.usage=/say <pesan ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Pemain terbanned {%0} +commands.ban.usage=/ban <nama> [alasan ...] + +commands.unban.success=Pemain diunbanned {%0} +commands.unban.usage=/pardon <name> + +commands.banip.invalid=Kami memasukan alamat IP yang salah atau pemain tersebut tidak online +commands.banip.offline.invalid=Tidak ada alamat IP data pemain atau alamat IP tidak valid +commands.banip.success=Alamat IP {%0} dibanned +commands.banip.success.players=Alamat ip terbanned {%0} milik {%1} +commands.banip.usage=/ban-ip <alamat|nama> [alasan ...] + +commands.unbanip.invalid=Kamu memasukan alamat IP yang salah +commands.unbanip.success=Unbanned IP address {%0} +commands.unbanip.usage=/pardon-ip <address> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +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 + +commands.stop.usage=/stop +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.kick.usage=/kick <player> [reason ...] + +commands.tp.success=Teleported {%0} to {%1} +commands.tp.success.coordinates=Teleported {%0} to {%1}, {%2}, {%3} +commands.tp.usage=/tp [target player] <destination player> OR /tp [target player] <x> <y> <z> [<y-rot> <x-rot>] + +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 <player> +commands.whitelist.remove.success=Removed {%0} from the whitelist +commands.whitelist.remove.usage=/whitelist remove <player> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Set own game mode to {%0} +commands.gamemode.success.other=Set {%0}'s game mode to {%1} +commands.gamemode.usage=/gamemode <mode> [player] + +commands.help.header=--- Showing help page {%0} of {%1} (/help <page>) --- +commands.help.usage=/help [page|command name] + +commands.message.usage=/tell <player> <private message ...> +commands.message.sameTarget=You can't send a private message to yourself! + +commands.difficulty.usage=/difficulty <new difficulty> +commands.difficulty.success=Set game difficulty to {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [<x> <y> <z>] +commands.spawnpoint.success=Set {%0}'s spawn point to ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Set the world spawn point to ({%0}, {%1}, {%2}) + +commands.weather.clear=Changing to clear weather +commands.weather.rain=Changing to rainy weather +commands.weather.thunder=Changing to rain and thunder +commands.weather.usage=/weather <clear|rain|thunder> [duration in seconds] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Player data not found for "{%0}", creating new profile +nukkit.data.playerCorrupted=Corrupted data found for "{%0}", creating new profile +nukkit.data.playerOld=Old Player data found for "{%0}", upgrading profile +nukkit.data.saveError=Could not save player "{%0}": {%1} + +nukkit.level.notFound=Level "{%0}" not found +nukkit.level.loadError=Could not load level "{%0}": {%1} +nukkit.level.generationError=Could not generate level "{%0}": {%1} +nukkit.level.tickError=Could not tick level "{%0}": {%1} +nukkit.level.chunkUnloadError=Error while unloading a chunk: {%0} +nukkit.level.backgroundGeneration=Spawn terrain for level "{%0}" is being generated in the background +nukkit.level.defaultError=No default level has been loaded +nukkit.level.preparing=Preparing level "{%0}" +nukkit.level.unloading=Unloading level "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Starting Minecraft: BE server version {%0} +nukkit.server.networkError=[Network] Stopped interface {%0} due to {%1} +nukkit.server.networkStart=Opening server on {%0}:{%1} +nukkit.server.info=This server is running {%0} version {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: BE {%4} (protocol version {%5}) +nukkit.server.license={%0} is distributed under the GPL License +nukkit.server.tickOverload=Can't keep up! Is the server overloaded? +nukkit.server.startFinished=Done ({%0}s)! For help, type "help" or "?" +nukkit.server.defaultGameMode=Default game type: {%0} +nukkit.server.query.start=Starting GS4 status listener +nukkit.server.query.info=Setting query port to {%0} +nukkit.server.query.running=Query is running on {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Could not register alias {%0} because it contains illegal characters +nukkit.command.alias.notFound=Could not register alias {%0} because it contains commands that do not exist: {%1} +nukkit.command.exception=Unhandled exception executing command "{%0}" in {%1}: {%2} + +nukkit.command.plugins.description=Gets a list of plugins running on the server +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Reloads the server configuration and plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reloading server... +nukkit.command.reload.reloaded=Reload complete. + +nukkit.command.status.description=Reads back the server's performance. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Fires garbage collection tasks +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Records timings to see performance of the server. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Gets the version of this server including any plugins in use +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=This server is not running any plugin by that name. Use /plugins to get a list of plugins. + +nukkit.command.give.description=Gives the specified player a certain amount of items +nukkit.command.give.usage=/give <player> <item[:damage]> [amount] [tags ...] + +nukkit.command.kill.description=Commit suicide or kill other players +nukkit.command.kill.usage=/kill [player] + +nukkit.command.particle.description=Adds particles to a world +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [count] [data] + +nukkit.command.time.description=Changes the time on each world +nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> + +nukkit.command.gamerule.description=Sets or queries a game rule value +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Prevents the specified player from using this server +nukkit.command.ban.ip.description=Prevents the specified IP address from using this server +nukkit.command.banlist.description=View all players banned from this server +nukkit.command.defaultgamemode.description=Set the default gamemode +nukkit.command.deop.description=Takes the specified player's operator status +nukkit.command.difficulty.description=Sets the game difficulty +nukkit.command.enchant.description=Adds enchantments on items +nukkit.command.effect.description=Adds/Removes effects on players +nukkit.command.gamemode.description=Changes the player to a specific game mode +nukkit.command.help.description=Shows the help menu +nukkit.command.kick.description=Removes the specified player from the server +nukkit.command.list.description=Lists all online players +nukkit.command.me.description=Performs the specified action in chat +nukkit.command.op.description=Gives the specified player operator status +nukkit.command.unban.player.description=Allows the specified player to use this server +nukkit.command.unban.ip.description=Allows the specified IP address to use this server +nukkit.command.save.description=Saves the server to disk +nukkit.command.saveoff.description=Disables server autosaving +nukkit.command.saveon.description=Enables server autosaving +nukkit.command.say.description=Broadcasts the given message as the sender +nukkit.command.seed.description=Shows the world seed +nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. +nukkit.command.spawnpoint.description=Sets a player's spawn point +nukkit.command.stop.description=Stops the server +nukkit.command.tp.description=Teleports the given player (or yourself) to another player or coordinates +nukkit.command.tell.description=Sends a private message to the given player +nukkit.command.weather.description=Sets the weather in current world +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Manages the list of players allowed to use this server + +nukkit.crash.create=An unrecoverable error has occurred and the server has crashed. Creating a crash dump +nukkit.crash.error=Could not create crash dump: {%0} +nukkit.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. +nukkit.crash.archive=The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. + +nukkit.debug.enable=LevelDB support enabled + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} moved wrongly! +nukkit.player.logIn={%0}[/{%1}:{%2}] logged in with entity id {%3} at ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] logged out due to {%3} +nukkit.player.invalidEntity={%0} tried to attack an invalid entity + +nukkit.plugin.load=Loading {%0} +nukkit.plugin.enable=Enabling {%0} +nukkit.plugin.disable=Disabling {%0} +nukkit.plugin.restrictedName=Restricted name +nukkit.plugin.incompatibleAPI=Incompatible API version +nukkit.plugin.unknownDependency=Unknown dependency +nukkit.plugin.circularDependency=Circular dependency detected +nukkit.plugin.genericLoadError=Could not load plugin "{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" uses spaces in its name, this is discouraged +nukkit.plugin.loadError=Could not load plugin "{%0}": {%1} +nukkit.plugin.duplicateError=Could not load plugin "{%0}": plugin exists +nukkit.plugin.fileError=Could not load "{%0}" in folder "{%1}": {%2} +nukkit.plugin.commandError=Could not load command {%0} for plugin {%1} +nukkit.plugin.aliasError=Could not load alias {%0} for plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" has registered a listener for "{%1}" on method "{%2}", but the event is Deprecated. +nukkit.plugin.eventError=Could not pass event "{%0}" to "{%1}": {%2} on {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/jpn/lang.ini b/src/main/resources/lang/jpn/lang.ini new file mode 100644 index 00000000000..a63b6baba2b --- /dev/null +++ b/src/main/resources/lang/jpn/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +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.outdatedClient=違うバージョンのクライアントです! +disconnectionScreen.outdatedServer=違うバージョンのサーバーです! +disconnectionScreen.serverFull=サーバーは満員です! +disconnectionScreen.noReason=サーバーから切断されました +disconnectionScreen.invalidSkin=無効なスキンです! +disconnectionScreen.invalidName=無効な名前です! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} は高いところから落ちてしまった +death.attack.inFire={%0}は炎に巻かれてしまった +death.attack.onFire={%0}はこんがりと焼けてしまった +death.attack.lava={%0}は溶岩遊泳を試みた +death.attack.lava.magma={%0} discovered floor was lava +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}は奈落の底へ落ちてしまった +death.attack.starve={%0} starved to death + +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.ingame=You can only perform this command as a player +commands.generic.unknown=未知のコマンドです。/helpでコマンドの一覧を確認してください +commands.generic.player.notFound=プレイヤーが見つかりません +commands.generic.usage=使い方: {%0} + +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.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.kill.all.successful=Killed all players +commands.kill.entities.successful=Killed all entities + +commands.banlist.ips=%d 個のIPアドレスがBANされています: +commands.banlist.players={%0} 人のプレイヤーがBANされています: +commands.banlist.usage=/banlist [IPアドレス|プレイヤー名] + +commands.defaultgamemode.usage=/defaultgamemode <0 | 1 | 2 | 3> +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.ban.success={%0} をBANしました +commands.ban.usage=/ban <プレイヤー名> [理由 ...] + +commands.unban.success={%0} のbanを解除しました +commands.unban.usage=/pardon <プレイヤー名> + +commands.banip.invalid=無効なIPアドレスが入力されたか、プレイヤーがオンラインになっていません +commands.banip.offline.invalid=There is no IP address in player data or IP address is invalid +commands.banip.success=IPアドレス "{%0}" をBANしました。 +commands.banip.success.players={%1} のIPアドレス {%0} をBANしました +commands.banip.usage=/ban-ip <IPアドレス|プレイヤー名> [理由] + +commands.unbanip.invalid=無効なIPアドレスです +commands.unbanip.success=IPアドレス {%0} のBANが解除されました +commands.unbanip.usage=/pardon-ip <IPアドレス> + +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.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 [対象のプレイヤー] <x> <y> <z> [<y> <x>のみ] + +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=ゲームモードを{%0}に変更しました +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 [プレイヤー名] [<x><y><z>] +commands.spawnpoint.success={%0} のスポーン地点を ({%1}, {%2}, {%3}) に変更しました + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=({%0},{%1},{%2}) にワールドのスポーンポイントを設定します + +commands.weather.clear=天気を晴れに変更します +commands.weather.rain=天気を雨に変更します +commands.weather.thunder=天気を雷雨に変更します +commands.weather.usage=/weather <clear|rain|thunder> [持続時間(単位:秒)] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- コンソールの表示 -------------------- + +nukkit.data.playerNotFound={%0} のプレイヤーデータが見つからないため、新たに作成します +nukkit.data.playerCorrupted={%0} のデータの破損が見つかったため、新たに作成します +nukkit.data.playerOld={%0} の古いプレイヤーデータが見つかったため、更新します +nukkit.data.saveError={%0}のデータを保存できませんでした: {%1} + +nukkit.level.notFound=ワールド "{%0}" が見つかりません +nukkit.level.loadError=ワールド "{%0}" を読み込むことができませんでした: {%1} +nukkit.level.generationError=ワールド"{%0}"を生成することができませんでした: {%1} +nukkit.level.tickError=ワールド "{%0}" をチェックすることができませんでした: {%1} +nukkit.level.chunkUnloadError=チャンクの書き込み中にエラーが発生しました: {%0} +nukkit.level.backgroundGeneration=ワールド"{%0}"の地形をバックグラウンドで生成しています +nukkit.level.defaultError=デフォルトのワールドが読み込まれていません +nukkit.level.preparing=ワールド "{%0}" を読み込んでいます +nukkit.level.unloading=ワールド"{%0}"の書き込みをしています +nukkit.level.updating=古いレベルのデータが見つかりました "{%0}" フォーマットを変換しています。 + +nukkit.server.start=Minecraft: BEサーバー({%0}に対応)を起動しています +nukkit.server.networkError=[Network] {%1}によって{%0}のインターフェイスが停止しました +nukkit.server.networkStart={%0}:{%1}上でサーバーを開始しています +nukkit.server.info=このサーバーは{%0}のバージョン{%1}「{%2}」(API {%3})で動作しています +nukkit.server.info.extended=このサーバーが実行している{%0}のバージョンは{%1}「{%2}」Minecraft BE {%4}用実装APIバージョン{%3}(プロトコルバージョン{%5})です +nukkit.server.license={%0}はGPLライセンスに基づき配布されています +nukkit.server.tickOverload=注意! サーバーが高負荷になっている可能性があります +nukkit.server.startFinished=起動完了({%0}秒)! "help"または"?"でヘルプを表示 +nukkit.server.defaultGameMode=デフォルトゲームタイプ: {%0} +nukkit.server.query.start=GS4ステータス リスナーを開始 +nukkit.server.query.info=クエリポートを設定: {%0} +nukkit.server.query.running=クエリーは {%0}:{%1} で動作しています +nukkit.server.rcon.emptyPasswordError=RCONを開始できませんでした : パスワードが設定されていません +nukkit.server.rcon.startupError=RCONを開始できませんでした : {%0} +nukkit.server.rcon.running=RCONは {%0}:{%1} で動作しています + +nukkit.command.alias.illegal=無効な文字が含まれているため、{%0}を登録できませんでした +nukkit.command.alias.notFound=存在しないコマンドが含まれているため、{%0}を登録できませんでした: {%1} +nukkit.command.exception=コマンド"{%0}"を{%1}で実行中に、処理できない例外が発生:{%2} + +nukkit.command.plugins.description=サーバー上で実行されているプラグインを一覧にして表示します +nukkit.command.plugins.success=プラグイン ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=サーバーの設定やプラグインを再読み込みします +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=サーバーを再読み込みしています... +nukkit.command.reload.reloaded=再読み込みが完了しました + +nukkit.command.status.description=サーバーのパフォーマンスを調べます +nukkit.command.status.usage=/status + +nukkit.command.gc.description=ガベージコレクションのタスクを起動しました +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=サーバーのパフォーマンスを確認する記録のタイミングを設定します +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=使用しているプラグインを含めたこのサーバーのバージョンを取得します +nukkit.command.version.usage=/version [プラグイン名] +nukkit.command.version.noSuchPlugin=このサーバーではその名前のどのプラグインも実行しまいません プラグインリストを取得するには /plugins を使用してください + +nukkit.command.give.description=指定したプレイヤーに一定量のアイテムを付与します +nukkit.command.give.usage=/give <プレイヤー名> <アイテム[:ダメージ値]> [量] [タグ] + +nukkit.command.kill.description=自殺または他のプレイヤーを殺すことができます +nukkit.command.kill.usage=/kill [プレイヤー名] + +nukkit.command.particle.description=ワールドにパーティクルを追加します +nukkit.command.particle.usage=/particle <パーティクル名> <x> <y> <z> <xd> <yd> <zd> [量] [データ値] + +nukkit.command.time.description=それぞれのワールドの時間を変更します +nukkit.command.time.usage=/time <set|add> <値> または /time <start|stop|query> + +nukkit.command.gamerule.description=ゲームルールの値を設定またはクエリする +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=指定したプレーヤーのサーバーの使用を拒否します +nukkit.command.ban.ip.description=指定したIPアドレスからのサーバーの使用を拒否します +nukkit.command.banlist.description=このサーバーでBANされたすべてのプレイヤーを表示します +nukkit.command.defaultgamemode.description=デフォルトのゲームモードを設定します +nukkit.command.deop.description=指定プレイヤーのOP権限を剥奪します +nukkit.command.difficulty.description=ゲーム難易度を設定します +nukkit.command.enchant.description=アイテムにエンチャントを付加します +nukkit.command.effect.description=プレイヤーに効果を付与/削除します +nukkit.command.gamemode.description=プレイヤーのゲームモードを変更します +nukkit.command.help.description=ヘルプメニューを表示します +nukkit.command.kick.description=指定したプレイヤーをサーバーから追い出します +nukkit.command.list.description=サーバーに接続しているプレイヤーを一覧表示します +nukkit.command.me.description=チャットで指定したアクションを実行します +nukkit.command.op.description=指定したプレイヤーにOP権限を付与します +nukkit.command.unban.player.description=指定したプレイヤーのBANを解除します +nukkit.command.unban.ip.description=指定したIPアドレスのBANを解除します +nukkit.command.save.description=サーバーを保存します +nukkit.command.saveoff.description=サーバーの自動保存を無効にします +nukkit.command.saveon.description=サーバーの自動保存を有効にします +nukkit.command.say.description=送信者として指定したメッセージを送信します +nukkit.command.seed.description=ワールドのシード値を表示します +nukkit.command.setworldspawn.description=ワールドのスポーン地点を設定します 座標が指定されていない場合はプレイヤーの現在地の座標が使用されます +nukkit.command.spawnpoint.description=プレイヤーのスポーン地点を設定します +nukkit.command.stop.description=サーバーを停止します +nukkit.command.tp.description=他のプレイヤーまたは指定した座標にプレイヤー(または自分自身)をテレポートさせます +nukkit.command.tell.description=指定したプレイヤーにプライベートメッセージを送信します +nukkit.command.weather.description=Sets the weather in current world +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=このサーバーの利用を許可するプレイヤーリストを管理します + +nukkit.crash.create=リカバリー不可能なエラーが発生し、サーバーがクラッシュ(動作を停止)しました。クラッシュダンプを作成しました +nukkit.crash.error=クラッシュダンプの作成に失敗: {%0} +nukkit.crash.submit=「{%0}」のファイルをクラッシュアーカイブにアップロードし、そのリンクを送ってください 出来る限り多くの情報を記載してください +nukkit.crash.archive=クラッシュダンプが自動的にクラッシュアーカイブに提出されました {%0}で閲覧またはID #{%1}を使用可能です + +nukkit.debug.enable=LevelDB形式の対応を有効にしました + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} が正しく移動できませんでした! +nukkit.player.logIn={%0}[/{%1}:{%2}] がエンティティID {%3} として ({%4}, {%5}, {%6}, {%7})にログインしました +nukkit.player.logOut={%0}[/{%1}:{%2}] は {%3} でログアウト +nukkit.player.invalidEntity={%0} は無効なエンティティに攻撃しました + +nukkit.plugin.load={%0} を読み込み中... +nukkit.plugin.enable={%0}を有効にしています… +nukkit.plugin.disable={%0}を無効にしています… +nukkit.plugin.restrictedName=不審な名前 +nukkit.plugin.incompatibleAPI=互換性がないAPIバージョン +nukkit.plugin.unknownDependency=不明な依存関係 +nukkit.plugin.circularDependency=循環依存が検出されました +nukkit.plugin.genericLoadError=プラグイン"{%0}"の読み込みに失敗しました +nukkit.plugin.spacesDiscouraged=プラグイン"{%0}"の名前に空白が使用されていますが、これはおすすめしません +nukkit.plugin.loadError=プラグイン"{%0}"を読み込むことができませんでした: {%1} +nukkit.plugin.duplicateError=プラグイン"{%0}"を読み込むことができませんでした: 同じプラグインが存在します +nukkit.plugin.fileError=フォルダー"{%0}"内の"{%1}"を読み込むことができませんでした: {%2} +nukkit.plugin.commandError=プラグイン{%1}のコマンド{%0}を読み込むことができませんでした +nukkit.plugin.aliasError=プラグイン {%1} で、コマンド{%0} に対するエイリアスを登録できませんでした +nukkit.plugin.deprecatedEvent=プラグイン"{%0}"がメソッド"{%2}"に"{%1}"のリスナーを登録しましたが、イベントが廃止されました +nukkit.plugin.eventError="{%1}"に"{%0}"Eventを渡すことができませんでした: {%3}での{%2} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/kor/lang.ini b/src/main/resources/lang/kor/lang.ini new file mode 100644 index 00000000000..6c2545a33a9 --- /dev/null +++ b/src/main/resources/lang/kor/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +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.outdatedClient=연결할 수 없습니다: 오래된 클라이언트! +disconnectionScreen.outdatedServer=연결할 수 없습니다: 오래된 서버! +disconnectionScreen.serverFull=이용자가 많은 인기 서버입니다. 나중에 다시 들러 자리가 있는지 확인하세요. +disconnectionScreen.noReason=연결이 끊어졌습니다 +disconnectionScreen.invalidSkin=유효하지 않거나 손상된 스킨입니다! +disconnectionScreen.invalidName=유효하지 않는 이름입니다! +disconnectionScreen.resourcePack=리소스 팩을 다운로드 또는 적용 중에 문제가 발생했습니다. + +death.fell.accident.generic={%0}이(가) 높은 곳에서 떨어졌습니다 +death.attack.inFire={%0}이(가) 화염에 휩싸였습니다 +death.attack.onFire={%0}이(가) 불타 죽었습니다 +death.attack.lava={%0}이(가) 용암에 빠졌습니다 +death.attack.lava.magma={%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}이(가) {%2}을(를) 사용한 {%1}에게 살해당했습니다 +death.attack.arrow={%0}이(가) {%1}에게 저격당했습니다 +death.attack.arrow.item={%0}이(가) {%2}을(를) 사용한 {%1}에게 저격당했습니다 +death.attack.fall={%0}이(가) 바닥으로 너무 심하게 떨어졌습니다 +death.attack.outOfWorld={%0}이(가) 월드 밖으로 떨어졌습니다 +death.attack.starve={%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.ingame=이 명령어는 플레이어로만 실행할 수 있습니다 +commands.generic.unknown=알 수 없는 명령어입니다. /help에서 명령어 목록을 참조하세요. +commands.generic.player.notFound=플레이어를 찾을 수 없습니다 +commands.generic.usage=사용법: {%0} + +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={%3}에 효과 {%0} (ID {%1}) * {%2} 만큼을 {%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.noItem=대상이 아이템을 들고 있지 않습니다 +commands.enchant.notFound=ID {%0}에 해당하는 효과부여가 없습니다 +commands.enchant.success=효과부여에 성공했습니다 +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.kill.all.successful=모든 플레이어가 사망했습니다 +commands.kill.entities.successful=모든 엔티티가 사망했습니다 + +commands.banlist.ips=차단한 IP 주소 총 {%0}개: +commands.banlist.players=차단한 플레이어 총 {%0}명: +commands.banlist.usage=/banlist [아이피|플레이어] + +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.ban.success={%0}님을 차단했습니다 +commands.ban.usage=/ban <플레이어> [사유 ...] + +commands.unban.success={%0}님의 차단을 해제했습니다 +commands.unban.usage=/pardon <플레이어> + +commands.banip.invalid=잘못된 IP 주소 또는 오프라인인 플레이어입니다 +commands.banip.offline.invalid=플레이어 데이터에 IP 주소가 없거나 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.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.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 [대상 플레이어] <x> <y> <z> [<수평회전> <수직회전>] + +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=내 게임 모드를 {%0}(으)로 변경했습니다 +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 [플레이어] [<x> <y> <z>] +commands.spawnpoint.success=설정된 {%0} 생성 지점은 ({%1}, {%2}, {%3})입니다 + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success= 설정된 월드 생성 지점은 ({%0}, {%1}, {%2})입니다 + +commands.weather.clear=날씨가 맑아집니다 +commands.weather.rain=비가 내리기 시작합니다 +commands.weather.thunder=폭풍우가 몰아치기 시작합니다 +commands.weather.usage=/weather <clear|rain|thunder> [지속 시간(초)] + +commands.xp.success={%1}님의 경험치가 {%0} 추가되었습니다 +commands.xp.success.levels={%1}님의 경험치가 {%0} 추가되었습니다 +commands.xp.success.levels.minus={%1}님의 경험치가 {%0} 레벨 감소되었습니다 +commands.xp.usage=/xp <수량> [플레이어] 또는 /xp <수량>L [플레이어] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound="{%0}"의 플레이어 데이터를 찾을 수 없습니다, 새로운 프로필을 생성합니다 +nukkit.data.playerCorrupted="{%0}"의 데이터가 손상되었습니다, 새로운 프로필을 생성합니다 +nukkit.data.playerOld="{%0}"의 플레이어 데이터가 오래되었습니다, 프로필을 업그레이드합니다 +nukkit.data.saveError=플레이어 "{%0}"의 데이터를 저장할 수 없습니다: {%1} + +nukkit.level.notFound="{%0}" 레벨을 찾을 수 없습니다 +nukkit.level.loadError="{%0}" 레벨을 불러올 수 없습니다: {%1} +nukkit.level.generationError="{%0}" 레벨을 생성할 수 없습니다: {%1} +nukkit.level.tickError="{%0}" 레벨의 틱을 처리할 수 없습니다: {%1} +nukkit.level.chunkUnloadError=청크 언로드 중 오류가 발생했습니다: {%0} +nukkit.level.backgroundGeneration="{%0}" 레벨의 스폰 지형이 백그라운드에서 생성 중입니다 +nukkit.level.defaultError=기본 레벨이 불러와지지 않았습니다 +nukkit.level.preparing="{%0}" 레벨을 준비 중입니다 +nukkit.level.unloading="{%0}" 레벨을 언로드 중입니다 +nukkit.level.updating="{%0}"의 레벨 데이터가 오래되었습니다, 포맷을 변환합니다 + +nukkit.server.start=Minecraft: BE 서버 버전 {%0}을(를) 시작 중입니다 +nukkit.server.networkError=[네트워크] {%0} 인터페이스가 {%1}(으)로 인해 작동 중단되었습니다 +nukkit.server.networkStart=서버를 {%0}:{%1}에서 여는 중입니다 +nukkit.server.info=이 서버는 {%0} 버전 {%1} "{%2}" (API {%3})을(를) 실행하고 있습니다 +nukkit.server.info.extended=이 서버는 Minecraft: BE {%4} (프로토콜 버전 {%5})용 API 버전 {%3}을(를) 포함하는 {%0} {%1} 「{%2}」을(를) 실행하고 있습니다 +nukkit.server.license={%0}은(는) GPL 라이선스 하에 배포됩니다 +nukkit.server.tickOverload=유지할 수 없습니다! 서버가 과부하되었나요? +nukkit.server.startFinished=완료 ({%0}초)! 도움말을 참조하시려면 "help" 또는 "?"를 입력해주세요 +nukkit.server.defaultGameMode=기본 게임 유형: {%0} +nukkit.server.query.start=GS4 상태 리스너를 시작 중입니다 +nukkit.server.query.info=Query 포트를 {%0}(으)로 설정 중입니다 +nukkit.server.query.running=Query가 {%0}:{%1}에서 실행 중입니다 +nukkit.server.rcon.emptyPasswordError=RCON을 시작하지 못했습니다: 비밀번호가 비어있습니다 +nukkit.server.rcon.startupError=RCON을 시작하지 못했습니다: {%0} +nukkit.server.rcon.running=RCON이 {%0}:{%1}에서 실행 중입니다 + +nukkit.command.alias.illegal=잘못된 문자를 포함하기에 '{%0}' 별칭을 등록할 수 없습니다 +nukkit.command.alias.notFound=존재하지 않는 명령어를 포함하기에 '{%0}' 별칭을 등록할 수 없습니다: {%1} +nukkit.command.exception={%1}에서 "{%0}" 명령어 실행 중 처리되지 않은 예외가 발생했습니다: {%2} + +nukkit.command.plugins.description=서버에서 실행 중인 플러그인 목록을 확인합니다 +nukkit.command.plugins.success=플러그인({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=서버 설정과 플러그인을 새로 고칩니다 +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=서버를 새로 고침 중입니다... +nukkit.command.reload.reloaded=새로 고침 완료. + +nukkit.command.status.description=서버의 성능을 읽습니다. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=쓰레기 수집 작업을 시작합니다 +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=서버의 성능 확인을 위해 타이밍을 기록합니다. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=타이밍 및 초기화를 활성화했습니다 +nukkit.command.timings.disable=타이밍을 비활성화했습니다 +nukkit.command.timings.timingsDisabled=/timings on을 입력해 타이밍을 활성화해주세요 +nukkit.command.timings.verboseEnable=버보스 타이밍을 활성화했습니다 +nukkit.command.timings.verboseDisable=버보스 타이밍을 비활성화했습니다 +nukkit.command.timings.reset=타이밍을 초기화했습니다 +nukkit.command.timings.rcon=주의: 타이밍 보고서 생성은 RCON으로 실행 시 서버 지연을 유발할 수 있습니다, 콘솔 또는 게임 내에서 /timings report 사용을 권장합니다 +nukkit.command.timings.uploadStart=타이밍 보고서 준비 중... +nukkit.command.timings.uploadError=업로드 오류: {%0}: {%1}, 자세한 정보는 로그를 확인하세요 +nukkit.command.timings.reportError=보고서를 붙여넣는 동안 오류가 발생했습니다, 자세한 정보는 로그를 확인하세요 +nukkit.command.timings.timingsLocation=타이밍 보고서 보기: {%0} +nukkit.command.timings.timingsResponse=타이밍 응답: {%0} +nukkit.command.timings.timingsWrite=타이밍이 {%0}에 작성되었습니다 + +nukkit.command.title.description=지정된 플레이어에게 제목을 보내거나 해당 플레이어의 제목 설정을 수정합니다 +nukkit.command.title.usage=/title <플레이어> <clear|reset> 또는 /title <플레이어> <title|subtitle|actionbar> <제목 텍스트> 또는 /title <플레이어> <times> <페이드 인 시간> <대기 시간> <페이드 아웃 시간> +nukkit.command.title.clear={%0}의 화면을 성공적으로 초기화했습니다 +nukkit.command.title.reset={%0}의 제목 애니메이션 설정을 성공적으로 초기화했습니다 +nukkit.command.title.title={%1}에게 "{%0}" 제목을 성공적으로 보여주었습니다 +nukkit.command.title.subtitle={%1}의 다음 제목의 부제를 "{%0}"(으)로 성공적으로 설정했습니다 +nukkit.command.title.actionbar={%1}에게 "{%0}" 액션바 제목을 성공적으로 표시했습니다 +nukkit.command.title.times.success={%3}의 제목 애니메이션 시간을 {%0}, {%1}, {%2}(으)로 성공적으로 설정했습니다 +nukkit.command.title.times.fail=제목 애니메이션의 시간은 숫자 값이어야 합니다 + +nukkit.command.version.description=사용 중인 플러그인을 포함해 이 서버의 버전을 확인합니다 +nukkit.command.version.usage=/version [플러그인 이름] +nukkit.command.version.noSuchPlugin=이 서버는 해당 이름의 플러그인을 실행하고 있지 않습니다. /plugins를 입력해 플러그인 목록을 확인하세요. + +nukkit.command.give.description=지정된 플레이어에게 일정량의 아이템을 지급합니다 +nukkit.command.give.usage=/give <플레이어> <아이템[:손상 정도]> [양] [태그 ...] + +nukkit.command.kill.description=자기 자신 또는 다른 플레이어를 죽입니다 +nukkit.command.kill.usage=/kill [플레이어] + +nukkit.command.particle.description=세계에 입자를 추가합니다 +nukkit.command.particle.usage=/particle <이름> <x> <y> <z> <xd> <yd> <zd> [개수] [데이터] + +nukkit.command.time.description=각 세계의 시간을 변경합니다 +nukkit.command.time.usage=/time <set|add> <값> 또는 /time <start|stop|query> + +nukkit.command.gamerule.description=게임 규칙 값을 설정 또는 쿼리합니다 +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=지정된 플레이어가 이 서버를 이용하지 못하도록 합니다 +nukkit.command.ban.ip.description=지정된 IP 주소가 이 서버를 이용하지 못하도록 합니다 +nukkit.command.banlist.description=이 서버에서 차단된 모든 플레이어 목록을 보여줍니다 +nukkit.command.defaultgamemode.description=기본 게임 모드를 설정합니다 +nukkit.command.deop.description=지정된 플레이어의 관리자 지위를 박탈합니다 +nukkit.command.difficulty.description=게임 난이도를 설정합니다 +nukkit.command.enchant.description=아이템에 마법을 부여합니다 +nukkit.command.effect.description=플레이어에게 효과를 부여/제거합니다 +nukkit.command.gamemode.description=플레이어를 특정 게임 모드로 변경합니다 +nukkit.command.help.description=도움말 메뉴를 보여줍니다 +nukkit.command.kick.description=지정된 플레이어를 서버에서 제거합니다 +nukkit.command.list.description=모든 온라인 플레이어 목록을 보여줍니다 +nukkit.command.me.description=대화에서 지정된 행동을 합니다 +nukkit.command.op.description=지정된 플레이어에게 관리자 지위를 부여합니다 +nukkit.command.unban.player.description=지정된 플레이어가 이 서버를 이용할 수 있도록 합니다 +nukkit.command.unban.ip.description=지정된 IP 주소가 이 서버를 이용할 수 있도록 합니다 +nukkit.command.save.description=서버를 디스크에 저장합니다 +nukkit.command.saveoff.description=서버 자동 저장을 비활성화합니다 +nukkit.command.saveon.description=서버 자동 저장을 활성화합니다 +nukkit.command.say.description=입력된 메시지를 보낸 사람으로 방송합니다 +nukkit.command.seed.description=세계 시드를 보여줍니다 +nukkit.command.setworldspawn.description=세계의 스폰 지점을 설정합니다. 좌표가 지정되지 않으면 플레이어의 좌표가 사용됩니다. +nukkit.command.spawnpoint.description=플레이어의 스폰 지점을 설정합니다 +nukkit.command.stop.description=서버를 중지합니다 +nukkit.command.tp.description=입력된 플레이어 (또는 자기 자신)을 다른 플레이어나 좌표로 순간이동시킵니다 +nukkit.command.tell.description=입력된 플레이어에게 귓속말을 보냅니다 +nukkit.command.weather.description=현재 세계의 날씨를 설정합니다 +nukkit.command.xp.description=지정된 플레이어에게 일정량의 경험치를 지급합니다 +nukkit.command.whitelist.description=이 서버를 이용할 수 있는 플레이어 목록을 관리합니다 + +nukkit.crash.create=복구 불가능한 오류가 발생해 서버가 강제 종료되었습니다. 충돌 덤프를 생성합니다 +nukkit.crash.error=충돌 덤프를 생성할 수 없습니다: {%0} +nukkit.crash.submit="{%0}" 파일을 충돌 보관소에 업로드하고 버그 보고 페이지에 링크를 제출해주세요. 가능한 한 많은 정보를 주시기 바랍니다. +nukkit.crash.archive=충돌 덤프가 자동으로 충돌 보관소에 제출되었습니다. {%0}에서 확인하거나 ID #{%1}을(를) 사용할 수 있습니다. + +nukkit.debug.enable=LevelDB 지원이 활성화되었습니다 + +nukkit.bugreport.create=오류가 감지되었습니다, 버그 보고서를 생성합니다. +nukkit.bugreport.error=버그 보고서를 생성할 수 없습니다: {%0} +nukkit.bugreport.archive=버그 보고서가 생성되었습니다: {%0} + +nukkit.player.invalidMove={%0}이(가) 잘못된 움직임을 보였습니다! +nukkit.player.logIn={%0}[/{%1}:{%2}]이(가) 개체 ID {%3}(으)로 ({%4}, {%5}, {%6}, {%7})에 로그인했습니다 +nukkit.player.logOut={%0}[/{%1}:{%2}]이(가) {%3}(으)로 인해 로그아웃되었습니다 +nukkit.player.invalidEntity={%0}이(가) 잘못된 개체를 공격하려고 시도했습니다 + +nukkit.plugin.load={%0}을(를) 불러오는 중입니다 +nukkit.plugin.enable={%0}을(를) 활성화 중입니다 +nukkit.plugin.disable={%0}을(를) 비활성화 중입니다 +nukkit.plugin.restrictedName=제한된 이름입니다 +nukkit.plugin.incompatibleAPI=호환되지 않는 API 버전입니다 +nukkit.plugin.unknownDependency=알 수 없는 의존입니다: {%0} +nukkit.plugin.circularDependency=순환적 의존이 감지되었습니다 +nukkit.plugin.genericLoadError="{%0}" 플러그인을 불러올 수 없습니다 +nukkit.plugin.spacesDiscouraged="{%0}" 플러그인은 이름에 공백을 사용합니다, 이는 권장되지 않습니다 +nukkit.plugin.loadError="{%0}" 플러그인을 불러올 수 없습니다: {%1} +nukkit.plugin.duplicateError="{%0}" 플러그인을 불러올 수 없습니다: 이미 존재하는 플러그인입니다 +nukkit.plugin.fileError="{%1}" 폴더에서 "{%0}"을(를) 불러올 수 없습니다: {%2} +nukkit.plugin.commandError={%0} 명령어를 {%1} 플러그인에서 불러올 수 없습니다 +nukkit.plugin.aliasError={%0} 별칭을 {%1} 플러그인에서 불러올 수 없습니다 +nukkit.plugin.deprecatedEvent="{%0}" 플러그인이 "{%1}"에 대한 리스너를 "{%2}" 메서드에서 등록했지만, 이 이벤트는 사용되지 않습니다. +nukkit.plugin.eventError="{%0}" 이벤트를 "{%1}"에 전달할 수 없습니다: {%3}에서의 {%2} + +nukkit.resources.invalid-path="{%0}" 리소스 팩 경로가 존재하며 디렉터리가 아닙니다 +nukkit.resources.unknown-format=포맷이 인식되지 않아 "{%0}"을(를) 불러올 수 없습니다 +nukkit.resources.fail="{%0}"을(를) 불러올 수 없습니다: {%1} +nukkit.resources.success={%0}개의 리소스 팩을 성공적으로 불러왔습니다 +nukkit.resources.zip.not-found="{%0}" 파일을 찾을 수 없습니다 +nukkit.resources.zip.no-manifest="manifest.json"을 찾을 수 없습니다 +nukkit.resources.zip.invalid-manifest="manifest.json"이 잘못되었거나 불완전합니다 diff --git a/src/main/resources/lang/language.list b/src/main/resources/lang/language.list new file mode 100644 index 00000000000..2f722e638c2 --- /dev/null +++ b/src/main/resources/lang/language.list @@ -0,0 +1,17 @@ +eng => English +chs => 中文(简体) +cht => 中文(繁體) +jpn => 日本語 +rus => Pyccĸий +spa => Español +pol => Polish +bra => Português-Brasil +kor => 한국어 +ukr => Українська +deu => Deutsch +ltu => Lietuviškai +idn => Indonesia +cze => Czech +tur => Turkish +fin => Suomi +ara => العربيه diff --git a/src/main/resources/lang/ltu/lang.ini b/src/main/resources/lang/ltu/lang.ini new file mode 100644 index 00000000000..8681afe96d3 --- /dev/null +++ b/src/main/resources/lang/ltu/lang.ini @@ -0,0 +1,382 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Lietuvių +language.selected=Pasirinkote {%0} ({%1}) kaip pagrindinę kalbą + +multiplayer.player.joined={%0} prisijungė į žaidimą +multiplayer.player.left={%0} atsijungė nuo žaidimo + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} gavo pasiekimą {%1} + +disconnectionScreen.outdatedClient=Pasenęs klientas! +disconnectionScreen.outdatedServer=Pasenęs serveris! +disconnectionScreen.serverFull=Serveris pilnas! +disconnectionScreen.noReason=Atjungtas nuo serverio +disconnectionScreen.invalidSkin=Netinkamas skin! +disconnectionScreen.invalidName=Netinkamas vardas! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} nukrito iš aukštai +death.attack.inFire={%0} sudegė +death.attack.onFire={%0} sudegė +death.attack.lava={%0} bandė plaukti lavoje +death.attack.lava.magma={%0} perkaito +death.attack.inWall={%0} buvo suplotas +death.attack.drown={%0} nuskendo +death.attack.cactus={%0} nusidūrė +death.attack.generic={%0} žuvo +death.attack.explosion={%0} sprogo +death.attack.explosion.player={%0} buvo susprogdintas {%1} +death.attack.magic={%0} buvo nužudytas magijos +death.attack.wither={%0} buvo nužudytas vitherio +death.attack.mob={%0} mirė nuo {%1} kirčio +death.attack.player={%0} mirė nuo {%1} kirčio +death.attack.player.item={%0} mirė nuo {%1} kirčio naudojant {%2} +death.attack.arrow={%0} buvo nušautas iš {%1} lanko +death.attack.arrow.item={%0} buvo nušautas iš {%1} lanko naudojant {%2} +death.attack.fall={%0} susiplojo ant žemės +death.attack.outOfWorld={%0} iškrito iš pasaulio +death.attack.starve={%0} starved to death + +gameMode.survival=Išgyvenimo rėžimas +gameMode.creative=Kūrybinis rėžimas +gameMode.adventure=Nuotykių rėžimas +gameMode.spectator=Stebėtojo rėžimas +gameMode.changed=Jūsų žaidimo rėžimas buvo pakeistas + +potion.moveSpeed=Greitis +potion.moveSlowdown=Lėtumas +potion.digSpeed=Greitas kasimas +potion.digSlowDown=Lėtas kasimas +potion.damageBoost=Jėga +potion.heal=Momentinė sveikata +potion.harm=Momentinė žala +potion.jump=Šoklumas +potion.confusion=Pykinimas +potion.regeneration=Atgijimas +potion.resistance=Atsparumas +potion.fireResistance=Atsparumas ugniai +potion.waterBreathing=Kvėpavimas po vandeniu +potion.invisibility=Nematomumas +potion.blindness=Aklumas +potion.nightVision=Naktinis matymas +potion.hunger=Alkis +potion.weakness=Silpnumas +potion.poison=Nuodai +potion.wither=Vitheris +potion.healthBoost=Papildoma sveikata +potion.absorption=Sugertis +potion.saturation=Pasisotinimas + +commands.generic.exception=Nežinoma klaida bandant įvykdyti komandą +commands.generic.permission=Jūs neturite teisės naudoti šią komandą +commands.generic.ingame=Jūs galite naudoti šią komandą tik kaip žaidėjas +commands.generic.unknown=Nežinoma komanda. Bandykite /help, norėdami gauti pagalbos +commands.generic.player.notFound=Žaidėjas nerastas +commands.generic.usage=Naudojimas: {%0} + +commands.time.added=Pridėta {%0} prie laiko +commands.time.set=Laikas nustatytas į {%0} +commands.time.query=Dabartinis laikas yra {%0} + +commands.me.usage=/me <veiksmas ...> + +commands.give.item.notFound=Nėra tokio daikto su pavadinimu {%0} +commands.give.success=Davėte {%0} * {%1} žaidėjui {%2} +commands.give.tagError=Neteisingas užrašymas: {%0} + +commands.effect.usage=/effect <žaidėjas> <efektas> [sekundės] [stiprumas] [slėpti-daleles] ARBA /effect <žaidėjas> clear +commands.effect.notFound=Nėra tokio monstro su ID {%0} +commands.effect.success=Davėte {%0} (ID {%1}) * {%2} žaidėjui {%3} {%4} sekundžių +commands.effect.success.removed=Paėmėte {%0} iš {%1} +commands.effect.success.removed.all=Išemėte visus efektus nuo {%0} +commands.effect.failure.notActive=Negalima išimti {%0} iš {%1}, nes jie neturi tokio efekto +commands.effect.failure.notActive.all=Negalima išmiti efektų iš {%0}, nes jie tokių neturi + +commands.enchant.noItem=Žaidėjas nelaiko jokio daikto rankoje +commands.enchant.notFound=Nėra tokio užkerėjimo ID {%0} +commands.enchant.success=Sėkmingai užkerėta +commands.enchant.usage=/enchant <žaidėjas> <užkerėjimo ID> [lygis] + +commands.particle.success=Rodomas efektas {%0} {%1} kartų +commands.particle.notFound=Nežinomas efektas {%0} + +commands.players.usage=/list +commands.players.list=Dabar prisijungę {%0}/{%1} žaidėjų: + +commands.kill.successful=Nužudyta (-as) {%0} +commands.kill.all.successful=Nužudyti visi žaidėjai +commands.kill.entities.successful=Nužudytos visos būtybės + +commands.banlist.ips=Yra iš viso {%0} užblokuotų IP adresų: +commands.banlist.players=Yra iš viso {%0} blokuotų žaidėjų: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=Pasaulio įprastinis žaidimo rėžimas nustatytas į {%0} + +commands.op.success={%0} dabar turi administratoriaus teises +commands.op.usage=/op <žaidėjas> + +commands.deop.success={%0} nebeturi administratoriaus teisių +commands.deop.usage=/deop <player> + +commands.say.usage=/say <žinutė ...> + +commands.seed.usage=/seed +commands.seed.success=Pasaulio generavimo formulė: {%0} + +commands.ban.success={%0} užblokuotas +commands.ban.usage=/ban <žaidėjas> [priežastis ...] + +commands.unban.success=Žaidėjas {%0} atblokuotas +commands.unban.usage=/pardon <žaidėjas> + +commands.banip.invalid=Įvedėte blogą IP adresą arba šis žaidėjas nėra prisijungęs prie serverio +commands.banip.offline.invalid=Žaidėjo duomenyse IP adresas nerastas arba įvestas blogas IP adresas +commands.banip.success=Užblokuotas IP adresas {%0} +commands.banip.success.players=Užblokuotas IP adresas {%0} kuris priklauso žaidėjui {%1} +commands.banip.usage=/ban-ip <IP|žaidėjas> [priežastis ...] + +commands.unbanip.invalid=Įvedėte blogą IP adresą +commands.unbanip.success=Atblokuotas IP adresas {%0} +commands.unbanip.usage=/pardon-ip <IP> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Įjungtas pasaulio auto išsaugojimas +commands.save.disabled=Išjungtas pasaulio auto išsaugojimas +commands.save.start=Išsaugoma... +commands.save.success=Pasaulis išsaugotas + +commands.stop.usage=/stop +commands.stop.start=Serveris išjungiamas + +commands.kick.success=Žaidėjas {%0} išmestas iš žaidimo +commands.kick.success.reason=Žaidėjas {%0} išmestas iš žaidimo: '{%1}' +commands.kick.usage=/kick <žaidėjas> [priežastis ...] + +commands.tp.success=Perkeltas(-a) {%0} pas {%1} +commands.tp.success.coordinates=Perkeltas(-a) {%0} į koordinates {%1}, {%2}, {%3} +commands.tp.usage=/tp [ką] <pas ką> ARBA /tp [ką] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=Yra {%0} (matyti {%1}) žaidėjai baltajame sąraše: +commands.whitelist.enabled=Baltasis sąrašas įjungtas +commands.whitelist.disabled=Baltasis sąrašas išjungtas +commands.whitelist.reloaded=Baltasis sąrašas perkrautas +commands.whitelist.add.success=Žaidėjas {%0} pridėtas į baltąjį sąrašą +commands.whitelist.add.usage=/whitelist add <žaidėjas> +commands.whitelist.remove.success=Žaidėjas {%0} pašalintas iš baltojo sąrašo +commands.whitelist.remove.usage=/whitelist remove <žaidėjas> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Jūsų žaidimo rėžimas pakeistas į {%0} +commands.gamemode.success.other=Žaidėjo {%0} žaidimo rėžimas pakeistas į {%1} +commands.gamemode.usage=/gamemode <rėžimas> [žaidėjas] + +commands.help.header=--- Rodomas pagalbos puslapis {%0} iš {%1} (/help <puslapis>) --- +commands.help.usage=/help [puslapis|komanda] + +commands.message.usage=/tell <žaidėjui> <žinutę ...> +commands.message.sameTarget=Jūs negalite siųsti žinučių sau! + +commands.difficulty.usage=/difficulty <sudėtingumo lygis 0-3> +commands.difficulty.success=Nustatytas sudėtingumo lygis į {%0} + +commands.spawnpoint.usage=/spawnpoint [žaidėjas] [<x> <y> <z>] +commands.spawnpoint.success=Nustatyta žaidėjo {%0} atsiradimo vieta ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Nustatyta nauja pasaulio atsiradimo vieta ({%0}, {%1}, {%2}) + +commands.weather.clear=Keičiama į giedrą +commands.weather.rain=Keičiama į lietų +commands.weather.thunder=Keičiama į lietų ir griaustinį +commands.weather.usage=/weather <clear|rain|thunder> [trukmė] + +commands.xp.success=Duota {%0} patirties žaidėjui {%1} +commands.xp.success.levels=Duota {%0} lygių žaidėjui {%1} +commands.xp.success.levels.minus=Paimta {%0} lygių iš {%1} +commands.xp.usage=/xp <kiekis> [žaidėjas] ARBA /xp <lygis>L [žaidėjas] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Žaidėjas "{%0}" nerastas, kuriamas naujas profilis +nukkit.data.playerCorrupted=Žaidėjo "{%0}" duomenys sugadinti, kuriamas naujas profilis +nukkit.data.playerOld=Žaidėjo "{%0}" duomenys pasenę, atnaujinamas profilis +nukkit.data.saveError=Neįmanoma išsaugoti žaidėjo "{%0}": {%1} + +nukkit.level.notFound=Pasaulis "{%0}" nerastas +nukkit.level.loadError=Neįmanoma įkelti pasaulio "{%0}": {%1} +nukkit.level.generationError=Neįmanoma sugeneruoti pasaulio "{%0}": {%1} +nukkit.level.tickError=Neįmanoma atnaujinti pasaulio "{%0}": {%1} +nukkit.level.chunkUnloadError=Klaida bandant iškrauti pasaulio dalį: {%0} +nukkit.level.backgroundGeneration=Atsiradimo vietovė pasaulyje "{%0}" šiuo metu generuojama fone +nukkit.level.defaultError=Nepakrautas joks įprastinis pasaulis +nukkit.level.preparing=Ruošiamas pasaulis "{%0}" +nukkit.level.unloading=Iškraunamas pasaulis "{%0}" +nukkit.level.updating=Rastas pasenęs pasaulis pavadinimu "{%0}", atnaujinamas formatas + +nukkit.server.start=Įjungiamas Minecraft: BE serveris (versija: {%0}) (Vertimas: nogalosa.lt) +nukkit.server.networkError=[Tinklas] Išjungta sąsaja {%0} dėl {%1} +nukkit.server.networkStart=Atidaromas serveris ant {%0}:{%1} +nukkit.server.info=Šis serveris veikia ant {%0} versijos {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Šis serveris veikia ant {%0} {%1} 「{%2}」 implementuodamas API versiją {%3} žaidimui Minecraft: BE {%4} (protokolo versija {%5}) +nukkit.server.license={%0} platinama pagal GPL licenciją +nukkit.server.tickOverload=Nespėju atnaujinti! Ar serveris perkrautas? +nukkit.server.startFinished=Baigta ({%0}s)! Jeigu reikia pagalbos, rašykite "help" arba "?" +nukkit.server.defaultGameMode=Įprastinis žaidimo rėžimas: {%0} +nukkit.server.query.start=Įjungiamas GS4 statistikos klausytojas +nukkit.server.query.info=Nustatomas query portas į {%0} +nukkit.server.query.running=Query veikia ant {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Neįmanoma įjungti RCON: neįrašytas slaptažodis +nukkit.server.rcon.startupError=Neįimanoma įjungti RCON: {%0} +nukkit.server.rcon.running=RCON veikia ant {%0}:{%1} + +nukkit.command.alias.illegal=Neįmanoma užregistruoti alternatyvos {%0}, nes ji turi netinkamų simbolių +nukkit.command.alias.notFound=Neįmanoma užregistruoti alternatyvos {%0}, nes ji turi komandų, kurios neegzistuoja: {%1} +nukkit.command.exception=Klaida bandant įvykdyti komandą "{%0}" - {%1}: {%2} + +nukkit.command.plugins.description=Įskiepių sąrašas, kurį naudoja serveris +nukkit.command.plugins.success=Įskiepiai ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Perkrauna serverio konfigūraciją ir įskiepius +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Perkraunamas serveris... +nukkit.command.reload.reloaded=Sėkmingai perkrauta. + +nukkit.command.status.description=Informacija apie serverio būseną. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Įjungia programinių šiukšlių valymą +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Įrašo efektyvumo statistiką serverio būsenos stebėjimui +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Statistika nustatyta iš naujo ir įjungtas įrašymas +nukkit.command.timings.disable=Statistikos įrašymas išjungtas +nukkit.command.timings.timingsDisabled=Prašome įjungti statistikos stebėjimą: /timings on +nukkit.command.timings.verboseEnable=Įjungta verbose statistika +nukkit.command.timings.verboseDisable=Išjungta verbose statistika +nukkit.command.timings.reset=Statistika nustatyta iš naujo +nukkit.command.timings.rcon=Dėmesio: Statistikos sudarymas per RCON sukels serverio strigimą, Jūs turėtumėte naudoti /timings report žaidime arba konsolėje +nukkit.command.timings.uploadStart=Sudaroma statistika... +nukkit.command.timings.uploadError=Įkėlimo klaida: {%0}: {%1}, dėl daugiau informacijos patikrinkite serverio žurnalą +nukkit.command.timings.reportError=Įvyko klaida įvedant statistiką, dėl daugiau informacijos patikrinkite serverio žurnalą +nukkit.command.timings.timingsLocation=Peržiūrėti sudarytą statistiką: {%0} +nukkit.command.timings.timingsResponse=Statistikos atsakymas: {%0} +nukkit.command.timings.timingsWrite=Statistiką įrašyta į {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Parodo serverio versiją bei įskiepio informaciją +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=Šis serveris neturi įskiepio su šiuo vardu. Naudokite /plugins, kad gautumėte įskiepių sąrašą. + +nukkit.command.give.description=Duoda pasirinktam žaidėjui tam tikrą skaičių nurodytų daiktų +nukkit.command.give.usage=/give <žaidėjas> <daiktas[:žala]> [kiekis] [duomenys ...] + +nukkit.command.kill.description=Nusižudyti arba nužudyti kitą žaidėją +nukkit.command.kill.usage=/kill [žaidėjas] + +nukkit.command.particle.description=Prideda dalelyčių į pasaulį +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [skaičius] [duomenys] + +nukkit.command.time.description=Pakeičia pasaulio laiką +nukkit.command.time.usage=/time <set|add> <reikšmė> OR /time <start|stop|query> + +nukkit.command.gamerule.description=Sets or queries a game rule value +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Užblokuoja pasirinktą žaidėją +nukkit.command.ban.ip.description=Užblokuoja pasirinktą IP +nukkit.command.banlist.description=Peržiūrėti visus užblokuotus žaidėjus +nukkit.command.defaultgamemode.description=Nustatyti įprastinį žaidimo rėžimą +nukkit.command.deop.description=Atima administravimo teises +nukkit.command.difficulty.description=Nustato žaidimo sunkumą +nukkit.command.enchant.description=Daikto užkerėjimas +nukkit.command.effect.description=Prideda/nuima efektą nuo žaidėjo +nukkit.command.gamemode.description=Pakeičia žaidėjo žaidimo rėžimą +nukkit.command.help.description=Parodo pagalbos meniu +nukkit.command.kick.description=Išspiria žaidėją iš žaidimo +nukkit.command.list.description=Parodo prisijungusius žaidėjus +nukkit.command.me.description=Parodo tam tikrą žaidėjo veiksmą +nukkit.command.op.description=Duoda žaidėjui administratoravimo teises +nukkit.command.unban.player.description=Atblokuoja žaidėją +nukkit.command.unban.ip.description=Atblokuoja IP +nukkit.command.save.description=Išsaugo serverį į diską +nukkit.command.saveoff.description=Išjungia serverio automatinį išsaugojimą +nukkit.command.saveon.description=Įjungia serverio automatinį išsaugojimą +nukkit.command.say.description=Išsiunčiama žinutė visiems žaidėjams +nukkit.command.seed.description=Parodo pasaulio generavimo formulę. +nukkit.command.setworldspawn.description=Nustato pasaulio atsiradimo vietą. Jeigu nepateiktos koordinatės, naudojamos žaidėjo. +nukkit.command.spawnpoint.description=Nustato žaidėjo atsiradimo vietą. +nukkit.command.stop.description=Išjungia serverį +nukkit.command.tp.description=Perkelia nurodytą žaidėją (arba patį žaidėją) pas kitą žaidėją arba į koordinates +nukkit.command.tell.description=Nusiunčiama privati žinutė žaidėjui +nukkit.command.weather.description=Nustatomas pasaulio oras +nukkit.command.xp.description=Duoda nurodytam žaidėjui nurodytą kiekį patirties +nukkit.command.whitelist.description=Nustatoma kas gali įeitį į serverį + +nukkit.crash.create=Serveris nulūžo su nepataisoma klaida. Kaupiama nulūžimo informaciją į failą +nukkit.crash.error=Neįmanoma įrašyti nulūžimo informacijos į failą: {%0} +nukkit.crash.submit=Prašome įkelti failą "{%0}" į Crash Archive ir numeskite nuorodą į Bug Reporting puslapį. Pridėkite kuo įmanoma daugiau išsamesnės informacijos. +nukkit.crash.archive=Nulūžimo informacija buvo automatiškai įkelta į Crash Archive. Jūs galite ją pažiūrėti čia {%0} arba naudoti ID #{%1}. + +nukkit.debug.enable=LevelDB palaikymas įjungtas + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} neteisingai pajudėjo! + +nukkit.player.logIn={%0}[/{%1}:{%2}] prisijungė su būtybės id {%3} koordinatėse ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] atsijungė dėl {%3} +nukkit.player.invalidEntity={%0} bandė atakuoti klaidingą būtybę + +nukkit.plugin.load=Kraunama {%0} +nukkit.plugin.enable=Įjungiama {%0} +nukkit.plugin.disable=Išjungiama {%0} +nukkit.plugin.restrictedName=Uždraustas vardas +nukkit.plugin.incompatibleAPI=Nepalaikoma API versija +nukkit.plugin.unknownDependency=Nežinoma priklausomybė +nukkit.plugin.circularDependency=Aptikta skritulinė priklausomybė +nukkit.plugin.genericLoadError=Negalima įkelti įskiepio "{%0}" +nukkit.plugin.spacesDiscouraged=Įskiepis "{%0}" naudoja tarpus savo pavadinime, tai yra nebepalaikoma +nukkit.plugin.loadError=Neįmanoma įkelti įskiepio "{%0}": {%1} +nukkit.plugin.duplicateError=Neįmanoma įkelti įskiepio "{%0}": įskiepis jau egzistuoja +nukkit.plugin.fileError=Neįmanoma įkelti "{%0}" kataloge "{%1}": {%2} +nukkit.plugin.commandError=Neįmanoma įkelti komandos {%0} įskiepiui {%1} +nukkit.plugin.aliasError=Neįmanoma įkelti alternatyvos {%0} įskiepiui {%1} +nukkit.plugin.deprecatedEvent=Įskiepis "{%0}" užregistravo listenerį "{%1}" ant metodo "{%2}", bet eventas jau nebepalaikomas. +nukkit.plugin.eventError=Neįmanoma pereiti per eventą "{%0}" to "{%1}": {%2} on {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/pol/lang.ini b/src/main/resources/lang/pol/lang.ini new file mode 100644 index 00000000000..2d89825b92e --- /dev/null +++ b/src/main/resources/lang/pol/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Polski +language.selected=Wybrano {%0} ({%1}) jako jezyk bazowy + +multiplayer.player.joined={%0} dolaczyl do gry +multiplayer.player.left={%0} opuscil gre + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} zdobyl osiagniecie {%1} + +disconnectionScreen.outdatedClient=Twoja wersja klienta jest starsza od wersji serwera! +disconnectionScreen.outdatedServer=Wersja serwera jest starsza od twojej wersji klienta! +disconnectionScreen.serverFull=Serwer jest pelny! +disconnectionScreen.noReason=Rozlaczono z serwerem +disconnectionScreen.invalidSkin=Nieprawidlowy skin! +disconnectionScreen.invalidName=Nieprawidlowy nick! +disconnectionScreen.resourcePack=Natrafiono na problem podczas pobierania lub ladowania paczki zasobow + +death.fell.accident.generic={%0} upadl z wysokosci +death.attack.inFire={%0} spalil sie w plomieniach +death.attack.onFire={%0} spalil sie na smierc +death.attack.lava={%0} probowal plywac w lawie +death.attack.lava.magma={%0} odkryl, ze podloga jest lawa +death.attack.inWall={%0} umarl w scianie +death.attack.drown={%0} utonal +death.attack.cactus={%0} zostal zakluty na smierc +death.attack.generic={%0} zginal +death.attack.explosion={%0} zostal wysadzony +death.attack.explosion.player={%0} zostal wysadzony przez {%1} +death.attack.magic={%0} zostal zabity przez magie +death.attack.wither={%0} zamienil sie w pyl +death.attack.mob={%0} zostal zabity przez {%1} +death.attack.player={%0} zostal zabity przez {%1} +death.attack.player.item={%0} zostal zabity przez {%1} za pomoca {%2} +death.attack.arrow={%0} zostal zastrzelony przez {%1} +death.attack.arrow.item={%0} zostal zastrzelony przez {%1} za pomoca {%2} +death.attack.fall={%0} uderzyl sie o ziemie zbyt mocno +death.attack.outOfWorld={%0} wypadl ze swiata +death.attack.starve={%0} zaglodzil sie na smierc + +gameMode.survival=Tryb przetrwania +gameMode.creative=Tryb kreatywny +gameMode.adventure=Tryb przygody +gameMode.spectator=Tryb obserwatora +gameMode.changed=Twoj tryb gry zostal zmieniony + +potion.moveSpeed=Predkosc +potion.moveSlowdown=Spowolnienie +potion.digSpeed=Predkosc kopania +potion.digSlowDown=Spowolnione kopanie +potion.damageBoost=Zwiekszona sila +potion.heal=Natychmiastowe leczenie +potion.harm=Natychmiastowe obrazenie +potion.jump=Zwiekszony skok +potion.confusion=Dezorientacja +potion.regeneration=Regeneracja +potion.resistance=Odpornosc +potion.fireResistance=Odpornosc na ogien +potion.waterBreathing=Oddychanie pod woda +potion.invisibility=Niewidzialnosc +potion.blindness=Slepota +potion.nightVision=Widzenie w nocy +potion.hunger=Glod +potion.weakness=Slabosc +potion.poison=Trucizna +potion.wither=Wither +potion.healthBoost=Zwiekszone zdrowie +potion.absorption=Absorbcja +potion.saturation=Nasycenie + +commands.generic.exception=Wystapil nieznany blad podczas wykonywania tej komendy +commands.generic.permission=Nie masz uprawnien do wykonania tej komendy +commands.generic.ingame=Ta komende mozesz wykonac tylko bedac graczem +commands.generic.unknown=Nieznana komenda. Wpisz /help aby uzyskac liste komend +commands.generic.player.notFound=Nie znaleziono tego gracza +commands.generic.usage=Uzycie: {%0} + +commands.time.added=Dodano {%0} do czasu +commands.time.set=Ustawiono czas na {%0} +commands.time.query=Godzina to {%0} + +commands.me.usage=/me <akcja ...> + +commands.give.item.notFound=Nie ma przedmiotu o nazwie {%0} +commands.give.success=Dano {%0} * {%1} dla {%2} +commands.give.tagError=Parsowanie znacznika danych zawiodlo: {%0} + +commands.effect.usage=/effect <gracz> <efekt> [sekundy] [moc] [ukryjCzasteczki] LUB /effect <gracz> clear +commands.effect.notFound=Nie ma moba z ID {%0} +commands.effect.success=Dano {%0} (ID {%1}) * {%2} dla {%3} na {%4} sekund +commands.effect.success.removed=Zabrano {%0} od {%1} +commands.effect.success.removed.all=Zabrano wszystkie efekty graczowi {%0} +commands.effect.failure.notActive=Nie udalo sie zabrac {%0} od {%1} poniewaz nie ma tego efektu +commands.effect.failure.notActive.all=Nie udalo sie zabrac zadnego efektu {%0} jako ze nie ma zadnego + +commands.enchant.noItem=Cel nie trzyma zadnego przedmiotu +commands.enchant.notFound=Nie ma enchantu z ID {%0} +commands.enchant.success=Zakleto +commands.enchant.usage=/enchant <gracz> <ID enchantu> [poziom] + +commands.particle.success=Odtwarzanie efektu {%0} przez {%1} razy +commands.particle.notFound=Nieznana nazwa efektu {%0} + +commands.players.usage=/list +commands.players.list=Jest {%0}/{%1} graczy na serwerze: + +commands.kill.successful=Zabito {%0} +commands.kill.all.successful=Zabito wszystkich graczy +commands.kill.entities.successful=Zabito wszystkie byty + +commands.banlist.ips=Jest {%0} razem zbanowanych graczy na IP: +commands.banlist.players=Jest {%0} razem zbanowanych graczy: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <tryb> +commands.defaultgamemode.success=Domyslny tryb gry to teraz {%0} + +commands.op.success=Dodano OP graczowi {%0} +commands.op.usage=/op <gracz> + +commands.deop.success=Zabrano OP graczowi {%0} +commands.deop.usage=/deop <player> + +commands.say.usage=/say <wiadomosc ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Zbanowano gracza {%0} +commands.ban.usage=/ban <nazwa> [powod ...] + +commands.unban.success=Odbanowano gracza {%0} +commands.unban.usage=/pardon <nazwa> + +commands.banip.invalid=Wprowadziles niepoprawny adres IP albo gracz nie jest na serwerze +commands.banip.offline.invalid=Nie ma takiego IP w danych gracza lub IP jest nieprawidlowe +commands.banip.success=Zbanowano adres IP {%0} +commands.banip.success.players=Zbanowano adres IP {%0} nalezacy do {%1} +commands.banip.usage=/ban-ip <adres|nazwa> [powod ...] + +commands.unbanip.invalid=Wprowadziles niepoprawny adres IP +commands.unbanip.success=Odbanowano adres IP {%0} +commands.unbanip.usage=/pardon-ip <adres> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Wlaczono automatyczne zapisywanie swiata +commands.save.disabled=Wylaczono automatyczne zapisywania swiata +commands.save.start=Zapisywanie... +commands.save.success=Zapisano swiat! + +commands.stop.usage=/stop +commands.stop.start=Zatrzymuje serwer + +commands.kick.success=Gracz {%0} zostal wyrzucony z gry +commands.kick.success.reason=Wyrzucono {%0} z gry: '{%1}' +commands.kick.usage=/kick <gracz> [powod ...] + +commands.tp.success=Przeteleportowano {%0} do {%1} +commands.tp.success.coordinates=Przeteleportowano {%0} do {%1}, {%2}, {%3} +commands.tp.usage=/tp [cel gracz] <cel gracz> LUB /tp [cel gracz] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=Jest {%0} (z {%1} widzianych) graczy na whiteliscie: +commands.whitelist.enabled=Wlaczono whiteliste +commands.whitelist.disabled=Wylaczono whiteliste +commands.whitelist.reloaded=Przeladowano whiteliste +commands.whitelist.add.success=Dodano {%0} do whitelisty +commands.whitelist.add.usage=/whitelist add <gracz> +commands.whitelist.remove.success=Usunieto {%0} z whitelisty +commands.whitelist.remove.usage=/whitelist remove <gracz> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Ustawiono swoj tryb gry na {%0} +commands.gamemode.success.other=Ustawiono tryb gry gracza {%0} na {%1} +commands.gamemode.usage=/gamemode <tryb> [gracz] + +commands.help.header=--- Wyswietlona strona pomocy {%0} z {%1} (/help <strona>) --- +commands.help.usage=/help [strona|nazwa komendy] + +commands.message.usage=/tell <gracz> <prywatna wiadomosc ...> +commands.message.sameTarget=Nie mozesz wyslac prywatnej wiadomosci do siebie + +commands.difficulty.usage=/difficulty <nowy poziom trudnosci> +commands.difficulty.success=Ustawiono poziom trudnosci gry na {%0} + +commands.spawnpoint.usage=/spawnpoint [gracz] [<x> <y> <z>] +commands.spawnpoint.success=Ustawiono punkt spawnu gracza {%0} na pozycji ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Ustawiono punkt spawnu swiata na pozycji ({%0}, {%1}, {%2}) + +commands.weather.clear=Zmiana pogody na bezchmurna +commands.weather.rain=Zmiana pogody na deszczowa +commands.weather.thunder=Zmiana pogody na deszczowa lub burzowa +commands.weather.usage=/weather <clear|rain|thunder> [trwanie w sekundach] + +commands.xp.success=Dano {%0} EXPa dla gracza {%1} +commands.xp.success.levels=Dano {%0} poziomow dla gracz {%1} +commands.xp.success.levels.minus=Zabrano {%0} poziomow graczowi {%1} +commands.xp.usage=/xp <ilosc EXPa> [gracz] OR /xp <ilosc poziomow>L [gracz] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Nie znaleziono danych gracza "{%0}", tworzenie nowego profilu +nukkit.data.playerCorrupted=Uszkodzone dane gracza "{%0}", tworzenie nowego profilu +nukkit.data.playerOld=Znaleziono stare dane gracza "{%0}", aktualizowanie profilu +nukkit.data.saveError=Nie udalo sie zapisac gracza "{%0}": {%1} + +nukkit.level.notFound=Poziom "{%0}" nie zostal znaleziony +nukkit.level.loadError=Nie udalo sie wczytac poziomu "{%0}": {%1} +nukkit.level.generationError=Nie udalo sie wygenerowac poziomu "{%0}": {%1} +nukkit.level.tickError=Nie udalo sie obsluzyc operacji tick w pozomie"{%0}": {%1} +nukkit.level.chunkUnloadError=Problem przy zwalnianiu chunka: {%0} +nukkit.level.backgroundGeneration=Tworzenie terenu dla poziomu "{%0}" jest generowane w tle +nukkit.level.defaultError=Zaden domyslny poziom nie zostal zaladowany +nukkit.level.preparing=Przygotowywanie poziomu "{%0}" +nukkit.level.unloading=Zwalnianie poziomu "{%0}" +nukkit.level.updating=Znaleziono informacje o poziomie dla "{%0}", zmiana formatu + +nukkit.server.start=Startowanie Serwera Minecraft: BE na wersji {%0} +nukkit.server.networkError=[Network] Zatrzymano interfejs {%0} z powodu {%1} +nukkit.server.networkStart=Otwieranie serwera na {%0}:{%1} +nukkit.server.info=Ten serwer dziala na {%0} w wersji {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Ten serwer dziala na {%0} {%1} 「{%2}」na implementacji API {%3} dla Minecraft: BE {%4} (wersja protokolu {%5}) +nukkit.server.license={%0} wydawany na licencji GPL +nukkit.server.tickOverload=Nie nadazam! Serwer jest przeciazony!? +nukkit.server.startFinished=Ukonczono w ({%0} sekund)! Aby uzyskac pomoc wpisz help lub ? +nukkit.server.defaultGameMode=Domyslny tryb gry: {%0} +nukkit.server.query.start=Uruchamianie statusu nasluchiwania GS4 +nukkit.server.query.info=Ustawiam query port na {%0} +nukkit.server.query.running=Query dziala na {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Wlaczanie RCON nie udalo sie: haslo jest puste +nukkit.server.rcon.startupError=Wlaczanie RCON nie udalo sie: {%0} +nukkit.server.rcon.running=RCON dziala na {%0}:{%1} + +nukkit.command.alias.illegal=Nie udalo sie zarejestrowac aliasu {%0} poniewaz zawiera niepoprawne znaki +nukkit.command.alias.notFound=Nie udalo sie zarejestrowac aliasu {%0} poniewaz zawiera komendy, ktore nie istnieja: {%1} +nukkit.command.exception=Nieoczekiwany wyjatek przy wykonywaniu komendy "{%0}" in {%1}: {%2} + +nukkit.command.plugins.description=Pobiera liste pluginow na serwerze +nukkit.command.plugins.success=Pluginy ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Przeladowuje konfiguracje serwera i pluginy +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Przeladowywanie serwera... +nukkit.command.reload.reloaded=Przeladowano serwer + +nukkit.command.status.description=Odczytuje wydajnosc serwera. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Uruchamia procesy grabage colectora +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Zapisuje dane zwiazane z wydajnoscia serwer, aby mozna bylo je sprawdzic +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Wysyla title do wyznaczonego gracza, lub zmienia ustawienia title dla tego gracza +nukkit.command.title.usage=/title <gracz> <clear|reset> LUB /title <gracz> <title|subtitle|actionbar> <tekst title> LUB /title <gracz> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Wyczyszczenie ekranu {%0} zakonczone sukcesem +nukkit.command.title.reset=Reset ustawien animacji title dla {%0} zakonczony sukcesem +nukkit.command.title.title=Pokazanie title "{%0}" dla gracza {%1} zakonczone sukcesem +nukkit.command.title.subtitle=Ustawienie subtitle do "{%0}" dla nastepnego title gracza {%1} zakonczone sukcesem +nukkit.command.title.actionbar=Pokazanie tytulu actionbar'a "{%0}" graczowi {%1} zakonczone sukcesem +nukkit.command.title.times.success=Ustawienie czasow animacji title {%0}, {%1}, {%2} dla {%3} zakonczone sukcesem +nukkit.command.title.times.fail=Ilosc animacji title musi byc liczba + +nukkit.command.version.description=Wyswietla wersje serwera badz pluginu uzywanego przez serwer +nukkit.command.version.usage=/version [nazwa pluginu] +nukkit.command.version.noSuchPlugin=Nie ma pluginu o takiej nazwia. Uzyj /plugins aby uzyskac liste pluginow. + +nukkit.command.give.description=Daje wybranemu graczowi odpowiednia ilosc przedmiotu +nukkit.command.give.usage=/give <gracz> <przedmiot[:uszkodzenie]> [ilosc] [tagi ...] + +nukkit.command.kill.description=Powoduje popelnienie samobojstwa lub zabija innego gracza +nukkit.command.kill.usage=/kill [gracz] + +nukkit.command.particle.description=Dodaje czasteczki do swiata +nukkit.command.particle.usage=/particle <nazwa> <x> <y> <z> <xd> <yd> <zd> [ilosc] [dane] + +nukkit.command.time.description=Zmienia czas na kazdym swiecie +nukkit.command.time.usage=/time <set|add> <wartosc> LUB /time <start|stop|query> + +nukkit.command.gamerule.description=Ustawia lub wysyla zapytanie o wartosc reguly gry +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Wysyla informacje o serwerze na Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Banuje gracza na serwerze +nukkit.command.ban.ip.description=Banuje IP gracza na serwerze +nukkit.command.banlist.description=Wyswietla wszystkich zbanowanych graczy +nukkit.command.defaultgamemode.description=Ustawia domyslny tryb gry +nukkit.command.deop.description=Odbiera wybranemu graczowi OP na serwerze +nukkit.command.difficulty.description=ustawia poziom trudnosci gry +nukkit.command.enchant.description=Dodaje enchanty na przedmioty +nukkit.command.effect.description=Dodaje/Usuwa efekty na graczu +nukkit.command.gamemode.description=Zmienia tryb gry gracza +nukkit.command.help.description=Wyswietla menu pomocy +nukkit.command.kick.description=Wyrzuca gracza z serwera +nukkit.command.list.description=Wyswietla liste graczy na serwerze +nukkit.command.me.description=Wykonuje akcje na czacie +nukkit.command.op.description=Daje OP danemu graczowi +nukkit.command.unban.player.description=Odbanowuje gracza na serwerze +nukkit.command.unban.ip.description=Odbanowuje adres IP gracza na serwerze +nukkit.command.save.description=Zapisuje dane serwera na dysk +nukkit.command.saveoff.description=Wylacza automatyczny zapis serwera +nukkit.command.saveon.description=Wlacza automatyczny zapis serwera +nukkit.command.say.description=Wyswietla wiadomosc graczom +nukkit.command.seed.description=Pokazuje seed swiata +nukkit.command.setworldspawn.description=Ustawia punkt spawnu swiata. Jezeli nie zostana podane wspolrzedne to zostana ustawione jako pozycja gracza +nukkit.command.spawnpoint.description=Ustawia punkt spawnu gracza +nukkit.command.stop.description=Zatrzymuje serwer +nukkit.command.tp.description=Teleportuje podanego gracza (albo siebie) do innego gracza badz wsporzednych +nukkit.command.tell.description=Wysyla wiadomosc do okreslonego gracza +nukkit.command.weather.description=Ustawia pogode w obecnym swiecie +nukkit.command.xp.description=Daje podanemu graczu okreslona liczbe EXPa +nukkit.command.whitelist.description=Zarzadza whitelista + +nukkit.crash.create=Nieodwracalny blad wystapil na serwerze. Serwer zatrzymal sie! Zapisuje tresc bledu: +nukkit.crash.error=Nie udalo sie zapisac powodu bledu: {%0} +nukkit.crash.submit=Prosze wrzucic plik "{%0}" do archiwum bledow i wyslac link na stronie zglaszania bledow. Podaj najwiecej informacji ile mozesz. +nukkit.crash.archive=Plik z bledem zostal automatycznie wrzucony do archiwum bledow. Mozesz go zobaczyc tutaj {%0} lub uzyc ID #{%1}. + +nukkit.debug.enable=Wsparcie LevelDB zostalo wlaczone! + +nukkit.bugreport.create=Blad zostal znaleziony, tworzenie reportu +nukkit.bugreport.error=Nie mozna bylo stworzyc reportu o bledzie: {%0} +nukkit.bugreport.archive=Report o bledzie zostal stworzony: {%0} + +nukkit.player.invalidMove={%0} poruszyl sie nieprawidlowo! +nukkit.player.logIn={%0}[/{%1}:{%2}] zalogowal sie z id bytu {%3} na wspolrzednych ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] rozlaczyl sie z powodu {%3} +nukkit.player.invalidEntity={%0} probowal zaatakowac niepoprawny byt + +nukkit.plugin.load=Wczytywanie {%0} +nukkit.plugin.enable=Wlaczanie {%0} +nukkit.plugin.disable=Wylaczanie {%0} +nukkit.plugin.restrictedName=Zabroniona nazwa +nukkit.plugin.incompatibleAPI=Niekompatybilna wersja API +nukkit.plugin.unknownDependency=Nieznana zaleznosc +nukkit.plugin.circularDependency=Wykryto zapetlona zaleznosc +nukkit.plugin.genericLoadError=Nie udalo sie wczytac pluginu "{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" uzywa spacji w nazwie! Jest to odradzane! +nukkit.plugin.loadError=Nie udalo sie wczytac pluginu "{%0}": {%1} +nukkit.plugin.duplicateError=Nie udalo sie wczytac pluginu "{%0}": plugin istnieje +nukkit.plugin.fileError=Nie udalo sie wczytac "{%0}" w folderze "{%1}": {%2} +nukkit.plugin.commandError=Nie udalo sie wczytac komendy {%0} dla pluginu {%1} +nukkit.plugin.aliasError=Nie udalo sie wczytac aliasu {%0} dla pluginu {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" zarejestrowal listener na "{%1}" w metodzie "{%2}", ale event jest przestarzaly +nukkit.plugin.eventError=Nie udalo sie wykonac eventu "{%0}" z "{%1}": {%2} na {%3} + +nukkit.resources.invalid-path=Sciezka do paczki zasobow "{%0}" istnieje i nie jest folderem +nukkit.resources.unknown-format=Nie mozna bylo zaladowac "{%0}", format nie zostal rozpoznany +nukkit.resources.fail=Nie mozna bylo zaladowac "{%0}": {%1} +nukkit.resources.success=Zaladowano {%0} paczek zasobow z sukcesem +nukkit.resources.zip.not-found=Plik "{%0}" nie zostal znaleziony +nukkit.resources.zip.no-manifest="manifest.json" nie zostal znaleziony +nukkit.resources.zip.invalid-manifest="manifest.json" jest nieprawidlowy, lub nieskonczony diff --git a/src/main/resources/lang/rus/lang.ini b/src/main/resources/lang/rus/lang.ini new file mode 100644 index 00000000000..2965ce16462 --- /dev/null +++ b/src/main/resources/lang/rus/lang.ini @@ -0,0 +1,384 @@ +# Файл перевода совместим с Minecraft: Bedrock Edition на уровне идентификаторов +# +# Сообщению не обязательно находиться тут, чтобы отображаться правильно в клиенте +# Здесь обязательно должны быть сообщения, которые отображаются самим Nukkit'ом +# +# Если Вы нашли ошибку, приглашаем помочь с переводом: +# https://github.com/NukkitX/Languages +# Перевод осуществлён Pub4Game и fromgate (сообщество Nukkit.ru) + +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.outdatedClient=Ваш клиент устарел! +disconnectionScreen.outdatedServer=Сервер устарел! +disconnectionScreen.serverFull=Сервер заполнен! +disconnectionScreen.noReason=Отключен от сервера +disconnectionScreen.invalidSkin=Неверный скин! +disconnectionScreen.invalidName=Недопустимое имя! +disconnectionScreen.resourcePack=Ошибка загрузки и применения набора ресурсов. + +death.fell.accident.generic={%0} упал(а) с высокого места +death.attack.inFire={%0} сгорел(а) заживо +death.attack.onFire={%0} сгорел(а) заживо +death.attack.lava={%0} решил(а) поплавать в лаве +death.attack.lava.magma={%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} выпал(а) из мира +death.attack.starve={%0} starved to death + +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.ingame=Вы можете использовать эту команду только в игре +commands.generic.unknown=Неизвестная команда. Используйте /help для просмотра списка команд +commands.generic.player.notFound=Игрок не найден +commands.generic.usage=Используйте: {%0} + +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.noItem=У игрока нет такого предмета +commands.enchant.notFound=Зачарования с ID {%0} не существует +commands.enchant.success=Зачарование прошло успешно +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.kill.all.successful=Убил всех игроков +commands.kill.entities.successful=Убил всех сущностей + +commands.banlist.ips=Всего заблокирован(о) {%0} 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.ban.success=Заблокирован игрок {%0} +commands.ban.usage=/ban <игрок> [причина ...] + +commands.unban.success=Разблокирован игрок {%0} +commands.unban.usage=/pardon <игрок> + +commands.banip.invalid=Введен неверный IP-адрес или данный игрок не в сети +commands.banip.offline.invalid=There is no IP address in player data or IP address is invalid +commands.banip.success=Заблокирован IP адрес {%0} +commands.banip.success.players=Заблокирован IP адрес {%0} принадлежащий {%1} +commands.banip.usage=/ban-ip <IP-адрес|игрок> [причина ...] + +commands.unbanip.invalid=Вы ввели неверный IP адрес +commands.unbanip.success=Разблокирован IP адрес {%0} +commands.unbanip.usage=/pardon-ip <IP-адрес> + +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.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 [целевой игрок] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list=В вайтлисте {%0} игроков (из {%1} отображаемых): +commands.whitelist.enabled=Вайтлист включен +commands.whitelist.disabled=Вайтлист выключен +commands.whitelist.reloaded=Список игроков в whitelist был перезагружен +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Установлен режим игры {%0} для себя +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 [игрок] [<x> <y> <z>] +commands.spawnpoint.success=Точка респауна игрока установлена на {%0} ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Точка респауна в мире установлена на ({%0}, {%1}, {%2}) + +commands.weather.clear=Изменяет погоду на солнечную +commands.weather.rain=Изменяет погоду на дождливую +commands.weather.thunder=Изменяет погоду на штормовую +commands.weather.usage=/weather <clear|rain|thunder> [продолжительность в секундах] + +commands.xp.success=Выдано {%0} очков опыта игроку {%1} +commands.xp.success.levels=Выдано {%0} уровней игроку {%1} +commands.xp.success.levels.minus=Изъято {%0} уровней у игрока {%1} +commands.xp.usage=/xp <опыт> [игрок] ИЛИ /xp <уровень>L [игрок] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Информация об игроке "{%0}" не найдена, создаётся новый профиль +nukkit.data.playerCorrupted=Повреждённые данные у игрока "{%0}", создание нового профиля +nukkit.data.playerOld=Обнаружен старый профиль игрока "{%0}", обновляем профиль +nukkit.data.saveError=Невозможно сохранить данные игрока "{%0}": {%1} + +nukkit.level.notFound=Мир "{%0}" не найден +nukkit.level.loadError=Невозможно загрузить мир "{%0}": {%1} +nukkit.level.generationError=Невозможно сгенерировать уровень "{%0}": {%1} +nukkit.level.tickError=Ошибка прорисовки мира "{%0}": {%1} +nukkit.level.chunkUnloadError=Ошибка при выгрузке чанка: {%0} +nukkit.level.backgroundGeneration=Генерируется территория спауна для мира "{%0}" +nukkit.level.defaultError=Мир по умолчанию не загружен +nukkit.level.preparing=Подготовка мира "{%0}" +nukkit.level.unloading=Выгрузка мира "{%0}" +nukkit.level.updating=Мир "{%0}" использует старый формат, конвертация + +nukkit.server.start=Запускается сервер Minecraft: BE версии {%0} +nukkit.server.networkError=[Сеть] Остановлен интерфейс {%0} из-за {%1} +nukkit.server.networkStart=Открытие сервера на {%0}:{%1} +nukkit.server.info=Этот сервер использует {%0}, версию {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Сервер использует {%0} {%1}「 {%2}」 , версия API {%3} для Minecraft: BE {%4} (версия протокола {%5}) +nukkit.server.license={%0} распространяется под лицензией GPL +nukkit.server.tickOverload=Сервер перегружен! +nukkit.server.startFinished=Загружено ({%0} секунд)! Для справки введите "help" или "?" +nukkit.server.defaultGameMode=Игровой режим по умолчанию: {%0} +nukkit.server.query.start=Запуск прослушивателя статуса GS4 +nukkit.server.query.info=Установлен порт query на {%0} +nukkit.server.query.running=Query запущен на {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Ошибка запуска RCON: пустой пароль +nukkit.server.rcon.startupError=Ошибка запуска RCON: {%0} +nukkit.server.rcon.running=RCON запущен на {%0}:{%1} + +nukkit.command.alias.illegal=Невозможно зарегистрировать альтернативу команды {%0}, поскольку она содержит недопустимые знаки +nukkit.command.alias.notFound=Невозможно зарегистрировать альтернативу команды {%0}, поскольку она содержит недопустимые команды: {%1} +nukkit.command.exception=Необработанное исключение при выполнении команды "{%0}" в {%1}: {%2} + +nukkit.command.plugins.description=Показывает список установленых плагинов на этом сервере. +nukkit.command.plugins.success=Плагины ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Перезагружает плагины и настройки сервера. +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Перезагрузка сервера... +nukkit.command.reload.reloaded=Перезагрузка завершена. + +nukkit.command.status.description=Отображает производительность сервера. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Запускает процессы сборщика мусора. +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Записывает тайминги, чтобы показать производительность сервера. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Тайминги включены и сброшены +nukkit.command.timings.disable=Тайминги выключены +nukkit.command.timings.timingsDisabled=Пожалуйста, включите тайминги, написав в чате /timings on +nukkit.command.timings.verboseEnable=Подробные тайминги включены +nukkit.command.timings.verboseDisable=Подробные тайминги выключены +nukkit.command.timings.reset=Тайминги сброшены +nukkit.command.timings.rcon=Внимание: Отчёт таймингов, выполненый через RCON, может вызвать задержки, следует использовать команду в игре или в консоли +nukkit.command.timings.uploadStart=Идёт подготовка отчёта таймингов... +nukkit.command.timings.uploadError=Ошибка загрузки: {%0}: {%1}, проверьте логи для подробностей +nukkit.command.timings.reportError=Случилась ошибка во время отчёта, проверьте логи для подробностей +nukkit.command.timings.timingsLocation=Вы можете просмотреть отчёт здесь: {%0} +nukkit.command.timings.timingsResponse=Ответ: {%0} +nukkit.command.timings.timingsWrite=Тайминги записаны в {%0} + +nukkit.command.title.description=Показывает заголовок для определённого игрока или изменяет настройки показа заголовков для него +nukkit.command.title.usage=/title <игрок> <clear|reset> ИЛИ /title <игрок> <title|subtitle|actionbar> <текст заголовка> ИЛИ /title <игрок> <times> <время появления> <продолжительность> <время исчезания> +nukkit.command.title.clear=Экран игрока {%0} успешно очищен +nukkit.command.title.reset=Настройки анимации заголовка для игрока {%0} успешно сброшены +nukkit.command.title.title=Заголовок "{%0}" успешно показан игроку {%1} +nukkit.command.title.subtitle=Подзаголовок "{%0}" успешно установлен для следующей надписи игрока {%1} +nukkit.command.title.actionbar=Заголовок над тулбаром "{%0}" успешно показан игроку {%1} +nukkit.command.title.times.success=Время проигрывания анимаций заголовка успешно изменено на {%0}, {%1}, {%2} для игрока {%3} +nukkit.command.title.times.fail=Время проигрывания анимаций должно быть числом + +nukkit.command.version.description=Показывает версию сервера, включая все используемые плагины +nukkit.command.version.usage=/version [название плагина] +nukkit.command.version.noSuchPlugin=На сервере не установлен плагин с таким названием. Используйте /plugins, чтобы получить список используемых плагинов. + +nukkit.command.give.description=Даёт определённому игроку определённое количество предметов +nukkit.command.give.usage=/give <игрок> <предмет[:урон (мета)]> [кол-во] + +nukkit.command.kill.description=Совершает суицид или убивает других игроков +nukkit.command.kill.usage=/kill [игрок] + +nukkit.command.particle.description=Добавляет частицы в мир. +nukkit.command.particle.usage=/particle <игрок> <x> <y> <z> <xd> <yd> <zd> [значение] [данные] + +nukkit.command.time.description=Изменяет время в мире. +nukkit.command.time.usage=/time <set|add> <значение> ИЛИ /time <start|stop|query> + +nukkit.command.gamerule.description=Устанавливает или запрашивает правило игры +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Блокирует определённого игрока на сервере. +nukkit.command.ban.ip.description=Блокирует определённый IP адрес на сервере. +nukkit.command.banlist.description=Показывает список всех заблокированных игроков на сервере. +nukkit.command.defaultgamemode.description=Устанавливает режим игры по умолчанию. +nukkit.command.deop.description=Убирает статус оператора с определённого игрока. +nukkit.command.difficulty.description=Устанавливает сложность игры. +nukkit.command.enchant.description=Adds enchantments on items +nukkit.command.effect.description=Добавляет/Убирает эффекты у игроков. +nukkit.command.gamemode.description=Изменяет режим игры у игрока. +nukkit.command.help.description=Показывает меню помощи. +nukkit.command.kick.description=Кикает определённого игрока с сервера. +nukkit.command.list.description=Показывает список всех игроков на сервере. +nukkit.command.me.description=Выполняет указанное действие в чате. +nukkit.command.op.description=Даёт определённому игроку статус оператора. +nukkit.command.unban.player.description=Разблокирует игрока на сервере. +nukkit.command.unban.ip.description=Разблокирует определенный IP адрес на сервере. +nukkit.command.save.description=Сохраняет все файлы сервера. +nukkit.command.saveoff.description=Отключает автосохранение сервера. +nukkit.command.saveon.description=Включает автосохранение сервера. +nukkit.command.say.description=Транслирует сообщение в чат. +nukkit.command.seed.description=Показывает сид мира. +nukkit.command.setworldspawn.description=Устанавливает точку респауна мира. Если координаты не введены, будут использованы координаты игрока. +nukkit.command.spawnpoint.description=Устанавливает точку возрождения игрока. +nukkit.command.stop.description=Выключает сервер. +nukkit.command.tp.description=Телепортирует определённого игрока (или Вас) к другому игроку или на определенные координаты +nukkit.command.tell.description=Отправляет приватное сообщение указанному игроку. +nukkit.command.weather.description=Устанавливает определенную погоду в мире. +nukkit.command.xp.description=Выдает игроку указанное количество единиц опыта или уровень +nukkit.command.whitelist.description=Управляет списком игроков, которым можно заходить на сервер. + +nukkit.crash.create=Произошла фатальная ошибка и сервер вышел из строя. Создание аварийного дампа +nukkit.crash.error=Не удалось создать дамп: {%0} +nukkit.crash.submit=Пожалуйста, загрузите файл"{%0}" в Crash Archive и отправьте ссылку на страницу исправления ошибок. Дайте как можно больше информации. +nukkit.crash.archive=Дамп выхода из строя сервера был автоматически отправлен в Crash Archive. Вы можете его просмотреть тут: {%0}, или использовать ID #{%1}. + +nukkit.debug.enable=Поддержка LevelDB включена + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} некорректно передвинулся! +nukkit.player.logIn={%0}[/{%1}:{%2}] вошел с id сущности {%3} на ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] отключился: {%3} +nukkit.player.invalidEntity={%0} попытался атаковать неправильную сущность + +nukkit.plugin.load=Загрузка {%0} +nukkit.plugin.enable=Включение {%0} +nukkit.plugin.disable=Выключение {%0} +nukkit.plugin.restrictedName=Ограниченное имя +nukkit.plugin.incompatibleAPI=Несовместимая версия API +nukkit.plugin.unknownDependency=Неизвестная зависимость +nukkit.plugin.circularDependency=Обнаружена круговая зависимость +nukkit.plugin.genericLoadError=Невозможно загрузить плагин "{%0}" +nukkit.plugin.spacesDiscouraged=Плагин "{%0}" использует пробелы в названии, это недопустимо +nukkit.plugin.loadError=Невозможно загрузить плагин "{%0}": {%1} +nukkit.plugin.duplicateError=Невозможно загрузить "{%0}": плагин уже существует +nukkit.plugin.fileError=Не удалось загрузить "{%0}" в папке "{%1}": {%2} +nukkit.plugin.commandError=Невозможно загрузить команду {%0} для плагина {%1} +nukkit.plugin.aliasError=Не удалось загрузить псевдоним {%0} для плагина {%1} +nukkit.plugin.deprecatedEvent=Плагин "{%0}" имеет зарегистрированный эвент "{%1}" по методу "{%2}", но событие считается устаревшим. +nukkit.plugin.eventError=Не мог пройти "{%0}" событий для "{%1}": {%2} на {%3} + +nukkit.resources.invalid-path=Путь "{%0}" существует и не является директорией +nukkit.resources.unknown-format=Невозможно загрузить "{%0}", т.к. формат не распознан +nukkit.resources.fail=Невозможно загрузить "{%0}": {%1} +nukkit.resources.success=Успешно загружено {%0} ресурс-паков +nukkit.resources.zip.not-found=Файл "{%0}" не найден +nukkit.resources.zip.no-manifest="manifest.json" не найден +nukkit.resources.zip.invalid-manifest="manifest.json" неправильный или неполный diff --git a/src/main/resources/lang/spa/lang.ini b/src/main/resources/lang/spa/lang.ini new file mode 100644 index 00000000000..47b2f19921f --- /dev/null +++ b/src/main/resources/lang/spa/lang.ini @@ -0,0 +1,381 @@ +# Language file compatible with Minecraft: Bedrock Edition identifiers +# +# A message doesn't need to be there to be shown correctly on the client. +# Only messages shown in Nukkit itself need to be here +# Using the language property from PocketMine-MP + +language.name=Español +language.selected={%0} ({%1}) elegido como el idioma base + +multiplayer.player.joined={%0} se ha conectado +multiplayer.player.left={%0} dejó el juego + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} acaba de ganar un logro {%1} + +disconnectionScreen.outdatedClient=Cliente desactualizado! +disconnectionScreen.outdatedServer=Servidor desactualizado! +disconnectionScreen.serverFull=El servidor está lleno! +disconnectionScreen.noReason=Desconectado del servidor +disconnectionScreen.invalidSkin=Skin inválido! +disconnectionScreen.invalidName=Nombre no válido! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} cayó desde un lugar alto +death.attack.inFire={%0} ardió en llamas +death.attack.onFire={%0} ardió hasta la muerte +death.attack.lava={%0} intentó nadar en lava +death.attack.lava.magma={%0} discovered floor was lava +death.attack.inWall={%0} se asfixió en una pared +death.attack.drown={%0} se ahogó +death.attack.cactus={%0} fue pinchado hasta la muerte +death.attack.generic={%0} murió +death.attack.explosion={%0} explotó +death.attack.explosion.player={%0} fué explotado por {%1} +death.attack.magic={%0} fue matado por magia +death.attack.wither={%0} murió por el efecto de wither +death.attack.mob={%0} fué herido por {%1} +death.attack.player={%0} fué herido por {%1} +death.attack.player.item={%0} fué herido por {%1} usando {%2} +death.attack.arrow={%0} fué disparado por {%1} +death.attack.arrow.item={%0} fué disparado por {%1} usando {%2} +death.attack.fall={%0} chocó el suelo fuertemente +death.attack.outOfWorld={%0} se cayó fuera del mundo +death.attack.starve={%0} starved to death + +gameMode.survival=Modo Supervivencia +gameMode.creative=Modo Creativo +gameMode.adventure=Modo Aventura +gameMode.spectator=Modo Espectador +gameMode.changed=Tu modo de juego ha sido actualizado + +potion.moveSpeed=Velocidad +potion.moveSlowdown=Lentitud +potion.digSpeed=Prisa +potion.digSlowDown=Fatiga Minera +potion.damageBoost=Fuerza +potion.heal=Vida Instantánea +potion.harm=Daño Instantáneo +potion.jump=Impulso de salto +potion.confusion=Náuseas +potion.regeneration=Regeneración +potion.resistance=Resistencia +potion.fireResistance=Resistencia al fuego +potion.waterBreathing=Respiración Acuática +potion.invisibility=Invisibilidad +potion.blindness=Ceguera +potion.nightVision=Visión nocturna +potion.hunger=Hambre +potion.weakness=Debilidad +potion.poison=Veneno +potion.wither=Wither +potion.healthBoost=Impulso de Vida +potion.absorption=Absorción +potion.saturation=Saturación + +commands.generic.exception=Error desconocido al intentar ejecutar este comando +commands.generic.permission=No tienes permiso para usar este comando +commands.generic.ingame=You can only perform this command as a player +commands.generic.unknown=Desconocido comando. Intenta /help para obtener una lista de comandos +commands.generic.player.notFound=Ese jugador no se encuentra +commands.generic.usage=Uso: {%0} + +commands.time.added={%0} Agregados a el tiempo +commands.time.set=Cambiar tiempo a {%0} +commands.time.query=El Tiempo es {%0} + +commands.me.usage=/me <acción...> + +commands.give.item.notFound=No existe ítem con el nombre {%0} +commands.give.success=Dado {%0} * {%1} a {%2} +commands.give.tagError=Error en parseo de etiqueta de datos: {%0} + +commands.effect.usage=/efecto <player> <effect> [seconds] [amplifier] [hideParticles] o /efecto <player> quitar +commands.effect.notFound=No hay efecto con esa ID {%0} +commands.effect.success=Dando {%0} (ID {%1}) * {%2} a {%3} por {%4} segundos +commands.effect.success.removed=Quitando {%0} de {%1} +commands.effect.success.removed.all=Removiendo todos los efectos de {%0} +commands.effect.failure.notActive=No se puede quitar {%0} de {%1} ya que no tiene el efecto +commands.effect.failure.notActive.all=No se puede quitar ningun efecto de {%0} ya que no tiene ninguno + +commands.enchant.noItem=El objetivo no tiene un objecto en la mano +commands.enchant.notFound=No existe un encantamiento con ID {%0} +commands.enchant.success=El encantamiento fue aplicado correctamente +commands.enchant.usage=/enchant <jugador> <ID encantamiento> [nivel] + +commands.particle.success=Reproduciendo efecto {%0} por {%1} veces +commands.particle.notFound=Nombre de efecto desconocido {%0} + +commands.players.usage=/lista +commands.players.list=Hay {%0}/{%1} jugadores en linea: + +commands.kill.successful={%0} Murió +commands.kill.all.successful=Mató a todos los jugadores +commands.kill.entities.successful=Mató a todas las entidades + +commands.banlist.ips=Hay %d IPs Bloqueadas: +commands.banlist.players={%0} Jugadores Bloqueados: +commands.banlist.usage=/banlist [ip|Jugador] + +commands.defaultgamemode.usage=/defaultgamemode <modo> +commands.defaultgamemode.success=El modo de juego por defecto del mundo es ahora {%0} + +commands.op.success=Privilegio Op a {%0} +commands.op.usage=/op <jugador> + +commands.deop.success=Removido privilegio Op a {%0} +commands.deop.usage=/deop <jugador> + +commands.say.usage=/say <mensaje ...> + +commands.seed.usage=/seed +commands.seed.success=Semilla: {%0} + +commands.ban.success=Jugador {%0} castigado +commands.ban.usage=/ban <nombre> [razón ...] + +commands.unban.success=Levantado castigo de jugador {%0} +commands.unban.usage=/pardon <nombre> + +commands.banip.invalid=Has introducido un IP no válida o el Jugador no está en linea +commands.banip.offline.invalid=La ip introducida es inválida o está desconectada +commands.banip.success=Ip {%0} Bloqueada +commands.banip.success.players=Ip Bloqueada {%0} perteneciente a {%1} +commands.banip.usage=/ban-ip <address|name> [Razón ...] + +commands.unbanip.invalid=Has introducido una IP no válida +commands.unbanip.success=IP {%0} desbloqueada +commands.unbanip.usage=/pardon-ip <IP> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +commands.save.enabled=Auto-guardar mundo activado +commands.save.disabled=Auto-guardado de mundo desactivado +commands.save.start=Guardando... +commands.save.success=Mundo guardado + +commands.stop.usage=/stop +commands.stop.start=Deteniendo el servidor + +commands.kick.success={%0} echado del juego +commands.kick.success.reason=Echado {%0} del juego: "{%1}" +commands.kick.usage=/kick <player> [Razón ...] + +commands.tp.success=Teletransportado {%0} a {%1} +commands.tp.success.coordinates=Teletransportado {%0} a {%1}, {%2}, {%3} +commands.tp.usage=/tp [Jugador] <destination player> O /tp [Jugador] <x> <y> <z> [<y-rot> <x-rot>] + +commands.whitelist.list={%0} Jugadores (de {%1} ) en lista blanca: +commands.whitelist.enabled=Lista blanca encendidida +commands.whitelist.disabled=Lista blanca encendidida +commands.whitelist.reloaded=Lista Blanca Recargada +commands.whitelist.add.success=Añadido {%0} a la Lista Blanca +commands.whitelist.add.usage=/whitelist add <player> +commands.whitelist.remove.success={%0} Fué removido de la Lista Blanca +commands.whitelist.remove.usage=/whitelist remove <player> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self={%0} Es ahora tu modo de juego +commands.gamemode.success.other={%0} Ha cambiado su modo de Juego a {%1} +commands.gamemode.usage=/gamemode <mode> [player] + +commands.help.header=---Mostrando ayuda, Pagina {%0} de {%1} (/help <page>) --- +commands.help.usage=/help [pagina|comando] + +commands.message.usage=/tell <player> <private message ...> +commands.message.sameTarget=No te envíes un mensaje privado a ti mismo! + +commands.difficulty.usage=/difficulty <new difficulty> +commands.difficulty.success=Cambiada la dificultad del juego a {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [<x> <y> <z>] +commands.spawnpoint.success={%0} su Punto de aparición es ahora ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=El punto de aparición en el mundo es ahora ({%0}, {%1}, {%2}) + +commands.weather.clear=Cambiando tiempo a soleado +commands.weather.rain=Cambiando tiempo a lluvia +commands.weather.thunder=Cambiando tiempo a lluvia con rayos +commands.weather.usage=/weather <clear|rain|thunder> [duración en segundos] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Archivos de lenguaje de Nukkit, solo para la consola -------------------- + +nukkit.data.playerNotFound=Datos del Jugador "{%0}" no encontrados, creando un nuevo Perfil +nukkit.data.playerCorrupted=Datos corruptos de "{%0}", creando nuevo Perfil +nukkit.data.playerOld=Datos antiguos de "{%0}" encontrados, actualizando Perfil +nukkit.data.saveError=Imposible guardar datos del Jugador "{%0}": {%1} + +nukkit.level.notFound=Nivel "{%0}" no encontrado +nukkit.level.loadError=No se puede cargar el nivel "{%0}": {%1} +nukkit.level.generationError=No se puede generar el nivel "{%0}": {%1} +nukkit.level.tickError=No se puede marcar el nivel "{%0}": {%1} +nukkit.level.chunkUnloadError=Error mientras se descargaba un chunk: {%0} +nukkit.level.backgroundGeneration=Terreno de aparicion para nivel "{%0}" está siendo generado en segundo plano +nukkit.level.defaultError=No ha sido cargado un nivel por defecto +nukkit.level.preparing=Preparando nivel "{%0}" +nukkit.level.unloading=Descargando nivel "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Iniciando Minecraft BE: Versión del servidor {%0} +nukkit.server.networkError=[Network] Interfaz {%0} detenida debido a {%1} +nukkit.server.networkStart=Abriendo servidor en {%0}:{%1} +nukkit.server.info=Este servidor está corriendo {%0} versión {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=Este servidor esta ejecutando {%0} {%1} {%2} implementando la API versión {%3} para Minecraft: BE {%4} (Protocolo versión {%5}) +nukkit.server.license={%0} es distribuido bajo la GPL License +nukkit.server.tickOverload=Imposible continuar! Está sobrecargado el servidor? +nukkit.server.startFinished=Listo ({%0}s)! Escribe "help" o "?" para recibir ayuda +nukkit.server.defaultGameMode=Modo de Juego por defecto: {%0} +nukkit.server.query.start=Iniciando GS4 status listener +nukkit.server.query.info=Configurando puerto query en {%0} +nukkit.server.query.running=Corriendo Query en {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Imposible registrar alias {%0} contiene caracteres inválidos +nukkit.command.alias.notFound=Imposible registrar alias {%0} contiene comandos que no existen: {%1} +nukkit.command.exception=Excepcion incontrolada al ejecutar el comando "{%0}" en {%1}: {%2} + +nukkit.command.plugins.description=Obtén una lista de plugins ejecutandose en el servidor +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Vuelve a cargar la configuración del servidor y sus plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reiniciando el servidor... +nukkit.command.reload.reloaded=Reinicio completo. + +nukkit.command.status.description=Mira el estado y rendimiento de el servidor. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Ejecuta tareas de recolección de basura +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Guarda ritmos para ver el rendimiento del servidor. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Obtén la version del servidor, incluyendo los plugins que utiliza +nukkit.command.version.usage=/version [Nombre del plugin] +nukkit.command.version.noSuchPlugin=Este servidor no contiene ningun plugin con ese nombre. Usa el comando /plugins para ver la lista de plugins de el servidor. + +nukkit.command.give.description=Obsequía, al jugador especificado, la cantidad de objetos deseada +nukkit.command.give.usage=/give <jugador> <objeto[:extra]> [cantidad] [etiquetas ...] + +nukkit.command.kill.description=Cometer un suicidio o matar a otros jugadores +nukkit.command.kill.usage=/kill [jugador] + +nukkit.command.particle.description=Añade particulas al mundo +nukkit.command.particle.usage=/particle <nombre> <x> <y> <z> <xd> <yd> <zd> [cantidad] [datos] + +nukkit.command.time.description=Cambia la hora en cada mundo +nukkit.command.time.usage="/time <set|add> <valor>" O "/time <start|stop|query>" + +nukkit.command.gamerule.description=Establece o consulta un valor de regla +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Previene que el jugador especificado utilice este servidor +nukkit.command.ban.ip.description=Previene que la dirección IP especificada utilice este servidor +nukkit.command.banlist.description=Muestra todos los jugadores betados de este servidor +nukkit.command.defaultgamemode.description=Especifíca el modo de juego por defecto +nukkit.command.deop.description=Retira el modo "operador" del jugador especificado +nukkit.command.difficulty.description=Definir dificultad de el juego +nukkit.command.enchant.description=Añade encantamientos a objetos +nukkit.command.effect.description=Añade o remueve efectos sobre los jugadores +nukkit.command.gamemode.description=Cambia el modo de juego del jugador +nukkit.command.help.description=Muestra el menú de ayuda +nukkit.command.kick.description=Remueve el jugador especificado del servidor +nukkit.command.list.description=Lista de todos los jugadores en línea +nukkit.command.me.description=Ejecuta una acción especifica en el chat +nukkit.command.op.description=Obsequía al jugador especificado la modalidad de "Operador" +nukkit.command.unban.player.description=Permite al jugador especificado utilizar este servidor +nukkit.command.unban.ip.description=Permite que la IP especificada utilice este servidor +nukkit.command.save.description=Guarda el servidor en el disco +nukkit.command.saveoff.description=Deshabilita el "Auto-Guardado" del servidor +nukkit.command.saveon.description=Habilita el "Auto-Guardado" del servidor +nukkit.command.say.description=Transmitir el mensaje como servidor +nukkit.command.seed.description=Muestra la semilla del mundo +nukkit.command.setworldspawn.description=Crea un punto de aparición. Si no hay coordenadas, se usan las del jugador. +nukkit.command.spawnpoint.description=Define un punto de aparicion de los jugadores +nukkit.command.stop.description=Detiene el servidor +nukkit.command.tp.description=Teletransporta a un jugador (O a ti mismo) a otro jugador o cordenadas +nukkit.command.tell.description=Enviar mensaje privado a un jugador +nukkit.command.weather.description=Establece el tiempo del mundo actual +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Administra la lista de jugadores permitidos en el servidor + +nukkit.crash.create=Se ha producido un error irrecuperable y el servidor ha crasheado. Creando un reporte de error +nukkit.crash.error=No se pudo crear volcado de crasheo: {%0} +nukkit.crash.submit=Porfavor sube el "{%0}" archivo y envia el link a la pagina de informe de errores, da tanta informacion como te sea posible. +nukkit.crash.archive=El volcado de crasheo ha sido enviado automaticamente a el Archivo de Crasheo. Puedes verlo en {%0} o usando el ID #{%1}. + +nukkit.debug.enable=Soporte LevelDB activado + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} movido incorrectamente! +nukkit.player.logIn={%0}[/{%1}:{%2}] Conectado con el nombre {%3} en ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] Desconectado por {%3} +nukkit.player.invalidEntity={%0} Intentó atacar una entidad invalida + +nukkit.plugin.load=Cargando {%0} +nukkit.plugin.enable=Activando {%0} +nukkit.plugin.disable=Deshabilitando {%0} +nukkit.plugin.restrictedName=Nombre Restringido +nukkit.plugin.incompatibleAPI=Versión API incompatible +nukkit.plugin.unknownDependency=Dependencia desconocida +nukkit.plugin.circularDependency=Dependencia circular detectada +nukkit.plugin.genericLoadError=No se puede cargar el complemento "{%0}" +nukkit.plugin.spacesDiscouraged=Complemento "{%0}" usa espacios en su nombre, esto no es recomendable +nukkit.plugin.loadError=No se puede cargar el complemento "{%0}": {%1} +nukkit.plugin.duplicateError=No se puede cargar el complemento "{%0}": ya existe +nukkit.plugin.fileError=No se puede cargar "{%0}" en la carpeta "{%1}": {%2} +nukkit.plugin.commandError=No se puede cargar el comando {%0} para el complemento {%1} +nukkit.plugin.aliasError=no se puede cargar el alias {%0} para el complemeto {%1} +nukkit.plugin.deprecatedEvent=Complemento "{%0}" ha registrado un oyente para "{%1}" en método "{%2}", pero el evento esta en des-uso. +nukkit.plugin.eventError=No se pudo pasar el evento "{%0}" a "{%1}": {%2} en {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/tur/lang.ini b/src/main/resources/lang/tur/lang.ini new file mode 100644 index 00000000000..3b5dce50ab1 --- /dev/null +++ b/src/main/resources/lang/tur/lang.ini @@ -0,0 +1,381 @@ +# Dil Paketi Minecraft ile uyumludur: Cep Paketi Sürümü +# +# Mesajın üyeye doğru şekilde görünmesine gerek yok. +# Sadece Nukkit'te görünen mesaj burada olmalı +# PocketMine-MP'deki dil özelliği kullanılmaktadır. + +language.name=Turkish +language.selected=Seçildi {%0} ({%1}) Taban dili + +multiplayer.player.joined={%0} oyuna katıldı +multiplayer.player.left={%0} oyunu terk etti + +chat.type.text=<{%0}> {%1} +chat.type.emote=* {%0} {%1} +chat.type.announcement=[{%0}] {%1} +chat.type.admin=[{%0}: {%1}] +chat.type.achievement={%0} başarımı kazandı {%1} + +disconnectionScreen.outdatedClient=Süresi Geçmiş Üyelik! +disconnectionScreen.outdatedServer=Süresi Dolmuş Server! +disconnectionScreen.serverFull=Server dolu! +disconnectionScreen.noReason=Server'a bağlanılamadı +disconnectionScreen.invalidSkin=Geçersiz skin! +disconnectionScreen.invalidName=Geçersiz isim! +disconnectionScreen.resourcePack=Kaynak paketi indirilirken ya da yüklenirken bir sorun oluştu. + +death.fell.accident.generic={%0} yüksek bir yerden düştü +death.attack.inFire={%0} Güme Gitti +death.attack.onFire={%0} Yanarak öldü +death.attack.lava={%0} Lav'da yüzmeye çalıştı +death.attack.lava.magma={%0} discovered floor was lava +death.attack.inWall={%0} Duvarda havasızlıktan öldü +death.attack.drown={%0} boğuldu +death.attack.cactus={%0} şişlenerek öldü +death.attack.generic={%0} öldü +death.attack.explosion={%0} havaya uçtu +death.attack.explosion.player={%0}, {%1} tarafından havaya uçuruldu. +death.attack.magic={%0} büyüyle öldürüldü +death.attack.wither={%0} buruştu +death.attack.mob={%0}, {%1} tarafından katledildi. +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 +death.attack.starve={%0} starved to death + +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.ingame=You can only perform this command as a player +commands.generic.unknown=Unknown command. Try /help for a list of commands +commands.generic.player.notFound=That player cannot be found +commands.generic.usage=Usage: {%0} + +commands.time.added=Added {%0} to the time +commands.time.set=Set the time to {%0} +commands.time.query=Time is {%0} + +commands.me.usage=/me <action ...> + +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.usage=/effect <player> <effect> [seconds] [amplifier] [hideParticles] OR /effect <player> clear +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.noItem=The target doesn't hold an item +commands.enchant.notFound=There is no such enchantment with ID {%0} +commands.enchant.success=Enchanting succeeded +commands.enchant.usage=/enchant <player> <enchantment ID> [level] + +commands.particle.success=Playing effect {%0} for {%1} times +commands.particle.notFound=Unknown effect name {%0} + +commands.players.usage=/list +commands.players.list=There are {%0}/{%1} players online: + +commands.kill.successful=Killed {%0} +commands.kill.all.successful=Killed all players +commands.kill.entities.successful=Killed all entities + +commands.banlist.ips=There are {%0} total banned IP addresses: +commands.banlist.players=There are {%0} total banned players: +commands.banlist.usage=/banlist [ips|players] + +commands.defaultgamemode.usage=/defaultgamemode <mode> +commands.defaultgamemode.success=The world's default game mode is now {%0} + +commands.op.success=Opped {%0} +commands.op.usage=/op <player> + +commands.deop.success=De-opped {%0} +commands.deop.usage=/deop <player> + +commands.say.usage=/say <message ...> + +commands.seed.usage=/seed +commands.seed.success=Seed: {%0} + +commands.ban.success=Banned player {%0} +commands.ban.usage=/ban <name> [reason ...] + +commands.unban.success=Unbanned player {%0} +commands.unban.usage=/pardon <name> + +commands.banip.invalid=You have entered an invalid IP address or a player that is not online +commands.banip.offline.invalid=There is no IP address in player data or IP address is invalid +commands.banip.success=Banned IP address {%0} +commands.banip.success.players=Banned IP address {%0} belonging to {%1} +commands.banip.usage=/ban-ip <address|name> [reason ...] + +commands.unbanip.invalid=You have entered an invalid IP address +commands.unbanip.success=Unbanned IP address {%0} +commands.unbanip.usage=/pardon-ip <address> + +commands.save.usage=/save-all +commands.save-on.usage=/save-on +commands.save-off.usage=/save-off +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 + +commands.stop.usage=/stop +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.kick.usage=/kick <player> [reason ...] + +commands.tp.success=Teleported {%0} to {%1} +commands.tp.success.coordinates=Teleported {%0} to {%1}, {%2}, {%3} +commands.tp.usage=/tp [target player] <destination player> OR /tp [target player] <x> <y> <z> [<y-rot> <x-rot>] + +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 <player> +commands.whitelist.remove.success=Removed {%0} from the whitelist +commands.whitelist.remove.usage=/whitelist remove <player> +commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Set own game mode to {%0} +commands.gamemode.success.other=Set {%0}'s game mode to {%1} +commands.gamemode.usage=/gamemode <mode> [player] + +commands.help.header=--- Showing help page {%0} of {%1} (/help <page>) --- +commands.help.usage=/help [page|command name] + +commands.message.usage=/tell <player> <private message ...> +commands.message.sameTarget=You can't send a private message to yourself! + +commands.difficulty.usage=/difficulty <new difficulty> +commands.difficulty.success=Set game difficulty to {%0} + +commands.spawnpoint.usage=/spawnpoint [player] [<x> <y> <z>] +commands.spawnpoint.success=Set {%0}'s spawn point to ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Set the world spawn point to ({%0}, {%1}, {%2}) + +commands.weather.clear=Changing to clear weather +commands.weather.rain=Changing to rainy weather +commands.weather.thunder=Changing to rain and thunder +commands.weather.usage=/weather <clear|rain|thunder> [duration in seconds] + +commands.xp.success=Given {%0} experience to {%1} +commands.xp.success.levels=Given {%0} levels to {%1} +commands.xp.success.levels.minus=Taken {%0} levels from {%1} +commands.xp.usage=/xp <amount> [player] OR /xp <level>L [player] + +# -------------------- Nukkit language files, only for console -------------------- + +nukkit.data.playerNotFound=Player data not found for "{%0}", creating new profile +nukkit.data.playerCorrupted=Corrupted data found for "{%0}", creating new profile +nukkit.data.playerOld=Old Player data found for "{%0}", upgrading profile +nukkit.data.saveError=Could not save player "{%0}": {%1} + +nukkit.level.notFound=Level "{%0}" not found +nukkit.level.loadError=Could not load level "{%0}": {%1} +nukkit.level.generationError=Could not generate level "{%0}": {%1} +nukkit.level.tickError=Could not tick level "{%0}": {%1} +nukkit.level.chunkUnloadError=Error while unloading a chunk: {%0} +nukkit.level.backgroundGeneration=Spawn terrain for level "{%0}" is being generated in the background +nukkit.level.defaultError=No default level has been loaded +nukkit.level.preparing=Preparing level "{%0}" +nukkit.level.unloading=Unloading level "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Starting Minecraft: BE server version {%0} +nukkit.server.networkError=[Network] Stopped interface {%0} due to {%1} +nukkit.server.networkStart=Opening server on {%0}:{%1} +nukkit.server.info=This server is running {%0} version {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: BE {%4} (protocol version {%5}) +nukkit.server.license={%0} is distributed under the GPL License +nukkit.server.tickOverload=Can't keep up! Is the server overloaded? +nukkit.server.startFinished=Done ({%0}s)! For help, type "help" or "?" +nukkit.server.defaultGameMode=Default game type: {%0} +nukkit.server.query.start=Starting GS4 status listener +nukkit.server.query.info=Setting query port to {%0} +nukkit.server.query.running=Query is running on {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Failed to start RCON: password is empty +nukkit.server.rcon.startupError=Failed to start RCON: {%0} +nukkit.server.rcon.running=RCON is running on {%0}:{%1} + +nukkit.command.alias.illegal=Could not register alias {%0} because it contains illegal characters +nukkit.command.alias.notFound=Could not register alias {%0} because it contains commands that do not exist: {%1} +nukkit.command.exception=Unhandled exception executing command "{%0}" in {%1}: {%2} + +nukkit.command.plugins.description=Gets a list of plugins running on the server +nukkit.command.plugins.success=Plugins ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Reloads the server configuration and plugins +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Reloading server... +nukkit.command.reload.reloaded=Reload complete. + +nukkit.command.status.description=Reads back the server's performance. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Fires garbage collection tasks +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Records timings to see performance of the server. +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Enabled timings and reset +nukkit.command.timings.disable=Disabled timings +nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on +nukkit.command.timings.verboseEnable=Enabled verbose timings +nukkit.command.timings.verboseDisable=Disabled verbose timings +nukkit.command.timings.reset=Timings reset +nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console +nukkit.command.timings.uploadStart=Preparing timings report... +nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information +nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information +nukkit.command.timings.timingsLocation=View timings report: {%0} +nukkit.command.timings.timingsResponse=Timings response: {%0} +nukkit.command.timings.timingsWrite=Timings written to {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Gets the version of this server including any plugins in use +nukkit.command.version.usage=/version [plugin name] +nukkit.command.version.noSuchPlugin=This server is not running any plugin by that name. Use /plugins to get a list of plugins. + +nukkit.command.give.description=Gives the specified player a certain amount of items +nukkit.command.give.usage=/give <player> <item[:damage]> [amount] [tags ...] + +nukkit.command.kill.description=Commit suicide or kill other players +nukkit.command.kill.usage=/kill [player] + +nukkit.command.particle.description=Adds particles to a world +nukkit.command.particle.usage=/particle <name> <x> <y> <z> <xd> <yd> <zd> [count] [data] + +nukkit.command.time.description=Changes the time on each world +nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> + +nukkit.command.gamerule.description=Bir oyun kuralı değeri belirler veya sorgular +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Prevents the specified player from using this server +nukkit.command.ban.ip.description=Prevents the specified IP address from using this server +nukkit.command.banlist.description=View all players banned from this server +nukkit.command.defaultgamemode.description=Set the default gamemode +nukkit.command.deop.description=Takes the specified player's operator status +nukkit.command.difficulty.description=Sets the game difficulty +nukkit.command.enchant.description=Adds enchantments on items +nukkit.command.effect.description=Adds/Removes effects on players +nukkit.command.gamemode.description=Changes the player to a specific game mode +nukkit.command.help.description=Shows the help menu +nukkit.command.kick.description=Removes the specified player from the server +nukkit.command.list.description=Lists all online players +nukkit.command.me.description=Performs the specified action in chat +nukkit.command.op.description=Gives the specified player operator status +nukkit.command.unban.player.description=Allows the specified player to use this server +nukkit.command.unban.ip.description=Allows the specified IP address to use this server +nukkit.command.save.description=Saves the server to disk +nukkit.command.saveoff.description=Disables server autosaving +nukkit.command.saveon.description=Enables server autosaving +nukkit.command.say.description=Broadcasts the given message as the sender +nukkit.command.seed.description=Shows the world seed +nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. +nukkit.command.spawnpoint.description=Sets a player's spawn point +nukkit.command.stop.description=Stops the server +nukkit.command.tp.description=Teleports the given player (or yourself) to another player or coordinates +nukkit.command.tell.description=Sends a private message to the given player +nukkit.command.weather.description=Sets the weather in current world +nukkit.command.xp.description=Gives the specified player a certain amount of experience +nukkit.command.whitelist.description=Manages the list of players allowed to use this server + +nukkit.crash.create=An unrecoverable error has occurred and the server has crashed. Creating a crash dump +nukkit.crash.error=Could not create crash dump: {%0} +nukkit.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. +nukkit.crash.archive=The crash dump has been automatically submitted to the Crash Archive. You can view it on {%0} or use the ID #{%1}. + +nukkit.debug.enable=LevelDB support enabled + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} moved wrongly! +nukkit.player.logIn={%0}[/{%1}:{%2}] logged in with entity id {%3} at ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] logged out due to {%3} +nukkit.player.invalidEntity={%0} tried to attack an invalid entity + +nukkit.plugin.load=Loading {%0} +nukkit.plugin.enable=Enabling {%0} +nukkit.plugin.disable=Disabling {%0} +nukkit.plugin.restrictedName=Restricted name +nukkit.plugin.incompatibleAPI=Incompatible API version +nukkit.plugin.unknownDependency=Unknown dependency +nukkit.plugin.circularDependency=Circular dependency detected +nukkit.plugin.genericLoadError=Could not load plugin "{%0}" +nukkit.plugin.spacesDiscouraged=Plugin "{%0}" uses spaces in its name, this is discouraged +nukkit.plugin.loadError=Could not load plugin "{%0}": {%1} +nukkit.plugin.duplicateError=Could not load plugin "{%0}": plugin exists +nukkit.plugin.fileError=Could not load "{%0}" in folder "{%1}": {%2} +nukkit.plugin.commandError=Could not load command {%0} for plugin {%1} +nukkit.plugin.aliasError=Could not load alias {%0} for plugin {%1} +nukkit.plugin.deprecatedEvent=Plugin "{%0}" has registered a listener for "{%1}" on method "{%2}", but the event is Deprecated. +nukkit.plugin.eventError=Could not pass event "{%0}" to "{%1}": {%2} on {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete diff --git a/src/main/resources/lang/ukr/lang.ini b/src/main/resources/lang/ukr/lang.ini new file mode 100644 index 00000000000..c35e1bb8ffb --- /dev/null +++ b/src/main/resources/lang/ukr/lang.ini @@ -0,0 +1,385 @@ +# Файл перекладу сумістний з Minecraft: Bedrock Edition на рівні ідентифікаторів +# +# Це повідомлення необов'язково повинно бути тут, щоб працювати у клієнті +# Тут тільки повідомлення з самого Nukkit +# +# Якщо ви знайшли помилку, то допоможіть виправити переклад. +# https://github.com/Nukkit/Languages +# +# Переклад @Charelene + +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.outdatedClient=Застарілий клієнт! +disconnectionScreen.outdatedServer=Застарілий сервер! +disconnectionScreen.serverFull=Сервер заповненний! +disconnectionScreen.noReason=Відключено від серверу +disconnectionScreen.invalidSkin=Некорректний скін! +disconnectionScreen.invalidName=Некорректне ім'я! +disconnectionScreen.resourcePack=Encountered a problem while downloading or applying resource pack. + +death.fell.accident.generic={%0} впав з високого місця +death.attack.inFire={%0} піджарився в вогні +death.attack.onFire={%0} зажарився насмерть +death.attack.lava={%0} вирішив поплавати в лаві +death.attack.lava.magma={%0} discovered floor was lava +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} випав з цього миру +death.attack.starve={%0} starved to death + +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.ingame=Цю команду можна виконувати тільки у грі +commands.generic.unknown=Невідома команда. Спробуйте /help для списку команд +commands.generic.player.notFound=Цього гравця неможливо знайти +commands.generic.usage=Використання: {%0} + +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=Віддано {%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.noItem=Ціль не тримає жодного предмета +commands.enchant.notFound=Не існує зачарування з ID {%0} +commands.enchant.success=Зачарування успішне +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.kill.all.successful=Killed all players +commands.kill.entities.successful=Killed all entities + +commands.banlist.ips={%0} всього заблокованих IP адрес: +commands.banlist.players={%0} всього заблокованих гравців: +commands.banlist.usage=/banlist [IP|гравці] + +commands.defaultgamemode.usage=/defaultgamemode <режим> +commands.defaultgamemode.success=На сервері встановлено {%0} + +commands.op.success={%0} тепер оператор +commands.op.usage=/op <гравець> + +commands.deop.success={%0} більше не оператор +commands.deop.usage=/deop <player> + +commands.say.usage=/say <повідомлення ...> + +commands.seed.usage=/seed +commands.seed.success=Зерно світу: {%0} + +commands.ban.success=Заблоковано гравця {%0} +commands.ban.usage=/ban <ім'я> [причина ...] + +commands.unban.success=Розблоковано гравця {%0} +commands.unban.usage=/pardon <ім'я> + +commands.banip.invalid=Ви ввели неправильну IP адресу або гравець оффлайн +commands.banip.offline.invalid=IP адреси в даних гравців не існує або адреса неправильна +commands.banip.success=Заблоковано IP адресу {%0} +commands.banip.success.players=Заблоковано IP адресу {%0} яка належить {%1} +commands.banip.usage=/ban-ip <address|name> [reason ...] + +commands.unbanip.invalid=Ви ввели неправильну IP адресу +commands.unbanip.success=Розбанено IP адресу {%0} +commands.unbanip.usage=/pardon-ip <address> + +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.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 [цільовий гравець] <x> <y> <z> [<y-rot> <x-rot>] + +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 <on|off|list|add|remove|reload> + +commands.gamemode.success.self=Встановлено {%0} для себе +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 [гравець] [<x> <y> <z>] +commands.spawnpoint.success={%0} тепер буде з'являтися на ({%1}, {%2}, {%3}) + +commands.setworldspawn.usage=/setworldspawn [<x> <y> <z>] +commands.setworldspawn.success=Точка відродження встановлена на ({%0}, {%1}, {%2}) + +commands.weather.clear=Змінено на сонячну погоду +commands.weather.rain=Змінено на дощову погоду +commands.weather.thunder=Змінено на дощ з грозою +commands.weather.usage=/weather <clear|rain|thunder> [тривалість] + +commands.xp.success=Видано {%0} досвіду гравцю {%1} +commands.xp.success.levels=Видано {%0} рівнів гравцю {%1} +commands.xp.success.levels.minus=Забрано {%0} рівнів з гравця {%1} +commands.xp.usage=/xp <кількість> [гравець] АБО /xp <рівень>L [гравець] + +# --------------------Файли мови Nukkit, тільки для консолі -------------------- + +nukkit.data.playerNotFound=Даних гравця не знайдено для "{%0}", створюємо новий профіль +nukkit.data.playerCorrupted=Дані гравця "{%0}" пошкоджено, створюємо новий профіль +nukkit.data.playerOld=Дані гравця "{%0}" застарілі, оновлюємо профіль +nukkit.data.saveError=Не вдалось зберігти гравця "{%0}": {%1} + +nukkit.level.notFound=Рівень "{%0}" не знайдено +nukkit.level.loadError=Не вдалось зберігти рівень "{%0}": {%1} +nukkit.level.generationError=Не вдалось згенерувати рівень "{%0}": {%1} +nukkit.level.tickError=Помилка промальовування рівня "{%0}": {%1} +nukkit.level.chunkUnloadError=Помилка при завантеженні чанку: {%0} +nukkit.level.backgroundGeneration=Spawn terrain for level "{%0}" is being generated in the background +nukkit.level.defaultError=Жоден рівень не було завантажено +nukkit.level.preparing=Готуємо рівень "{%0}" +nukkit.level.unloading=Вигрузка рівня "{%0}" +nukkit.level.updating=Old level data found for "{%0}", converting format + +nukkit.server.start=Запуск сервера Minecraft: BE версії {%0} +nukkit.server.networkError=[Мережа] Інтерфейс {%0} зупинено по причині {%1} +nukkit.server.networkStart=Відкриваємо сервер на {%0}:{%1} +nukkit.server.info=Цей сервер використовує {%0} версії {%1} "{%2}" (API {%3}) +nukkit.server.info.extended=This server is running {%0} {%1} 「{%2}」 implementing API version {%3} for Minecraft: BE {%4} (protocol version {%5}) +nukkit.server.license={%0} поширюється під ліцензією GPL +nukkit.server.tickOverload=Зараз рухне! Ти що, сервер перевантажив? +nukkit.server.startFinished=Готово ({%0}с)! Для допомоги, наберіть "help" або "?" +nukkit.server.defaultGameMode=Режим гри за замовчуванням: {%0} +nukkit.server.query.start=Запуск прослухувача статусу GS4 +nukkit.server.query.info=Встановлено порт query на {%0} +nukkit.server.query.running=Query запущено на {%0}:{%1} +nukkit.server.rcon.emptyPasswordError=Помилка при запуску RCON: пароль пустий +nukkit.server.rcon.startupError=Помилка при запуску RCON: {%0} +nukkit.server.rcon.running=RCON запущений на {%0}:{%1} + +nukkit.command.alias.illegal=Не вдалось зареєструвати альтернативу команди {%0} бо вона містить недопустимі символи +nukkit.command.alias.notFound=Не вдалось зареєструвати альтернативу команди {%0} оскільки вона містить команди, що не існують: {%1} +nukkit.command.exception=Необроблене виключення при виконанні команди "{%0}" у {%1}: {%2} + +nukkit.command.plugins.description=Отримує список плагінів, які встановлено на цей сервер +nukkit.command.plugins.success=Плагіни ({%0}): {%1} +nukkit.command.plugins.usage=/plugins + +nukkit.command.reload.description=Перезавантажує конфігурацію та плагіни сервера +nukkit.command.reload.usage=/reload +nukkit.command.reload.reloading=Перезавантаження сервера... +nukkit.command.reload.reloaded=Перезавантаження завершено. + +nukkit.command.status.description=Виводить інформацію стосовно продуктивності сервера. +nukkit.command.status.usage=/status + +nukkit.command.gc.description=Запускає процес збору сміття +nukkit.command.gc.usage=/gc + +nukkit.command.timings.description=Записує тайминги, щоб показати продуктивність сервера +nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> +nukkit.command.timings.enable=Тайминги ввімкнено і скинуто +nukkit.command.timings.disable=Тайминги вимкнуто +nukkit.command.timings.timingsDisabled=Будь ласка, увімкніть тайминги командою /timings +nukkit.command.timings.verboseEnable=Детальні тайминги ввімкнено +nukkit.command.timings.verboseDisable=Детальні тайминги вимкнено +nukkit.command.timings.reset=Тайминги скинуто +nukkit.command.timings.rcon=Увага: Звіт зроблений через RCON спричинить повільну роботу сервера, використовуйте /timings report в грі або консолі +nukkit.command.timings.uploadStart=Звіт таймингів готується... +nukkit.command.timings.uploadError=Помилка завантаження: {%0}: {%1}, перевірте логи для деталей +nukkit.command.timings.reportError=Помилка при формуванні звіту, перевірте логи для деталей +nukkit.command.timings.timingsLocation=Показано звіт таймингів: {%0} +nukkit.command.timings.timingsResponse=Відповідь: {%0} +nukkit.command.timings.timingsWrite=Тайминги записано до {%0} + +nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player +nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> +nukkit.command.title.clear=Successfully cleared {%0}'s screen +nukkit.command.title.reset=Successfully reset title animation settings for {%0} +nukkit.command.title.title=Successfully shown "{%0}" title to {%1} +nukkit.command.title.subtitle=Successfully set subtitle to "{%0}" for {%1}'s next title +nukkit.command.title.actionbar=Successfully shown "{%0}" actionbar title to {%1} +nukkit.command.title.times.success=Successfully set title animation times to {%0}, {%1}, {%2} for {%3} +nukkit.command.title.times.fail=Times of title animations must be numeric values + +nukkit.command.version.description=Отримує версію цього серверу та використовані плагіни +nukkit.command.version.usage=/version [назва плагіну] +nukkit.command.version.noSuchPlugin=Цей сервер не використовує плагін з цією назвою. Використовуйте /plugins для списку плагінів. + +nukkit.command.give.description=Дає гравцю деяку кількість предметів +nukkit.command.give.usage=/give <гравець> <предмет[:пошкодження]> [кількість] [відмітки ...] + +nukkit.command.kill.description=Накласти на себе руки або вбити іншого гравця +nukkit.command.kill.usage=/kill [гравець] + +nukkit.command.particle.description=Додати частинки до миру +nukkit.command.particle.usage=/particle <назва> <x> <y> <z> <xd> <yd> <zd> [кількість] [дані] + +nukkit.command.time.description=Змінює час у кожному мирі +nukkit.command.time.usage=/time <set|add> <значення> АБО /time <start|stop|query> + +nukkit.command.gamerule.description=Визначає або запитує значення правила гри +nukkit.command.gamerule.usage=/gamerule <rule> [value] + +nukkit.command.debug.description=Uploads server information to Hastebin +nukkit.command.debug.usage=/debugpaste + +nukkit.command.ban.player.description=Забороняє вказанному гравцю користуватися сервером +nukkit.command.ban.ip.description=Забороняє вказанній IP адресі користуватися сервером +nukkit.command.banlist.description=Показує усіх заблокованих гравців на цьому сервері +nukkit.command.defaultgamemode.description=Встановлює режим гри за замовчуванням +nukkit.command.deop.description=Забирає у гравця статус оператора сервера +nukkit.command.difficulty.description=Встановлює складність гри +nukkit.command.enchant.description=Додає зачарування до предмета +nukkit.command.effect.description=Додає або вбирає ефекти з гравців +nukkit.command.gamemode.description=Змінює режим гри зазначенному гравцю +nukkit.command.help.description=Показує меню допомоги +nukkit.command.kick.description=Прибирає зазначенного гравця з серверу +nukkit.command.list.description=Показує список гравців на сервері +nukkit.command.me.description=Виконує зазначену дію у чаті +nukkit.command.op.description=Дає зазначенному гравцю оператора +nukkit.command.unban.player.description=Дозволяє зазначенному гравцю користуватися сервером +nukkit.command.unban.ip.description=Дозволяє зазначеній IP адресі користуватися сервером +nukkit.command.save.description=Зберігає усі дані на диск +nukkit.command.saveoff.description=Вимикає автозберігання серверу +nukkit.command.saveon.description=Вмикає автозберігання серверу +nukkit.command.say.description=Транслює повідомлення отримувачу +nukkit.command.seed.description=Показує зерно світу +nukkit.command.setworldspawn.description=Зазначує точку відродження. Якщо не задано координат, буде обрано координати гравця. +nukkit.command.spawnpoint.description=Задає точку відродження гравця +nukkit.command.stop.description=Вимикає сервер +nukkit.command.tp.description=Телепортує зазначеного гравця (або себе) до іншого або до координат +nukkit.command.tell.description=Відправляє повідомлення зазначенному гравцю +nukkit.command.weather.description=Встановлює погоду +nukkit.command.xp.description=Видає зазначену кількість досвіду гравцю +nukkit.command.whitelist.description=Дає змогу управляти білим списком цього серверу + +nukkit.crash.create=З'явилась невідновлювана помилка і сервер було аварійно зупинено. Створення аварійного дампу. +nukkit.crash.error=Помилка при створенні аварійного дампу: {%0} +nukkit.crash.submit=Будь ласка завантажте файл "{%0}" до багтрекеру. Надайте якомога більше інформації. +nukkit.crash.archive=Аварійний дамп було завантажено до Архіву автоматично. Ви можете переглянути його тут - {%0} або використати ID #{%1}. + +nukkit.debug.enable=Підтримка LevelDB ввімкнена + +nukkit.bugreport.create=An error has been detected, creating a bug report. +nukkit.bugreport.error=Could not create bug report: {%0} +nukkit.bugreport.archive=A bug report has been created: {%0} + +nukkit.player.invalidMove={%0} неправильно рухався! +nukkit.player.logIn={%0}[/{%1}:{%2}] увішов з id сутності {%3} на ({%4}, {%5}, {%6}, {%7}) +nukkit.player.logOut={%0}[/{%1}:{%2}] вийшов по причині {%3} +nukkit.player.invalidEntity={%0} намагався атакувати невірну сутність + +nukkit.plugin.load=Завантаження {%0} +nukkit.plugin.enable=Ввімкнення {%0} +nukkit.plugin.disable=Вимкнення {%0} +nukkit.plugin.restrictedName=Заборонене ім'я +nukkit.plugin.incompatibleAPI=Несумісна версія API +nukkit.plugin.unknownDependency=Невідома залежна +nukkit.plugin.circularDependency=Знайдена кругова залежність +nukkit.plugin.genericLoadError=Не вдалось завантажити плагін "{%0}" +nukkit.plugin.spacesDiscouraged=Плагін "{%0}" використовує пробіл в назві, так не роблять +nukkit.plugin.loadError=Не вдалось завантажити плагін "{%0}": {%1} +nukkit.plugin.duplicateError=Не вдалось завантажити плагін "{%0}": плагін існує +nukkit.plugin.fileError=Не вдалось завантажити "{%0}" у директорії "{%1}": {%2} +nukkit.plugin.commandError=Не вдалось завантажити команду {%0} for plugin {%1} +nukkit.plugin.aliasError=Не вдалось завантажити альтернативу команді {%0} для плагіну {%1} +nukkit.plugin.deprecatedEvent=Плагін "{%0}" має зареєстровану події "{%1}" у методі "{%2}", але подія застаріла +nukkit.plugin.eventError=Не зміг пройти "{%0}" подій для "{%1}": {%2} на {%3} + +nukkit.resources.invalid-path=Resource packs path "{%0}" exists and is not a directory +nukkit.resources.unknown-format=Could not load "{%0}" due to format not recognized +nukkit.resources.fail=Could not load "{%0}": {%1} +nukkit.resources.success=Successfully loaded {%0} resource packs +nukkit.resources.zip.not-found=File "{%0}" is not found +nukkit.resources.zip.no-manifest="manifest.json" is not found +nukkit.resources.zip.invalid-manifest="manifest.json" is invalid or incomplete From 003debbdcf7368011caa6214182965670bae3a98 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:12:41 +0200 Subject: [PATCH 03/21] . --- src/main/java/cn/nukkit/Nukkit.java | 8 ++------ src/main/java/cn/nukkit/block/Block.java | 2 +- src/main/java/cn/nukkit/block/BlockAnvil.java | 2 +- src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java | 2 +- src/main/java/cn/nukkit/block/BlockBone.java | 2 +- src/main/java/cn/nukkit/block/BlockChest.java | 2 +- src/main/java/cn/nukkit/block/BlockDoor.java | 2 +- src/main/java/cn/nukkit/block/BlockEndPortalFrame.java | 2 +- src/main/java/cn/nukkit/block/BlockEndRod.java | 2 +- src/main/java/cn/nukkit/block/BlockEnderChest.java | 2 +- src/main/java/cn/nukkit/block/BlockFurnaceBurning.java | 2 +- src/main/java/cn/nukkit/block/BlockLadder.java | 2 +- src/main/java/cn/nukkit/block/BlockLightningRod.java | 2 +- src/main/java/cn/nukkit/block/BlockSmokerLit.java | 2 +- src/main/java/cn/nukkit/block/BlockStonecutterBlock.java | 2 +- src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java | 2 +- src/main/java/cn/nukkit/block/BlockTrapdoor.java | 2 +- src/main/java/cn/nukkit/block/BlockTrappedChest.java | 2 +- src/main/java/cn/nukkit/block/BlockWallSign.java | 2 +- 19 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/main/java/cn/nukkit/Nukkit.java b/src/main/java/cn/nukkit/Nukkit.java index 8485c56426b..c2657300084 100644 --- a/src/main/java/cn/nukkit/Nukkit.java +++ b/src/main/java/cn/nukkit/Nukkit.java @@ -73,7 +73,6 @@ public static void main(String[] args) { OptionSpec<String> vSpec = parser.accepts("v", "Set verbosity of logging").withRequiredArg().ofType(String.class); OptionSpec<String> verbositySpec = parser.accepts("verbosity", "Set verbosity of logging").withRequiredArg().ofType(String.class); OptionSpec<String> languageSpec = parser.accepts("language", "Set a predefined language").withOptionalArg().ofType(String.class); - OptionSpec<Void> nettyDebugSpec = parser.accepts("debug", "Enables debug stuff"); // Parse arguments OptionSet options = parser.parse(args); @@ -87,11 +86,8 @@ public static void main(String[] args) { return; } - // Netty logger for debug info - if (options.has(nettyDebugSpec)) { - InternalLoggerFactory.setDefaultFactory(Log4J2LoggerFactory.INSTANCE); - ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); - } + InternalLoggerFactory.setDefaultFactory(Log4J2LoggerFactory.INSTANCE); + ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); TITLE = options.has(titleSpec); diff --git a/src/main/java/cn/nukkit/block/Block.java b/src/main/java/cn/nukkit/block/Block.java index a687d2ded7a..bf86e788d0e 100644 --- a/src/main/java/cn/nukkit/block/Block.java +++ b/src/main/java/cn/nukkit/block/Block.java @@ -60,7 +60,7 @@ public abstract class Block extends Position implements Metadatable, Cloneable, /** * A commonly used block face pattern */ - protected static final int[] faces2534 = {2, 5, 3, 4}; + protected static final int[] FACES2534 = {2, 5, 3, 4}; protected Block() {} diff --git a/src/main/java/cn/nukkit/block/BlockAnvil.java b/src/main/java/cn/nukkit/block/BlockAnvil.java index 457cbf70241..ad5a354dea6 100644 --- a/src/main/java/cn/nukkit/block/BlockAnvil.java +++ b/src/main/java/cn/nukkit/block/BlockAnvil.java @@ -15,7 +15,7 @@ */ public class BlockAnvil extends BlockFallableMeta implements Faceable { - private static final int[] faces = {1, 2, 3, 0}; + private static final int[] FACES = {1, 2, 3, 0}; private static final String[] NAMES = { "Anvil", diff --git a/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java b/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java index cf097f3d4b3..56d7373ead6 100644 --- a/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java +++ b/src/main/java/cn/nukkit/block/BlockBlastFurnaceLit.java @@ -40,7 +40,7 @@ public Item toItem() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) diff --git a/src/main/java/cn/nukkit/block/BlockBone.java b/src/main/java/cn/nukkit/block/BlockBone.java index b8ee934f677..4811adacf73 100644 --- a/src/main/java/cn/nukkit/block/BlockBone.java +++ b/src/main/java/cn/nukkit/block/BlockBone.java @@ -13,7 +13,7 @@ */ public class BlockBone extends BlockSolid implements Faceable { - private static final int[] faces = { + private static final int[] FACES = { 0, 0, 0b1000, diff --git a/src/main/java/cn/nukkit/block/BlockChest.java b/src/main/java/cn/nukkit/block/BlockChest.java index 0e32cdf1f2d..db706d03db1 100644 --- a/src/main/java/cn/nukkit/block/BlockChest.java +++ b/src/main/java/cn/nukkit/block/BlockChest.java @@ -89,7 +89,7 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { BlockEntityChest chest = null; - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); for (int side = 2; side <= 5; ++side) { if ((this.getDamage() == 4 || this.getDamage() == 5) && (side == 4 || side == 5)) { diff --git a/src/main/java/cn/nukkit/block/BlockDoor.java b/src/main/java/cn/nukkit/block/BlockDoor.java index 08579d94e56..c059ad106e6 100644 --- a/src/main/java/cn/nukkit/block/BlockDoor.java +++ b/src/main/java/cn/nukkit/block/BlockDoor.java @@ -22,7 +22,7 @@ public abstract class BlockDoor extends BlockTransparentMeta implements Faceable public static final int DOOR_HINGE_BIT = 0x01; public static final int DOOR_POWERED_BIT = 0x02; - private static final int[] faces = {1, 2, 3, 0}; + private static final int[] FACES = {1, 2, 3, 0}; protected BlockDoor(int meta) { super(meta); diff --git a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java index fc1a33b31d2..3ce0ad0f498 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java @@ -13,7 +13,7 @@ */ public class BlockEndPortalFrame extends BlockTransparentMeta implements Faceable { - private static final int[] faces = {2, 3, 0, 1}; + private static final int[] FACES = {2, 3, 0, 1}; public BlockEndPortalFrame() { this(0); diff --git a/src/main/java/cn/nukkit/block/BlockEndRod.java b/src/main/java/cn/nukkit/block/BlockEndRod.java index b4794dfa369..2bb4b1d2fdb 100644 --- a/src/main/java/cn/nukkit/block/BlockEndRod.java +++ b/src/main/java/cn/nukkit/block/BlockEndRod.java @@ -14,7 +14,7 @@ */ public class BlockEndRod extends BlockTransparentMeta implements Faceable { - private static final int[] faces = {0, 1, 3, 2, 5, 4}; + private static final int[] FACES = {0, 1, 3, 2, 5, 4}; public BlockEndRod() { this(0); diff --git a/src/main/java/cn/nukkit/block/BlockEnderChest.java b/src/main/java/cn/nukkit/block/BlockEnderChest.java index 158deb864c0..47120263881 100644 --- a/src/main/java/cn/nukkit/block/BlockEnderChest.java +++ b/src/main/java/cn/nukkit/block/BlockEnderChest.java @@ -92,7 +92,7 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(this, this, true, true); diff --git a/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java b/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java index 048dfbfd712..0cecc9f2ce8 100644 --- a/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java +++ b/src/main/java/cn/nukkit/block/BlockFurnaceBurning.java @@ -66,7 +66,7 @@ public int getLightLevel() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) diff --git a/src/main/java/cn/nukkit/block/BlockLadder.java b/src/main/java/cn/nukkit/block/BlockLadder.java index 7906b886c4d..e1dea0dcfa9 100644 --- a/src/main/java/cn/nukkit/block/BlockLadder.java +++ b/src/main/java/cn/nukkit/block/BlockLadder.java @@ -15,7 +15,7 @@ */ public class BlockLadder extends BlockTransparentMeta implements Faceable { - private static final int[] faces = { + private static final int[] FACES = { 0, //never use 1, //never use 3, diff --git a/src/main/java/cn/nukkit/block/BlockLightningRod.java b/src/main/java/cn/nukkit/block/BlockLightningRod.java index b7de521c04d..0d2d86a8da4 100644 --- a/src/main/java/cn/nukkit/block/BlockLightningRod.java +++ b/src/main/java/cn/nukkit/block/BlockLightningRod.java @@ -7,7 +7,7 @@ public class BlockLightningRod extends BlockTransparentMeta { - private static final int[] faces = {0, 1, 2, 3, 4, 5}; + private static final int[] FACES = {0, 1, 2, 3, 4, 5}; public BlockLightningRod() { this(0); diff --git a/src/main/java/cn/nukkit/block/BlockSmokerLit.java b/src/main/java/cn/nukkit/block/BlockSmokerLit.java index 332162d2b16..aad7f8d08bc 100644 --- a/src/main/java/cn/nukkit/block/BlockSmokerLit.java +++ b/src/main/java/cn/nukkit/block/BlockSmokerLit.java @@ -40,7 +40,7 @@ public Item toItem() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(block, this, true, true); CompoundTag nbt = new CompoundTag() .putList(new ListTag<>("Items")) diff --git a/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java b/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java index c36868440ce..479b1d4fd0c 100644 --- a/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java +++ b/src/main/java/cn/nukkit/block/BlockStonecutterBlock.java @@ -53,7 +53,7 @@ public double getMaxY() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); return super.place(item, block, target, face, fx, fy, fz, player); } diff --git a/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java b/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java index 65e22f1f337..381ed798412 100644 --- a/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java +++ b/src/main/java/cn/nukkit/block/BlockTerracottaGlazed.java @@ -43,7 +43,7 @@ public Item[] getDrops(Item item) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); return this.getLevel().setBlock(block, this, true, true); } diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoor.java b/src/main/java/cn/nukkit/block/BlockTrapdoor.java index a96e7f944ba..1c75b219a12 100644 --- a/src/main/java/cn/nukkit/block/BlockTrapdoor.java +++ b/src/main/java/cn/nukkit/block/BlockTrapdoor.java @@ -22,7 +22,7 @@ public class BlockTrapdoor extends BlockTransparentMeta implements Faceable { public static final int TRAPDOOR_OPEN_BIT = 0x08; public static final int TRAPDOOR_TOP_BIT = 0x04; - private static final int[] faces = {2, 1, 3, 0}; + private static final int[] FACES = {2, 1, 3, 0}; public BlockTrapdoor() { this(0); diff --git a/src/main/java/cn/nukkit/block/BlockTrappedChest.java b/src/main/java/cn/nukkit/block/BlockTrappedChest.java index aba41a49f0f..88ea7931980 100644 --- a/src/main/java/cn/nukkit/block/BlockTrappedChest.java +++ b/src/main/java/cn/nukkit/block/BlockTrappedChest.java @@ -35,7 +35,7 @@ public String getName() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { BlockEntityChest chest = null; - this.setDamage(Block.faces2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(Block.FACES2534[player != null ? player.getDirection().getHorizontalIndex() : 0]); for (BlockFace side : Plane.HORIZONTAL) { if ((this.getDamage() == 4 || this.getDamage() == 5) && (side == BlockFace.WEST || side == BlockFace.EAST)) { diff --git a/src/main/java/cn/nukkit/block/BlockWallSign.java b/src/main/java/cn/nukkit/block/BlockWallSign.java index 695352b0b60..a43c3c8c8ab 100644 --- a/src/main/java/cn/nukkit/block/BlockWallSign.java +++ b/src/main/java/cn/nukkit/block/BlockWallSign.java @@ -9,7 +9,7 @@ */ public class BlockWallSign extends BlockSignPost { - private static final int[] faces = { + private static final int[] FACES = { 3, 2, 5, From 7d989f3c236b58adf166274b0346d48a6d1b0365 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:16:08 +0200 Subject: [PATCH 04/21] . --- src/main/java/cn/nukkit/block/BlockAnvil.java | 2 +- src/main/java/cn/nukkit/block/BlockBone.java | 2 +- src/main/java/cn/nukkit/block/BlockDoor.java | 2 +- src/main/java/cn/nukkit/block/BlockEndPortalFrame.java | 2 +- src/main/java/cn/nukkit/block/BlockEndRod.java | 2 +- src/main/java/cn/nukkit/block/BlockLadder.java | 2 +- src/main/java/cn/nukkit/block/BlockLightningRod.java | 2 +- src/main/java/cn/nukkit/block/BlockTrapdoor.java | 2 +- src/main/java/cn/nukkit/block/BlockWallSign.java | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/cn/nukkit/block/BlockAnvil.java b/src/main/java/cn/nukkit/block/BlockAnvil.java index ad5a354dea6..466a86065bd 100644 --- a/src/main/java/cn/nukkit/block/BlockAnvil.java +++ b/src/main/java/cn/nukkit/block/BlockAnvil.java @@ -83,7 +83,7 @@ public String getName() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { int damage = this.getDamage(); - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(FACES[player != null ? player.getDirection().getHorizontalIndex() : 0]); if (damage >= 4 && damage <= 7) { this.setDamage(this.getDamage() | 0x04); } else if (damage >= 8 && damage <= 11) { diff --git a/src/main/java/cn/nukkit/block/BlockBone.java b/src/main/java/cn/nukkit/block/BlockBone.java index 4811adacf73..7945abb9fbe 100644 --- a/src/main/java/cn/nukkit/block/BlockBone.java +++ b/src/main/java/cn/nukkit/block/BlockBone.java @@ -63,7 +63,7 @@ public BlockFace getBlockFace() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(((this.getDamage() & 0x3) | faces[face.getIndex()])); + this.setDamage(((this.getDamage() & 0x3) | FACES[face.getIndex()])); this.getLevel().setBlock(block, this, true); return true; } diff --git a/src/main/java/cn/nukkit/block/BlockDoor.java b/src/main/java/cn/nukkit/block/BlockDoor.java index c059ad106e6..b55eab93cbd 100644 --- a/src/main/java/cn/nukkit/block/BlockDoor.java +++ b/src/main/java/cn/nukkit/block/BlockDoor.java @@ -238,7 +238,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl return false; } - int direction = faces[player != null ? player.getDirection().getHorizontalIndex() : 0]; + int direction = FACES[player != null ? player.getDirection().getHorizontalIndex() : 0]; Block left = this.getSide(player.getDirection().rotateYCCW()); Block right = this.getSide(player.getDirection().rotateY()); diff --git a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java index 3ce0ad0f498..3637c8daa19 100644 --- a/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java +++ b/src/main/java/cn/nukkit/block/BlockEndPortalFrame.java @@ -118,7 +118,7 @@ public BlockFace getBlockFace() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(faces[player != null ? player.getDirection().getHorizontalIndex() : 0]); + this.setDamage(FACES[player != null ? player.getDirection().getHorizontalIndex() : 0]); this.getLevel().setBlock(this, this, true, true); return true; diff --git a/src/main/java/cn/nukkit/block/BlockEndRod.java b/src/main/java/cn/nukkit/block/BlockEndRod.java index 2bb4b1d2fdb..183a6321c08 100644 --- a/src/main/java/cn/nukkit/block/BlockEndRod.java +++ b/src/main/java/cn/nukkit/block/BlockEndRod.java @@ -76,7 +76,7 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(faces[player != null ? face.getIndex() : 0]); + this.setDamage(FACES[player != null ? face.getIndex() : 0]); this.getLevel().setBlock(block, this, true, true); return true; diff --git a/src/main/java/cn/nukkit/block/BlockLadder.java b/src/main/java/cn/nukkit/block/BlockLadder.java index e1dea0dcfa9..0862d813a48 100644 --- a/src/main/java/cn/nukkit/block/BlockLadder.java +++ b/src/main/java/cn/nukkit/block/BlockLadder.java @@ -146,7 +146,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl @Override public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { - if (!this.getSide(BlockFace.fromIndex(faces[this.getDamage()])).isSolid()) { + if (!this.getSide(BlockFace.fromIndex(FACES[this.getDamage()])).isSolid()) { this.getLevel().useBreakOn(this); return Level.BLOCK_UPDATE_NORMAL; } diff --git a/src/main/java/cn/nukkit/block/BlockLightningRod.java b/src/main/java/cn/nukkit/block/BlockLightningRod.java index 0d2d86a8da4..c3d885efe68 100644 --- a/src/main/java/cn/nukkit/block/BlockLightningRod.java +++ b/src/main/java/cn/nukkit/block/BlockLightningRod.java @@ -64,7 +64,7 @@ public double getMaxZ() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - this.setDamage(faces[player != null ? face.getIndex() : 0]); + this.setDamage(FACES[player != null ? face.getIndex() : 0]); this.getLevel().setBlock(this, this, true, true); return true; } diff --git a/src/main/java/cn/nukkit/block/BlockTrapdoor.java b/src/main/java/cn/nukkit/block/BlockTrapdoor.java index 1c75b219a12..8221eb80b53 100644 --- a/src/main/java/cn/nukkit/block/BlockTrapdoor.java +++ b/src/main/java/cn/nukkit/block/BlockTrapdoor.java @@ -200,7 +200,7 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl top = face != BlockFace.UP; } - meta |= faces[facing.getHorizontalIndex()]; + meta |= FACES[facing.getHorizontalIndex()]; if (top) { meta |= TRAPDOOR_TOP_BIT; diff --git a/src/main/java/cn/nukkit/block/BlockWallSign.java b/src/main/java/cn/nukkit/block/BlockWallSign.java index a43c3c8c8ab..4f37c012312 100644 --- a/src/main/java/cn/nukkit/block/BlockWallSign.java +++ b/src/main/java/cn/nukkit/block/BlockWallSign.java @@ -38,7 +38,7 @@ public String getName() { public int onUpdate(int type) { if (type == Level.BLOCK_UPDATE_NORMAL) { if (this.getDamage() >= 2 && this.getDamage() <= 5) { - if (this.getSide(BlockFace.fromIndex(faces[this.getDamage() - 2])).getId() == Item.AIR) { + if (this.getSide(BlockFace.fromIndex(FACES[this.getDamage() - 2])).getId() == Item.AIR) { this.getLevel().useBreakOn(this); } return Level.BLOCK_UPDATE_NORMAL; From 0c9f26402c590659c0519ec18327339db8ec3e91 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Tue, 26 Mar 2024 22:32:53 +0200 Subject: [PATCH 05/21] Use normal block-state-updater --- build.gradle.kts | 1 - gradle/libs.versions.toml | 2 +- .../java/cn/nukkit/customblock/CustomBlockDefinition.java | 2 +- .../java/cn/nukkit/customblock/CustomBlockManager.java | 2 +- src/main/java/cn/nukkit/customblock/CustomBlockState.java | 2 +- src/main/java/cn/nukkit/customblock/GsonNBTMapper.java | 8 ++++---- .../customblock/comparator/AlphabetPaletteComparator.java | 2 +- .../customblock/comparator/HashedPaletteComparator.java | 2 +- .../cn/nukkit/level/format/leveldb/BlockStateMapping.java | 2 +- .../nukkit/level/format/leveldb/NukkitLegacyMapper.java | 8 ++++---- .../format/leveldb/structure/BlockStateSnapshot.java | 2 +- .../level/format/leveldb/structure/StateBlockStorage.java | 8 ++++---- src/main/java/cn/nukkit/utils/BinaryStream.java | 4 ++-- 13 files changed, 22 insertions(+), 23 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index cb869c23ed2..9f1e0486a40 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,7 +19,6 @@ repositories { mavenCentral() maven("https://repo.opencollab.dev/maven-releases") maven("https://repo.opencollab.dev/maven-snapshots") - maven("https://repo.mznt.eu/snapshots") //TODO } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f0eda4c861..437eb3d6250 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ snappy = { group = "org.xerial.snappy", name = "snappy-java", version = "1.1.10. expiringmap = { group = "net.jodah", name = "expiringmap", version = "0.5.11" } jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version = "9.13" } jopt-simple = { group = "net.sf.jopt-simple", name = "jopt-simple", version = "5.0.4" } -blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.10-ONBT-SNAPSHOT" } +blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.10-SNAPSHOT" } lmbda = { group = "org.lanternpowered", name = "lmbda", version = "2.0.0" } noise = { group = "net.daporkchop.lib", name = "noise", version = "0.5.6-SNAPSHOT" } lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.26" } diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java b/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java index c3727230159..9e2f0e12872 100644 --- a/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java +++ b/src/main/java/cn/nukkit/customblock/CustomBlockDefinition.java @@ -1,7 +1,7 @@ package cn.nukkit.customblock; import cn.nukkit.customblock.container.BlockContainer; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import lombok.Data; @Data diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockManager.java b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java index cae9d1c47b2..65c21be5a9b 100644 --- a/src/main/java/cn/nukkit/customblock/CustomBlockManager.java +++ b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java @@ -19,7 +19,7 @@ import cn.nukkit.level.format.leveldb.NukkitLegacyMapper; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; -import com.nukkitx.nbt.*; +import org.cloudburstmc.nbt.*; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.*; import lombok.extern.log4j.Log4j2; diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockState.java b/src/main/java/cn/nukkit/customblock/CustomBlockState.java index 626d6c38537..3b2e70d9a46 100644 --- a/src/main/java/cn/nukkit/customblock/CustomBlockState.java +++ b/src/main/java/cn/nukkit/customblock/CustomBlockState.java @@ -2,7 +2,7 @@ import cn.nukkit.customblock.container.BlockContainerFactory; import cn.nukkit.nbt.tag.CompoundTag; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import lombok.Data; @Data diff --git a/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java b/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java index 5f1420f87e9..916901b0637 100644 --- a/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java +++ b/src/main/java/cn/nukkit/customblock/GsonNBTMapper.java @@ -2,10 +2,10 @@ import com.google.gson.*; import com.google.gson.internal.LazilyParsedNumber; -import com.nukkitx.nbt.NbtList; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; +import org.cloudburstmc.nbt.NbtList; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.nbt.NbtType; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.lang.reflect.Type; diff --git a/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java b/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java index a0513aef90a..39f420a02c8 100644 --- a/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java +++ b/src/main/java/cn/nukkit/customblock/comparator/AlphabetPaletteComparator.java @@ -1,6 +1,6 @@ package cn.nukkit.customblock.comparator; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import java.util.Comparator; diff --git a/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java b/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java index ec75d6bbf58..47e02ecd15c 100644 --- a/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java +++ b/src/main/java/cn/nukkit/customblock/comparator/HashedPaletteComparator.java @@ -1,7 +1,7 @@ package cn.nukkit.customblock.comparator; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import java.nio.charset.StandardCharsets; import java.util.Comparator; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java index a9e868c1608..2fcf7b19bca 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java @@ -8,7 +8,7 @@ import net.jodah.expiringmap.ExpiringMap; import org.cloudburstmc.blockstateupdater.*; import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import com.nukkitx.network.util.Preconditions; import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java index 014c0b11f10..81b679b456e 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java @@ -2,10 +2,10 @@ import cn.nukkit.block.Block; import cn.nukkit.level.GlobalBlockPalette; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.nbt.NbtUtils; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; +import org.cloudburstmc.nbt.NbtType; +import org.cloudburstmc.nbt.NbtUtils; import java.io.InputStream; import java.util.List; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java index 263eab3a199..7a4e7ddb982 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java @@ -1,7 +1,7 @@ package cn.nukkit.level.format.leveldb.structure; import cn.nukkit.level.format.leveldb.BlockStateMapping; -import com.nukkitx.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMap; import lombok.Builder; import lombok.Getter; import lombok.ToString; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java index 8f36f10322a..7997ff08265 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java @@ -8,10 +8,10 @@ import cn.nukkit.level.util.BitArrayVersion; import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.MainLogger; -import com.nukkitx.nbt.NBTInputStream; -import com.nukkitx.nbt.NBTOutputStream; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; +import org.cloudburstmc.nbt.NBTInputStream; +import org.cloudburstmc.nbt.NBTOutputStream; +import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; diff --git a/src/main/java/cn/nukkit/utils/BinaryStream.java b/src/main/java/cn/nukkit/utils/BinaryStream.java index 4008d814452..057e7205c9d 100644 --- a/src/main/java/cn/nukkit/utils/BinaryStream.java +++ b/src/main/java/cn/nukkit/utils/BinaryStream.java @@ -20,8 +20,8 @@ import cn.nukkit.network.LittleEndianByteBufOutputStream; import cn.nukkit.network.protocol.types.EntityLink; import cn.nukkit.network.protocol.types.ExperimentData; -import com.nukkitx.nbt.NBTOutputStream; -import com.nukkitx.nbt.NbtUtils; +import org.cloudburstmc.nbt.NBTOutputStream; +import org.cloudburstmc.nbt.NbtUtils; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; From 430174cb28ae0e86ed96df9bba5efbbf0c55e704 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:39:55 +0200 Subject: [PATCH 06/21] Fix nukkit.yml being excluded --- .gitignore | 2 +- src/main/resources/lang/ara/nukkit.yml | 91 ++++++++++++++++++++++ src/main/resources/lang/bra/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/chs/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/cht/nukkit.yml | 102 ++++++++++++++++++++++++ src/main/resources/lang/cze/nukkit.yml | 99 +++++++++++++++++++++++ src/main/resources/lang/deu/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/eng/nukkit.yml | 101 ++++++++++++++++++++++++ src/main/resources/lang/fin/nukkit.yml | 91 ++++++++++++++++++++++ src/main/resources/lang/jpn/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/kor/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/ltu/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/pol/nukkit.yml | 99 +++++++++++++++++++++++ src/main/resources/lang/rus/nukkit.yml | 104 +++++++++++++++++++++++++ src/main/resources/lang/spa/nukkit.yml | 98 +++++++++++++++++++++++ src/main/resources/lang/tur/nukkit.yml | 101 ++++++++++++++++++++++++ src/main/resources/lang/ukr/nukkit.yml | 104 +++++++++++++++++++++++++ 17 files changed, 1579 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/lang/ara/nukkit.yml create mode 100644 src/main/resources/lang/bra/nukkit.yml create mode 100644 src/main/resources/lang/chs/nukkit.yml create mode 100644 src/main/resources/lang/cht/nukkit.yml create mode 100644 src/main/resources/lang/cze/nukkit.yml create mode 100644 src/main/resources/lang/deu/nukkit.yml create mode 100644 src/main/resources/lang/eng/nukkit.yml create mode 100644 src/main/resources/lang/fin/nukkit.yml create mode 100644 src/main/resources/lang/jpn/nukkit.yml create mode 100644 src/main/resources/lang/kor/nukkit.yml create mode 100644 src/main/resources/lang/ltu/nukkit.yml create mode 100644 src/main/resources/lang/pol/nukkit.yml create mode 100644 src/main/resources/lang/rus/nukkit.yml create mode 100644 src/main/resources/lang/spa/nukkit.yml create mode 100644 src/main/resources/lang/tur/nukkit.yml create mode 100644 src/main/resources/lang/ukr/nukkit.yml diff --git a/.gitignore b/.gitignore index 201025d9bd4..f792cb21d79 100644 --- a/.gitignore +++ b/.gitignore @@ -246,7 +246,7 @@ rebel-remote.xml # Nukkit generated files banned-ips.json banned-players.json -nukkit.yml +/nukkit.yml ops.txt server.log server.properties diff --git a/src/main/resources/lang/ara/nukkit.yml b/src/main/resources/lang/ara/nukkit.yml new file mode 100644 index 00000000000..ce384079e65 --- /dev/null +++ b/src/main/resources/lang/ara/nukkit.yml @@ -0,0 +1,91 @@ +# Advanced configuration file for Nukkit +# 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: + #Multi-language setting + #Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu + language: "ara" + #Whether to send all strings translated to server locale or let the device handle them + force-language: false + shutdown-message: "تم ايقاف الخادم" + #Allow listing plugins via Query + query-plugins: true + #Show a console message when a plugin uses deprecated API methods + deprecated-verbose: true + #Number of AsyncTask workers + #If set to auto, it'll try to detect the number of cores (and at least 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #If > 1, it will show debug messages in the console + level: 1 + +level-settings: + #The default format that levels will use when created + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Amount of chunks sent to players per tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Max amount of chunks processed each tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + +aliases: + #Aliases for commands + #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) \ No newline at end of file diff --git a/src/main/resources/lang/bra/nukkit.yml b/src/main/resources/lang/bra/nukkit.yml new file mode 100644 index 00000000000..ccef8ead782 --- /dev/null +++ b/src/main/resources/lang/bra/nukkit.yml @@ -0,0 +1,98 @@ +# Configurações avançadas para o Nukkit +# Algumas destas configurações são seguras, outras podem destruir o seu servidor caso modificadas incorretamente +# Novas configurações/padrões não irão aparecer neste arquivo ao atualizar. + +settings: + #Configuração de Multi-Linguagem + #Disponível: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "bra" + #Se é para enviar todos os textos traduzidos para a linguagem do servidor ou se é para o próprio dispositivo traduzir elas + force-language: false + shutdown-message: "Servidor fechado" + #Permitir a listagem dos plugins via query + query-plugins: true + #Mostrar no console uma mensagem quando um plugin usa métodos obsoletos da API + deprecated-verbose: true + #Número de trabalhadores da AsyncTask + #Se marcado como auto, ele irá tentar detectar o número de núcleos (e no mínimo 4) + async-workers: auto + +network: + #Limite de lotes de packets, em bytes. Somente estes packets serão compactados + #Coloque 0 para compactar tudo, -1 para desativar. + batch-threshold: 256 + #Nível de compressão usado pelo Zlib ao enviar packets compactados. Maior = mais uso na CPU, menos bandwidth utilizado. + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Se maior de 1, as mensagens para debug irão ser mostradas no console + level: 1 + +level-settings: + #O formato padrão que os mundos irão usar ao serem criados + default-format: leveldb + #Mudar automaticamente o tick rate dos mundos para manter 20 ticks por segundo + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Altera a base do tick rate (1 = 20 ticks por segundos, 2 = 10 ticks por segundo, etc.) + base-tick-rate: 1 + #Fazer tick em todos os players em todos os ticks mesmo quando as outras opções não permitem isto. + always-tick-players: false + +chunk-sending: + #Número de chunks enviados para os players por tick + per-tick: 4 + #Número de chunks que deverão ser enviados antes de spawnar o player + spawn-threshold: 56 + #Salva uma cópia serializada do chunk na memória para enviar mais rapidamente. + #Útil em mundos normalmente estásticos que muitos players entram ao mesmo tempo + cache-chunks: false + +chunk-ticking: + #Máximo número de chunks processados em cada tick + per-tick: 40 + #Raio de chunks em volta do player a ser tickados + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Máximo número de chunks na fila de chunks para serem gerados + queue-size: 8 + #Máximo número de chunks na fila de chunks para serem populados + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Aliases para comandos + #Exemplos: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/chs/nukkit.yml b/src/main/resources/lang/chs/nukkit.yml new file mode 100644 index 00000000000..0d7b5b8abea --- /dev/null +++ b/src/main/resources/lang/chs/nukkit.yml @@ -0,0 +1,98 @@ +# Nukkit 高级设置 +# 警告:此处部分设置是安全的,而一些在设置不当的情况下可能会损害您的服务器 +# 升级后,新的设置及默认值将不会自动显示在这里,它们将以默认值运行直到您手动更新配置文件 + +settings: + #多语言设置 + #可用:eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "chs" + #服务器强制语言 + force-language: false + shutdown-message: "服务器已关闭" + #允许使用Query协议查询您的插件 + query-plugins: true + #当某插件使用不被推荐的API时,在控制台提醒 + deprecated-verbose: true + #异步线程数量 + #如果设置为auto,则自动识别CPU核心数量(最少4线程) + async-workers: auto + +network: + #数据包大小阀值(单位:字节)。仅这些包会被压缩 + #设为 0 以压缩全部。设为 -1 以禁用此功能 + batch-threshold: 256 + #压缩等级。等级越高,CPU 占用越高,占用带宽越少 + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #当调试级别 > 1 时,将在控制台显示调试信息 + level: 1 + +level-settings: + #默认生成的地图格式 + default-format: leveldb + #自动改变世界 TPS 以维持每秒 20 ticks(TPS:ticks per second) + auto-tick-rate: true + auto-tick-rate-limit: 20 + #设置基本 TPS(1 = 20TPS,2 = 10TPS,以此类推) + base-tick-rate: 1 + #始终保持玩家Tick,即使其他选项不允许这么做 + always-tick-players: false + +chunk-sending: + #每 tick 内发送给玩家区块的数量 + per-tick: 4 + #玩家生成前需要的区块数量 + spawn-threshold: 56 + #在内存中存储一系列的区块复制以加快区块发送 + #在多玩家同时加入一个大多是静态的服务器时较有效 + cache-chunks: false + +chunk-ticking: + #每 tick 中处理的区块数量 + per-tick: 40 + #玩家周围区块处理的半径 + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #等待队列中,被生成的区块的数量上限 + queue-size: 8 + #等待队列中,被填充的区块的数量上限 + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #决定服务器是否以 players/playername.dat 的形式保存玩家数据。默认 true 为保存。 + #如果设置为 false,nukkit 将不会保存玩家数据,这样一些与玩家数据有关的插件就可以大展身手了。 + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #指令别称 + #例如: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/cht/nukkit.yml b/src/main/resources/lang/cht/nukkit.yml new file mode 100644 index 00000000000..0f7c47a114a --- /dev/null +++ b/src/main/resources/lang/cht/nukkit.yml @@ -0,0 +1,102 @@ +# Nukkit 進階設定 +# 警告:此處部分設定是安全的,而在一些設定不當的情況下可能會損害您的伺服器。 +# 升級後,新的設定及預設值將不會自動顯示在這裡,它們將以預設值運行直到您手動更新設定檔 + +settings: + #多語言設定 + #可用:eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "cht" + #伺服器強制語言 + force-language: false + #伺服器關閉訊息 + shutdown-message: "伺服器已關閉" + #允許使用Query協定查詢您的插件 + query-plugins: true + #當某插件使用不推薦的API時,在後台提醒 + deprecated-verbose: true + #非同步線程數量,如果設定為auto,則自動識別CPU核心數量(最少4線程) + async-workers: auto + +network: + #數據包大小閥值(單位:位元組),這些包將被壓縮 + #設為0,壓縮全部。設為-1,停用功能 + batch-threshold: 256 + #壓縮等級,等級越高,CPU佔用越高,佔用頻寬越少 + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #當調試級別 > 1 時,將在控制台顯示調試資訊 + level: 1 + +level-settings: + #預設生成的地圖格式 + default-format: leveldb + #自動保持Level的tick速度 + auto-tick-rate: true + auto-tick-rate-limit: 20 + #基本tick速率(1 = 20tick,2 = 10tick,以此類推) + base-tick-rate: 1 + #始終保持玩家Tick + always-tick-players: false + +chunk-sending: + #區塊發送設定 + #組織時間(tick)內發送給玩家區塊的數量 + per-tick: 4 + #玩家完成進服需要的區塊數量 + spawn-threshold: 56 + #使用區塊緩存 + #緩解多玩家同時加入時的伺服器壓力 + cache-chunks: false + +chunk-ticking: + #區塊處理設定 + #每組織時間(tick)中處理的區塊數量 + per-tick: 40 + #玩家周圍區塊處理的半徑設定 + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #區塊生成設定 + #等待隊列中,被生成的區塊的數量上限 + queue-size: 8 + #等待隊列中,被填充的區塊的數量上限 + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + #自動儲存 + autosave: 6000 + +player: + #設置為 ture,玩家資料將被儲存為 player/<玩家代號>.dat + #設置為 false,nukkit 將不會儲存玩家資料為 "dat" 檔案,以便插件可以在其上執行某些操作。 + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #指令別稱 + #例: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/cze/nukkit.yml b/src/main/resources/lang/cze/nukkit.yml new file mode 100644 index 00000000000..79fc4dedd5f --- /dev/null +++ b/src/main/resources/lang/cze/nukkit.yml @@ -0,0 +1,99 @@ +# Hlavni nastavovaci soubor pro Nukkit +# Některé z těchto nastavení jsou bezpečné, jiné mohou přerušit váš server, pokud jsou nesprávně upraveny +# Nové nastavení/výchozí hodnoty se v tomto souboru automaticky nezobrazí při upgradu + +settings: + #Multi-jazykove nastaveni + #Dostupne: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "cze" + #Whether to send all strings translated to server locale or let the device handle them + #Zda chcete odeslat všechny řetězce přeložené do lokálního serveru nebo nechat zařízení zvládnout + force-language: false + shutdown-message: "Server se vypíná" + #Povolí prohlížení pluginu pomoci Query + query-plugins: true + #Console pošle zprávu pokud plugin používá zastaralé metody API + deprecated-verbose: true + #Počet pracovníků AsyncTasku + #Pokud je nastaven na automatické, pokusí se detekovat počet jader (a nejméně 4) + async-workers: auto + +network: + #Threshold pro dávkové pakety v bajtech. Pouze tyto pakety budou komprimovány + #Nastavte na 0, chcete-li vše komprimovat, -1 zakázat + batch-threshold: 256 + #Compression level používaný Zlib při odesílání dávkových paketů. Vyšší = více CPU, méně využití šířky pásma + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Pokud je nastavena 1, console posílá debug zprávy + level: 1 + +level-settings: + #Výchozí formát, který budou světy používat při vytvoření + default-format: leveldb + #Automaticky měnit level tick a zachovat 20 ticků za sekundu + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Nastaví základní ticky (1 = 20 ticků za sekundu, 2 = 10 ticku za sekundu atd.). + base-tick-rate: 1 + #Tickne všechny hráče každý tick, i když to ostatní nastavení zakáže + always-tick-players: false + +chunk-sending: + #Množství chunků které se pošlou hráčovi každý tick + per-tick: 4 + #Množství chunků, které je třeba poslat před tím, než se hráč spawne + spawn-threshold: 56 + #Uloží serializovanou kopii chunků v paměti pro rychlejší odesílání + #Použije se ve většině statických světech, kde se najednou připojuje spousta hráčů + cache-chunks: false + +chunk-ticking: + #Max množství chunků které se zpracovává každý tick + per-tick: 40 + #Radius chunků kolem hráče, který se má ticknout + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. Množství chunků v čekací frontě, která má být generována + queue-size: 8 + #Max. množství chunků ktere vyčkává ve frontě na "osídlení" + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #Pokud je true, data hráče budou uložena jako players/playername.dat + #Pokud je false, nukkit neukládá data hráče jako "datové" soubory, aby mohly pluginy něco udělat. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Aliasy pro příkazy + #Například: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/deu/nukkit.yml b/src/main/resources/lang/deu/nukkit.yml new file mode 100644 index 00000000000..cb98f7c5846 --- /dev/null +++ b/src/main/resources/lang/deu/nukkit.yml @@ -0,0 +1,98 @@ +# Fortgeschrittene Konfiguration für Nukkit +# Bei fehlerhaften Einstellungen kann der Server abstürzen +# Neue Einstellungen/Standards erscheinen nicht automatisch beim upgraden + +settings: + #Multilingual + #Verfügbar: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "deu" + #Sollen alle Nachrichten in Serversprache ausgegeben werden oder sollen Sie vom Client verarbeitet werden + force-language: false + shutdown-message: "Server heruntergefahren" + #Erlaube das auflisten von Plugins über eine Query + query-plugins: true + #Soll in der Konsole eine Nachricht erscheinen, wenn ein Plugin eine veraltete Funkttiopn verwendet + deprecated-verbose: true + #Anzahl an AsyncTasks + #Wenn es auf auto gestellt ist, wird Nukkit versuchen die Anzahl der Cores zu ermitteln (und setzt mindestens 4) + async-workers: auto + +network: + #Grenze der gesendeten Dateien/Pakete zur gleiden Zeit, in Bytes. Nur diese Pakete werden komprimiert + #Setze zu 0 to um alles zu komprimieren, -1 um die Komprimierung deaktivieren + batch-threshold: 256 + #Komprimierungslevel von Zlib wenn eine Menge Pakete/Daten gesendet werden. Höher = mehr CPU, weniger Bandbreitennutzung + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Wenn > 1, werden Debug-Nachrichten in der Konsole angezeigt + level: 1 + +level-settings: + #Das Standard-Welt Format wenn neue Welten erstellt werden + default-format: leveldb + #Automatiches ändern von Ticks in Welten, um 20 Ticks zu erreichen + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Setzt die Tickgeschwindigkeit (1 = 20 Ticks pro Sekunde, 2 = 10 Ticks pro Sekunde, usw.) + base-tick-rate: 1 + #Tick alle Spieler auch wenn es andere Einstellungen verhindern + always-tick-players: false + +chunk-sending: + #Anzahl der Chuncks die während eines Ticks zum Spieler gesendet wird + per-tick: 4 + #Anzahl an Chuncks die gesendet werden sollen, bevor der Spieler spawnt + spawn-threshold: 56 + #Speichere eine Kopie eines Chuncks im Arbeitspeicher um Chuncks schneller zu verarbeiten + #Hilfreich in statischen Welten wo viele Spieler zur gleichen Zeit den Server betreten + cache-chunks: false + +chunk-ticking: + #Maximale Anzahl an Chuncks die, während eines Ticks, verarbeitet werden + per-tick: 40 + #Radius von Chuncks, die bei jedem Tick, bei einem Spieler verarbeitet werden + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Maximale Anzahl der Chuncks die generiert werden müssen, in der Warteschlange + queue-size: 8 + #Maximale Anzahl der Chuncks die geladen werden müssen, in der Warteschchlange + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Aliasse für Befehle + #Beispiele: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Die Welten, die vom Server gebraucht werden. Optionen sind auf dem Generator spezifisch, und können zu unerwartetem + #Verhalten führen oder komplett ignoriert werden. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/eng/nukkit.yml b/src/main/resources/lang/eng/nukkit.yml new file mode 100644 index 00000000000..ac813b08fe0 --- /dev/null +++ b/src/main/resources/lang/eng/nukkit.yml @@ -0,0 +1,101 @@ +# Advanced configuration file for Nukkit +# 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: + #Multi-language setting + #Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, idn, cze, tur, fin + language: "eng" + #Whether to send all strings translated to server locale or let the device handle them + force-language: false + 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 + #Number of AsyncTask workers + #If set to auto, it'll try to detect the number of cores (and at least 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #If > 1, it will show debug messages in the console + level: 1 + +level-settings: + #The default format that levels will use when created + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Amount of chunks sent to players per tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Max amount of chunks processed each tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + #How often worlds and player data are saved when auto-save=on in server.properties + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + #Attacking entities resets sprinting, you can disable that here + attack-stop-sprint: true + +aliases: + #Aliases for commands + #Examples: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + #options: + nether: + #seed: my_cool_nether_seed + generator: nether + #options: diff --git a/src/main/resources/lang/fin/nukkit.yml b/src/main/resources/lang/fin/nukkit.yml new file mode 100644 index 00000000000..d12267af533 --- /dev/null +++ b/src/main/resources/lang/fin/nukkit.yml @@ -0,0 +1,91 @@ +# Advanced configuration file for Nukkit +# 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: + #Multi-language setting + #Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, idn, cze, tur, fin + language: "fin" + #Whether to send all strings translated to server locale or let the device handle them + force-language: false + shutdown-message: "Palvelin suljettu" + #Allow listing plugins via Query + query-plugins: true + #Show a console message when a plugin uses deprecated API methods + deprecated-verbose: true + #Number of AsyncTask workers + #If set to auto, it'll try to detect the number of cores (and at least 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #If > 1, it will show debug messages in the console + level: 1 + +level-settings: + #The default format that levels will use when created + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Amount of chunks sent to players per tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Max amount of chunks processed each tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + +aliases: + #Aliases for commands + #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) diff --git a/src/main/resources/lang/jpn/nukkit.yml b/src/main/resources/lang/jpn/nukkit.yml new file mode 100644 index 00000000000..313427f2164 --- /dev/null +++ b/src/main/resources/lang/jpn/nukkit.yml @@ -0,0 +1,98 @@ +# Nukkitの設定ファイル(機械翻訳みたいな和訳でごめんなさいw) +# 安全な設定もありますが、誤って設定すると鯖が壊れる設定の項目もあります。 +# 鯖をアップデートしても、新しい設定はこのファイルには追加されません。 + +settings: + #言語設定 + #現在選べる言語: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "jpn" + #端末に送る言語を強制するかどうか + force-language: false + shutdown-message: "サーバーが終了しました" + #クエリーからプラグインリストを見えるようにする + query-plugins: true + #プラグインが非推奨APIメソッドを使用すると、コンソールにメッセージを表示します + deprecated-verbose: true + #AsyncTask workersの数 + #auto に設定すると, コア数を検出しようとします(少なくとも4) + async-workers: auto + +network: + #バイト単位のバッチ処理パケットのしきい値。これらのパケットは圧縮されます + #0にするとすべてのパケットを圧縮します, -1 で無効です. + batch-threshold: 256 + #バッチパケットを送信するときのzlibの圧縮レベルですす。 値を大きくするとcpuに多く負荷かけ、低くすると通信量が増えます。 + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #1より大きいと、コンソールにデバッグメッセージが表示されます + level: 1 + +level-settings: + #マップが使用するデフォルトのフォーマット + default-format: leveldb + #自動的にレベルが毎秒20ティックを維持するためにtickの速度を変えます。 + auto-tick-rate: true + auto-tick-rate-limit: 20 + #基本のチックレートを設定します。 (1 = 20 ticks/毎秒, 2 = 10 ticks /毎秒, etc.) + base-tick-rate: 1 + #ほかの設定がこれを許可しなくても、全員のtickを同じにする + always-tick-players: false + +chunk-sending: + #チックあたりのプレイヤーに送られるチャンクの量 + per-tick: 4 + #プレーヤーがスポーンする前に送信する必要があるチャンクの量 + spawn-threshold: 56 + #より速く送信するために、メモリ内にチャンクのシリアル化されたコピーを保存します + #プレイヤーがたくさんが同時に参加し、主にあまりブロックを設置したり壊したりしないワールドに効果ありです。 + cache-chunks: false + +chunk-ticking: + #各チックで処理する最大のチャンク量 + per-tick: 40 + #プレイヤーの周りのチャンクの半径のtick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + # マップ生成のキューが待機できるチャンクのサイズ + queue-size: 8 + #読み込みのキューが待機できるチャンクのサイズ + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #trueの場合、プレーヤーデータは players/playername.dat として保存されます + #falseの場合、nukkitはプレーヤのデータを "dat"ファイルとして保存せず、プラグインが何かを実行できるようにします。 + save-player-data: true + #スキンを変更する時のクールダウンを設定(秒単位)。なお不要な場合は0を設定してください。 + skin-change-cooldown: 15 + +aliases: + #コマンドのショートカット + #例: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/kor/nukkit.yml b/src/main/resources/lang/kor/nukkit.yml new file mode 100644 index 00000000000..2f5df28df88 --- /dev/null +++ b/src/main/resources/lang/kor/nukkit.yml @@ -0,0 +1,98 @@ +# Nukkit용 고급 구성 파일 +# 이 설정들 중 일부는 안전하지만, 다른 것들은 잘못 수정되면 서버를 미작동하게 할 수 있습니다. +# 새로운 설정/기본은 업그레이드할 때 이 파일에 자동으로 나타나지 않습니다 + +settings: + #다국어 설정 + #사용 가능: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "kor" + #모든 문자열을 서버 로캘로 번역해 전송하거나 기기가 처리할 수 있게 할지 여부 + force-language: false + shutdown-message: "Server closed" + #Query를 통한 플러그인 목록 보기를 허용합니다 + query-plugins: true + #플러그인이 사용되지 않는 API 메서드를 사용할 때 콘솔 메시지를 표시합니다 + deprecated-verbose: true + #AsyncTask Works의 개수 + #auto로 설정하면, 자동으로 코어의 개수를 감지하려고 시도합니다(최소 4개) + async-workers: auto + +network: + #일괄 처리 패킷에 대한 임곗값(바이트)입니다. 이 패킷들만 압축됩니다 + #모두 압축하려면 0으로 설정하고, 비활성화하려면 -1으로 설정하세요 + batch-threshold: 256 + #일괄 처리된 패킷을 전송할 때 Zlib에 사용되는 압축 수준입니다. 높을수록 더 많은 CPU 사용량으로, 적은 대역폭을 사용합니다 + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #1보다 크면, 콘솔에 디버그 메시지를 표시합니다 + level: 1 + +level-settings: + #레벨이 생성될 때 사용할 기본 포맷 + default-format: mcregion + #1초당 20틱을 유지하기 위해 자동으로 레벨의 틱을 변경합니다 + auto-tick-rate: true + auto-tick-rate-limit: 20 + #기본 틱을 설정합니다 (1 = 1초당 20틱, 2 = 1초당 10틱, 등등.) + base-tick-rate: 1 + #다른 설정들이 이를 허용하지 않더라도 모든 플레이어를 틱 처리합니다 + always-tick-players: false + +chunk-sending: + #플레이어에게 틱 당 보낼 청크의 양 + per-tick: 4 + #플레이어를 생성하기 전에 보내져야 할 청크의 양 + spawn-threshold: 56 + #빠른 전송을 위해 메모리에 직렬화된 청크의 복사본을 저장합니다 + #많은 플레이어가 동시에 참여하는 대부분 정적인 세계에서 유용합니다 + cache-chunks: false + +chunk-ticking: + #각 틱 당 처리될 청크의 최대량 + per-tick: 40 + #플레이어 주변의 틱 처리할 청크의 범위 + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #대기열에 있는 생성 대기 중인 청크의 최대량 + queue-size: 8 + #대기열에 있는 채울 대기 중인 청크의 최대량 + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #true로 설정하면, 플레이어 데이터가 players/플레이어 이름.dat으로 저장됩니다 + #false로 설정하면, Nukkit이 플레이어 데이터를 "dat" 파일로 저장하지 않으므로 플러그인에서 무언가를 수행할 수 있습니다. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #명령어의 별칭 + #예: + # showtheversion: version + # savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/ltu/nukkit.yml b/src/main/resources/lang/ltu/nukkit.yml new file mode 100644 index 00000000000..a4da92130c5 --- /dev/null +++ b/src/main/resources/lang/ltu/nukkit.yml @@ -0,0 +1,98 @@ +# Konfigūracijos failas Nukkit platformai +# Vieni nustatymai yra saugūs, kiti gali sulaužyti Jūsų serverį, jeigu įrašytos blogos reikšmės +# Atnaujinant serverio platformą nustatymai turėtų išlikti + +settings: + #Kalbos nustatymas + #Galimos kalbos: bra, chs, cht, cze, deu, eng, idn, jpn, kor, ltu, pol, rus, spa, tur, ukr + language: "ltu" + #Ar priverstinai įjungti kalbą ar leisti pačiam įrenginiui susidoroti su vertimu + force-language: false + shutdown-message: "Serveris išjungtas" + #Rodo įskiepius per Query + query-plugins: true + #Rodyti pranešimą konsolėje, jei įskiepiai naudoja senus API metodus + deprecated-verbose: true + #AsyncTask workerių skaičius + #Jeigu nustatytas ant auto, jis automatiškai bandys aptikti branduolių skaičių (ir bent 4) + async-workers: auto + +network: + #Didžiausas leistinas paketų rūšiavimas bitais. Tik šie paketai bus suspausti + #Nustatykite 0, jeigu norite suspausti visus paketus, -1, kad išjungti + batch-threshold: 256 + #Suspaudimo lygis naudojant Zlib kai siunčiami suspausti paketai. Didesnė reikšmė = daugiau CPU, mažiau tinklo naudojimo + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Jeigu > 1, jis rodys derinimo pranešimus konsolėje + level: 1 + +level-settings: + #Įprastinis formatas kuriant pasaulius + default-format: leveldb + #Automatiškai keisti impuslų greitį, kad atiktiktų pasaulio 20 impulsų per sekundę greitį + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Nustatyti impulsų skaičių (1 = 20 impulsai per sekundę, 2 = 10 impulsai per sekunde ir t.t.) + base-tick-rate: 1 + #Atnaujinti visus žaidėjus pasaulyje kiekvieną impulsą, nors kiti nustatymai to neleidžia + always-tick-players: false + +chunk-sending: + #Chunkų skaičius nusiunčiamas žaidėjui per vieną impulsą + per-tick: 4 + #Minimalus chunkų skaičius norint, kad žaidėjas atsirastų pasaulyje + spawn-threshold: 56 + #Įrašyti pasaulio informaciją į RAM, kad informacija būtų greičiau pasiekiama + #Tobulas dalykas, jeigu pasaulis yra daugiausiai statinis ir į jį prisijungia daug žaidėjų vienu metu + cache-chunks: false + +chunk-ticking: + #Chunkų limitas, kurį gali apdoroti serveris per vieną impulsą + per-tick: 40 + #Kiek chunkų apdoroti aplink kiekvieną žaidėją + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Kiek daugiausiai chunkų gali laukti generavimo laukimo eilėje + queue-size: 8 + #Kiek daugiausiai chunkų gali laukti antrinio generavimo laukimo eilėje + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #Jeigu įvestis "true", tai žaidėjo informacija bus išsaugota faile players/zaidejo-vardas.dat + #Jeigu įvestis "false", nukkit neišsaugos žaidėjo informacijos į "dat" failus, kad įskiepiai galėtų kažką padaryti su jais + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Alternatyvos komandoms + #Pavyzdžiai: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/pol/nukkit.yml b/src/main/resources/lang/pol/nukkit.yml new file mode 100644 index 00000000000..9256c8728f3 --- /dev/null +++ b/src/main/resources/lang/pol/nukkit.yml @@ -0,0 +1,99 @@ +# Zaawansowany plik konfiguracyjny Nukkit'a +# Niektore z tych ustawien sa bezpieczne, inne moga zniszczyc Twoj serwer jesli zostana zle zmienione +# Nowe ustawienia nie pojawia sie automatycznie w tym pliku podczas aktualizacji + +settings: + #Ustawienia jezyka + #Dostepne: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "pol" + #Wyslac wiadomosci w jezyku serwera czy pozwolic urzadzeniu wybrac + force-language: false + shutdown-message: "Serwer wylaczony" + #Pozwol wyswietlic pluginy za pomoca Query + query-plugins: true + #Wyswietl wiadomosc w konsoli o wykorzystywaniu przez plugin starej funkcji API + deprecated-verbose: true + #Liczba AsyncTask worker'ow + #Jesli jest ustawione na auto, bedzie probowac wykryc ilosc rdzeni (i przynajmniej 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Jesli wartosc jest wieksza niz 1, bedzie pokazywac wiadomosci debug'u w konsoli + level: 1 + +level-settings: + #Podstawowy format, uzywany przez poziomy podczas tworzenia + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Ilosc chunk'ow wysylanych do gracza na tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Maksymalna ilosc chunk'ow przetwarzanych co tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #Jesli ustawione na true, bedzie zapisywac gracza jako players/nickgracza.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Alias'y dla komend + #Przyklad: + #alias: komenda LUB alias: [komenda1, komenda2, komenda3, itd.] + #pokazmiwersje: version + #zapisziwylacz: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/rus/nukkit.yml b/src/main/resources/lang/rus/nukkit.yml new file mode 100644 index 00000000000..f78d4af7284 --- /dev/null +++ b/src/main/resources/lang/rus/nukkit.yml @@ -0,0 +1,104 @@ +# Расширенные настройки сервера Nukkit +# Некоторые из этих параметров безопасны, иные могут нарушить работу вашего сервера, если вы неправильно их измените +# Новые параметры и настройки не будут добавляться автоматически при обновлении этого файла +# +# Если Вы нашли ошибку, приглашаем помочь с переводом: +# https://github.com/Nukkit/Languages +# +# Перевод осуществлён Pub4Game и fromgate (сообщество Nukkit.ru) + +settings: + #Выбор языка. + #Доступные языки: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "rus" + #true для отправки переведённых строк на сервер, или false - возложить перевод на устройство + force-language: false + shutdown-message: "Сервер выключен" + #Передавать список плагинов в ответ на запросы Query + query-plugins: true + #Показывать сообщение в консоли, когда плагин использует устаревшие методы API + deprecated-verbose: true + #Количество рабочих потоков AsyncTask + #При значении auto будет рассчитано исходя из числа ядер процессора (но не меньше 4) + async-workers: auto + +network: + #Предельное значение объема серии пакетов (в байтах), при достижении которого они будут сжаты. Сжиматься будут только эти пакеты. + #Укажите 0 - чтобы сжимать всё, -1 - чтобы отключить сжатие + batch-threshold: 256 + #Уровень сжатия используется Zlib при отправке сжатых пакетов + #Чем больше - тем больше нагрузка на процессор, но меньше нагрузка на сеть + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #При значении > 1 будет отображать дополнительную отладочную информацию в консоли + level: 1 + +level-settings: + #Формат мира, который будет использоваться (по умолчанию) при создании миров + default-format: leveldb + #Автоматически изменять уровень частоты тика, чтобы соответствовать 20 тиков в секунду + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Базовая частота тика (1 = 20 тиков в секунду, 2 = 10 тиков в секунду и т.п.) + base-tick-rate: 1 + #Просчитывать игроков каждый тик, даже если другие настройки это запрещают + always-tick-players: false + +chunk-sending: + #Количество чанков отправляемое игроку за тик + per-tick: 4 + #Количество чанков, которое должно быть отправлено перед спавном игрока + spawn-threshold: 56 + #Кэшировать чанки в памяти, для обеспечения быстрой отправки + #Полезно в основном статических мирах, где много игроков присоединятся одновременно + cache-chunks: false + +chunk-ticking: + #Максимальное количестов чанков, обрабатываемых за один тик + per-tick: 40 + #Радиус (в чанках) вокруг игрока, в пределах которого будут обрабатываться чанки + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Максимальное количество чанков в очереди ожидания генерации + queue-size: 8 + #Максимальное количество чанков в очереди ожидания "популяции" + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #Если выбрано true,то данные игрока будут храниться в файле players/playername.dat + #Если выбрано false, Nukkit не будет хранить данные игрока в "dat" файлах, для того чтобы плагины с ними что-то делали. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Псевдонимы (алиасы) для команд + #Например: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/spa/nukkit.yml b/src/main/resources/lang/spa/nukkit.yml new file mode 100644 index 00000000000..4114adfc7eb --- /dev/null +++ b/src/main/resources/lang/spa/nukkit.yml @@ -0,0 +1,98 @@ +# Configuración avanzada para Nukkit +# Algunos de estos ajustes son seguros, otros pueden destruir tu servidor si son modificados incorrectamente +# Nuevos ajustes / valores predeterminados no aparecerán automáticamente en este archivo al actualizar . + +settings: + #Configuración de multi-lenguaje + #Disponibles: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "spa" + #Ya sea para enviar todos los mensajes traducidos desde la consola o dejar que el dispositivo los interprete + force-language: false + shutdown-message: "Servidor cerrado" + #Permitir lista de plugins va Query + query-plugins: true + #Mostrar un mensaje en la consola cuando un plugin utiliza métodos de la API en desuso + deprecated-verbose: true + #Número de trabajadores AsyncTask + #Si se establece en auto = automático, tratará de detectar el número de núcleos ( al menos 4 ) + async-workers: auto + +network: + #Umbral para los paquetes a enviar , en bytes . Sólo estos paquetes se comprimirán + #Ajustar a 0 para comprimir todo, -1 para desactivar. + batch-threshold: 256 + #Nivel de compresion usada para el envió de paquetes. Alta = más CPU, menos banda ancha + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Si es mayor que 1, mostrara mensajes de depuración en la consola + level: 1 + +level-settings: + #El formato por defecto que los niveles utilizarán cuando se creen + default-format: leveldb + #Cambian automáticamente los niveles de velocidad para mantener 20 ticks por segundo + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Establece el número de ticks base ( 1 = 20 ticks por segundo , 2 = 10 ticks por segundo , etc. ) + base-tick-rate: 1 + #Ticks a todos los jugadores incluso cuando otros ajustes no permiten esto. + always-tick-players: false + +chunk-sending: + #Cantidad de paquetes que se envián al jugador por tick + per-tick: 4 + #Cantidad de paquetes que necesitan ser enviados antes de que aparezca el jugador + spawn-threshold: 56 + #Guardar una copia serializada del chunk en la memoria para acelerar el envío + #Útil en mundos mayormente estáticos donde muchos jugadores se unen al mismo tiempo + cache-chunks: false + +chunk-ticking: + #Máxima cantidad de paquetes procesados por tick + per-tick: 40 + #Radio de chunks alrededor de un jugador por tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Máxima cantidad de chunks en espera de ser generados + queue-size: 8 + #Máxima cantidad de chunks en espera de ser ocupados + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Alias de comandos + #Ejemplos: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: diff --git a/src/main/resources/lang/tur/nukkit.yml b/src/main/resources/lang/tur/nukkit.yml new file mode 100644 index 00000000000..ac813b08fe0 --- /dev/null +++ b/src/main/resources/lang/tur/nukkit.yml @@ -0,0 +1,101 @@ +# Advanced configuration file for Nukkit +# 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: + #Multi-language setting + #Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, idn, cze, tur, fin + language: "eng" + #Whether to send all strings translated to server locale or let the device handle them + force-language: false + 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 + #Number of AsyncTask workers + #If set to auto, it'll try to detect the number of cores (and at least 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #If > 1, it will show debug messages in the console + level: 1 + +level-settings: + #The default format that levels will use when created + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Amount of chunks sent to players per tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Max amount of chunks processed each tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + #How often worlds and player data are saved when auto-save=on in server.properties + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + #Attacking entities resets sprinting, you can disable that here + attack-stop-sprint: true + +aliases: + #Aliases for commands + #Examples: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + #options: + nether: + #seed: my_cool_nether_seed + generator: nether + #options: diff --git a/src/main/resources/lang/ukr/nukkit.yml b/src/main/resources/lang/ukr/nukkit.yml new file mode 100644 index 00000000000..65e706fc5f5 --- /dev/null +++ b/src/main/resources/lang/ukr/nukkit.yml @@ -0,0 +1,104 @@ +# Розширені налаштування серверу Nukkit +# Деякі з цих налаштувань безпечні, інші ж можуть зламати ваш сервер при некорректних змінах +# Нові налаштування або умовчання не з'являться автоматично у цьому файлі при оновленні +# +# Якщо ви знайшли помилку, то допоможіть виправити переклад. +# https://github.com/Nukkit/Languages +# +# Переклад @Charelene + +settings: + #Налаштування мови + #Доступні: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, cze + language: "ukr" + #Вибір пристрою для обробки перекладу (true — сервер буде оброблювати переклад) + force-language: false + shutdown-message: "Сервер вимкнено" + #Дозволити передавати список плагінів через Query + query-plugins: true + #Показувати повідомлення у консолі, коли плагін використовує застарілі методи API + deprecated-verbose: true + #Кількість працюючих потоків AsyncTask + #Якщо auto, число буде розраховане з кількості ядер (як мінімум 4) + async-workers: auto + +network: + #Граничне значення обсягу серії пакетів (в байтах), при досягненні якого вони будуть стиснуті. Стискатися будуть тільки ці пакети. + #Встановіть 0, щоб стискати все; -1, щоб вимкнути стискання + batch-threshold: 256 + #Рівень стискання, який використовує Zlib при відправленні пакетів + #Чим більше значення - тим більше навантаження на процесор, але менше навантаження на мережу + compression-level: 4 + #Enable network encryption + encryption: false + +debug: + #Якщо > 1, у консолі будуть з'являтися повідомлення відладки + level: 1 + +level-settings: + #Формат, що рівні використовуватимуть при створенні + default-format: leveldb + #Автоматично змінювати рівень частоти тіка, щоб відповідати 20 тіків в секунду + auto-tick-rate: true + auto-tick-rate-limit: 20 + #Базова частота тіка (1 = 20 тіків/сек , 2 = 10 тіків/сек, і т.д.) + base-tick-rate: 1 + #Прораховувати гравців кожен тік, навіть якщо інші настройки це забороняють + always-tick-players: false + +chunk-sending: + #Кількість чанків, яка відправляється гравцеві за тік + per-tick: 4 + #Кількість чанків, які має бути відправлено перед відродженням гравця + spawn-threshold: 56 + #Кешувати чанки в пам'яті, для забезпечення швидкого відправлення + #Корисно в основному статичних світах, де багато гравців приєднуються одночасно + cache-chunks: false + +chunk-ticking: + #Максимальна можливість чанків, яка може оброблюватись за один тік + per-tick: 40 + #Радіус чанків навколо гравця, які будуть оброблюватись + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Макс. кількість чанків у черзі завантаження для генерації + queue-size: 8 + #Макс. кількість чанків у черзі завантаження для населення + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + +aliases: + #Альтернативи існуючим командам + #Наприклад: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + options: + nether: + #seed: my_cool_nether_seed + generator: nether + options: From 964ce762d81eb31114fd7300d62998657efd89e6 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Wed, 27 Mar 2024 00:49:04 +0200 Subject: [PATCH 07/21] Encryption enabled by default + remove timings translations --- src/main/resources/lang/ara/lang.ini | 15 ---- src/main/resources/lang/ara/nukkit.yml | 2 +- src/main/resources/lang/bra/lang.ini | 19 ----- src/main/resources/lang/bra/nukkit.yml | 2 +- src/main/resources/lang/chs/lang.ini | 19 ----- src/main/resources/lang/chs/nukkit.yml | 2 +- src/main/resources/lang/cht/lang.ini | 19 ----- src/main/resources/lang/cht/nukkit.yml | 2 +- src/main/resources/lang/cze/lang.ini | 19 ----- src/main/resources/lang/cze/nukkit.yml | 2 +- src/main/resources/lang/deu/lang.ini | 19 ----- src/main/resources/lang/deu/nukkit.yml | 2 +- src/main/resources/lang/eng/lang.ini | 19 ----- src/main/resources/lang/eng/nukkit.yml | 2 +- src/main/resources/lang/fin/lang.ini | 19 ----- src/main/resources/lang/fin/nukkit.yml | 2 +- src/main/resources/lang/idn/lang.ini | 19 ----- src/main/resources/lang/idn/nukkit.yml | 101 +++++++++++++++++++++++++ src/main/resources/lang/jpn/lang.ini | 19 ----- src/main/resources/lang/jpn/nukkit.yml | 2 +- src/main/resources/lang/kor/lang.ini | 19 ----- src/main/resources/lang/kor/nukkit.yml | 2 +- src/main/resources/lang/ltu/lang.ini | 19 ----- src/main/resources/lang/ltu/nukkit.yml | 2 +- src/main/resources/lang/pol/lang.ini | 19 ----- src/main/resources/lang/pol/nukkit.yml | 2 +- src/main/resources/lang/rus/lang.ini | 19 ----- src/main/resources/lang/rus/nukkit.yml | 2 +- src/main/resources/lang/spa/lang.ini | 19 ----- src/main/resources/lang/spa/nukkit.yml | 2 +- src/main/resources/lang/tur/lang.ini | 19 ----- src/main/resources/lang/tur/nukkit.yml | 2 +- src/main/resources/lang/ukr/lang.ini | 19 ----- src/main/resources/lang/ukr/nukkit.yml | 2 +- 34 files changed, 117 insertions(+), 335 deletions(-) create mode 100644 src/main/resources/lang/idn/nukkit.yml diff --git a/src/main/resources/lang/ara/lang.ini b/src/main/resources/lang/ara/lang.ini index 5bb42115c73..d530539ce0f 100644 --- a/src/main/resources/lang/ara/lang.ini +++ b/src/main/resources/lang/ara/lang.ini @@ -243,21 +243,6 @@ nukkit.command.status.description=Reads back the server's performance. nukkit.command.status.usage=/status nukkit.command.gc.description=Fires garbage collection tasks nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Records timings to see performance of the server. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen diff --git a/src/main/resources/lang/ara/nukkit.yml b/src/main/resources/lang/ara/nukkit.yml index ce384079e65..8e187200091 100644 --- a/src/main/resources/lang/ara/nukkit.yml +++ b/src/main/resources/lang/ara/nukkit.yml @@ -24,7 +24,7 @@ network: #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #If > 1, it will show debug messages in the console diff --git a/src/main/resources/lang/bra/lang.ini b/src/main/resources/lang/bra/lang.ini index 2e5ea3f6fdf..97f5a4824fb 100644 --- a/src/main/resources/lang/bra/lang.ini +++ b/src/main/resources/lang/bra/lang.ini @@ -263,22 +263,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Ativa o garbage collection nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Grava timings para ver a performance do servidor. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -308,9 +292,6 @@ nukkit.command.time.usage=/time <alterar|adicionar> <value> OU /time <iniciar|pa nukkit.command.gamerule.description=Define ou questiona um valor da regra de jogo nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Bloqueia o player especificado de usar este servidor nukkit.command.ban.ip.description=Bloqueia o IP especificado de usar este servidor nukkit.command.banlist.description=Ver todos os jogadores banidos deste servidor diff --git a/src/main/resources/lang/bra/nukkit.yml b/src/main/resources/lang/bra/nukkit.yml index ccef8ead782..55b0fb97a39 100644 --- a/src/main/resources/lang/bra/nukkit.yml +++ b/src/main/resources/lang/bra/nukkit.yml @@ -24,7 +24,7 @@ network: #Nível de compressão usado pelo Zlib ao enviar packets compactados. Maior = mais uso na CPU, menos bandwidth utilizado. compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Se maior de 1, as mensagens para debug irão ser mostradas no console diff --git a/src/main/resources/lang/chs/lang.ini b/src/main/resources/lang/chs/lang.ini index ba406e071c9..66dae23696e 100644 --- a/src/main/resources/lang/chs/lang.ini +++ b/src/main/resources/lang/chs/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=启动垃圾清理任务 nukkit.command.gc.usage=/gc -nukkit.command.timings.description=收集时序信息,以检视服务器的性能。 -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=重置时序并启用计时 -nukkit.command.timings.disable=禁用计时 -nukkit.command.timings.timingsDisabled=请输入 /timings on 启用计时 -nukkit.command.timings.verboseEnable=启用详细时序 -nukkit.command.timings.verboseDisable=禁用详细时序 -nukkit.command.timings.reset=重置时序 -nukkit.command.timings.rcon=警告:通过 RCON 呈交的时序报告可能会导致延迟突发,您应当在游戏或控制台中使用 /timings report -nukkit.command.timings.uploadStart=准备时序报告中... -nukkit.command.timings.uploadError=上传出错:{%0}: {%1}, 请检查日志获取更多信息 -nukkit.command.timings.reportError=粘贴报告时发生错误,请检查日志获取更多信息 -nukkit.command.timings.timingsLocation=查看时序报告:{%0} -nukkit.command.timings.timingsResponse=时序响应:{%0} -nukkit.command.timings.timingsWrite=时序写入 {%0} - nukkit.command.title.description=向一个玩家显示屏幕标题,或者更改该玩家显示的屏幕标题设置 nukkit.command.title.usage=/title <玩家> <clear|reset> 或 /title <玩家> <title|subtitle|actionbar> <标题内容> 或 /title <玩家> <次数> <淡入时间> <持续时间> <淡出时间> nukkit.command.title.clear=已清除 {%0} 的标题 @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <数值> 或 /time <start|stop|query> nukkit.command.gamerule.description=设置或查询游戏规则的值 nukkit.command.gamerule.usage=/gamerule <规则> [值] -nukkit.command.debug.description=上传服务器信息至 Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=禁止指定的玩家加入此服务器 nukkit.command.ban.ip.description=禁止指定的 IP 地址加入此服务器 nukkit.command.banlist.description=查看该服务器封禁的所有玩家 diff --git a/src/main/resources/lang/chs/nukkit.yml b/src/main/resources/lang/chs/nukkit.yml index 0d7b5b8abea..1deb8ac54d3 100644 --- a/src/main/resources/lang/chs/nukkit.yml +++ b/src/main/resources/lang/chs/nukkit.yml @@ -24,7 +24,7 @@ network: #压缩等级。等级越高,CPU 占用越高,占用带宽越少 compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #当调试级别 > 1 时,将在控制台显示调试信息 diff --git a/src/main/resources/lang/cht/lang.ini b/src/main/resources/lang/cht/lang.ini index 05deb64e4e6..42447806662 100644 --- a/src/main/resources/lang/cht/lang.ini +++ b/src/main/resources/lang/cht/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=進行清理 nukkit.command.gc.usage=/gc -nukkit.command.timings.description=紀錄計時資料,以檢視伺服器的效能。 -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=啟用定時和重啟 -nukkit.command.timings.disable=停用定時 -nukkit.command.timings.timingsDisabled=啟用定時工具透過 /timings on -nukkit.command.timings.verboseEnable=啟用詳細的定時 -nukkit.command.timings.verboseDisable=停用詳細的定時 -nukkit.command.timings.reset=定時重啟 -nukkit.command.timings.rcon=警告:在 RCON 上使用容易導致延遲, 您必須在後台或是遊戲中使用 /timings report -nukkit.command.timings.uploadStart=準備定時報告中... -nukkit.command.timings.uploadError=上傳出錯:{%0}: {%1}, 檢查紀錄檔以了解更多訊息 -nukkit.command.timings.reportError=發生錯誤已建立錯誤報告, 請檢視紀錄檔以了解更多訊息 -nukkit.command.timings.timingsLocation=查看定時報告:{%0} -nukkit.command.timings.timingsResponse=計時響應:{%0} -nukkit.command.timings.timingsWrite=計時資料已被儲存至 {%0} - nukkit.command.title.description=顯示螢幕標題給一個玩家,或者更改該玩家顯示的螢幕標題設置 nukkit.command.title.usage=/title <玩家> <clear|reset> 或 /title <玩家> <title|subtitle|actionbar> <標題內容> 或 /title <玩家> <times> <淡入時間> <持續時間> <淡出時間> nukkit.command.title.clear=已清除 {%0} 的標題 @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <數值> 或 /time <start|stop|query> nukkit.command.gamerule.description=可設定或查詢遊戲規則值 nukkit.command.gamerule.usage=/gamerule <規則> [值] -nukkit.command.debug.description=上傳伺服器資料至 Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=禁止指定的玩家進入伺服器 nukkit.command.ban.ip.description=禁止指定的 IP 位址進入伺服器 nukkit.command.banlist.description=檢視伺服器禁止列表 diff --git a/src/main/resources/lang/cht/nukkit.yml b/src/main/resources/lang/cht/nukkit.yml index 0f7c47a114a..ea6e9e3b5c1 100644 --- a/src/main/resources/lang/cht/nukkit.yml +++ b/src/main/resources/lang/cht/nukkit.yml @@ -24,7 +24,7 @@ network: #壓縮等級,等級越高,CPU佔用越高,佔用頻寬越少 compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #當調試級別 > 1 時,將在控制台顯示調試資訊 diff --git a/src/main/resources/lang/cze/lang.ini b/src/main/resources/lang/cze/lang.ini index a8e46c8c273..be868d9e623 100644 --- a/src/main/resources/lang/cze/lang.ini +++ b/src/main/resources/lang/cze/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Uvolnuji RAM, vyhazovanim bordelu nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Nahravam casovani, pro test vykonu serveru. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Casovani zapnuto a restartuji -nukkit.command.timings.disable=Vypinam casovani -nukkit.command.timings.timingsDisabled=Prosim zapni casovani pouzitim /timings on -nukkit.command.timings.verboseEnable=Zapnuto uzvanene casovani -nukkit.command.timings.verboseDisable=Vypinam uzvanene casovani -nukkit.command.timings.reset=Reset casovani -nukkit.command.timings.rcon=Upozorneni: Nahlaseni casovani uspesne RCON bude delat lag ostny, pouzijte /timings report ve hre nebo v konzoli -nukkit.command.timings.uploadStart=Pripravuji nahlaseni casovani... -nukkit.command.timings.uploadError=Chyba Uploadu: {%0}: {%1}, kontroluji logy pro vic informaci -nukkit.command.timings.reportError=Objevila se chybicka pri vypisovani reportu, zkontrolujte logy pro vic informaci -nukkit.command.timings.timingsLocation=Zobrazuji nahlaseni casovani: {%0} -nukkit.command.timings.timingsResponse=Ohlas casovani: {%0} -nukkit.command.timings.timingsWrite=Casovani napsano do {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <value> NEBO /time <start|stop|query> nukkit.command.gamerule.description=Nastaví nebo vyšle dotaz na hodnotu herního pravidla nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Zabrani danemu hraci pouzit tento server nukkit.command.ban.ip.description=Zabrani dane IP adrese pouzit tento server nukkit.command.banlist.description=Zobrazi seznam zaBANovanych hracu diff --git a/src/main/resources/lang/cze/nukkit.yml b/src/main/resources/lang/cze/nukkit.yml index 79fc4dedd5f..3e8c5de924b 100644 --- a/src/main/resources/lang/cze/nukkit.yml +++ b/src/main/resources/lang/cze/nukkit.yml @@ -25,7 +25,7 @@ network: #Compression level používaný Zlib při odesílání dávkových paketů. Vyšší = více CPU, méně využití šířky pásma compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Pokud je nastavena 1, console posílá debug zprávy diff --git a/src/main/resources/lang/deu/lang.ini b/src/main/resources/lang/deu/lang.ini index 21d3454e088..43dd3e509b0 100644 --- a/src/main/resources/lang/deu/lang.ini +++ b/src/main/resources/lang/deu/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Startet Automatische Speicherbereinigungs Aufgaben nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Nimmt Zeitmessungen auf, um die Severauslastung zu ermitteln. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Timings & Reset aktiviert -nukkit.command.timings.disable=Timings deaktiviert -nukkit.command.timings.timingsDisabled=Bitte aktiviere Timings, indem du '/timings on' eingibst -nukkit.command.timings.verboseEnable=Aktiviere dauerhafte Timingsanzeige -nukkit.command.timings.verboseDisable=Deaktiviere dauerhafte Timingsanzeige -nukkit.command.timings.reset=Timings Reset -nukkit.command.timings.rcon=Warnung: Timings Report von RCON aus treten vielleicht Lags auf, du solltest /timings report im Spiel oder Konsole verwenden -nukkit.command.timings.uploadStart=Bereite Timings Report vor... -nukkit.command.timings.uploadError=Beim Hochladen trat ein Fehler auf: {%0}: {%1}, Siehe in die Logs um mehr Informationen zu erhalten -nukkit.command.timings.reportError=Beim Ausgeben der Timings trat ein Fehler auf, siehe in die Logs um mehr Informationen zu erhalten -nukkit.command.timings.timingsLocation=Timings wurden hier hochgeladen: {%0} -nukkit.command.timings.timingsResponse=Du kannst die Ergebnisse hier lesen: {%0} -nukkit.command.timings.timingsWrite=Zeiten geschrieben zu {%0} - nukkit.command.title.description=Sendet einen Titel an den angegebenen Spieler oder ändert die Titeleinstellungen für diesen Spieler. nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <Name/Zeit> ODER /time <start|stop|que nukkit.command.gamerule.description=Setzt einen Spielregelwert fest oder fragt ihn ab nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Bannt einen Spieler und verhindert das er den Server betreten kann nukkit.command.ban.ip.description=Bannt eine IP und verhindert das sie den Server betreten kann nukkit.command.banlist.description=Listet alle gebannten Spieler auf diff --git a/src/main/resources/lang/deu/nukkit.yml b/src/main/resources/lang/deu/nukkit.yml index cb98f7c5846..6f7c3db40d1 100644 --- a/src/main/resources/lang/deu/nukkit.yml +++ b/src/main/resources/lang/deu/nukkit.yml @@ -24,7 +24,7 @@ network: #Komprimierungslevel von Zlib wenn eine Menge Pakete/Daten gesendet werden. Höher = mehr CPU, weniger Bandbreitennutzung compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Wenn > 1, werden Debug-Nachrichten in der Konsole angezeigt diff --git a/src/main/resources/lang/eng/lang.ini b/src/main/resources/lang/eng/lang.ini index df31415f845..eee72a20f38 100644 --- a/src/main/resources/lang/eng/lang.ini +++ b/src/main/resources/lang/eng/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Fires garbage collection tasks nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Records timings to see performance of the server. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> nukkit.command.gamerule.description=Sets or queries a game rule value nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Prevents the specified player from using this server nukkit.command.ban.ip.description=Prevents the specified IP address from using this server nukkit.command.banlist.description=View all players banned from this server diff --git a/src/main/resources/lang/eng/nukkit.yml b/src/main/resources/lang/eng/nukkit.yml index ac813b08fe0..d1617ab9a08 100644 --- a/src/main/resources/lang/eng/nukkit.yml +++ b/src/main/resources/lang/eng/nukkit.yml @@ -24,7 +24,7 @@ network: #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #If > 1, it will show debug messages in the console diff --git a/src/main/resources/lang/fin/lang.ini b/src/main/resources/lang/fin/lang.ini index 96c9a8afca5..3053488c68c 100644 --- a/src/main/resources/lang/fin/lang.ini +++ b/src/main/resources/lang/fin/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Suorita garbage collection nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Tallenna ajoitukset nähdäksesi tietoa palvelimen suorituskyvystä -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Ajoitukset aktivoitu ja nollattu -nukkit.command.timings.disable=Ajoitukset poistettu käytöstä -nukkit.command.timings.timingsDisabled=Ota ajoitukset käyttöön kirjoittamalla /timings on -nukkit.command.timings.verboseEnable=Otettu käyttöön verbose timings -nukkit.command.timings.verboseDisable=Poistettu verbose timings käytöstä -nukkit.command.timings.reset=Ajoitukset nollattu -nukkit.command.timings.rcon=Varoitus: Ajoitusraportit käyttäen RCONia voivat vaikuttaa suorituskykyyn. On suositeltavaa käyttää /timings report -komentoa pelissä tai konsolissa. -nukkit.command.timings.uploadStart=Valmistellaan ajoitusten raporttia... -nukkit.command.timings.uploadError=Virhe lähettäessä raporttia: {%0}: {%1}, tarkista loki saadaksesi lisätietoja -nukkit.command.timings.reportError=Virhe liittäessä raporttia, tarkista loki saadaksesi lisätietoja -nukkit.command.timings.timingsLocation=Näytä ajoitusten raportti: {%0} -nukkit.command.timings.timingsResponse=Ajoitusten vastaus: {%0} -nukkit.command.timings.timingsWrite=Ajoitukset tallennettu sijaintiin {%0} - nukkit.command.title.description=Lähetä otsikko pelaajalle tai muuta pelaajan otsikko asetuksia nukkit.command.title.usage=/title <pelaaja> <clear|reset> TAI /title <pelaaja> <title|subtitle|actionbar> <otsikon teksti> TAI /title <pelaaja> <aika> <fade in aika> <kesto> <fade out aika> nukkit.command.title.clear=Pyyhittiin pelaajan {%0} näyttö @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <arvo> TAI /time <start|stop|query> nukkit.command.gamerule.description=Aseta pelisäännön arvo tai kysy sitä nukkit.command.gamerule.usage=/gamerule <sääntö> [arvo] -nukkit.command.debug.description=Lähetä palvelimen tiedot -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Estä pelaaja nukkit.command.ban.ip.description=Estä IP osoite nukkit.command.banlist.description=Näytä kaikki estetyt pelaajat diff --git a/src/main/resources/lang/fin/nukkit.yml b/src/main/resources/lang/fin/nukkit.yml index d12267af533..631a2492fff 100644 --- a/src/main/resources/lang/fin/nukkit.yml +++ b/src/main/resources/lang/fin/nukkit.yml @@ -24,7 +24,7 @@ network: #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #If > 1, it will show debug messages in the console diff --git a/src/main/resources/lang/idn/lang.ini b/src/main/resources/lang/idn/lang.ini index ae17103ed3e..95e2fb15d9b 100644 --- a/src/main/resources/lang/idn/lang.ini +++ b/src/main/resources/lang/idn/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Fires garbage collection tasks nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Records timings to see performance of the server. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> nukkit.command.gamerule.description=Sets or queries a game rule value nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Prevents the specified player from using this server nukkit.command.ban.ip.description=Prevents the specified IP address from using this server nukkit.command.banlist.description=View all players banned from this server diff --git a/src/main/resources/lang/idn/nukkit.yml b/src/main/resources/lang/idn/nukkit.yml new file mode 100644 index 00000000000..d1617ab9a08 --- /dev/null +++ b/src/main/resources/lang/idn/nukkit.yml @@ -0,0 +1,101 @@ +# Advanced configuration file for Nukkit +# 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: + #Multi-language setting + #Available: eng, chs, cht, jpn, rus, spa, pol, bra, kor, ukr, deu, ltu, idn, cze, tur, fin + language: "eng" + #Whether to send all strings translated to server locale or let the device handle them + force-language: false + 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 + #Number of AsyncTask workers + #If set to auto, it'll try to detect the number of cores (and at least 4) + async-workers: auto + +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 of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage + compression-level: 4 + #Enable network encryption + encryption: true + +debug: + #If > 1, it will show debug messages in the console + level: 1 + +level-settings: + #The default format that levels will use when created + default-format: leveldb + #Automatically change levels tick rate to maintain 20 ticks per second + auto-tick-rate: true + 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: + #Amount of chunks sent to players per tick + per-tick: 4 + #Amount of chunks that need to be sent before spawning the player + spawn-threshold: 56 + #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: false + +chunk-ticking: + #Max amount of chunks processed each tick + per-tick: 40 + #Radius of chunks around a player to tick + tick-radius: 3 + clear-tick-list: false + +chunk-generation: + #Max. amount of chunks in the waiting queue to be generated + queue-size: 8 + #Max. amount of chunks in the waiting queue to be populated + population-queue-size: 8 + +leveldb: + #Use native LevelDB implementation for better performance + use-native: false + #Set LevelDB memory cache size + cache-size-mb: 80 + +ticks-per: + #How often worlds and player data are saved when auto-save=on in server.properties + autosave: 6000 + +player: + #If true, player data will be saved as players/playername.dat + #If false, nukkit won't save player data as "dat" files, in order that plugins can do something on it. + save-player-data: true + #The time between skin change action in seconds, set to 0 if you dont want the cooldown + skin-change-cooldown: 15 + #Attacking entities resets sprinting, you can disable that here + attack-stop-sprint: true + +aliases: + #Aliases for commands + #Examples: + #showtheversion: version + #savestop: [save-all, stop] + +worlds: + #Worlds that the server will use. Options are specific to the chosen generator, and may result in broken generation or + #be ignored completely. + world: + #seed: 404 + generator: normal + #options: + nether: + #seed: my_cool_nether_seed + generator: nether + #options: diff --git a/src/main/resources/lang/jpn/lang.ini b/src/main/resources/lang/jpn/lang.ini index a63b6baba2b..8bde2bc0a6c 100644 --- a/src/main/resources/lang/jpn/lang.ini +++ b/src/main/resources/lang/jpn/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=ガベージコレクションのタスクを起動しました nukkit.command.gc.usage=/gc -nukkit.command.timings.description=サーバーのパフォーマンスを確認する記録のタイミングを設定します -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <値> または /time <start|stop|quer nukkit.command.gamerule.description=ゲームルールの値を設定またはクエリする nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=指定したプレーヤーのサーバーの使用を拒否します nukkit.command.ban.ip.description=指定したIPアドレスからのサーバーの使用を拒否します nukkit.command.banlist.description=このサーバーでBANされたすべてのプレイヤーを表示します diff --git a/src/main/resources/lang/jpn/nukkit.yml b/src/main/resources/lang/jpn/nukkit.yml index 313427f2164..dcbee75870a 100644 --- a/src/main/resources/lang/jpn/nukkit.yml +++ b/src/main/resources/lang/jpn/nukkit.yml @@ -24,7 +24,7 @@ network: #バッチパケットを送信するときのzlibの圧縮レベルですす。 値を大きくするとcpuに多く負荷かけ、低くすると通信量が増えます。 compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #1より大きいと、コンソールにデバッグメッセージが表示されます diff --git a/src/main/resources/lang/kor/lang.ini b/src/main/resources/lang/kor/lang.ini index 6c2545a33a9..4dbec2b59df 100644 --- a/src/main/resources/lang/kor/lang.ini +++ b/src/main/resources/lang/kor/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=쓰레기 수집 작업을 시작합니다 nukkit.command.gc.usage=/gc -nukkit.command.timings.description=서버의 성능 확인을 위해 타이밍을 기록합니다. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=타이밍 및 초기화를 활성화했습니다 -nukkit.command.timings.disable=타이밍을 비활성화했습니다 -nukkit.command.timings.timingsDisabled=/timings on을 입력해 타이밍을 활성화해주세요 -nukkit.command.timings.verboseEnable=버보스 타이밍을 활성화했습니다 -nukkit.command.timings.verboseDisable=버보스 타이밍을 비활성화했습니다 -nukkit.command.timings.reset=타이밍을 초기화했습니다 -nukkit.command.timings.rcon=주의: 타이밍 보고서 생성은 RCON으로 실행 시 서버 지연을 유발할 수 있습니다, 콘솔 또는 게임 내에서 /timings report 사용을 권장합니다 -nukkit.command.timings.uploadStart=타이밍 보고서 준비 중... -nukkit.command.timings.uploadError=업로드 오류: {%0}: {%1}, 자세한 정보는 로그를 확인하세요 -nukkit.command.timings.reportError=보고서를 붙여넣는 동안 오류가 발생했습니다, 자세한 정보는 로그를 확인하세요 -nukkit.command.timings.timingsLocation=타이밍 보고서 보기: {%0} -nukkit.command.timings.timingsResponse=타이밍 응답: {%0} -nukkit.command.timings.timingsWrite=타이밍이 {%0}에 작성되었습니다 - nukkit.command.title.description=지정된 플레이어에게 제목을 보내거나 해당 플레이어의 제목 설정을 수정합니다 nukkit.command.title.usage=/title <플레이어> <clear|reset> 또는 /title <플레이어> <title|subtitle|actionbar> <제목 텍스트> 또는 /title <플레이어> <times> <페이드 인 시간> <대기 시간> <페이드 아웃 시간> nukkit.command.title.clear={%0}의 화면을 성공적으로 초기화했습니다 @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <값> 또는 /time <start|stop|query> nukkit.command.gamerule.description=게임 규칙 값을 설정 또는 쿼리합니다 nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=지정된 플레이어가 이 서버를 이용하지 못하도록 합니다 nukkit.command.ban.ip.description=지정된 IP 주소가 이 서버를 이용하지 못하도록 합니다 nukkit.command.banlist.description=이 서버에서 차단된 모든 플레이어 목록을 보여줍니다 diff --git a/src/main/resources/lang/kor/nukkit.yml b/src/main/resources/lang/kor/nukkit.yml index 2f5df28df88..9579a00dd9e 100644 --- a/src/main/resources/lang/kor/nukkit.yml +++ b/src/main/resources/lang/kor/nukkit.yml @@ -24,7 +24,7 @@ network: #일괄 처리된 패킷을 전송할 때 Zlib에 사용되는 압축 수준입니다. 높을수록 더 많은 CPU 사용량으로, 적은 대역폭을 사용합니다 compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #1보다 크면, 콘솔에 디버그 메시지를 표시합니다 diff --git a/src/main/resources/lang/ltu/lang.ini b/src/main/resources/lang/ltu/lang.ini index 8681afe96d3..89f826fd3f0 100644 --- a/src/main/resources/lang/ltu/lang.ini +++ b/src/main/resources/lang/ltu/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Įjungia programinių šiukšlių valymą nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Įrašo efektyvumo statistiką serverio būsenos stebėjimui -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Statistika nustatyta iš naujo ir įjungtas įrašymas -nukkit.command.timings.disable=Statistikos įrašymas išjungtas -nukkit.command.timings.timingsDisabled=Prašome įjungti statistikos stebėjimą: /timings on -nukkit.command.timings.verboseEnable=Įjungta verbose statistika -nukkit.command.timings.verboseDisable=Išjungta verbose statistika -nukkit.command.timings.reset=Statistika nustatyta iš naujo -nukkit.command.timings.rcon=Dėmesio: Statistikos sudarymas per RCON sukels serverio strigimą, Jūs turėtumėte naudoti /timings report žaidime arba konsolėje -nukkit.command.timings.uploadStart=Sudaroma statistika... -nukkit.command.timings.uploadError=Įkėlimo klaida: {%0}: {%1}, dėl daugiau informacijos patikrinkite serverio žurnalą -nukkit.command.timings.reportError=Įvyko klaida įvedant statistiką, dėl daugiau informacijos patikrinkite serverio žurnalą -nukkit.command.timings.timingsLocation=Peržiūrėti sudarytą statistiką: {%0} -nukkit.command.timings.timingsResponse=Statistikos atsakymas: {%0} -nukkit.command.timings.timingsWrite=Statistiką įrašyta į {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <reikšmė> OR /time <start|stop|query nukkit.command.gamerule.description=Sets or queries a game rule value nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Užblokuoja pasirinktą žaidėją nukkit.command.ban.ip.description=Užblokuoja pasirinktą IP nukkit.command.banlist.description=Peržiūrėti visus užblokuotus žaidėjus diff --git a/src/main/resources/lang/ltu/nukkit.yml b/src/main/resources/lang/ltu/nukkit.yml index a4da92130c5..b6160d57099 100644 --- a/src/main/resources/lang/ltu/nukkit.yml +++ b/src/main/resources/lang/ltu/nukkit.yml @@ -24,7 +24,7 @@ network: #Suspaudimo lygis naudojant Zlib kai siunčiami suspausti paketai. Didesnė reikšmė = daugiau CPU, mažiau tinklo naudojimo compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Jeigu > 1, jis rodys derinimo pranešimus konsolėje diff --git a/src/main/resources/lang/pol/lang.ini b/src/main/resources/lang/pol/lang.ini index 2d89825b92e..690fdf73b40 100644 --- a/src/main/resources/lang/pol/lang.ini +++ b/src/main/resources/lang/pol/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Uruchamia procesy grabage colectora nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Zapisuje dane zwiazane z wydajnoscia serwer, aby mozna bylo je sprawdzic -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Wysyla title do wyznaczonego gracza, lub zmienia ustawienia title dla tego gracza nukkit.command.title.usage=/title <gracz> <clear|reset> LUB /title <gracz> <title|subtitle|actionbar> <tekst title> LUB /title <gracz> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Wyczyszczenie ekranu {%0} zakonczone sukcesem @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <wartosc> LUB /time <start|stop|query> nukkit.command.gamerule.description=Ustawia lub wysyla zapytanie o wartosc reguly gry nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Wysyla informacje o serwerze na Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Banuje gracza na serwerze nukkit.command.ban.ip.description=Banuje IP gracza na serwerze nukkit.command.banlist.description=Wyswietla wszystkich zbanowanych graczy diff --git a/src/main/resources/lang/pol/nukkit.yml b/src/main/resources/lang/pol/nukkit.yml index 9256c8728f3..c159af1e48b 100644 --- a/src/main/resources/lang/pol/nukkit.yml +++ b/src/main/resources/lang/pol/nukkit.yml @@ -24,7 +24,7 @@ network: #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Jesli wartosc jest wieksza niz 1, bedzie pokazywac wiadomosci debug'u w konsoli diff --git a/src/main/resources/lang/rus/lang.ini b/src/main/resources/lang/rus/lang.ini index 2965ce16462..376f5667841 100644 --- a/src/main/resources/lang/rus/lang.ini +++ b/src/main/resources/lang/rus/lang.ini @@ -264,22 +264,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Запускает процессы сборщика мусора. nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Записывает тайминги, чтобы показать производительность сервера. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Тайминги включены и сброшены -nukkit.command.timings.disable=Тайминги выключены -nukkit.command.timings.timingsDisabled=Пожалуйста, включите тайминги, написав в чате /timings on -nukkit.command.timings.verboseEnable=Подробные тайминги включены -nukkit.command.timings.verboseDisable=Подробные тайминги выключены -nukkit.command.timings.reset=Тайминги сброшены -nukkit.command.timings.rcon=Внимание: Отчёт таймингов, выполненый через RCON, может вызвать задержки, следует использовать команду в игре или в консоли -nukkit.command.timings.uploadStart=Идёт подготовка отчёта таймингов... -nukkit.command.timings.uploadError=Ошибка загрузки: {%0}: {%1}, проверьте логи для подробностей -nukkit.command.timings.reportError=Случилась ошибка во время отчёта, проверьте логи для подробностей -nukkit.command.timings.timingsLocation=Вы можете просмотреть отчёт здесь: {%0} -nukkit.command.timings.timingsResponse=Ответ: {%0} -nukkit.command.timings.timingsWrite=Тайминги записаны в {%0} - nukkit.command.title.description=Показывает заголовок для определённого игрока или изменяет настройки показа заголовков для него nukkit.command.title.usage=/title <игрок> <clear|reset> ИЛИ /title <игрок> <title|subtitle|actionbar> <текст заголовка> ИЛИ /title <игрок> <times> <время появления> <продолжительность> <время исчезания> nukkit.command.title.clear=Экран игрока {%0} успешно очищен @@ -309,9 +293,6 @@ nukkit.command.time.usage=/time <set|add> <значение> ИЛИ /time <start nukkit.command.gamerule.description=Устанавливает или запрашивает правило игры nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Блокирует определённого игрока на сервере. nukkit.command.ban.ip.description=Блокирует определённый IP адрес на сервере. nukkit.command.banlist.description=Показывает список всех заблокированных игроков на сервере. diff --git a/src/main/resources/lang/rus/nukkit.yml b/src/main/resources/lang/rus/nukkit.yml index f78d4af7284..b20b2056beb 100644 --- a/src/main/resources/lang/rus/nukkit.yml +++ b/src/main/resources/lang/rus/nukkit.yml @@ -30,7 +30,7 @@ network: #Чем больше - тем больше нагрузка на процессор, но меньше нагрузка на сеть compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #При значении > 1 будет отображать дополнительную отладочную информацию в консоли diff --git a/src/main/resources/lang/spa/lang.ini b/src/main/resources/lang/spa/lang.ini index 47b2f19921f..d0fe509cf07 100644 --- a/src/main/resources/lang/spa/lang.ini +++ b/src/main/resources/lang/spa/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Ejecuta tareas de recolección de basura nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Guarda ritmos para ver el rendimiento del servidor. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage="/time <set|add> <valor>" O "/time <start|stop|query>" nukkit.command.gamerule.description=Establece o consulta un valor de regla nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Previene que el jugador especificado utilice este servidor nukkit.command.ban.ip.description=Previene que la dirección IP especificada utilice este servidor nukkit.command.banlist.description=Muestra todos los jugadores betados de este servidor diff --git a/src/main/resources/lang/spa/nukkit.yml b/src/main/resources/lang/spa/nukkit.yml index 4114adfc7eb..9b5a12e8102 100644 --- a/src/main/resources/lang/spa/nukkit.yml +++ b/src/main/resources/lang/spa/nukkit.yml @@ -24,7 +24,7 @@ network: #Nivel de compresion usada para el envió de paquetes. Alta = más CPU, menos banda ancha compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Si es mayor que 1, mostrara mensajes de depuración en la consola diff --git a/src/main/resources/lang/tur/lang.ini b/src/main/resources/lang/tur/lang.ini index 3b5dce50ab1..3ed00c9cbe1 100644 --- a/src/main/resources/lang/tur/lang.ini +++ b/src/main/resources/lang/tur/lang.ini @@ -261,22 +261,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Fires garbage collection tasks nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Records timings to see performance of the server. -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Enabled timings and reset -nukkit.command.timings.disable=Disabled timings -nukkit.command.timings.timingsDisabled=Please enable timings by typing /timings on -nukkit.command.timings.verboseEnable=Enabled verbose timings -nukkit.command.timings.verboseDisable=Disabled verbose timings -nukkit.command.timings.reset=Timings reset -nukkit.command.timings.rcon=Warning: Timings report done over RCON will cause lag spikes, you should use /timings report in game or console -nukkit.command.timings.uploadStart=Preparing timings report... -nukkit.command.timings.uploadError=Upload Error: {%0}: {%1}, check logs for more information -nukkit.command.timings.reportError=An error happened while pasting the report, check logs for more information -nukkit.command.timings.timingsLocation=View timings report: {%0} -nukkit.command.timings.timingsResponse=Timings response: {%0} -nukkit.command.timings.timingsWrite=Timings written to {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -306,9 +290,6 @@ nukkit.command.time.usage=/time <set|add> <value> OR /time <start|stop|query> nukkit.command.gamerule.description=Bir oyun kuralı değeri belirler veya sorgular nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Prevents the specified player from using this server nukkit.command.ban.ip.description=Prevents the specified IP address from using this server nukkit.command.banlist.description=View all players banned from this server diff --git a/src/main/resources/lang/tur/nukkit.yml b/src/main/resources/lang/tur/nukkit.yml index ac813b08fe0..d1617ab9a08 100644 --- a/src/main/resources/lang/tur/nukkit.yml +++ b/src/main/resources/lang/tur/nukkit.yml @@ -24,7 +24,7 @@ network: #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #If > 1, it will show debug messages in the console diff --git a/src/main/resources/lang/ukr/lang.ini b/src/main/resources/lang/ukr/lang.ini index c35e1bb8ffb..b28ab0f9eda 100644 --- a/src/main/resources/lang/ukr/lang.ini +++ b/src/main/resources/lang/ukr/lang.ini @@ -265,22 +265,6 @@ nukkit.command.status.usage=/status nukkit.command.gc.description=Запускає процес збору сміття nukkit.command.gc.usage=/gc -nukkit.command.timings.description=Записує тайминги, щоб показати продуктивність сервера -nukkit.command.timings.usage=/timings <reset|report/paste|on|off|verbon|verboff> -nukkit.command.timings.enable=Тайминги ввімкнено і скинуто -nukkit.command.timings.disable=Тайминги вимкнуто -nukkit.command.timings.timingsDisabled=Будь ласка, увімкніть тайминги командою /timings -nukkit.command.timings.verboseEnable=Детальні тайминги ввімкнено -nukkit.command.timings.verboseDisable=Детальні тайминги вимкнено -nukkit.command.timings.reset=Тайминги скинуто -nukkit.command.timings.rcon=Увага: Звіт зроблений через RCON спричинить повільну роботу сервера, використовуйте /timings report в грі або консолі -nukkit.command.timings.uploadStart=Звіт таймингів готується... -nukkit.command.timings.uploadError=Помилка завантаження: {%0}: {%1}, перевірте логи для деталей -nukkit.command.timings.reportError=Помилка при формуванні звіту, перевірте логи для деталей -nukkit.command.timings.timingsLocation=Показано звіт таймингів: {%0} -nukkit.command.timings.timingsResponse=Відповідь: {%0} -nukkit.command.timings.timingsWrite=Тайминги записано до {%0} - nukkit.command.title.description=Sends a title to the specified player or modifies title settings for that player nukkit.command.title.usage=/title <player> <clear|reset> OR /title <player> <title|subtitle|actionbar> <title text> OR /title <player> <times> <fade in time> <stay time> <fade out time> nukkit.command.title.clear=Successfully cleared {%0}'s screen @@ -310,9 +294,6 @@ nukkit.command.time.usage=/time <set|add> <значення> АБО /time <start nukkit.command.gamerule.description=Визначає або запитує значення правила гри nukkit.command.gamerule.usage=/gamerule <rule> [value] -nukkit.command.debug.description=Uploads server information to Hastebin -nukkit.command.debug.usage=/debugpaste - nukkit.command.ban.player.description=Забороняє вказанному гравцю користуватися сервером nukkit.command.ban.ip.description=Забороняє вказанній IP адресі користуватися сервером nukkit.command.banlist.description=Показує усіх заблокованих гравців на цьому сервері diff --git a/src/main/resources/lang/ukr/nukkit.yml b/src/main/resources/lang/ukr/nukkit.yml index 65e706fc5f5..5c9196cc0a2 100644 --- a/src/main/resources/lang/ukr/nukkit.yml +++ b/src/main/resources/lang/ukr/nukkit.yml @@ -30,7 +30,7 @@ network: #Чим більше значення - тим більше навантаження на процесор, але менше навантаження на мережу compression-level: 4 #Enable network encryption - encryption: false + encryption: true debug: #Якщо > 1, у консолі будуть з'являтися повідомлення відладки From 875f43bd302d6ceaca6a3cc69714ceca6914885e Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:24:48 +0200 Subject: [PATCH 08/21] Dependencies --- build.gradle.kts | 4 +--- gradle/libs.versions.toml | 8 +++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9f1e0486a40..1959d3c64bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,9 +25,7 @@ dependencies { api(libs.network) api(libs.natives) api(libs.fastutil) - api(libs.fastutil1) - api(libs.fastutil2) - api(libs.fastutil3) + api(libs.bundles.fastutilmaps) api(libs.guava) api(libs.gson) api(libs.snakeyaml) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 437eb3d6250..33803804cba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,14 +2,15 @@ junit = "5.9.2" log4j = "2.20.0" jline = "3.22.0" +fastutilmaps = "8.5.13-SNAPSHOT" [libraries] network = { group = "com.nukkitx.network", name = "raknet", version = "1.6.28-SNAPSHOT" } natives = { group = "com.nukkitx", name = "natives", version = "1.0.3" } fastutil = { group = "com.nukkitx", name = "fastutil-lite", version = "8.1.1" } -fastutil1 = { group = "com.nukkitx.fastutil", name = "fastutil-int-short-maps", version = "8.5.3" } -fastutil2 = { group = "com.nukkitx.fastutil", name = "fastutil-object-int-maps", version = "8.5.3" } -fastutil3 = { group = "com.nukkitx.fastutil", name = "fastutil-object-object-maps", version = "8.5.3" } +fastutil-int-short-maps = { group = "org.cloudburstmc.fastutil.maps", name = "int-short-maps", version.ref = "fastutilmaps" } +fastutil-object-int-maps = { group = "org.cloudburstmc.fastutil.maps", name = "object-int-maps", version.ref = "fastutilmaps" } +fastutil-object-object-maps = { group = "org.cloudburstmc.fastutil.maps", name = "object-object-maps", version.ref = "fastutilmaps" } guava = { group = "com.google.guava", name = "guava", version = "30.1.1-jre" } gson = { group = "com.google.code.gson", name = "gson", version = "2.10.1" } snakeyaml = { group = "org.yaml", name = "snakeyaml", version = "1.33" } @@ -42,6 +43,7 @@ junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engi log4j = [ "log4j-api", "log4j-core" ] terminal = [ "jline-terminal", "jline-terminal-jna", "jline-reader", "terminal-console" ] junit = [ "junit-jupiter-api", "junit-jupiter-engine" ] +fastutilmaps = [ "fastutil-int-short-maps", "fastutil-object-int-maps", "fastutil-object-object-maps" ] [plugins] shadow = { id = "com.github.johnrengelman.shadow", version = "8.0.0" } From 84d4487e33bbef66a3b1c12c5f754b06e177730e Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:08:50 +0200 Subject: [PATCH 09/21] Resolved --- build.gradle.kts | 1 - gradle/libs.versions.toml | 1 - src/main/java/cn/nukkit/Player.java | 11 ++++---- src/main/java/cn/nukkit/block/Block.java | 3 ++- .../command/defaults/StatusCommand.java | 2 +- src/main/java/cn/nukkit/level/Level.java | 3 +-- .../format/leveldb/BlockStateMapping.java | 26 +++++++++---------- 7 files changed, 22 insertions(+), 25 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1959d3c64bb..fb5af1abd73 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,6 @@ dependencies { exclude(group = "org.iq80.leveldb", module = "leveldb") } api(libs.snappy) - api(libs.expiringmap) api(libs.jwt) api(libs.bundles.terminal) api(libs.bundles.log4j) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33803804cba..3d8d49a0d42 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,7 +17,6 @@ snakeyaml = { group = "org.yaml", name = "snakeyaml", version = "1.33" } leveldb = { group = "org.iq80.leveldb", name = "leveldb", version = "0.11-SNAPSHOT" } leveldbjni = { group = "net.daporkchop", name = "leveldb-mcpe-jni", version = "0.0.10-SNAPSHOT" } snappy = { group = "org.xerial.snappy", name = "snappy-java", version = "1.1.10.5" } -expiringmap = { group = "net.jodah", name = "expiringmap", version = "0.5.11" } jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version = "9.13" } jopt-simple = { group = "net.sf.jopt-simple", name = "jopt-simple", version = "5.0.4" } blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.10-SNAPSHOT" } diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index efad3ecca47..eb2478dedcc 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -883,13 +883,12 @@ public Position getNextPosition() { return this.newPosition != null ? new Position(this.newPosition.x, this.newPosition.y, this.newPosition.z, this.level) : this.getPosition(); } - private static final Vector3 ZERO_VECTOR3 = new Vector3(0, 0, 0); - - public Vector3 getPositionOffset() { + public AxisAlignedBB getNextPositionBB() { if (this.newPosition == null) { - return ZERO_VECTOR3; + return this.boundingBox; } - return this.newPosition.subtract(this); + Vector3 diff = this.newPosition.subtract(this); + return this.boundingBox.getOffsetBoundingBox(diff.x, diff.y, diff.z); } /** @@ -2249,7 +2248,7 @@ public boolean onUpdate(int currentTick) { this.resetFallDistance(); } else { - if (this.checkMovement && this.riptideTicks < 1 && !this.isGliding() && !server.getAllowFlight() && this.inAirTicks > 20 && !this.getAllowFlight() && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING) && this.speed != null && !ZERO_VECTOR3.equals(this.speed)) { + if (this.checkMovement && this.riptideTicks < 1 && !this.isGliding() && !server.getAllowFlight() && this.inAirTicks > 20 && !this.getAllowFlight() && !this.isSleeping() && !this.isImmobile() && !this.isSwimming() && this.riding == null && !this.hasEffect(Effect.LEVITATION) && !this.hasEffect(Effect.SLOW_FALLING) && this.speed != null && !(this.speed.x == 0 && this.speed.y == 0 && this.speed.z == 0)) { double expectedVelocity = (-this.getGravity()) / ((double) this.getDrag()) - ((-this.getGravity()) / ((double) this.getDrag())) * Math.exp(-((double) this.getDrag()) * ((double) (this.inAirTicks - this.startAirTicks))); double diff = Math.abs(Math.abs(expectedVelocity) - Math.abs(this.speed.y)); diff --git a/src/main/java/cn/nukkit/block/Block.java b/src/main/java/cn/nukkit/block/Block.java index bf86e788d0e..466fbc3c3d2 100644 --- a/src/main/java/cn/nukkit/block/Block.java +++ b/src/main/java/cn/nukkit/block/Block.java @@ -35,7 +35,8 @@ */ public abstract class Block extends Position implements Metadatable, Cloneable, AxisAlignedBB, BlockID { - public static final int MAX_BLOCK_ID = 1024; + @SuppressWarnings("UnnecessaryBoxing") + public static final int MAX_BLOCK_ID = Integer.valueOf("1024"); public static final int DATA_BITS = 6; public static final int DATA_SIZE = 1 << DATA_BITS; public static final int DATA_MASK = DATA_SIZE - 1; diff --git a/src/main/java/cn/nukkit/command/defaults/StatusCommand.java b/src/main/java/cn/nukkit/command/defaults/StatusCommand.java index 2c2c6498ea3..2d0afdae143 100644 --- a/src/main/java/cn/nukkit/command/defaults/StatusCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/StatusCommand.java @@ -50,7 +50,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) sender.sendMessage(TextFormat.GOLD + "TPS / Average: " + tpsColor + NukkitMath.round(tps, 2) + " / " + NukkitMath.round(server.getTicksPerSecondAverage(), 2)); - sender.sendMessage(TextFormat.GOLD + "Load / Average: " + tpsColor + server.getTickUsage() + "% / " + server.getTickUsageAverage() + '%'); + sender.sendMessage(TextFormat.GOLD + "Load / Average: " + tpsColor + server.getTickUsage() + "% / " + server.getTickUsageAverage() * 100 + '%'); //sender.sendMessage(TextFormat.GOLD + "Network upload: " + TextFormat.GREEN + NukkitMath.round((server.getNetwork().getUpload() / 1024 * 1000), 2) + " kB/s"); diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index e689b90f720..0fd0f8a16a6 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -2415,8 +2415,7 @@ public Item useItemOn(Vector3 vector, Item item, BlockFace face, float fx, float } if (player != null) { - Vector3 diff = player.getPositionOffset(); - if (hand.getBoundingBox().intersectsWith(player.getBoundingBox().getOffsetBoundingBox(diff.x, diff.y, diff.z))) { + if (hand.getBoundingBox().intersectsWith(player.getNextPositionBB())) { this.sendBlocks(player, new Block[]{block, target}, UpdateBlockPacket.FLAG_NONE); // Prevent ghost blocks return null; // Player in block } diff --git a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java index 2fcf7b19bca..37c0569f907 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java @@ -1,28 +1,29 @@ package cn.nukkit.level.format.leveldb; +import cn.nukkit.level.format.leveldb.structure.BlockStateSnapshot; import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterChunker; import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterVanilla; -import cn.nukkit.level.format.leveldb.structure.BlockStateSnapshot; import cn.nukkit.utils.MainLogger; -import net.jodah.expiringmap.ExpirationPolicy; -import net.jodah.expiringmap.ExpiringMap; -import org.cloudburstmc.blockstateupdater.*; -import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; -import org.cloudburstmc.nbt.NbtMap; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.nukkitx.network.util.Preconditions; import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.*; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.cloudburstmc.blockstateupdater.*; +import org.cloudburstmc.blockstateupdater.util.tagupdater.CompoundTagUpdaterContext; +import org.cloudburstmc.nbt.NbtMap; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import static cn.nukkit.level.format.leveldb.LevelDBConstants.*; +import static cn.nukkit.level.format.leveldb.LevelDBConstants.PALETTE_VERSION; public class BlockStateMapping { private static final Logger log = LogManager.getLogger("LevelDB-Logger"); @@ -33,10 +34,9 @@ public class BlockStateMapping { private static final int LATEST_UPDATER_VERSION; private static final BlockStateMapping INSTANCE = new BlockStateMapping(PALETTE_VERSION); - private static final ExpiringMap<NbtMap, NbtMap> BLOCK_UPDATE_CACHE = ExpiringMap.builder() - .maxSize(1024) - .expiration(60, TimeUnit.SECONDS) - .expirationPolicy(ExpirationPolicy.ACCESSED) + private static final Cache<NbtMap, NbtMap> BLOCK_UPDATE_CACHE = CacheBuilder.newBuilder() + .maximumSize(1024) + .expireAfterAccess(60, TimeUnit.SECONDS) .build(); static { @@ -243,7 +243,7 @@ public BlockStateSnapshot getUpdatedState(NbtMap state) { } public NbtMap updateVanillaState(NbtMap state) { - NbtMap cached = BLOCK_UPDATE_CACHE.get(state); + NbtMap cached = BLOCK_UPDATE_CACHE.getIfPresent(state); if (cached == null) { int version = state.getInt("version"); // TODO: validate this when updating next time cached = CONTEXT.update(state, LATEST_UPDATER_VERSION == version ? version - 1 : version); From 9a975f19508159953879333197997794b1b612b7 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Mon, 1 Apr 2024 21:26:47 +0300 Subject: [PATCH 10/21] Update Network Continue work started by SupremeMortal on raknet-v2 --- build.gradle.kts | 1 + gradle/libs.versions.toml | 5 +- .../properties/BlockProperties.java | 2 +- .../customblock/properties/BlockProperty.java | 2 +- .../properties/BooleanBlockProperty.java | 2 +- .../properties/IntBlockProperty.java | 2 +- .../properties/UnsignedIntBlockProperty.java | 2 +- .../format/leveldb/BlockStateMapping.java | 2 +- .../level/format/leveldb/LevelDBProvider.java | 2 +- .../serializer/ChunkSectionSerializers.java | 2 +- src/main/java/cn/nukkit/network/Network.java | 15 +- .../cn/nukkit/network/RakNetInterface.java | 136 +++++++++++------- .../network/encryption/EncryptionUtils.java | 2 +- .../nukkit/network/protocol/DataPacket.java | 4 - .../network/session/NetworkPlayerSession.java | 4 + .../network/session/RakNetPlayerSession.java | 89 ++++++------ 16 files changed, 146 insertions(+), 126 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fb5af1abd73..ca67610a4ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,7 @@ repositories { dependencies { api(libs.network) + api(libs.epoll) api(libs.natives) api(libs.fastutil) api(libs.bundles.fastutilmaps) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d8d49a0d42..f05d11c5309 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,13 +5,14 @@ jline = "3.22.0" fastutilmaps = "8.5.13-SNAPSHOT" [libraries] -network = { group = "com.nukkitx.network", name = "raknet", version = "1.6.28-SNAPSHOT" } +network = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version = "1.0.0.CR1-20240330.101522-15" } +epoll = { group = "io.netty", name = "netty-transport-native-epoll", version = "4.1.101.Final" } natives = { group = "com.nukkitx", name = "natives", version = "1.0.3" } fastutil = { group = "com.nukkitx", name = "fastutil-lite", version = "8.1.1" } fastutil-int-short-maps = { group = "org.cloudburstmc.fastutil.maps", name = "int-short-maps", version.ref = "fastutilmaps" } fastutil-object-int-maps = { group = "org.cloudburstmc.fastutil.maps", name = "object-int-maps", version.ref = "fastutilmaps" } fastutil-object-object-maps = { group = "org.cloudburstmc.fastutil.maps", name = "object-object-maps", version.ref = "fastutilmaps" } -guava = { group = "com.google.guava", name = "guava", version = "30.1.1-jre" } +guava = { group = "com.google.guava", name = "guava", version = "33.1.0-jre" } gson = { group = "com.google.code.gson", name = "gson", version = "2.10.1" } snakeyaml = { group = "org.yaml", name = "snakeyaml", version = "1.33" } leveldb = { group = "org.iq80.leveldb", name = "leveldb", version = "0.11-SNAPSHOT" } diff --git a/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java b/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java index 0c825a0621f..d0a75187997 100644 --- a/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java +++ b/src/main/java/cn/nukkit/customblock/properties/BlockProperties.java @@ -3,7 +3,7 @@ import cn.nukkit.customblock.properties.exception.BlockPropertyNotFoundException; import cn.nukkit.utils.functional.ToIntTriFunctionTwoInts; import cn.nukkit.utils.functional.ToLongTriFunctionOneIntOneLong; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import net.daporkchop.lib.common.function.plain.TriFunction; diff --git a/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java index 7d0e4ad57e1..2e9f57d295e 100644 --- a/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java +++ b/src/main/java/cn/nukkit/customblock/properties/BlockProperty.java @@ -2,7 +2,7 @@ import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import java.io.Serializable; import java.math.BigInteger; diff --git a/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java index a3fdfaf6cf3..b04de47074e 100644 --- a/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java +++ b/src/main/java/cn/nukkit/customblock/properties/BooleanBlockProperty.java @@ -2,7 +2,7 @@ import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import java.io.Serializable; import java.math.BigInteger; diff --git a/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java index 507b6fb5fee..f9baba3ee11 100644 --- a/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java +++ b/src/main/java/cn/nukkit/customblock/properties/IntBlockProperty.java @@ -4,7 +4,7 @@ import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; import cn.nukkit.math.NukkitMath; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import java.io.Serializable; diff --git a/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java b/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java index 6f40613b019..8bc5d5ec889 100644 --- a/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java +++ b/src/main/java/cn/nukkit/customblock/properties/UnsignedIntBlockProperty.java @@ -3,7 +3,7 @@ import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyPersistenceValueException; import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyValueException; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import java.io.Serializable; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java index 37c0569f907..4e1e81777b7 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java @@ -4,9 +4,9 @@ import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterChunker; import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterVanilla; import cn.nukkit.utils.MainLogger; +import com.google.common.base.Preconditions; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import com.nukkitx.network.util.Preconditions; import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java index 6c035b1af3b..54bc15fb1bd 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBProvider.java @@ -19,9 +19,9 @@ import cn.nukkit.nbt.tag.Tag; import cn.nukkit.utils.ChunkException; import cn.nukkit.utils.LevelException; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.nukkitx.network.util.Preconditions; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; diff --git a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java index cbd9fb18553..7e1a3fb7c87 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/serializer/ChunkSectionSerializers.java @@ -2,7 +2,7 @@ import cn.nukkit.level.format.leveldb.structure.ChunkBuilder; import cn.nukkit.level.format.leveldb.structure.StateBlockStorage; -import com.nukkitx.network.util.Preconditions; +import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; public class ChunkSectionSerializers { diff --git a/src/main/java/cn/nukkit/network/Network.java b/src/main/java/cn/nukkit/network/Network.java index 6aa8ab4c40a..794acc758e8 100644 --- a/src/main/java/cn/nukkit/network/Network.java +++ b/src/main/java/cn/nukkit/network/Network.java @@ -1,7 +1,6 @@ package cn.nukkit.network; import cn.nukkit.Nukkit; -import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.network.protocol.*; import cn.nukkit.utils.BinaryStream; @@ -10,7 +9,6 @@ import io.netty.buffer.ByteBuf; import lombok.extern.log4j.Log4j2; -import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -137,17 +135,8 @@ public Server getServer() { return server; } - public void processBatch(byte[] payload, Collection<DataPacket> packets, CompressionProvider compression, @Nullable Player player) throws Exception { - //byte[] data; - //try { - - // Allow first batch to be bigger so large skin in login packet won't get the player kicked - byte[] data = compression.decompress(payload, player != null && player.getSkin() == null ? 6291456 : 3145728); - - //} catch (Exception e) { - // log.error("Exception while inflating batch packet", e); - // return; - //} + public void processBatch(byte[] payload, Collection<DataPacket> packets, CompressionProvider compression) throws Exception { + byte[] data = compression.decompress(payload, 6291456); BinaryStream stream = new BinaryStream(data); int count = 0; diff --git a/src/main/java/cn/nukkit/network/RakNetInterface.java b/src/main/java/cn/nukkit/network/RakNetInterface.java index 9c6711b77a6..d0b80fc7a87 100644 --- a/src/main/java/cn/nukkit/network/RakNetInterface.java +++ b/src/main/java/cn/nukkit/network/RakNetInterface.java @@ -10,43 +10,93 @@ import cn.nukkit.network.session.RakNetPlayerSession; import cn.nukkit.utils.Utils; import com.google.common.base.Strings; -import com.nukkitx.network.raknet.*; +import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollDatagramChannel; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramPacket; +import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.util.internal.PlatformDependent; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.extern.log4j.Log4j2; +import org.cloudburstmc.netty.channel.raknet.RakChannelFactory; +import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; +import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerRateLimiter; import java.lang.reflect.Constructor; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.IntFunction; /** * @author MagicDroidX * Nukkit Project */ @Log4j2 -public class RakNetInterface implements RakNetServerListener, AdvancedSourceInterface { +public class RakNetInterface implements AdvancedSourceInterface { private final Server server; - private final RakNetServer raknet; private Network network; - private byte[] advertisement; - + private final List<Channel> channels = new ObjectArrayList<>(); private final Map<InetSocketAddress, RakNetPlayerSession> sessions = new HashMap<>(); private final Queue<RakNetPlayerSession> sessionCreationQueue = PlatformDependent.newMpscQueue(); + private final long serverId = ThreadLocalRandom.current().nextLong(); + public RakNetInterface(Server server) { this.server = server; - this.raknet = new RakNetServer(new InetSocketAddress(Strings.isNullOrEmpty(this.server.getIp()) ? "0.0.0.0" : this.server.getIp(), this.server.getPort()), Runtime.getRuntime().availableProcessors()); - this.raknet.setProtocolVersion(11); - this.raknet.bind().join(); - this.raknet.setListener(this); + boolean disableNative = Boolean.parseBoolean(System.getProperty("disableNativeEventLoop")); + + Transport transport; + if (!disableNative && Epoll.isAvailable()) { + transport = new Transport(EpollDatagramChannel.class, EpollEventLoopGroup::new); + } else { + transport = new Transport(NioDatagramChannel.class, NioEventLoopGroup::new); + } + + EventLoopGroup group = transport.eventLoopGroupFactory.apply(Runtime.getRuntime().availableProcessors()); + + ServerBootstrap bootstrap = new ServerBootstrap() + .channelFactory(RakChannelFactory.server(transport.datagramChannel)) + .group(group) + .option(RakChannelOption.RAK_GUID, this.serverId) + .childOption(RakChannelOption.RAK_ORDERING_CHANNELS, 1) + .handler(new ChannelInitializer<Channel>() { + @Override + protected void initChannel(Channel channel) { + if (server.getPropertyBoolean("enable-query", false)) { + channel.pipeline().addLast("query-handler", new SimpleChannelInboundHandler<DatagramPacket>() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) { + server.handlePacket(packet.sender(), packet.content()); + } + }); + } + } + }) + .childHandler(new ChannelInitializer<Channel>() { + @Override + protected void initChannel(Channel channel) { + RakNetPlayerSession nukkitSession = new RakNetPlayerSession(RakNetInterface.this, channel); + channel.pipeline().addLast("nukkit-handler", nukkitSession); + RakNetInterface.this.sessionCreationQueue.offer(nukkitSession); + } + }); + + String address = Strings.isNullOrEmpty(this.server.getIp()) ? "0.0.0.0" : this.server.getIp(); + + this.channels.add(bootstrap.bind(address, this.server.getPort()).awaitUninterruptibly().channel()); } @Override @@ -58,7 +108,7 @@ public void setNetwork(Network network) { public boolean process() { RakNetPlayerSession session; while ((session = this.sessionCreationQueue.poll()) != null) { - InetSocketAddress address = session.getRakNetSession().getAddress(); + InetSocketAddress address = (InetSocketAddress) session.getChannel().remoteAddress(); try { PlayerCreationEvent event = new PlayerCreationEvent(this, Player.class, Player.class, null, address); this.server.getPluginManager().callEvent(event); @@ -92,8 +142,7 @@ public boolean process() { @Override public int getNetworkLatency(Player player) { - RakNetServerSession session = this.raknet.getSession(player.getSocketAddress()); - return session == null ? -1 : (int) session.getPing(); + return (int) player.getNetworkSession().getPing(); } @Override @@ -117,33 +166,33 @@ public void close(Player player, String reason) { @Override public void shutdown() { this.sessions.values().forEach(session -> session.disconnect("Shutdown")); - this.raknet.close(); + this.channels.forEach(channel -> channel.close().awaitUninterruptibly()); } @Override public void emergencyShutdown() { this.sessions.values().forEach(session -> session.disconnect("Shutdown")); - this.raknet.close(); + this.channels.forEach(channel -> channel.close().awaitUninterruptibly()); } @Override public void blockAddress(InetAddress address) { - this.raknet.block(address); + this.channels.get(0).pipeline().get(RakServerRateLimiter.class).blockAddress(address, 100, TimeUnit.DAYS); } @Override public void blockAddress(InetAddress address, int timeout) { - this.raknet.block(address, timeout, TimeUnit.SECONDS); + this.channels.get(0).pipeline().get(RakServerRateLimiter.class).blockAddress(address, timeout, TimeUnit.SECONDS); } @Override public void unblockAddress(InetAddress address) { - this.raknet.unblock(address); + this.channels.get(0).pipeline().get(RakServerRateLimiter.class).unblockAddress(address); } @Override public void sendRawPacket(InetSocketAddress socketAddress, ByteBuf payload) { - this.raknet.send(socketAddress, payload); + this.channels.get(0).write(new DatagramPacket(payload, socketAddress)); } @Override @@ -159,12 +208,16 @@ public void setName(String name) { .add(ProtocolInfo.MINECRAFT_VERSION_NETWORK) .add(Integer.toString(info.getPlayerCount())) .add(Integer.toString(info.getMaxPlayerCount())) - .add(Long.toString(this.raknet.getGuid())) + .add(Long.toString(this.serverId)) .add(subMotd) .add(Server.getGamemodeString(this.server.getDefaultGamemode(), true)) .add("1"); - this.advertisement = joiner.toString().getBytes(StandardCharsets.UTF_8); + byte[] advertisement = joiner.toString().getBytes(StandardCharsets.UTF_8); + + for (Channel channel : this.channels) { + channel.config().setOption(RakChannelOption.RAK_ADVERTISEMENT, Unpooled.wrappedBuffer(advertisement)); + } } @Override @@ -186,39 +239,18 @@ public Integer putPacket(Player player, DataPacket packet, boolean needACK, bool return null; } - @Override - public boolean onConnectionRequest(InetSocketAddress address, InetSocketAddress realAddress) { - return true; - } - - @Override - public byte[] onQuery(InetSocketAddress inetSocketAddress) { - return this.advertisement; - } - - @Override - public void onSessionCreation(RakNetServerSession session) { - // We need to make sure this gets put into the correct thread local hashmap - // for ticking or race conditions will occur. - if (session.getEventLoop().inEventLoop()) { - this.onSessionCreation0(session); - } else { - session.getEventLoop().execute(() -> this.onSessionCreation0(session)); - } + public Network getNetwork() { + return this.network; } - private void onSessionCreation0(RakNetServerSession session) { - RakNetPlayerSession nukkitSession = new RakNetPlayerSession(this, session); - session.setListener(nukkitSession); - this.sessionCreationQueue.offer(nukkitSession); - } + private static class Transport { - @Override - public void onUnhandledDatagram(ChannelHandlerContext ctx, DatagramPacket datagramPacket) { - this.server.handlePacket(datagramPacket.sender(), datagramPacket.content()); - } + private final Class<? extends DatagramChannel> datagramChannel; + private final IntFunction<EventLoopGroup> eventLoopGroupFactory; - public Network getNetwork() { - return this.network; + private Transport(Class<? extends DatagramChannel> datagramChannel, IntFunction<EventLoopGroup> eventLoopGroupFactory) { + this.datagramChannel = datagramChannel; + this.eventLoopGroupFactory = eventLoopGroupFactory; + } } -} \ No newline at end of file +} diff --git a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java index f95d3f4f385..9e68611b63c 100644 --- a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java +++ b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java @@ -1,5 +1,6 @@ package cn.nukkit.network.encryption; +import com.google.common.base.Preconditions; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; @@ -14,7 +15,6 @@ import com.nimbusds.jwt.SignedJWT; import com.nukkitx.natives.aes.AesFactory; import com.nukkitx.natives.util.Natives; -import com.nukkitx.network.util.Preconditions; import lombok.experimental.UtilityClass; import javax.crypto.Cipher; diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index c45145917f5..65fe3b470d5 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -6,7 +6,6 @@ import cn.nukkit.utils.BinaryStream; import cn.nukkit.utils.SnappyCompression; import cn.nukkit.utils.Zlib; -import com.nukkitx.network.raknet.RakNetReliability; /** * @author MagicDroidX @@ -19,9 +18,6 @@ public abstract class DataPacket extends BinaryStream implements Cloneable { @Deprecated private int channel = Network.CHANNEL_NONE; - @Deprecated - public RakNetReliability reliability = RakNetReliability.RELIABLE_ORDERED; - public abstract byte pid(); public abstract void decode(); diff --git a/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java b/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java index 166828045f8..1ffbf002208 100644 --- a/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java @@ -26,4 +26,8 @@ default void flush() { default void setEncryption(SecretKey encryptionKey, Cipher encryptionCipher, Cipher decryptionCipher) { } + + default long getPing() { + return 0; + } } diff --git a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java index 50abfd475cd..7f26eca0511 100644 --- a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java @@ -13,15 +13,19 @@ import com.google.common.base.Preconditions; import com.nukkitx.natives.sha256.Sha256; import com.nukkitx.natives.util.Natives; -import com.nukkitx.network.raknet.*; -import com.nukkitx.network.util.DisconnectReason; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.internal.PlatformDependent; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.extern.log4j.Log4j2; import org.apache.logging.log4j.message.FormattedMessage; +import org.cloudburstmc.netty.channel.raknet.RakChannel; +import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; +import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -35,10 +39,10 @@ import java.util.concurrent.atomic.AtomicLong; @Log4j2 -public class RakNetPlayerSession implements NetworkPlayerSession, RakNetSessionListener { +public class RakNetPlayerSession extends SimpleChannelInboundHandler<RakMessage> implements NetworkPlayerSession { private final RakNetInterface server; - private final RakNetServerSession session; + private final Channel channel; private final Queue<DataPacket> inbound = PlatformDependent.newSpscQueue(); private final Queue<DataPacket> outbound = PlatformDependent.newMpscQueue(); @@ -55,15 +59,15 @@ public class RakNetPlayerSession implements NetworkPlayerSession, RakNetSessionL private Cipher decryptionCipher; private final AtomicLong sentEncryptedPacketCount = new AtomicLong(); - public RakNetPlayerSession(RakNetInterface server, RakNetServerSession session) { + public RakNetPlayerSession(RakNetInterface server, Channel channel) { this.server = server; - this.session = session; - this.tickFuture = session.getEventLoop().scheduleAtFixedRate(this::networkTick, 0, 20, TimeUnit.MILLISECONDS); + this.channel = channel; + this.tickFuture = channel.eventLoop().scheduleAtFixedRate(this::networkTick, 0, 20, TimeUnit.MILLISECONDS); } @Override - public void onEncapsulated(EncapsulatedPacket packet) { - ByteBuf buffer = packet.getBuffer(); + protected void channelRead0(ChannelHandlerContext channelHandlerContext, RakMessage msg) throws Exception { + ByteBuf buffer = msg.content(); short packetId = buffer.readUnsignedByte(); if (packetId == 0xfe) { int len = buffer.readableBytes(); @@ -104,32 +108,19 @@ public void onEncapsulated(EncapsulatedPacket packet) { buffer.readBytes(packetBuffer); try { - this.server.getNetwork().processBatch(packetBuffer, this.inbound, compressionIn, this.player); + this.server.getNetwork().processBatch(packetBuffer, this.inbound, compressionIn); } catch (Exception e) { this.disconnect("Sent malformed packet"); - log.error("[{}] Unable to process batch packet", (this.player == null ? this.session.getAddress() : this.player.getName()), e); + log.error("[{}] Unable to process batch packet", (this.player == null ? this.channel.remoteAddress() : this.player.getName()), e); } } else if (Nukkit.DEBUG > 1) { - log.debug("Unknown EncapsulatedPacket: " +packetId); + log.debug("Unknown RakMessage: " + packetId); } } @Override - public void onDirect(ByteBuf byteBuf) { - // We don't allow any direct packets so ignore. - } - - @Override - public void onSessionChangeState(RakNetState rakNetState) { - } - - @Override - public void onDisconnect(DisconnectReason reason) { - if (reason == DisconnectReason.TIMED_OUT) { - this.disconnect("Timed out"); - } else { - this.disconnect("Disconnected from Server"); - } + public void channelInactive(ChannelHandlerContext ctx) { + this.disconnect("Disconnected from Server"); // TODO: timeout reason } @Override @@ -144,41 +135,38 @@ public void disconnect(String reason) { } // Give it a short time to make sure cancel message is delivered - this.session.getEventLoop().schedule(() -> this.session.close(), 10, TimeUnit.MILLISECONDS); + this.channel.eventLoop().schedule(() -> this.channel.close(), 10, TimeUnit.MILLISECONDS); } @Override public void sendPacket(DataPacket packet) { - if (this.session.isClosed()) { + if (!this.channel.isActive()) { return; } if (packet.pid() != ProtocolInfo.BATCH_PACKET) { packet.tryEncode(); } + this.outbound.offer(packet); } @Override public void sendImmediatePacket(DataPacket packet, Runnable callback) { - if (this.session.isClosed()) { + if (!this.channel.isActive()) { return; } + this.sendPacket(packet); - this.session.getEventLoop().execute(() -> { + this.channel.eventLoop().execute(() -> { this.networkTick(); callback.run(); }); } - @Override - public void flush() { - this.session.getEventLoop().execute(this::networkTick); - } - private void networkTick() { - if (this.session.isClosed()) { + if (!this.channel.isActive()) { return; } @@ -193,7 +181,7 @@ private void networkTick() { batched.put(buf); try { - this.sendPacket(this.compressionOut.compress(batched, Server.getInstance().networkCompressionLevel), RakNetPriority.IMMEDIATE); + this.sendPacket(this.compressionOut.compress(batched, Server.getInstance().networkCompressionLevel)); } catch (Exception e) { log.error("Unable to compress disconnect packet", e); } @@ -204,7 +192,7 @@ private void networkTick() { toBatch.clear(); } - this.sendPacket(((BatchPacket) packet).payload, RakNetPriority.MEDIUM); + this.sendPacket(((BatchPacket) packet).payload); } else { toBatch.add(packet); } @@ -214,7 +202,7 @@ private void networkTick() { this.sendPackets(toBatch); } } catch (Throwable e) { - log.error("[{}] Failed to tick RakNetPlayerSession", this.session.getAddress(), e); + log.error("[{}] Failed to tick RakNetPlayerSession", this.channel.remoteAddress(), e); } } @@ -245,13 +233,13 @@ private void sendPackets(Collection<DataPacket> packets) { } try { - this.sendPacket(this.compressionOut.compress(batched, Server.getInstance().networkCompressionLevel), RakNetPriority.MEDIUM); + this.sendPacket(this.compressionOut.compress(batched, Server.getInstance().networkCompressionLevel)); } catch (Exception e) { log.error("Unable to compress batched packets", e); } } - private void sendPacket(byte[] compressedPayload, RakNetPriority priority) { + private void sendPacket(byte[] compressedPayload) { ByteBuf finalPayload = ByteBufAllocator.DEFAULT.directBuffer((this.compressionInitialized ? 10 : 9) + compressedPayload.length); // prefix(1)+id(1)+encryption(8)+data finalPayload.writeByte(0xfe); @@ -280,7 +268,7 @@ private void sendPacket(byte[] compressedPayload, RakNetPriority priority) { finalPayload.writeBytes(compressedPayload); } - this.session.send(finalPayload, priority); + this.channel.writeAndFlush(finalPayload); } @Override @@ -305,8 +293,8 @@ public Player getPlayer() { return this.player; } - public RakNetServerSession getRakNetSession() { - return this.session; + public Channel getChannel() { + return this.channel; } public String getDisconnectReason() { @@ -320,6 +308,15 @@ public void setEncryption(SecretKey encryptionKey, Cipher encryptionCipher, Ciph this.decryptionCipher = decryptionCipher; } + @Override + public long getPing() { + if (this.channel instanceof RakChannel) { + RakChannel rakChannel = (RakChannel) this.channel; + return rakChannel.rakPipeline().get(RakSessionCodec.class).getPing(); + } + return -1; + } + private static final ThreadLocal<Sha256> HASH_LOCAL = ThreadLocal.withInitial(Natives.SHA_256); private byte[] generateTrailer(ByteBuf buf) { @@ -338,4 +335,4 @@ private byte[] generateTrailer(ByteBuf buf) { hash.reset(); } } -} \ No newline at end of file +} From 2b0d7d2a9bcc6352002f29afc474ae5c24311cca Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:03:36 +0300 Subject: [PATCH 11/21] These are not needed --- src/main/java/cn/nukkit/Player.java | 7 ------- .../cn/nukkit/network/session/NetworkPlayerSession.java | 4 ---- 2 files changed, 11 deletions(-) diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index eb2478dedcc..30c2eb66a0b 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -6675,10 +6675,6 @@ public void setDimension(int dimension) { @Override protected void preSwitchLevel() { - // Make sure batch packets from the previous world gets through first - this.networkSession.flush(); - - // Remove old chunks this.unloadChunks(true); } @@ -6702,9 +6698,6 @@ protected void afterSwitchLevel() { GameRulesChangedPacket packet = new GameRulesChangedPacket(); packet.gameRulesMap = level.getGameRules().getGameRules(); this.dataPacket(packet); - - // Reset sleeping timer - this.timeSinceRest = 0; } /** diff --git a/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java b/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java index 1ffbf002208..928bbf317fa 100644 --- a/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/NetworkPlayerSession.java @@ -12,10 +12,6 @@ public interface NetworkPlayerSession { void sendPacket(DataPacket packet); void sendImmediatePacket(DataPacket packet, Runnable callback); - default void flush() { - - } - void disconnect(String reason); Player getPlayer(); From 0038ffbbbcf42c136fe591c73aa38e3f266b1177 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Sun, 12 May 2024 14:22:17 +0300 Subject: [PATCH 12/21] Only register packets which are supposed to be received --- src/main/java/cn/nukkit/Player.java | 29 ------- src/main/java/cn/nukkit/network/Network.java | 83 +++++-------------- .../protocol/AdventureSettingsPacket.java | 1 + .../network/protocol/CraftingEventPacket.java | 1 + .../network/protocol/DimensionDataPacket.java | 2 +- .../network/protocol/EntityFallPacket.java | 1 + .../protocol/ItemFrameDropItemPacket.java | 1 + .../protocol/MoveEntityDeltaPacket.java | 23 +---- .../network/protocol/NPCRequestPacket.java | 1 + .../protocol/ResourcePackDataInfoPacket.java | 8 +- .../protocol/ScriptCustomEventPacket.java | 1 + .../SetLocalPlayerAsInitializedPacket.java | 1 + 12 files changed, 29 insertions(+), 123 deletions(-) diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 7adf020e2c8..115a48da968 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -3794,20 +3794,6 @@ public void onCompletion(Server server) { Command.broadcastCommandMessage(this, new TranslationContainer("commands.gamemode.success.self", Server.getGamemodeString(this.gamemode))); } return; - case ProtocolInfo.ITEM_FRAME_DROP_ITEM_PACKET: - if (!this.spawned) { - return; - } - - ItemFrameDropItemPacket itemFrameDropItemPacket = (ItemFrameDropItemPacket) packet; - Vector3 frame = this.temporalVector.setComponents(itemFrameDropItemPacket.x, itemFrameDropItemPacket.y, itemFrameDropItemPacket.z); - if (frame.distanceSquared(this) < 1000) { - BlockEntity itemFrame = this.level.getBlockEntityIfLoaded(this.chunk, frame); - if (itemFrame instanceof BlockEntityItemFrame) { - ((BlockEntityItemFrame) itemFrame).dropItem(this); - } - } - return; case ProtocolInfo.MAP_INFO_REQUEST_PACKET: if (this.inventory == null) { return; @@ -4622,21 +4608,6 @@ public void onCompletion(Server server) { } } return; - case ProtocolInfo.FILTER_TEXT_PACKET: - if (!this.spawned) { - return; - } - - FilterTextPacket filterTextPacket = (FilterTextPacket) packet; - if (filterTextPacket.text == null || filterTextPacket.text.length() > 64) { - this.getServer().getLogger().debug(username + ": FilterTextPacket with too long text"); - return; - } - FilterTextPacket textResponsePacket = new FilterTextPacket(); - textResponsePacket.text = filterTextPacket.text; - textResponsePacket.fromServer = true; - this.dataPacket(textResponsePacket); - return; case ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET: PacketViolationWarningPacket PVWpk = (PacketViolationWarningPacket) packet; if (pkIDs == null) { diff --git a/src/main/java/cn/nukkit/network/Network.java b/src/main/java/cn/nukkit/network/Network.java index 794acc758e8..1f270b8f17a 100644 --- a/src/main/java/cn/nukkit/network/Network.java +++ b/src/main/java/cn/nukkit/network/Network.java @@ -218,105 +218,60 @@ public void unblockAddress(InetAddress address) { private void registerPackets() { this.packetPool = new Class[512]; - this.registerPacket(ProtocolInfo.ADD_ENTITY_PACKET, AddEntityPacket.class); - this.registerPacket(ProtocolInfo.ADD_ITEM_ENTITY_PACKET, AddItemEntityPacket.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(ProtocolInfo.BATCH_PACKET, BatchPacket.class); + + this.registerPacket(ProtocolInfo.ANIMATE_PACKET, AnimatePacket.class); this.registerPacket(ProtocolInfo.BLOCK_ENTITY_DATA_PACKET, BlockEntityDataPacket.class); - this.registerPacket(ProtocolInfo.BLOCK_EVENT_PACKET, BlockEventPacket.class); this.registerPacket(ProtocolInfo.BLOCK_PICK_REQUEST_PACKET, BlockPickRequestPacket.class); this.registerPacket(ProtocolInfo.BOOK_EDIT_PACKET, BookEditPacket.class); - this.registerPacket(ProtocolInfo.BOSS_EVENT_PACKET, BossEventPacket.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.COMMAND_REQUEST_PACKET, CommandRequestPacket.class); this.registerPacket(ProtocolInfo.CONTAINER_CLOSE_PACKET, ContainerClosePacket.class); - this.registerPacket(ProtocolInfo.CONTAINER_OPEN_PACKET, ContainerOpenPacket.class); - this.registerPacket(ProtocolInfo.CONTAINER_SET_DATA_PACKET, ContainerSetDataPacket.class); - this.registerPacket(ProtocolInfo.CRAFTING_DATA_PACKET, CraftingDataPacket.class); - this.registerPacket(ProtocolInfo.DISCONNECT_PACKET, DisconnectPacket.class); this.registerPacket(ProtocolInfo.ENTITY_EVENT_PACKET, EntityEventPacket.class); - this.registerPacket(ProtocolInfo.FULL_CHUNK_DATA_PACKET, LevelChunkPacket.class); - this.registerPacket(ProtocolInfo.GAME_RULES_CHANGED_PACKET, GameRulesChangedPacket.class); this.registerPacket(ProtocolInfo.INTERACT_PACKET, InteractPacket.class); - this.registerPacket(ProtocolInfo.INVENTORY_CONTENT_PACKET, InventoryContentPacket.class); - this.registerPacket(ProtocolInfo.INVENTORY_SLOT_PACKET, InventorySlotPacket.class); this.registerPacket(ProtocolInfo.INVENTORY_TRANSACTION_PACKET, InventoryTransactionPacket.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_V1, LevelSoundEventPacketV1.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.MODAL_FORM_REQUEST_PACKET, ModalFormRequestPacket.class); this.registerPacket(ProtocolInfo.MODAL_FORM_RESPONSE_PACKET, ModalFormResponsePacket.class); - this.registerPacket(ProtocolInfo.MOVE_ENTITY_ABSOLUTE_PACKET, MoveEntityAbsolutePacket.class); - this.registerPacket(ProtocolInfo.MOVE_PLAYER_PACKET, MovePlayerPacket.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.PLAYER_HOTBAR_PACKET, PlayerHotbarPacket.class); - this.registerPacket(ProtocolInfo.PLAY_SOUND_PACKET, PlaySoundPacket.class); - this.registerPacket(ProtocolInfo.PLAY_STATUS_PACKET, PlayStatusPacket.class); - this.registerPacket(ProtocolInfo.REMOVE_ENTITY_PACKET, RemoveEntityPacket.class); this.registerPacket(ProtocolInfo.REQUEST_CHUNK_RADIUS_PACKET, RequestChunkRadiusPacket.class); - this.registerPacket(ProtocolInfo.RESOURCE_PACKS_INFO_PACKET, ResourcePacksInfoPacket.class); - this.registerPacket(ProtocolInfo.RESOURCE_PACK_STACK_PACKET, ResourcePackStackPacket.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_PACK_CHUNK_DATA_PACKET, ResourcePackChunkDataPacket.class); this.registerPacket(ProtocolInfo.RESOURCE_PACK_CHUNK_REQUEST_PACKET, ResourcePackChunkRequestPacket.class); this.registerPacket(ProtocolInfo.PLAYER_SKIN_PACKET, PlayerSkinPacket.class); this.registerPacket(ProtocolInfo.RESPAWN_PACKET, RespawnPacket.class); - this.registerPacket(ProtocolInfo.RIDER_JUMP_PACKET, RiderJumpPacket.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_PLAYER_GAME_TYPE_PACKET, SetPlayerGameTypePacket.class); - this.registerPacket(ProtocolInfo.SET_SPAWN_POSITION_PACKET, SetSpawnPositionPacket.class); - this.registerPacket(ProtocolInfo.SET_TITLE_PACKET, SetTitlePacket.class); - this.registerPacket(ProtocolInfo.SET_TIME_PACKET, SetTimePacket.class); this.registerPacket(ProtocolInfo.SERVER_SETTINGS_REQUEST_PACKET, ServerSettingsRequestPacket.class); - this.registerPacket(ProtocolInfo.SERVER_SETTINGS_RESPONSE_PACKET, ServerSettingsResponsePacket.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_ATTRIBUTES_PACKET, UpdateAttributesPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_BLOCK_PACKET, UpdateBlockPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_TRADE_PACKET, UpdateTradePacket.class); this.registerPacket(ProtocolInfo.SET_LOCAL_PLAYER_AS_INITIALIZED_PACKET, SetLocalPlayerAsInitializedPacket.class); - this.registerPacket(ProtocolInfo.NETWORK_CHUNK_PUBLISHER_UPDATE_PACKET, NetworkChunkPublisherUpdatePacket.class); - this.registerPacket(ProtocolInfo.AVAILABLE_ENTITY_IDENTIFIERS_PACKET, AvailableEntityIdentifiersPacket.class); - //this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET_V2, LevelSoundEventPacket.class); - this.registerPacket(ProtocolInfo.SPAWN_PARTICLE_EFFECT_PACKET, SpawnParticleEffectPacket.class); - this.registerPacket(ProtocolInfo.BIOME_DEFINITION_LIST_PACKET, BiomeDefinitionListPacket.class); - //this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET, LevelSoundEventPacket.class); this.registerPacket(ProtocolInfo.LECTERN_UPDATE_PACKET, LecternUpdatePacket.class); this.registerPacket(ProtocolInfo.NETWORK_SETTINGS_PACKET, NetworkSettingsPacket.class); this.registerPacket(ProtocolInfo.PLAYER_AUTH_INPUT_PACKET, PlayerAuthInputPacket.class); - this.registerPacket(ProtocolInfo.CREATIVE_CONTENT_PACKET, CreativeContentPacket.class); this.registerPacket(ProtocolInfo.PACKET_VIOLATION_WARNING_PACKET, PacketViolationWarningPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_ABILITIES_PACKET, UpdateAbilitiesPacket.class); - this.registerPacket(ProtocolInfo.REQUEST_ABILITY_PACKET, RequestAbilityPacket.class); - this.registerPacket(ProtocolInfo.UPDATE_ADVENTURE_SETTINGS_PACKET, UpdateAdventureSettingsPacket.class); this.registerPacket(ProtocolInfo.EMOTE_PACKET, EmotePacket.class); - this.registerPacket(ProtocolInfo.FILTER_TEXT_PACKET, FilterTextPacket.class); - this.registerPacket(ProtocolInfo.TOAST_REQUEST_PACKET, ToastRequestPacket.class); - this.registerPacket(ProtocolInfo.DEATH_INFO_PACKET, DeathInfoPacket.class); this.registerPacket(ProtocolInfo.REQUEST_NETWORK_SETTINGS_PACKET, RequestNetworkSettingsPacket.class); - this.registerPacket(ProtocolInfo.SERVER_TO_CLIENT_HANDSHAKE_PACKET, ServerToClientHandshakePacket.class); this.registerPacket(ProtocolInfo.CLIENT_TO_SERVER_HANDSHAKE_PACKET, ClientToServerHandshakePacket.class); this.registerPacket(ProtocolInfo.REQUEST_PERMISSIONS_PACKET, RequestPermissionsPacket.class); this.registerPacket(ProtocolInfo.SET_DEFAULT_GAME_TYPE_PACKET, SetDefaultGameTypePacket.class); this.registerPacket(ProtocolInfo.SETTINGS_COMMAND_PACKET, SettingsCommandPacket.class); + + // Unused but sent by the client + this.registerPacket(ProtocolInfo.SET_ENTITY_LINK_PACKET, SetEntityLinkPacket.class); + this.registerPacket(ProtocolInfo.SET_ENTITY_MOTION_PACKET, SetEntityMotionPacket.class); + this.registerPacket(ProtocolInfo.LEVEL_SOUND_EVENT_PACKET, LevelSoundEventPacket.class); + this.registerPacket(ProtocolInfo.RIDER_JUMP_PACKET, RiderJumpPacket.class); + this.registerPacket(ProtocolInfo.REQUEST_ABILITY_PACKET, RequestAbilityPacket.class); + this.registerPacket(ProtocolInfo.NETWORK_STACK_LATENCY_PACKET, NetworkStackLatencyPacket.class); + this.registerPacket(ProtocolInfo.NPC_REQUEST_PACKET, NPCRequestPacket.class); + this.registerPacket(ProtocolInfo.MOVE_ENTITY_ABSOLUTE_PACKET, MoveEntityAbsolutePacket.class); + this.registerPacket(ProtocolInfo.MOB_ARMOR_EQUIPMENT_PACKET, MobArmorEquipmentPacket.class); + this.registerPacket(ProtocolInfo.MAP_CREATE_LOCKED_COPY_PACKET, MapCreateLockedCopyPacket.class); + this.registerPacket(ProtocolInfo.GUI_DATA_PICK_ITEM_PACKET, GUIDataPickItemPacket.class); + this.registerPacket(ProtocolInfo.EMOTE_LIST_PACKET, EmoteListPacket.class); + this.registerPacket(ProtocolInfo.DISCONNECT_PACKET, DisconnectPacket.class); + this.registerPacket(ProtocolInfo.BOSS_EVENT_PACKET, BossEventPacket.class); + this.registerPacket(ProtocolInfo.ANVIL_DAMAGE_PACKET, AnvilDamagePacket.class); } } diff --git a/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java b/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java index 5ec76e5c5eb..98378cef3c5 100644 --- a/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/AdventureSettingsPacket.java @@ -6,6 +6,7 @@ /** * @author Nukkit Project Team */ +@Deprecated @ToString public class AdventureSettingsPacket extends DataPacket { diff --git a/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java b/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java index 8dfa5457f3f..34e33085e53 100644 --- a/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/CraftingEventPacket.java @@ -9,6 +9,7 @@ /** * @author Nukkit Project Team */ +@Deprecated @ToString public class CraftingEventPacket extends DataPacket { diff --git a/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java index 9975fb2621c..aca511f7baf 100644 --- a/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java @@ -11,7 +11,7 @@ public class DimensionDataPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.DIMENSION_DATA_PACKET; - private static final List<DimensionDefinition> DEFAULT_DEFINITIONS = new ObjectArrayList() { + private static final List<DimensionDefinition> DEFAULT_DEFINITIONS = new ObjectArrayList<DimensionDefinition>() { { add(new DimensionDefinition("minecraft:overworld", 319, -64, 1)); } diff --git a/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java b/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java index 0c415501069..104d4b5875b 100644 --- a/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/EntityFallPacket.java @@ -2,6 +2,7 @@ import lombok.ToString; +@Deprecated @ToString public class EntityFallPacket extends DataPacket { diff --git a/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java b/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java index 1a14bee948f..d1619c75a20 100644 --- a/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ItemFrameDropItemPacket.java @@ -6,6 +6,7 @@ /** * Created by Pub4Game on 03.07.2016. */ +@Deprecated @ToString public class ItemFrameDropItemPacket extends DataPacket { diff --git a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java index 0370b18c558..373ccf6dc38 100644 --- a/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MoveEntityDeltaPacket.java @@ -30,14 +30,7 @@ public byte pid() { @Override public void decode() { - this.getEntityRuntimeId(); - this.flags = this.getByte(); - this.x = getCoordinate(FLAG_HAS_X); - this.y = getCoordinate(FLAG_HAS_Y); - this.z = getCoordinate(FLAG_HAS_Z); - this.yawDelta = getRotation(FLAG_HAS_YAW); - this.headYawDelta = getRotation(FLAG_HAS_HEAD_YAW); - this.pitchDelta = getRotation(FLAG_HAS_PITCH); + this.decodeUnsupported(); } @Override @@ -53,20 +46,6 @@ public void encode() { putRotation(FLAG_HAS_PITCH, this.pitchDelta); } - private float getCoordinate(int flag) { - if ((flags & flag) != 0) { - return this.getLFloat(); - } - return 0; - } - - private double getRotation(int flag) { - if ((flags & flag) != 0) { - return this.getByte() * 1.40625; - } - return 0d; - } - private void putCoordinate(int flag, float value) { if ((flags & flag) != 0) { this.putLFloat(value); diff --git a/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java b/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java index d4914fb5717..79123e147d5 100644 --- a/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/NPCRequestPacket.java @@ -39,6 +39,7 @@ public void decode() { @Override public void encode() { + this.reset(); this.putEntityRuntimeId(this.entityRuntimeId); this.putByte((byte) requestType.ordinal()); this.putString(this.commandString); diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackDataInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackDataInfoPacket.java index d1960c9ec2c..551c5a990bb 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackDataInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackDataInfoPacket.java @@ -30,13 +30,7 @@ public class ResourcePackDataInfoPacket extends DataPacket { @Override public void decode() { - this.packId = UUID.fromString(this.getString()); - this.maxChunkSize = this.getLInt(); - this.chunkCount = this.getLInt(); - this.compressedPackSize = this.getLLong(); - this.sha256 = this.getByteArray(); - this.premium = this.getBoolean(); - this.type = this.getByte(); + this.decodeUnsupported(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java b/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java index 59875d19d94..1dc114d97eb 100644 --- a/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ScriptCustomEventPacket.java @@ -2,6 +2,7 @@ import lombok.ToString; +@Deprecated @ToString public class ScriptCustomEventPacket extends DataPacket { diff --git a/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java b/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java index 6e8ce62897b..d5e8a6d7d0c 100644 --- a/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/SetLocalPlayerAsInitializedPacket.java @@ -21,6 +21,7 @@ public void decode() { @Override public void encode() { + this.reset(); this.putUnsignedVarLong(eid); } } From 630692762b11d1634490c3c2b514011d28a11167 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Fri, 28 Jun 2024 15:38:05 +0300 Subject: [PATCH 13/21] Update leveldb palette to 1.20.70 --- gradle/libs.versions.toml | 2 +- .../customblock/CustomBlockManager.java | 2 +- .../customblock/util/BlockPropertyDumper.java | 79 ------------------ .../format/leveldb/BlockStateMapping.java | 16 ++-- .../format/leveldb/LevelDBConstants.java | 6 +- .../format/leveldb/NukkitLegacyMapper.java | 7 +- src/main/resources/block_palette_662.nbt | Bin 0 -> 176502 bytes src/main/resources/leveldb_palette.nbt | Bin 105946 -> 0 bytes .../resources/runtime_block_states_594.dat | Bin 52862 -> 0 bytes .../resources/runtime_block_states_662.dat | Bin 0 -> 52833 bytes 10 files changed, 20 insertions(+), 92 deletions(-) delete mode 100644 src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java create mode 100644 src/main/resources/block_palette_662.nbt delete mode 100644 src/main/resources/leveldb_palette.nbt delete mode 100644 src/main/resources/runtime_block_states_594.dat create mode 100644 src/main/resources/runtime_block_states_662.dat diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5d63cc869ef..3d1f2581d62 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ leveldbjni = { group = "net.daporkchop", name = "leveldb-mcpe-jni", version = "0 snappy = { group = "org.xerial.snappy", name = "snappy-java", version = "1.1.10.5" } jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version = "9.13" } jopt-simple = { group = "net.sf.jopt-simple", name = "jopt-simple", version = "5.0.4" } -blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.10-SNAPSHOT" } +blockstateupdater = { group = "org.cloudburstmc", name = "block-state-updater", version = "1.20.70-SNAPSHOT" } lmbda = { group = "org.lanternpowered", name = "lmbda", version = "2.0.0" } noise = { group = "net.daporkchop.lib", name = "noise", version = "0.5.6-SNAPSHOT" } lombok = { group = "org.projectlombok", name = "lombok", version = "1.18.26" } diff --git a/src/main/java/cn/nukkit/customblock/CustomBlockManager.java b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java index 65c21be5a9b..1297eb90666 100644 --- a/src/main/java/cn/nukkit/customblock/CustomBlockManager.java +++ b/src/main/java/cn/nukkit/customblock/CustomBlockManager.java @@ -240,7 +240,7 @@ private List<NbtMap> loadVanillaPalette(int version) throws FileNotFoundExceptio try (InputStream stream = Files.newInputStream(path)) { return ((NbtMap) NbtUtils.createGZIPReader(stream).readTag()).getList("blocks", NbtType.COMPOUND); } catch (Exception e) { - throw new AssertionError("Error loading block palette leveldb_palette.nbt", e); + throw new AssertionError("Error while loading vanilla palette", e); } } diff --git a/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java b/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java deleted file mode 100644 index f0798aa9875..00000000000 --- a/src/main/java/cn/nukkit/customblock/util/BlockPropertyDumper.java +++ /dev/null @@ -1,79 +0,0 @@ -package cn.nukkit.customblock.util; - -import cn.nukkit.block.*; -import cn.nukkit.customblock.properties.*; -import cn.nukkit.customblock.properties.exception.InvalidBlockPropertyMetaException; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import lombok.extern.log4j.Log4j2; - -@Log4j2 -public class BlockPropertyDumper { - - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - private static final BlockProperties PROPERTIES = null; // new BlockProperties(BlockDripleafBig.TILT_PROPERTY, BlockDripleafBig.HEAD_PROPERTY, VanillaProperties.DIRECTION); - - public static void main(String[] args) { - JsonArray blockStates = new JsonArray(); - long maxStates = getBlockStatesCount(PROPERTIES); - - - for (int meta = 0; meta < (1 << Block.DATA_BITS); meta++) { - if (meta >= maxStates) { - log.info("Max states reached: {}", maxStates); - break; - } - - try { - blockStates.add(createJsonBlockState(PROPERTIES, meta)); - } catch (InvalidBlockPropertyMetaException e) { - break; // Nukkit has more states than our block - } - } - - JsonObject blockDefinition = new JsonObject(); - blockDefinition.add("minecraft:block", blockStates); - - - System.out.println(GSON.toJson(blockDefinition)); - log.info("Created {} block states", blockStates.size()); - } - - private static JsonObject createJsonBlockState(BlockProperties properties, int meta) { - JsonObject json = new JsonObject(); - if (properties != null) { - for (String propertyName : properties.getNames()) { - BlockProperty<?> property = properties.getBlockProperty(propertyName); - if (property instanceof EnumBlockProperty) { - json.add(property.getPersistenceName(), GSON.toJsonTree(properties.getPersistenceValue(meta, propertyName))); - } else if (property instanceof BooleanBlockProperty) { - json.addProperty(property.getPersistenceName(), properties.getBooleanValue(meta, propertyName)); - } else { - json.add(property.getPersistenceName(), GSON.toJsonTree(properties.getValue(meta, propertyName))); - } - } - } - return json; - } - - private static long getBlockStatesCount(BlockProperties properties) { - long maxStates = 1; - for (String propertyName : properties.getNames()) { - BlockProperty<?> property = properties.getBlockProperty(propertyName); - if (property instanceof BooleanBlockProperty) { - maxStates *= 2; - } else if (property instanceof IntBlockProperty) { - int values = ((IntBlockProperty) property).getMaxValue() + 1 - ((IntBlockProperty) property).getMinValue(); - maxStates *= values; - } else if (property instanceof UnsignedIntBlockProperty) { - long values = ((UnsignedIntBlockProperty) property).getMaxValue() + 1 - ((UnsignedIntBlockProperty) property).getMinValue(); - maxStates *= values; - } else if (property instanceof EnumBlockProperty) { - maxStates *= ((EnumBlockProperty<?>) property).getValues().length; - } - } - return maxStates; - } -} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java index 4e1e81777b7..18adeafcca1 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/BlockStateMapping.java @@ -1,5 +1,6 @@ package cn.nukkit.level.format.leveldb; +import cn.nukkit.block.BlockID; import cn.nukkit.level.format.leveldb.structure.BlockStateSnapshot; import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterChunker; import cn.nukkit.level.format.leveldb.updater.BlockStateUpdaterVanilla; @@ -28,7 +29,6 @@ public class BlockStateMapping { private static final Logger log = LogManager.getLogger("LevelDB-Logger"); private static final Logger serverLog = LogManager.getLogger(MainLogger.class); - private static final int UPDATE_BLOCK = 284; private static final CompoundTagUpdaterContext CONTEXT; private static final int LATEST_UPDATER_VERSION; @@ -36,7 +36,7 @@ public class BlockStateMapping { private static final Cache<NbtMap, NbtMap> BLOCK_UPDATE_CACHE = CacheBuilder.newBuilder() .maximumSize(1024) - .expireAfterAccess(60, TimeUnit.SECONDS) + .expireAfterAccess(2, TimeUnit.MINUTES) .build(); static { @@ -62,6 +62,11 @@ public class BlockStateMapping { updaters.add(BlockStateUpdater_1_19_80.INSTANCE); updaters.add(BlockStateUpdater_1_20_0.INSTANCE); updaters.add(BlockStateUpdater_1_20_10.INSTANCE); + updaters.add(BlockStateUpdater_1_20_30.INSTANCE); + updaters.add(BlockStateUpdater_1_20_40.INSTANCE); + updaters.add(BlockStateUpdater_1_20_50.INSTANCE); + updaters.add(BlockStateUpdater_1_20_60.INSTANCE); + updaters.add(BlockStateUpdater_1_20_70.INSTANCE); updaters.add(BlockStateUpdaterVanilla.INSTANCE); boolean chunkerSupport = Boolean.parseBoolean(System.getProperty("leveldb-chunker")); @@ -195,7 +200,8 @@ public int getLegacyData(int runtimeId) { if (data == -1) { log.warn("Can not find legacyId! No runtime2legacy mapping for " + runtimeId); data = this.legacyMapper.runtimeToLegacyData(this.getDefaultRuntimeId()); - Preconditions.checkArgument(data != -1, "Can not find legacyData for default runtimeId: " + this.getDefaultRuntimeId()); } + Preconditions.checkArgument(data != -1, "Can not find legacyData for default runtimeId: " + this.getDefaultRuntimeId()); + } return data; } @@ -211,14 +217,14 @@ public void setDefaultBlock(int legacyId, int legacyData) { public int getDefaultRuntimeId() { if (this.defaultRuntimeId == -1) { - this.setDefaultBlock(UPDATE_BLOCK, 0); + this.setDefaultBlock(BlockID.INFO_UPDATE, 0); } return this.defaultRuntimeId; } public BlockStateSnapshot getDefaultState() { if (this.defaultState == null) { - this.setDefaultBlock(UPDATE_BLOCK, 0); + this.setDefaultBlock(BlockID.INFO_UPDATE, 0); } return this.defaultState; } diff --git a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java index 103a1f190fa..3b1956d7479 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/LevelDBConstants.java @@ -4,14 +4,14 @@ public class LevelDBConstants { // This is protocol version if block palette used in storage - public static final int PALETTE_VERSION = 594; //v1_20_10; + public static final int PALETTE_VERSION = 662; // 1.20.70 // By combining this versions we can get block state version // NOTE: This is not necessary bumped everytime with PALETTE_VERSION // Last time this was bumped in 1.18.10 public static final int STATE_MAYOR_VERSION = 1; public static final int STATE_MINOR_VERSION = 20; - public static final int STATE_PATCH_VERSION = 10; - public static final int STATE_VERSION = makeVersion(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION) + 32; // 32 updaters were added in 1.20.10 + public static final int STATE_PATCH_VERSION = 70; + public static final int STATE_VERSION = makeVersion(STATE_MAYOR_VERSION, STATE_MINOR_VERSION, STATE_PATCH_VERSION) + 5; // 5 updaters were added in 1.20.70 // Chunk version that will be currently used as default public static final int LATEST_CHUNK_VERSION = 40; // SubChunk version used for serializing chunks into storage diff --git a/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java index 81b679b456e..6c52040d8f1 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/NukkitLegacyMapper.java @@ -16,10 +16,11 @@ public static void registerStates(BlockStateMapping mapping) { List<NbtMap> states = loadBlockPalette(); for (int i = 0; i < states.size(); i++) { NbtMap state = states.get(i); - if (state.containsKey("name_hash") || state.containsKey("network_id")) { + if (state.containsKey("name_hash") || state.containsKey("network_id") || state.containsKey("block_id")) { NbtMapBuilder builder = NbtMapBuilder.from(state); builder.remove("name_hash"); builder.remove("network_id"); + builder.remove("block_id"); state = builder.build(); } state.hashCode(); // cache hashCode @@ -28,10 +29,10 @@ public static void registerStates(BlockStateMapping mapping) { } public static List<NbtMap> loadBlockPalette() { - try (InputStream stream = NukkitLegacyMapper.class.getClassLoader().getResourceAsStream("leveldb_palette.nbt")) { + try (InputStream stream = NukkitLegacyMapper.class.getClassLoader().getResourceAsStream("block_palette_" + LevelDBConstants.PALETTE_VERSION + ".nbt")) { return ((NbtMap) NbtUtils.createGZIPReader(stream).readTag()).getList("blocks", NbtType.COMPOUND); } catch (Exception e) { - throw new AssertionError("Error loading block palette leveldb_palette.nbt", e); + throw new AssertionError("Error while loading leveldb block palette", e); } } diff --git a/src/main/resources/block_palette_662.nbt b/src/main/resources/block_palette_662.nbt new file mode 100644 index 0000000000000000000000000000000000000000..8f4957b95013dd748bac4ed50c3e370729664453 GIT binary patch literal 176502 zcmYg&Wmpzn*S3TpAxMLOl+q2-B@NQu-5t`6AYIbk-Ca_W(%oGmDc$g1Gv3d4eEwR; z-fOQoS8eB-fhg?N3-BM}a=MFv;xFtz3_-;=Z>ancNfEIR+pbqV*x}HL(Z46bQc;Js zT&;SXv>BXet4`NLc1|i7f@8F#Y)rl<JH}o=xgNJfpL55aWc*nh8Qn78;`}y*iN>^l z$fMJH2c2;~=7S;G%agHQ!ZM~q9~%Gn!64s3B0v(exEMW#@;lN;^ms|k>~S;Us~=W> z_({UCi*wx`8!}vXwH{l16Se6ldT5mX5GnQ=AFdzrh}Dbc#T2<gXDAYdNYW_}-0HkO zbQ`uy{j%ZD+$|EOWRR^u9ffAW|LMNbKtE0@Ple^@6~i^1(~oBu(fQ|4x+GuhS({?| zG8dD1JM}bST$1l$#^|C&P#8Ni6nQ?rIz<{Tk8@8b`4MramKGUTKAsr@<THQw`cMf@ z^$Ry{Jfm-PgGM~9Iob)uAMVd$KO^_!`1btv2}7^-e)eoR^|;X4=(&4TjO=iHA|pGp zg-7quyN43Jpb!YL)z!dRV`phs%X>5nq!h)ilncg#t!1BcyszBDg8NkOSPJ(@ZnxV^ zna`Xo{sHrY_zaIX>CxU=rcZ?tt2^}3o|nrj*|!lHJ#W)Hq;YsNSJ@eqMT84+Jet+0 zqtHw8<sH1>+Fhy&&L@|Q@sJ~qzXklE>g1Np4~`A<d8&}3RhA%SUA*}-Musf&I0zLe zXE*O8fn0c{miYpcfEzb$|9Zt!-VT<LgHVjkEWqCo_3PA?bHpMg2MQxcIkifWzyH>& zuTxfELTV^EWEeSOjLCW_IYt>dHo85&h;{vf`#QB{5`;p@v7m3)l3+W5{<8lioS37N zI3i*s4YJJ1<gXA)4o*EggP<$<?DeLub@9p}Lpi(U1^W!#Bv~3P=3*`l?#kCV#UjkF zVR~H}EOWoNqB6_fNiUJHI+jVi_r82xp<@!s7SgkJnrIQ2yd~W#0CQTONkAu$kc-J& zEthRD%y!d!KE{2P<47p_AQ@JY`AJi4y@Q3ZIs~UUZqcY0F)$R(;yD-fb0nQ@|EtT9 zb^{^a8Dm3a<6EA_3sf{wOA%VMZ<eObo*A`*R58zYrq>+#3aP4aGEK!2RlQ{y_(2`a zeS?k6v#9q!9B>WmYnf-g2{Vb<`lzdzXSarA85m_NJ?&=YQw~~(r)YRAU&koYonIi1 z-8Jp>RP6r2%5YL`|Fjl7qlyvh_E}wXtVkzb%`1PnWk+)Cv1gygc$V<v?(g?1x>U6$ z`0RU})pwG~^|whm)CQOF%$CpJmc-CYE92Bvh1brA0Vck;iUaW4m7X&F^qN|~wM6G= z8q2Pb+@%EpB9Vu|`V&BmxeThL6sq1-57kxRR3;ayy3Tqm5PGCS2o*8oZZ^QKk4lab z!e#9W$4Kaiyo5p!%A}l|XH6PVd~i`E$P5xG=S%LO1q`!zc#;5<Ho-Dm0N{c~M|a`^ z#0Bos^{U8vJ@Qhiw^-$9CIk%f17u3*5yC}EJz*cDE^?t{K8XmzGmh_AzfbnaK8#uU z661AoMJQaofhYpULpGcIS~uf8p`DS8#MK<N-Y#{=PL~V42fpPlI)t*+B6OM<zVy<8 z(*FCR?eS%?Ms75cNQv4J4t=b|l#epe&x)P6yu|OD=Xh^2J^2nQQQU<?NhhyE$H%@$ z3*ELv`HaIN-Wv*_?94p%TS{kDWs;BtoylCe*CJqEos(_L5nSJBv-$n>b`BtrbT%KS z!=OC;EegBlL>YKF<l*1YLW7=XVE+Azh%_hPB82vl2=mWO64b}?-p9fdK0;#w>*uoz za*c&;)LD_Sa+g)LZ1i6NF{I6Mv}jC`xeL~x#R*lLCSLE_Z;=z3>?KBp>&~%d3-;E^ zGTcM!(6>bi^Am{rT3?o+ONZCwXWH*ZAN2$f`xsh94sygR^cfGOa~&=8<E9IIKywUs zZtWXY7saQSCyk}r<jgB&Jn0oae+Q4TB>-(jb11l<a?LSr%6&J$kk}jr<JE#evVdj6 zi*hkhA-l-Bu-xC(W7pmsNpDK79Vhud(E_)S4L06HTV4Hf3K4F$nrw%UIC4b{uPN}% zvAwg&msBZqd$_9lK#2XC_fJ0$-@?VLTV2%;Ty3?=;i?#Pw*X<>4};&cG!C8Z7B?Eg z#hd9HrA(4FTg7*Ny2eOd^^;>O7o8l<&X^<%JHmY2l(c6^SYT$C>y5qdt(6LE_Oqy+ zJ5It&VP+z%eq-Ts*obOk7D_)|-PRZY5@E&tKZnOLwTrkkuM0^mLQ%;tBF?#%NGuRh z_Vs?jV4!_`34bZ_?&E8DMLRivmSdO^2y=D^-u~h;vh+mY+vWM(7`&7mB<1F|XKfZ% z<4u)~lT>SWq!~P<Ho<x=w6Uy~X*-?ymDjp>O7xoCuw?X_;cRwgH`gCp{&GZoYyM)W zt2YyU`rP;0WOb!VaruTYa_oHg=3}<P2l>(|R58m;631DRkgCf)Os5H2HjJs)=vI>` zKD;bpBR$sa=vLpAPp?M|SnMw&U{f7>=NU|@lbhhh{OJ<dFUr-e$mx4y^gg$odc$+$ zXV(5^b(_C_G^vK-DTEo~B47!7z0OJK$=h#UJO3g>Q9bb7al`<|RR*iEf`KV4;*qmH z)k2S7m^OxThS@qxcL_!PWPd)$%f|iSA%6t96H3<5l9axq2zeRv-svNy)`y6JDk6C% zPqD+_T-SE^)rY#G;_*x3embd5{ZuoHkph$|Jwr}`G&-cpAy^!#A$jYY4Kl-#sHO%N zVu<uio+dJ$92oeKdFywr^urO5Wu)(z%fnbFLTc+bX%;qbPuz(42%>kfAM7ZDP~r2M zn~&OY`hyJT(ugJD6WKB74b^puA|qpa^Mt6;ajovfbT>~N@qXzcnKO67l-DfY9+2+N z>B{1W^yJyv6&7MZ%KBA(J#QM?<v|S|(m{CAAi;VamA#6s<EOD&VSF93h&R9LI%vYS zIls}=@ygTU&Q*wZQOGsHz772Tc%W{yeh?#J_@Qs5k|3=?>zH;gEeMMB*1F#!c!{w0 z3mPvASxKGWCKe(dS>)LFdYk*FNWD?g_deF5Y44+$)>)@$WZvalZ}A7=qkJ@>c1)AO z8+gTL?Amd{S}>3hJ3`4lpV0@q;80V&W6WSK6-7spYNvSa*S9_IP}7@ldOwl$R#T?o zaDVfGmAo$@w%GP!@w1_Z6=!md)(zX+Sc9QoMr18kSxg@q(rE8gQsY_4dlF(Ls9Dv8 z4Y?p(W=iP`nB?6*NZ<yhC6%=TdOA5Kvv<mI@)5^Y28c`54u?K(Dk=<BmuOYp3wSbH zaH>Sa9j=AQ3eokKD6%H<o+1?OsKs>C2V!KpYeZssZ+%hSjFlOxHmKxPK+m+%h@2@M zrlUFy*|j26F(aRqWm%()7ba!5Pa4$sI!>^q@SI|-jHa6>@Z~5rH(xJGQY(=&=A_=W zYaR$5>Gv{W{3&G}(@MEHebuO6&A%nAvB~gT;fRPS+*RhGo2XUh)a0FXs>_$gr%@t* zeY%(`_PLT!BLvIMhZPnW$Emer>6E5{0e<o!8G3HVC}k~KV?NliC}XmZAr!rJ9s!C7 z%owixgmPJ{XoF@UlJQAQn8AvW`=WY+wSA#G=K~xqzka`#(#<5BBaT$3<DASOiwgOq zcE2!ltZ^LiV7rKRM^$c8Gzw>&5M7U_Gv<|p#G9Qxiewh<&=7P96l3eu8k`0B`Th=A zc{a~d_pgM#Jy@6yl&Oi+ZDWx?Z8Rm{7<-f!jLpl>(|tzB(XwVP#^pwA)u#Fshks%c zuP>(GNc|z$!E`id@R0J5Egx3?9jk_N7-8T_4n%v%%eOAhn9d#w{*;BOm*)UCIfVPF zd+in?&{_f_Fpa&07$WdPapzK}$qYlBSq#r$v`|SbX~}-nmhmGVF`eZC#r|swZIQPT zl%y$M>KP-&>0^#}SZh6$K}yhs)L%6Hl3%QqpsO)Ac0mop9+px2#f{{6Bq6+lQ2y+e zAN!>`S4+_6IXazCOrTLz;yVN172~{8A>ng{HY9XyLN_QC{ELuA$Ho_^hvhG;H(K8< z2HCxV&2St3c{DIz^2QVT>Fe)la<#y947H=_Tk<YyBlrw~iLjO5Xl0P2QmJ!A99`6~ znVpjxn=7!T5PljM#S?U3&uqE!-Fx65uA})Uet%{07a6`Tj~rSE)yfbJp$Tdn{(#af zA-w!xQ2WW<LK7jLCS;9x_h)`SDD4`RVL+@3aiP0XvZ<d4v;)^$Z&Jfw`4ykvWe~X% zDX3wsBQXvXmR{E*0Pa<@?E8?Y1rl^(+_Y3tRs?{)(4{`lg#&!+GO7zw03Xyzzos2G za|6T$UopMNdr&FEiK`XFa)K_5`OAtX@IWVhv#=p3I2efm=MCUr-b{vfC@5@s*}fS8 zfbAHEr!7(s%K=(8%RtHWKTp5a9d-nO4xR_jqJRplW&1XrjjRA4?n@;vKL9=iJENq@ zRv~hJrHE!V%%A{3^;KZnr2+_n*vH<Ie}Mm}<%ekSm+gGawj=^Qc>a+64B1<Ncsk}r z{GX@IF6lgyRft^_<5q4N&7jhEDVqPm+iCf|kqDH#A;XaVN+tVn9Rc<(-CGzu;I->_ z1s)i4Y?6jRxQ=1A;+BI1^6&L!yMdnOjA2uWS0Oh0QdDhKHvE@JN^WGXLV%3FJIA$y zpp4X2xAlLXLUjc8K7-LRb9nIZ@>d?f;_)MR>YW3@RN_!*Cj))f9h#B^fsV6`*T4XP zR{Cyk0y5p;pf*F~@4S#*r+UWt&(q^5gO8x9qp?v%ke}8SCu}JO%rcG9vYIe3M!$vz zRUwwM|AlSwq~;9))`(p<iYCFMqjS496o98{ZCY56AV6Sy7PFuM%`n`U!+_L;%U@Ve z+4!#bH$Z-NQhc;Q2CVIzG-Rj%mHyzG!fzxq1nMcknSU+;j8U%R0Xl4HbjHUJgabhZ z1gX*APZ^ONZ{(omH7Pc)V1l0n1rQFCkF>voi`V=S4P<~7t{C;dnFxJIvS5E4eEzz3 zg9P<qoH~oMBH+zqpfCF0i?VA}a5sR+VPlb*_zto!cVYN>ITCq7_Z6jIsbhvGF(4=U zx2`|;TjNqe=XC^e7mxLKyrLI4(2`l_4H8ZF|FrYS?T(594ilGJu}1(#>vacJAA_iv zdn|6f-GSf;<qD1Ce%K%XCa2?#65i#p-9IPVrCn~pxI84yA~uo<0kVk?m9uTWJV4A; zb2#tC;{rW=lA{IlSs>Q3u@~HQtuJ{Z1~==HYd-jbdJTq_%)w2L<g?`MViEYiu+GQf z|Fp_M8xEFdO7;Wp1*TXx-S>dl)o~MkU5X(A0QhU0Pyb$)?k7dnvE{WHDB82t!4e4s z3Pno#XPa#J9s_J#r@uB*#?&c}|2}*R&rK=_eDk;Ih^#HhrogAfPr82qy#G=eBm9Rq zhf?;mB`C0`BTf2jn$!~-L*abO*$85S+z@rpU1AP+HilRo9%Qq^A8+6W^-7|)h90y< zf_ncNvwxd}=uL_7a%A{dnxxiae2^1z_dU54n|Bd4%Gp-y2!{w<fixt-K=irj-zJ5D z7II#RHRgg^hAJ@ZxtYQ3(Ms&}JYccYP<bXDgl@0vzLgeK4Xwai191})?fE6IMu<<J z((#p7mOq0K4&W*0fN53rW$EKfkpE6PHS9mN=nU4yG9Vw}8ciDrOy?2}{QuNe97<7( z0ozcC9uX4l2tXa1{Lsw&hui6_fGUW>ml8%Zcu=%O2hpK8<bZ`wmNn%sb|^@7an4~a zUt`oNXA`U=lnp8psR{yMehw=<JIMw-@*S)F9S91vySMQEFR`Jbq=l3rZf3nR_)W3} z+Rymd*Om%;-qd(L2?Hu-<rz=dPW6SP|H&GLC`9l6_VHJ<<!c2}AWznpU6-LiJ}vhP zoc}^>#$Wo^7+e%yE9de9A^*^b)pIleR-7v)(Y6!_h;*PB{!z#+_ASz>9{Rx3%G{o@ z92o*wfq(VKerP8BqsPaO9yNwx!NWJUS~9s9Z~x;WPC6Ud`}{KEy)Tdy0*}kOL6G+0 z`4{$G{ZYVw_(vuly^#WHa-B<i9-KbJMQ7&!tL~8<9lSPJBT-1HW|{hx<U)EA_g~nv z^mcr(^}b;>d%^%47jJ*xTizQBfN@=8&N2cV>?l0T78m*cCQ6WLg{Wn7_gxPQ-Iv0S zgAo@DfdB%}vz8ev*y~sD*b~6=jvHb^`mek@buRiK=8qIX+MJY=nP-|FRt>!Dy6(N^ z0XP<vbRSf~IVH@aZt>-7cR0`^;~Ty%gUa81;j@~~=m!_qze*cIjzEeW<&NNgMb0bZ zhF^ccnhWurv0^M06Z9jfzVKhM2_WPhyP8k4&VvGm`cWfvd>L~QIsGZ@D*yfs1AEpZ z&LxZc;UvHza9=nS@y_!jAkEHMe3BcKM)J#9RF~QeKt=CQlmRqk4k7UPX-8adNWKEQ zClbRf9XJbIlZ^Pm8_IfE9*T|FHy~iXH<#$S46)3B%+AzZy=lzOaO!R33;~#@vKi2T zAXz>(cc`DBiQHpUgdi*#nQFGvGZ0n?8nLnFA^%K0uRM~jLNtbvUEiFSZ9*3W`scdZ z-;{!X-Pc&w0z|E4#w_Z;CO3ZXcJ>l<qp4^l_21yG+ZrkR3}j2y&vIvca8r{ZBN4nR zB{^8pM*+<>Ya$L>v@$D69x@dM9vg!u*aaZ8Rwsry-sOw`BCq6mM;Z?>uM?d7#03TE z%q(qyeNF%YGa{7f&RL5ExDwVgh{Z!L23lPhY95a3Od#u?dhRF~dCaw^$^WGYt2ZC> zzpIbHeUc~W7U7nOEW|bkUkW+r6+A5juzAas9emEU1%e3)6^Z7r+<#D>tMq~unDt(% zXQW6u8xOcJ!KWACeS8UFZSImaL*%2E0DOOg_JQ3Z3C#7*XZ@~!_m_+@Eg3L@!<BSa zA(s_M-|6@{YycU^+^*1Tvkj184h%G`#aSO7aF`eFZ?c1$M?{bymqJLBEvlc0n3@Cq z`r%X=(c0Ce5_nN2;fwrT<|f#FZ1|t`-V)r)-Y+28Ge}u!NBD1Ymt1o;{p;QEbMfqV z3ZI66p=d`tRRjk%MZwah_5U(1W&~U7-w2t)lwGz~0h{}A-AK^?MjPK4sg^AkxV+2N z(aGC3suFnEiP3bLr{oQGt&78%b{<fy!m(EG<vf^5#Iott7+?=&OtQ6w6n(-ir2pAn zkJO2P9n;RP$@jmGGwWWmKMo8Fy>;KyN?=&L{vi17e^obaT{jPjDBU@ZzkBoH)X6G% zJn4-b&%b-ifo;AQIEMsCy118H<G|5;qg*_m3~8j(rP4DQ?_KscFwE?{H2!tod9RbG z|1?7rg`hyDA|&yZu6v*V*`S^X54os9LL&!#f=%0VsqzWDwe<W+rUx~vY28Ct*t0<x zrib!G6Rf1cVWfM1-Skcq7$YaeQFQXLfNuPaf(F+AVIRF4x%Te{VVl2c_aOkB%B+Pa zyx=5xGwFzQOhXH{?D-fT?Ee~`-bVgtQ${Q3(zMj-?!UoD@!Y=wF(oTuPSLa&gg9E` zu!07Rz)!YNdsH7XVAfQf#rPnA0h%s)czyjATN7&#F2h$gGycD7*AuR<Y$|vGN^iPD zofiU!j3*%u<jxE(mT{|+2Lqn8s%wh-fbeuKoorA*gvwQ3dtKYGfjY4{RQtTz7yv~0 zPFvWT_#DC0Mw+Eb!l1~Z+SaXhwb*j-!Y;;3_+QC;Iu|VhNjklG+`W+qIwDur>>6 zSlBm;bfN*8;B~L>t_#7rd^S~V1S|#d!p$K5yL^N>&g@Qr$ur;lwyC23A67B8od3L8 zHq2oCH`6Wl_5Q2(zqA!8;SjxY&^in!yI}5jj-Pe^tGO&#_vRCdTNPo*gqpEhOaS>Z zE#f)=>@C#uMf?WAG-&EOh`4`(0?R@pb7AY>)y9NOFb7EBqnLV6*M9}5T*$HcU#a<< zMza6A>Gk?ImxA}BPP#uY|8@6zHm=+S;POI_PFeA}lbgj)I7F%-7^ja0lNrucLFlya z<4OusXKTo=@w-N&*S*grNbzEo<w}!k^@8-_IKPa18m4dl(0$h^?q`IyA^0eD!JQRz z2G>=c|Fg*qgWynFHrP-(KcIn}<B*tRc2(k!fxpm;uIfFVFJ2e~S8oW0`Y2PA*X{It zSm6&i{GVM}>oZFpX{s3|$rVZiC1bRHt|z}!`PI?CyFW}8e|1Tp!&~&j=k+vfNW-wO zxz9(PxPe#k79DvT8Td0vgm?Rr`EP_G#skj=kg@2|L@1M9d%8W~t&tqKhEsm})g{<N ziQ^>yz>-^3H&Xb>XkCu|tuzmf>FzY`r~`8?#qyMWH$~AU0+TL7n_v3}v^%0gK+sSf zQ!jdm0^O8%-xz~@zDZBI`^9NnQYZWH=hgVn`yS@4kjIA#)xyBz!zmAVYqKcV5Ty?) zdeZ_m0bfs4(gI}%bO?W;ldqr>ghEJm2pRR7@GFFr|Ao#~;jtJJ7(Ld8@-osza|=Uo z|F2kMsq|k25~mxd?uC|{fJ3hB`f)UOWAu`pMQ<r`$+)76<Qg}^RaV21o5*32@1II9 zytuSTF&E|V3=OS``^`dFsWl80EE5#8T7DE!Bff0Spv`acus5Z_M06fele90rDd?lb z{Nfbj?4<Mx@#Veim_q1Q{xgbjwjR-ux}<&kc9{buW~36;6CqDA2Vx+Ga;O)MK_qVW zdiPA25Y>Jx<nTdnb&l6quH1Mz4#nu}mT!We8g<fS-nrof`rEW|sJd=qU?R3$Zf+(g zSISfF&4!geVtWf_>s8`7(>P)Bxz~y(i{EFG2|HAWGjWb)IJ$*VU(UW{=CB##bW^vj zIR<`w)al!zno?mHd=b|7Y8Dr6z5B(F<qh=-MqfhY&ZI|W#J6y7I$zMvr<fv_!bwSC z(!w>BE<7YF!_+~|rXQS!1H@zkuWFS=XODq1Dh!h9Mh!;@Te~FH#9qHkG5(v@r>8^w z@SB;+s5&97O>@JnzasezBO)brK10FpOEXL(WoLGWH6>tNxRNz`ch7n9-%42ZQ6L(M z3}$vy@{N5yv5lWVjM)6Wrr4{TA6UucH`GYFjInl{z~*OZD$<1GU8idbt*R%|q$Ars zl%k;z*=>*PqLHHO(L}RZdUh}yE<yH`vHOa3nmAH|yu3cjZ#vgmHrV;vCL-Cr)Se^W z4}UqmKh`!`8Lz2prM`I8t(F>wy~(^b%>|v1B?)&_J-F37_S!VZ5U!=k&{L9+n9C$9 z?BKEUmS%zpMtd3Q%JLl=A<KC0%GKdd50DGv_-IAw%dh~>^=j2u!{A7~z0K9d46cm5 zBtQD&sOe5w;eO;V_8k=ris8VLjl<#%8f1yw&UViZhQ;1+s|U|iCBQ&Lt4WGfX-Iej z`7C$FL5HW20rjir>X5docP#h0CkwwUzV`qBaGLe42rcI9nIMB$jvfLl;s<WU*MzaF zrh6#*C76h>4bPX97uTQaI^4DNGxR^VuArPU=5}@#v?_d1?hjR3^cgdQulJL7nG1=% z)mDMa)bCmOF-LMPMV3v*XN1srJ%|<|Yqt5~z5LI5rjw;jj1-Qy5^;99*Vptn0puvs z=pTNZz5ns<55UYYyRSf)`9>kgw6LyN;4T<d{iotsN?Z>GtNpZ9qhEK)X%1|NT9J5k zXuiavPjK+mD>6*U501LymzhxCtL(!R@q(R2u4aU3dYmtv{2cewz0rS>)_IkCClTyt zZ>Do2R+<JBw`SXu^thi`WpyiBDk)fV8Dv9#@aBO0;7zG0{0H7?NTs$_)pKcXxc(xq z#`uDZ2Pa>N9HTfO2evq+Up62|ZSDM%Pc9~c9HueF)8#m&2>QLrm@A=({^PMuK31CK z(x1;#X)(?;8vNyjmTmXQ1R@hezs%g<C^0dHA7W8NO`>?zkgp!t2Od`6BQlfD(0-X* zlR{H_%eCO-+cRvtIywF$<*9J^v8558(vQF6*iPAf!@zS1IXZi$u-=Cuo<#t9^-Ec> zny$>==I$(phj1FLXi=n!#0REfi(R&djjy?5Cu@enG&pUW(K=mDnC6xiL?I2TgYQTt zzxVg{6{xB$Vtzc3IX<O$NtCqt=o=+bu=uklF8GCr!-VLNbbVk*+wM=UP%Za!@p#J9 zC`&~fQ<Pq7{zMme7~%~@HIE)imT`fxP^C?tv%i%D16cF%%{BG6{T4l)j!u3;wS4yc z*B8>T3<*~kQ&A(exLx?{a?+oWS9?2Ep2l_`9ck8bz4qrNQ=B31G*lquJCd%VG?ydA z^RvWBl|0&6oBY|rju6gA7crygB^90KJ9t`J*j&^TRmv1$=+pSZ8Sy^cCk$0lsxyhh zL;MZ-TH&)vsxOm0Yz(o~xM05sxt>&-@cUPaMa@f+Vf1A&0aOnYre8%GXI*)n7&DR? z-^PnmsZioNEo@nxC;3x#Cv831^W{hW2^>o>ez~Nc9XH@ZSNqK5RS^7<wOYd2P|fqW z%9AK+-wb&ulDn*LY={E48{IBwrp4&lh^5{*M@l2WN$sYsEqO5~#A)HUrW7DP9v<ex zm{^37&z8XnQD>Wlq0-zwZQ*!}OkgvrwBPJ1%GJFLzm#;O!4GZlt**eX<Qb5%o`~~| zz^a^0E~ZOSTXFwulL`<A{Q*a8ikjqAe0WKe5<4-7`e_QZ1{Hqp4J%g{kH3b7i~r6) zJ-V4;e0X{RLzk5hTsOtM`#sEb?f`~uZY(njtzRe(x3ckStKd;&V{8r60zDo*BUTNa zDm2!d5*?NYHKF#6(R00}xkcs#b8zgr$DEMwE7hvT0PQlrUv7Qwt?xe-%dsRZJ<kVU zO0_-StOj7s;TqAI#E*(u`6Th^%%_Da2Onab-@TouP?6}dh!CVl8x*dVgc}+PY#TyY zV>hV^@TMsg(9)tb?v#43lC464$WmeU99}}isCwl=qQkwrZL*4olzK+A>293k=TyAb z*oo(7@vix#Y46;iz)389kK4UGyq4Zte>;!VEet<mxSbY4+iEKornFM8tafS`l~GEP z&IKcvDeZ(>@$H?8MX*n)8qLHrn5~s(wfG^iNfwi@*h%7MD=-G6T3S_3<fqTMF&ebd z?c65nuy5n$EA&|Au)v`&dQ&{=55F>XkTT>PD(L&^P(({UYd~ib<4wc;ix`QhT{~gL z%?=|z(7AOwnz!6Gx)-Hg+Z(y~<|VL1Ku=_zB=Ta79>wkPwT~E_npxw};rYqZTcheA z+r)Pa2OPOI<2H%Aw)9dg1Dg=OTzb@wutC9AZ3S(1UbcbFS8McMf;WzG>Sm2$bJ|h( zaO!F6OZi9DUq$59rRqM*!}P;5H=NYkr0m{Z54EO$JsB}XnWnD~7a*YWV&Q!bPAK{C z2V27Doy6d!@WqZ|Ww9Gx*ZGOv{#V?GY`={na(d>osF27+ccI)NZ>Mie&_^~ik6guW zNr9Bg&)(O818GB}6P+>HHy5YXYDG#C)B7Cp!bQVe8rLo3A83~z?uJkF_kPUAUT*I_ zdF&<}4^n<lUe^B*%v>Y!Tm6udT!uMXNWxsjgOWQG0fWhb@+%+fyY5JXO-d5ZII0Nu zN>efYHFXI4qx7Z<<OeS=Wq(S;)e!8O+^W>}H^Rw`VlDoJy#eP)oN*$ReTCJz975T& zsZ5Uq+>&gRaSOF@hx%f_Py(4dy8d<_Qqs#&;un_t3`cGv2G-<{HmDv_a%Rb?%%u9n z6ZXDFG(HO3ahaE2R+m4d*^`4tCa}jA2`0_UxnL?)%k1Gc@-gbIc6v69KEz)ztW8EL z*(P?SmTgbAEZruCqu0PQ+-0VztnvHcQLTho8Xd+$P87aZntGIQLdz9^)|P9S1Hbv* z%MGSB?gHk>N2l`d>NJDQ%00fLkhSjo{IIo=U}7_^F_)`;2|QnHNV*tOa)Y+yQ|Oq{ zW^`7dR4W`xeYYvIy~?;?!D{taV_GQ_?ZKKjsw)8>1U?LH-pjMF1HUn0RoR%(`N$io zLwEGN{UraIw4o<AzQsL}S0|3GT3n3|5e6?R=+o&it7&&x?Is30d5KPj@WYPiuQ^iC z3ckc|>#=xz3fpWq_jvf8OqeNRAO9(T$K=Y%e*%ua1-EJItUtsflX-Q^bf>-R$Hr{g z_BBTE+KB{=f~?PhP~VQAXdZp)P}}i*37Vs;jyc6<Jg=WHj>JhmXN$C=)0Z?_axV$_ zn9f%V<+@$D6x`){6;gaWPjF1lAK|_$hLRf8l~YCNu^CEbmKJl{ge8mp5qr4Cw10j~ zITf$A510QgD_B@s97My(gw-lwkHR#`^|ik}H|OI|-Hf0=yCIAG21&Qli;BFa!^^7S zMezx7YvrniZw}cI;G&2wIW!a2$!ABrTSxcP-6Z*y^!0{TOuHAff?%JSb$8@l!YJRC z$J|~chx7Q1NRV!A%i9mb8``$seP(Z?AAHqNv)%3yytbzcN8PGG5?hj+tn#EjYvc6} zr;L+%jRt2oX|29nr48L^!T#NMy~)NS91L|O?0Nj?Pfe6;TG>Z3wFHCb@fB+04_{UT zk-uBjlg~}0elX!x|C8`U^3%-xPP#OltXDk}XUnQvZP2~KQ^U}(%#+6OQI0eZe+W;i z)L>^tE#HJQ$y~XN7ysJJEJNoss#<N3tKA_&9(dDf=AiF<6d*1~kE)>nqF&xCccN#% zG^E`4vpPlN#eEd`LeuKOnF9FMQ+re<5(e<z)z_a<qolxlR~aatUNHcZ`;#ITJA|nL zE^;;od^4(4dOcob?jSP}f#lx5y8ecD;&PDAaxYRtw9@FQSk*>-NVkpcjdta7&GB3H z^>1$OfkdstoYnPB5X!XN<Ckd?K=9sF$P@v@V|&d48AO4Dt{t<{lq6_Ru48V*B9-2p z>R?&Qqu~_fZL-0#HFsS~LEwcfEWWA^@bxXiru%tykUyHhumY{Ll00DpAGhhD;89ij zOOD3*?`q_=5vo82*5+Kj1UmDz;jPbynj9j?R%OCeMsO&;H|@OqqV{z?th>E`GhU(+ zi@r4`R>+e=tF(Tb?_b>--5TvWEI6jOi?!ySuBf?pWNI|l6l~qnQNwzir7<z}y8mN# zGeJv}8_U)c-@8g>op3G<(lDbQ)TO=UxzkR!UHwryrn}ks3Vt8OqFG8Mbo)L(SQ8Ow zzqhGm25;anr9O|?{o#HM6Z#kqO;toidg+Qxi4lbP>eJ&#<3mMkN<NB|w@Q}#_{fyF z(y!f^uUX(Q!wW>EH2S)I{U?-enxkE4<($*ZB-L2gIf1Q~f*YPrV5=X=hMg%(kM**_ z@YfTq9JCDUWsL5rEg**%1D2W**m7_(BLWr(=kaU5y&TNIkUqK$Q4587nOp4h<L<Zz zB4;{AXI}~6@j6Gk?}Fax3@Pv?pgakN@KAlSx+K7rV@z>4D?bSY4k@FZ{iKzPOf$1s zXc!;{PSH7^Y(xQDx8G5k0iE?vmQ!=Iax!UV)K2zGvcPeT>biYxfVtdU`x*goqJ(Yr z&oAhm;gACA(Uom~;5=8Y>~=rkS41QHBWS$dI&#Bias-AnFM?+0?1)hy#MpBrnt876 zGKf(3^cp|wz3*6vqKfIUY|e&$gQFn@i<vqL?_eNH)`Hj#AWC80EcgZjU1t09KaRrG zv$QxSlr&-=OF>IJ7D9^w`?xOjy9q#;jxZ<vUJk}%NE2#djH3gB3RRTf7y{=5v_p<T zXAayr;$Bk2Q=}|bof{zl$E+iN$bnjycx{xxuuTq+E6~c>q?whB9dwcc#}wEv=zIZ8 zd#$c{fCUTnw*4>IlrA!?GrRgLZs5{Q)4Vt!@MF4~Gc<q`4=Ps4Pw1Vlkb>p&ZJIFP zyidnj1YrG6E~O4WU?CY7ZTc_h_*e|-h>db>Ku7T@O#>}}<@C0*$E-eJ{iNEO2(4U3 zn%Vxc*YyIR?5?q73KLM4A92rw4G6kg3&Jw>8|)1!NK1^Q2?7M23}HY6Io4~NiVjA} zzSaW1O2ExNr&!*W`W^t*V!w1&9}t)_on=pgOqtb|yqW*GM!?N6r`QIEMiq?07*u^J zfTE;UnLRI{^yoou9~~(U6@uby+20P}yrg#YGY8=Gtk0m=3&0@7ekqJ<fm?^1;^g>U z5#YRqR=mD7V9>|Y)h2r26t$z}PjBp^WeC(pfoR+SGRpbO{!_qS38O(D6QC{q%hHev z&m+ggbcbeCjT4J{wjQe!-vg^=P#TawiP{X+ueECgA@ciJRw=P5ePmca)?XENfo^eJ zDl-FS_3`;&f|)+jTj^RWu(M$j?tLnw1IRCL_FP5)EXIdRw+9p8b{#`VE$a+o`eX?F z^G_hJ>u)S$LG!4Nmd$`u9~$UqVqQ`cQKal&*DIg`OZl`FAG(0Rt<mBC0;8O+6@8>8 z@q!{{(2bs92`UXpeFvKTUgxs+G!Ua|3Y%&OJAWZUVs(73%WmR_xGk)hQ;?|%M7=}^ z^8axFt(n`cEiS+r_q&~2MC_smNFK<S-Xr~MAHCP*xN-vT2*Oq?!2)FXeQY(y@)c4k zhj0i>BC~+X{S~@|BX<+Xe~6@eIs2-$jYc3UsTjr-t;kVca!kaDU7)eGhJnVdv;~de z{xi<a+N4$vG_ESFd?B*qB_t;lV7O`tF^2(*1;1L<68a1QBxx`H|5jI=_%e+As0Ts{ z8e~1(U;$Wz3b?cZSf|@f`A5M#xTV)^m<kJoM0CGM073x~t>-hPTnd>7j99G8eg7YM z_njl*h)w(zMarF>1?7@;beWvMdAp!b^$O!4;!D<(1N<@PZb|@4?8~M2*q79C7}9i* z%OzM_wMdoNdO+T-CQl!N>Gtqf2@kvICnVkEF3Ebqc$VysXaIYN_&(KOrbP1Jg_XNv zZwM<=P#_y0m;jlzFI8V{Md$zknC`<E|6k`ifx)>p^rIdODbVR3iR|`;K4ZN%tsww> zmh~4E1A<6>CRZ#(E7uDN2ZKmH2A~N2YP91ez=P(7c+v;p5|tY^FSXPSfuqq^-RkNJ zfMX}l1!F1f1)y9qOQZK{FaSixWgv0nM<oYE3Rm7MqgqZd5Qg5_R^Ou4vwe9+P~wbg zN5RGUA5-&@@p0?IiV8Cv4KN&l%nzJg;gEC$ya<HPtNJhV7OY#XAzu6nDcJSGIq3xq z+9n!N0lUN9nKM3sQa$nh$EEyk1<2?at>}m3S1nL(w=#VHC78zdbC#v}HNZp-?deEJ zu9?fQ)*ov*H-wde!KGS={VxHn=?=%7V?F{Q6U0{K8h}>*h9Ql0qhWyqlqs|(&T2T& zyLdjISN8fsYkU?zwHSp~PQZ{x2;H>?+nTKY)kiS*Fa0fHfVvBG=GXoDvV?#l1#vY= z1r5OV!|a;vH$aYo%-jJu0qTD+rM7vwLFO!<i)8~2lu;mbiQ+wX?ZT2NFq+ttgO5aB z0JbVu3(7alr6@z7GZw?r^MKL<0E_>zHFl*@3rWA<=+tgCZ7=8n4<rdL@H(NDV=$!S zW8jg&-cd9u9|84p`ueu@1CRg~6)-0#NJ<|dJ*jr6uoOrDjmd>AEWlapGAD1)*@%<S zDy*?V2)aXQlU6HIfWjj)rQ~v<QJ}n5Y%Oa4bvWvxId#A`NOIm@J#}9}H$e<KvH#22 z`Omf!`{<*8785fZNb6hKdXzx2ll+ph|Ei%7h|b$p7KSDK9<W<}x$?KNeFGLS&%z*s zzF$dSNP%Zy?{6UVB?8xXsX*wls;ciS0F=EBZXzMM$3>B{dD>CEKwtuaElw|<H<iQ) zu$R-x&iKFFBm3x5O-NG=nR_TwGREA#u|S*l$fz*kRsknIw<Uwgf2)CuuVZQiW*}`Z z@EJcBxX~!RsFYO&^t~(>N9p%9n2Xa6MWN=vkSvatsHcTI!y}b1F<#_YjKYynwt!15 z+X=qPt%)$H(!N`^d9Y>U8{?HJP6+)VSWT)!kDO>xSIxWmK_Om^L|7G9zsC}(Rk~Zu zXbxY$#|LK)KOkzLg70VWZ%sCQzo=Iwz4z%z)&`%pHT|${D@PjN5}_%Du@n4ATqpCl zIF+0ua6Cm@s2`xo6S^1?mWmrN3U$@byLx_44L6(D(muIcjgwS3j8S58XIp)ZJkg@} zl-y;ca1>OUyJf_L9^c?1uGe?}m$PGS)j8aiM&&)LX!!Mlz<vK`;S{rw22`}C<5*Hm zN#g|JxpyhdP8X_ZWOmQ!^xWgnOCLXuzbr8nVNRC2PfXbpt{269M`0rDDQ`U&+p3Ds zLfRfnW&04hH+(+$P!+Bglk*B;j8FRoF-IzjZCp}Mb(o@mC-P@L;}&azZNzx;w*md9 zr(yO5ZquP0#2I*_7@?`NGCzdNp}MHXw}N8L2|@;7Q5e*NAWx7ngcmjD#PSGn5tT`c zrHFbNmeR3s+EKOomq%%C<a1QT9P^&P%h65-dtdI^;Q9D1tAE!^aXbq`jtXgWdEdo) zdeI%}Ds{L*f>2N_!QipZl)U%+ijhwCoY1%5NH*Qv$#b2Nha7%i%6PY<(j+cqY{XG@ zbL;%fRq7(M)nMc#vC}Z;c{z~&$Ggye;pgVkm7Z3NcNmYqEAD;}l%FO^@wj|`9X^&% z9jH|#9};H}=+9(zNmFGj8p7?bfSlLC#qM3)82MA{eDgdFOH!gwYqs+uflc!oV?-Cv zaLaMnS>HfEhKY?ka^Xk6?pw2P=}sn1x|ui;ll>84>-se7Sy69gmP^re>-`cAB7i8@ z{yD%364S|_E#)ryey^(+$?5A@W476M?XHw2!iTOl>D}MCtkh7tZ~04CTW^~}*dNtz z=Fb-3Aymh=y3x5AsWy4jB~Fh+GL}mdf9vqb5k(&aHGDA={j!_8`1XZkld~o@xP%%7 ziANV#4$)%F(-gBQ46-o)!M=bBcHB6Vf{ANrRa|5X0}DfWISH(_K)A0dNv&#_O01(^ z30+y_<P_L1kDfZsYap}<5rkH_?8p83iI58Ji1BGl?(3)Y`9#i>J%2bOxQGahFpR)z z(&j6nJMjSOK*4TU4x;ots_$nRP<AdVD^k~vrWs)h)UhE-g7W@KBU=>EpJ*isJw_>o zq2CDm`4PF}1&SL4Rx#MV5G<FMh?fjen}@uw{vr0&78YV(ORZ0vN>G@}pT!h6@|Ki~ z++<yN&S3l__DbgS17i3IIu2bzmJXDROcYX8@&K*un1=^6(d|3GdfATEQWN1(=ML=x zB!bqPlt0kT#&DB8BFr*j+Z$1bI;+L{1dgRr8lOXOw<_qrMP+n4O(sy~;8s8M7MAet zPXxI=xGoVlH%B$~c14YrR*fu+Ep4t#kEWrpDGE6oX#e$3?!rP)sAi?|LSmCSx5S_A zv~kTy88a!<V!K3xF~0I!PG$&t?*tpfxb-mqaQ+uhI?aQ4m_uZP2R(D5rwjgPILiYM z`8rDqN1#wrwe->1j2i9;_csXLJH5Gcil=`$zwMTdeiL5hZl%VqOUajuz#GZdndBxa zP8OdJAB;5jl>L}$2WQwZSvb1v(GkbFt2SdFrBghc`qmPSb@Ek=)O~A>c!pt<V6J?R zf6e7WToO^Bbe4P)hm^y3XF`M|CPTQwK+jKpQ3^Mjm&eO3Hrs}9!@h&=*Us}npH#n0 zob9DkgeF*oztbCB+E+?UUYldQ4$j!t;D@8jS`YF=xldHeRa(?F?Li+&o}ArMlPJI% z4mJ<|g%G-Dz%*(ST=%Tb{ln<cbLUaXXTy0XKVf)dhgY4u2Ws95=HMe@yfXnQnrQI0 z0UlYo=O_vk;Xb>2x0Y)8g8VaFjACD9F^kGNEVE*%FYK#!<r4-LsZMxOT(!ml&u&;} z0oX)G3KvEx;uoYh^XBs0DuhXUt+W(H8dcNSDui+rMN8#TF}7VO!JXG)=46tXS#SOp z;BpugNb!5zom-idDdb76`I_np!!y*Q;#CEyAB56QW~S|}@x3YTQD_$T+2Y-L|1R@) z!J|+y;R`b=A8WmxyG!={COZgf*`MZ>h~qlF63GaoF>c(xjJ4d92J~WmdWQCXH&Ryt zoAaZ=Bg+TsPY_ml*t1T$Y9b{slt9$q)V$j-882|3Dle~BvMyjd-&fsOTB&l$zCeCX zpGUGu;Dhp{C)$`!E5Sv|d?9mdsY=^1FiGJ%1^d0@_I1Nwg!MT7qLsg+aTza?#F}I$ zcyIskKrud0-M(wf1Ek3fan~TGQ*`!N?=HfraC;xwiAZjv4Tp$_r~fE8oAQEd*6qK3 z9G@k=Swq$m3BxI#WS`lbVrVRad~5mW3e_AYrrmM9|IRU}Rca8nEhI`Tk5Z62>ZjWJ zhp!X*YCpv>a1ZHZ;e=R?mU$rLP@NHDi!MIf3M|~cBrRTe0h#+1lV6wj=2v9VZq0N| za>n^F-&gy%uGBNYCm()=%zBfz#i%BWZH5(ljTXv;_I@8FafUQ)zbcR_4m@iOy~e~u z;H>TWJn+it;TIN8&(0`Ii=3Z&A~zZ4*{gVAGBgXz!X!5a)^T&kKMNZ;X}F)Ro27UH zu%~5mklyA!$CD>52E4M;s&!9)r2EGFlU|d}{{fCtqEp$LJbLZ%JpxCEHu`P3?04U= zCwy`5wb=K1*U|ag?7R}t&d2PD50MlSomZv>-`9@cBg_~4RonDP|Kad${|Bj7kxJ<1 zj=JP=fY47Xnp>=7cxVSFnlQEBnz(x+J|2w1#lj9$j`(ywk6ZX_tORV|(Z+sV4|w<Z z?XbXz<=55HCNnB5@q`P^iS8U>q+#~Gx^U?E#^b(HFM#=hi8r{8c6-TT3F!fEvd4F# zCYs3Rkm1Z!9OJ9dkf1l7^N;CF5@}>uIwZG)(HkP4Z>&svwY1x9@+<_;$iMhHYZ<q5 z5s@4o@V-9=R|b+~u4-&tnu=e+v}1qcQOv(y;TPhXLUw!7*^K2EGReg|J4=G9d`3=A zHyJpH|K@YYl7}=B4NqRj2hpgSLK3BP;CVI{&1v?H=Skqp(^#|(t5603Mcb9BxXV@b zAOQ@8t3iDS73AlWLw<NpTKs88k13w;rY*7G4@EdQ_pGm=0>V@~R%*97Ak@%=SOLDM z05_`Oi|pfNUWe{iX7GZqi0jlR4Y5kGrNWO8h;I&cRVTlbk-)w&LB6o)DtUSL3MQzi zq1vJg_9wp3oZJ`FnFs<l<WTw)h30|l_i*Be+c%~?D1zv@`v><nyom9?$fEt%8i|mM zyuSgTXi8+RynjmD{WVp{#T=8uw1hNScJFduh=+u$MgqgnDxy_NW4)e|K&r*f85fE> z^!<||?^PMS##hd44Nr?(=N^P*JMA>Nsau42QclH`tyEn;#)>YjU3a(5_-nJSBcay> zk?3J{nK^HIA8tj)PE;6*2F(giXHp#~QewUnL8T$YGK5p)aJO#2y$c*>>Dr(+zxyR0 z6K?OoYPK;|?u1g{#vFZicv7Q_`s?KPrgMNG{g|5c8{lWQ4Ovr0k7hx7``ugohY;ja z_F1A|cDnbMWZ$+PScUIzF+6o5Gx@WXiqINte|;27+_B-b%HouYvo#oDqT{N5Q@v{^ zCuaFvXYTgpo!wq2T^)QJpQMPc1L<{>S>5wU!Op3>UROHXIDy>09R?2Fw^@S2sGrZD ze>;@;WNJ2yzmvODOWOPEr{(cL^o7ACV98=h_^SYw(dcduB5WpZ>WN;(7z(}~(h*dC zR6If;4ViP3Rr6EF7si^?6dX`$Q;VGnjlsyN&v@HSlo{s~_9BcVkuk-cHpGH?&)+$c zQb-McyS@tj;T?R3ep-l=ymGVDQ591M7FOB3Sr^skDL%}58qxJg+Xqi|hHMkWa)=Ui znh%8A4!~Oa+EEH<<$CG#xxn1lGHv2|t{|TJ5I8@D0oB!?1yx9bv-C0P-j%Km6Qpu) zZyi{k#m7IASSAic%2wA~<$TJ*WUd(~#ETw+@I`OEqd8d;^bq)yI}EeKHts}Xi8HW7 z)Vd9-MPo-5T}~pcWi>j2rot>1SZ>~KfLM51CZq14=o1$a6i!~N;#PK?B4}eCtm7z9 zyfv8f>7$;kw0a}YgQyCw4Tuu{%Ch#SC}8buW?^jtE(J!CaHS@54Y7sNXy+{H)V-+_ zgvdj(ofo#1)My7<!d#2y7x`OQNs`<lerI3akH#y|<`Xp!&YEVZlwO|RD#b{9)l>%w zV)-nxI7<Q$#bhPwYRG#A(eXpMRRO0@uS%^UjCoE+{v7YfzE};m>@bPe!rC{DnmhVl z(lICwj9i8g{2FoH=2%K|lp$-x7On=6&(GXWjbEYMRv{)LeR#VF#68ch*`4~JMQNa1 zAm@SqxF+z*cT#NpV$@I%0=*V7uc8(Mm>)g)S`<B+4|a2i&G7>vyJ2rmP90Yu@FPIJ z!&Pyvjn)iVe%sWw265_;cAuXHVKt%V(WYP!j-=~Ap<!(Z88zkA4;hmkXmf=f?7xD^ z@igzfi_2*DCZpq#@+Mcyqadl#X~Hb>L}g*jj?9_y%k_Q#0%+f#>MT~vR)c|ZVXcz^ z_(jn$0rNc1vkpz-xu7x~Qlf&F17OG<cqXia>a|(6RY=Jwz9@ry?{7OV(W_{LRT#c& z)AW5<6ps}s&XWsZ-xMKfD*d(`D!hC{xv9-up$RKV7O`uMQd6_^ZCUdDl5;B(m>%jF z@w2}cEASm?Vt&&pVE@y-Qg=tC;=BcMSW4>YB1)R|Rh%I9g_EeZG;GTk2#Pjg&N&rq zj*E~wiymfRS{$??bfAfjKJq%42mE=zG#<rJey~Z>2+hgYu}TU?98-6RCoz772ND>D zNEv<$jMvcl(f1>gBQ*`BLCcbPddaP**0~~CteZ@RvmeK?NG%h^hoH&@t$}2pwD>!+ zH0uNvqv^MuhR@au=GuwI%XC<6!Ww74xN^JIF)-Jj`JR6Cw9u0{(7cJ7D%An2!q6#Z z`cIanP=T&f^s0f@FCm?|ion;b@NrzE%Chci3s}N3JJn6+hdcioM@P#g`9X`{KrxSw zbj`Cq%m`F|WWDKqRBg>cP}<;D@0Eh8HBjRd4DVqlyxtxklN3T1>nKK;$&T7XoEN@3 zg6X<%X;|yRJTDgd+fZ?I&6sa!8Cg}43OO#ca5dtFZ-zqYBxTJPn?^MA{ONj<>WqX_ zsO0(Us_}Lmm3H~TEJ@*+S;JFB5NIU56m@vhS0v}1Xhk<y$XAEU9Xmh)+gtv&ayb4R zAhYX09ew*dGrIP7&H|*(EF|bEO%6f=N*$dySbDM`=;2!)72n)Ev3UH}f!oI%8`~O$ z9wjy?p{6D5TbiViLkG1N2>qGlw(wbB&5;Ys2Yed}x8jwn`{W$dcfJ{<wa$J#Tk$V> zMNE$F=G~ptoGn`o2Z{ywjw=^efqH0j`a1Qq{4f-P#c6DQSwrB}3&*#J_@OuN2&?9J z9H>yGD@uYZ)i>)nJ7o}dq>pWl=a4w#gGwIwjlqVwF;~b_B%2&Rv^6;Imql+pTM>Ek zu~cJWMo?zQe#Ug02F#)5FPqP%*4K!fSmN!!izC3)VD?ER+DuL8tiXbJtm!CA+#V9P z1QfV{Wa90tQz-<s<+S#?uOOBsC|BIp#;S<TE4>jCLxw<WLtS`nY;({e4~e(RJH*H; zzg<1mptiOOPDB$nu^*J5NU<f+>xi-+Sp>6{B$a+r#<R(a<{qB>9iXBrLCTfizgrpj z;WYRpQuHMz%t%z_y?F2BduzFBQhF_NG|?XOFQ@fuvK(X&zv$MS<E`s6(Vi|c1n&}c zyjb|vhq7ZtgN+u~;ziB~y0P9ykYc}+b1RmG!4&Wl%{D5uEX*BBzr`D_42N>GmfkuF zx|=iTge3~~4!JRom=vK&!;^+Q{+XHhV|{vmdOJhy!`gdv#6mCCRR0xirQPniq^F$* z(bF8~cSwk*@{e6Ej?CdPIm^24m-?=0Y#%vcc80<?#YA7G8MQEq%yFFJ2mZeg=L>yS z{)otSo(E%!i0{Y?Rx8EJ<GKEM&P7r&atRbN@vBz-Fw`aZL-LRfX*iNu%}<9!a~K*! z4z6@}Vk<^n?<%f~TEw}@8aoNbxN1yn>S4a?W|=2`P5JkE>Pyl4V$a-#9RqCKqrx+x zP7>MLPBZ>^+4IUf8*r)jRLP^Zb3Rkj1BXy8qikzJx4Y~w2VU+8y__iX54U(pZ{!vd zzG|Y2Jy>SznDuk5xKP)Sebo`)PP4;tJu_@%K6qUC4)_@p+t(Peu*|p%FytzR5c-lL z5v5y5woWpTCzOeSO0GfYczkHCk!7V<HU6Z6J<0LcO?{>#MTS6`WFGd7?PZdzC`|un z*28X%d_Vnin`m2iIE5_rTYSenj{`<u`Unpk0jqgwzp-bfL|4*o8^3hFw>b2nuT?1n zAb)fL_Pq7}$5T$FfhQE4WdBb>iky+C8+@ngGvmI=(L=-fIy__LdlH3s4Su7lEkpa? z1sTIs$)8I*U$mL_fRE~?e$$;gtcX*9AN%9NAR5@oeD?G@-Iyn`FtE!All->PU5!c` z+|}%bKo@}8&(}l@ckQp+LXAX_sW{T6quz`HeCxm3Sh<2rgd*;k>CI{r2>Fu$0<Dtp zg{q0UBGlEw8GN|WA?TOYh}f0{7Ktubio3yAA&%B{ITqJCIJA{IP>Cg^IrW+JQIbSz zkUw7&o^8=ph1<#p&rNSC{KffFw{q@H1!;sRCfZ254e7d@STUx(!|B+Y;pe{JoR6Wl z2F1y5Iw+|}QDQS~r5|e0pAS2?8*9m*e%H3O>YpK%h4716Js3!NdsuF|+<qy8Chz$A z@zm#al?$1g&&~M5?OA>7n==0O2PX&V1L3MC-}5=U^tvFuN2uR8al<Mgfl9>w$lP@Y zGn!er?H2}n5JkxPe6)a5fCilVKd!zqE{gX1n-EY?I;2rRx<e2IL_tCt=|;LiI+he^ z=?;OVyStG_x<eZ2kcQo7X7Ats&GYX1oH^Ht@2P8dW@q2~XOO(qfOOnUM~kFBnW46a z)k{+r;g^V>+%maul7ZXcWz(~+z&4~5_(-UTK7y&uHTH@C+~3zGdhn?N)^U!7rGt>e zF>Ip|3+gX7UcD9WyfwxdX}>D3Lw>Z6BhYRYpoW9kFA`i80eH}HlPXLx%m6Ny!@fDc z02ipupA-Spu*dFy1{@tCnDT?#Y7tBd5vi}ELE&~|G*ArCIF7%ZC8FSB^+>txx&K8U zUJArl+s^}32lq?XM@OLJo%c!)A)FzyolF9Rso8bu?>le-!TG!L-QdB&e_KI-`ne29 zI)<IW&_S7y^^TikP!c4o3BHP0e6?THLC6lk5Q+lK938vA@H{+0SZ8b(-;>^6F%5wD zJF%zX{9_%6US6N<(hZh2VDze5H;yZ;Ukz~5)5iH3U{KY&bJtXntT&*t<3qk@t`7;N zfB}6zbHU9%qadSMd@}HW@DR9kU4Gi3D{58@png{IC+5+<f{Qr}Z?!$+6$qQLV~E2J zhxG+UnkalwYUzd+)CkeH7CO2Q0%a<EKkZBxtpbg!O8uBlqXE&$x*2jl7nKEF_I&&G zmu|ZWC_5;n_fJ`7HOQ#>Ho#%lZhjAl+=l(Lqgz2x=JBzYAz$b12IffSl}{=T0*qSQ z&W0<JIe-z~6)<mq9EAWOank*NR7mCzw1%sXAVd4&6!-j-nM{hY`*#FQ@(AEQif9LV zLSjKP^OsA$c|s5<DC{J5wYCp{uB*^dHC&kihP1%aM`!6ZAWI!P`umfnh!h?lx%;!C zZvfgp%h1--D%u5Zp-m|3Y;ErVm&ML=ex8s(P`q7oT;|6LSi}4C;9+$*07A!&Qy%-T z|EZzQ_zN&1l<yR&qFljVfeuF<Jm6o!Siz;J8Y6Q|Bs}WLj<@*zxfY?Vd><U+Tf1A% zl8iryy!F^P^y@vu;93D&P|6Wbnbtlq;?uAX{|*!7y#IVq|0fa-I6<8Pmkhe0`%UeC zp6_MR^dl0E^AsK>UeAZvy6<*OAqR>Tb-M}RLXgV&E}u&vNbuF}<B3za-<q99E%(5K zt3B-5NaRKa<o!Q@w#w_!$pYE||DmA2boUXV)r6(2R~KSjEQ(?di@?Ax$=QITcDFyc zB~u_of4oiTzWDtC-0oTid69&$WvZ0G;h`D#0dNHEW;;9!*uo2o`WlDOtUxgOYq=ev zP(ioxoS70%{oF@=rTzchWD~ZHS%P5f94j@$*x!JTN0}xby$8*QXD`MiAk_A;K_d4v zeRly{L>t`Zw-ChlggZlHpmSF~%Pc@qZ#*7p-&ww?L|lZ++kE$gJsXE(nBT|hq;0~4 z1R%}vLFa%?$Yk&-#jjA%F@3gRj50W?HdxQ)_b<_}?$NVBbgveN!VsdVy<0ob0jtC| z;OtQOaZnqFvdxha!q6eN`Ix`gaHI%ots&_)2u50a&yG*SImrjWMVO4Im%~mJfT`FW z$&+dO9S#J%nV1<J>7p-h;T<O&nbrBPK{Q}q<P$3hZFYCHOh>nDpa~FZoYzYiP6$!O zKF_4uAh>CwH!2+e(P$o~)Rz#UfgG$X2m>Dbj^YHD_NBMefTFP&t~W-h@xXw*o^=J9 z41x+@ynTLjj|V{VO`oqhfdfDcWdtt5ZuuOrBI{IPK|bb~`q2)AW<IppClbJ#7iM9P z?D{|q?tu6EdpGRwAe`XCd=xwhLUHWTRmqQVVctPz=?)8V)m7~PC@Oz2R_(U$o&F<> z-9-s5Y^+ef12e~N1@7Ysf_kgpSb~PwzhNe{zOstQE9!F#fujda!1Jtgb)4h>BbTQy zrElJ0bl#+*p?PP6!7I;5@ZX1khSosH+>x$eC<x$Ia)W^0fyhRzqX&Oa2r_7YyPY%I z(E}a=PaJ8pR77q<8oZBRAwpSd>I_tM2q5dL&&FR&1Q2!v_834-BZThu7XcjwfhcWT zONMcHrd>1~Cmb?<Ga3|r3xB@$5p)c$>@utN$p@jtNvH_BcM~8ZpV{fFd!oG(MPyc# zO%wsw4MI>9{P~x!4>RGn-ND<Le8AI}J_w`3s8UV;-TRUPg}K`7we%9K(00=^?<E)( z=m?}AN=ot=oV@<sSHMe0AO~4D&GMOsQ}DiUU@Wv3`DCDgO}1T~LlYM<m1>>S-4Vm+ z3YpIq7}bC7-L((7o8U&04|rndSM2<_>Y`wtZtQqGpd+UA$2304<KrYHpxxmd{`Fsk zytoCje0AaJA<ztc&lgum_<k5XR0eOCKU6}M;M6=|C$IF+=|yz+shRiT8LK=w0B0iE z${{CcgeY`o%nNaK#lr3gPy)>BU|Q3)Jq*C7-3L6m6<)xVj++f^A}$Y~ZiFYynL@Vk zATW>ghl+PeKpY?y`Z~6Vsof5b{E~B}KU@H~!yy`3{NHe9UAad1%#r9hh``8QchwFd zI6u(s3K#@w9XnAG3x5!5zd=S@lIs!Yi*DoT|8tmnrOXfEaL0*4My%x&d<kJp{f<=* z3~D+>ZDtPhSWftb7$w=d`Vkq_2v1!T*zpk9_O_sVA{LDp3%?QL3TNwj9l-4iYI!CH z_J1Cm->ckZ)+2&4&CJn;5N3rgxpt|KC?WW!>q8WHZfRH5G5rT?BfVe{;uVEk`(uGv zt~L#benxm#j-};|nC1#{pS%&rIsG?Iqaol<(Tv~c8~{>iDf;+#REPj`-j_4qxFSr# zII7+k14G&8RTlU^Z}0eAS)@RksK=N)aGn7swtmz5wi!g!dXy<$5MEX9IddS60LLkt zjo&~`aIIr`Z!kESJCx@C9`sG8z+r8(Wn+I4fT+#2MNS}Fkaws>dG(nPQ8ZoMZ+IX9 z>S65Oq;QB5k>MZK!_|OOBRMZ5>QCe;0-Bq)&t3>Wp9JX-BmC4=6^TVqk|&UIjyX8N z9Mf-UC}ji8#U2rD*Tm3yK=is<n~mlOP-*$CjW|A{3~DMl{+CXfZZ;4Ukk<a^GP@5P z7x!U#1*$G!8(qoVUDXx?rw`<;(QtbA8fd4pnP*3D5pAl~j3o{nZ{4EISA{qTe*Lj# zivLw^=1QY$3NR7J?!wNAeWXDlnXgNq{!c5!Tmef5P5?9q8u)wYuXKR%2mIt)@FbuE zX*$!0!5Ek&9VeO8HAJ&;Ot92f+t=^%LG!LrO~ZS1A!1;UX<uRSd<{0wV}(=JsA6hB zUe>A8l`-OcAk^~ott7(uekk;R5Z@`Vs0)gN#P*}gZs4^e;FzYWMnSkH)I6mni@2sz zUQLC#MkEuJhFA!>h<JWO40zvXe9{@BUy+U>sWn~GIAGxJXnXkVj}?H%&~aj?<=)DU zz-JC~+5V0LVbbqJa)e3Ln?#WyWdC3@PVjrgNu|9fqNy9<tep*K5ICCQdoICsh!IHA zI~|e-u=Dy9lSX>_#FxSZ{C-o*zWHCt>NgyY6N27v{1$mpj2OP6?cqI#?TFU-`&lGR z2J{C0v0LH}#PzS}k?w!P`kc756uf2*H~*u}foS>@<?;5%aKtdy&zL8>f^mSJ-9pR* zVgUer_jYa`A<_vc0@Adthrm^o$sD;1!iV5HAC~(M;-?UozSanYLw6o%l895nD*WJ& z9zx583`49J99sZBN?GmjKXuLY(hkSRv><p2kOb;kQN)abd_gl5MC=J$VCzHR{1kpW zqtpP4_zoV7?h^9WZy^YKNv`h6>*3<Sba?|;bjkEbWZnLx4sZ<VBT9isWCJ%H!J`FF z+q}cWJ_4L(SN+I+_&2bh!rA`fmO*GiFRrUUK8ZPA1wL)4i{~N6s_4&F)c+;4P+W>z zBq9U;&P_HPD<G!#F@GcD{}qzWzl?Pds!{z5Ymr!x6Jk&+VTec{zU-Zs2xn~eRc#RG zxpwJ%V?YWx6N@rYjejA4%Dh|{Pe$k{t(yO@Wpvw7(<T514CDoPlXfOz=m!UfIsBJc z=b0Gm*g&cn?(7KdU7wqr74tC{Di=Om`R`c(lTb!U6rvRM0r>9~tzoPh3l$ldd`I$5 zcHH2Xm+^{mG@s9}LpYzlre10^i+h3m?dM(IFKJ2An7LY2sXj%z*mvNUKcYc21QI<3 ze3*^k?|Xx+9HzLH8blO+%1%V@PjLm23NHpE)J<)ij@*A?3zj5%5n};SCmwKms&393 zs^Qj=G_|;)h=c~bk}qD~Ug9H3(hVuMg=&rDb0|JN=6^5#9Z}BxO}I{dU*r~WRrWM5 zn5i`e!c)1w{e!(AIK$S6hzp;HR;K(GZKy0Xb@02R(J^y~-nIuCop>(3?AF3CPo@P% z?Mr#!NnVq~EhYLNng<*7me2U&oXBx_KiCmBic3^#*4eQfhpw>VM~48Pdw3O1x;}@x zH7b&e-@X3dJAxZ->ECLMn=cEa&psqDr2KrMI;KT3!NZd)#hy?AC3`|U>V*@rGmf@T z>d;eK?Md^?A}eeoo|Hj(k-zJ4kPO{q6ye@7`{JshAKUZjmZsySAmWg|SqaBkj_HM_ z?#K8=85dWfJv!kuOweV2+{wZctQZ!n_6#V=W6Jur`X&=iyS((Vi}4K8%WuSz8EzG7 zt~_^QShSvy2CtssjHr5Tl6eju$C}G0Lv9p-GD-;?u8`<b)F!kZp7otU@*3jPA#XdH zG~icA%FoMUEFrJ-=!`Kh-QwSLb{?a1Q|@cork5m^>ECi#)Uqd<siOXTrNo2>D|vkB zSjwO74nv2$%<-v&Nl9+O5jW6S`$ZxCzlToG8R9T&UwNPO+rp)C>EB3Erp$@SR^SAN z)b!+(Tk-a2b-kl3eV9~}!1MT6(?^_wB1RNYH;YYs<pthDQ<JEKtTelkn?FI48{S_& zMAnk6u!6(0?i%I?a%$Lk(R`T)18?eH%@Wov`?ZvExYUHyA&p5~enVZs_xfPnh;@^{ zGhk9TMu=2Ykhbs@+sJM(VH^IAB!=_6y^ntkA1UQp<Ije@5u4#6W&e!jh2@;M3R1!P z@GUpbz+39NCGStdFm)D$`ma7OnUEVTb3FY^6bUC`BkAT?a>!Ys5*U|H(RJ%8jWngo zn$;>1>aTv2-@Y$f(x_xbc?*fF49Y`NlraBYr~fX|wnT$C3V)}x8szJ}+`r-PC|lO3 z<P@P6cv$8qLlq;fa(W-^$G!^C)Q$=qXI?F!7%jPr=CW;KMiLE)pJgu0BQP>v9A(CA z3N-Gm63~R2q{-Ew`LY)!upu{v0wk%k=4VYcX1zs>Q>Hi1$DE`{OHG2Zan+Ap=FzoK z#0U&^nc0Hge*03J?N&y$G=dP=>D{{YBGJA?o$%__g|Im)8*)|%aR7FGFoMrMGo7Uw z0XG#p6BV`>=76OM9{1jiWvV$Ak}j)VY*rx1SLLSKIESrmj*0a7L_1SLXOI3X_JE~C zrRXcsb!?=3P^SM+R7JZ!+&7^1@AfRg9wwoa06x9#Ez;lbXaYxHXqGZAQS*OTLR+h$ zN8(j78@5SyUd|bE`z3(e>)Rl$ZT*u8NmYrSQ9**GS*kCYwM{L=!<4`VISb2qk+Q-{ zV}>Qnu~NPwOk<`vRQ@jDxAwfro6#?8uAc?F05HW!Zpx27E^!4l%k?F52HtJpUVUn` zeTrmay?pL~d2kkSfw{2}LsV~*&4%=KP4VEsX}?JY(1JbtyS)Pel#6pajb1xivnoKf zB+-p<V@cfra}HiB8}m-xp+ucnDF3U7DIz(NHr!p`v2V~&$ZvciHgttD18{wxTyZ7u zq>`vIHC%doNp$%=g^$J9cHma;hpgZXYdHBzW+4AKF|H@AGy6(K-J<i-VI(1`W)C7c zL`_R2T3T@2S|fQZ4&)xb?e2BBTOpYn4>IOIwrV!)OXhvUZkxD>;3Y{E-ZwT$JP;uG z_ItPS&N`co#<YYh0(XRQ&34}5waJ4gezr8zYy@&vxB?oKOU-3GlE=`xxJ|GfkxaS2 zVaYbSu7?7#5?p;tHVD)!uLgA##v~|N5_+S&ZJHK8Xd~@rTPLG0mhQJvnpG)o!PX5B ze!J*p;;rH$xvWL<XnwkG!aSu!rl<!pR0NA|sPym`rCHZphU<HrMRNf7meqKx+<4ul zjZe>0IsTQH0HLf@I13jYXgXSUsF!s#os>f))FV@lsB1mEiZIu-fpxVLv7W0?`z2ze zCUZ9qQU}PRy(7Q^dl(F7{pfMM98j2V8Oj6Rbkn+R=D#M!$iC)qrYPWQNd{BUEXuY# zNVmIEOR1!i<wYG>o<STOg*nc;EgLd~z(Xkw3u3Zj4V>Exrz?ZD%MSFR7`^H0KxYs; zi21x1)<BKox)sTy$%@75*FfFn0D!PwyfO6=xEBdfRee@!N)VM28c3&CUxwwX6S6Kn z(oJBUMNgyFqTi|j+}w203CUjSH3|L?Rqc7Ce{?Yyq3fQ(zq58GOVkN;Zv-7R=H-OS zK19#9pZg&Ex3)TQtgqFr-jE^A4CNR`q<q+qnf*kKc{ZW4FE0w|d~6WlKT3o#&A9Xe zwpjb9>`@~s)C;#DSIxCOlEu(^pqZfqks#GLbTpCeYh|+z^x;&q*e^y01#Gb%Q<tK} zlWEQ$zA5{nWH&b%g6N%YEAJ8}7s_TF>VB5ZdCY-L$;+X&p<~c?#(}@-`bWbwqBK~% zEwenh#9Mzy(JJ-JM1H6>+N=U=_*SmbNzJ7=l0}J;<1J<l0&or3UZAsX5db*WBF9;+ zUjoutPx2psj*<gOO7-mi<U?f>!0pGYC9)4s_=9X*X*~6*X|Dt4TyVPV_}<P9+D<yq zhdoT(B_c3%u)dkc-EaET7tfg1_w26?*uI{;+vndeJ$MC(6PUBI%>{z;0e<Spc_2tv zOw^0K!&6~4Szw->QAXdwqy18cK4!4?h3ZRF7M-o2AU5_X-*L?MGI3hx%|sgS=M$%c zF#DfEX9{v<pdVOOSw1`9EeI20a?x(behgrc-_!nvr&9ew4qj65|2=VOotEL)k9gwp zh5$w%Ax>8bIX6Uab3asPkGdsIJslyjr7Nk>x{kt8fs@xk-ra2<p6!Xp4?l&f_UG=I z+qA85D}4yp$~k0$y!A%BG@{renK2D{ys~y9kT&caXSw}lcv<l*Qd@FnIdoi#_5=6f z@!(7M$TiFeNxUJV?XhdyWkpW#@RZJ#8p)-<ZT6ucx6;yvo>P+dXZ5a(e+7Pk`6tKX z<{q+0Le-<8$-8KaSFV&wN5E}3=_whd79GZzkLI2R=)<9dl{w9C33-lrw?_CjUWfbc zjNCgLeyEWmqF;l;qxeHUY;ofS1-4Gb(kL+pB>wy{r^FCVIm$yF^g%2=H|*Ai8#Cms zr=UFd$<u*crExODIGMPlm9>urbYxFZcXmr3LKIZ?x$K#9`5Yu!hlZSAcyqs(ETqG? zS>*u!Utw6oPDD0CoxCLouXwMd%EzNn$mBjHCGn?3a-m_;7E;)6VsFFezBs5SE_QIY zZX_2@q|Zw^z2A@@aU!iM!`}=PArpKaF!J?Wv{f~oMzZmhON<JxwDjc{>k1aO(&RHh z$%^$`GY9Ua3hhSIndjpzh|&@+{A9`@Bpwi1Q#CQW!h8B-)WlL=BJFA1Snt1yR9C6b zc;k?#USXT9K5kZjv00u+FJyYP3@kDE2K{7!j@}v`b*y%cWduBm9iccnSKAfe8GK4v zdZ^(8drW&KvgYwS(pxzxT_@Io<$^9jmsP2V4sQV>4L7G+Au)I{2*K7XlT^CSVs!Qp zB_@@qM3vCoT`4M1B1C4C$@sqicD%L=)}!%V`S5Ra<gV7w(3j6OJ27-wRF!NYxII6u zf|v*YO*o5YSp`{!EVh+iJAExxlpt-{SH)~nE6Dzi_4}6E(~MGmtFF{goKS)bw^^v7 z7e&dMi;F878_#}xZrRBHC9@F&F|$#&kwL(6`kc|e#FYcfj>jwN)W(%#@>p+J<Bls1 zcYc&{=|!p8sB1X+gjG;$JS!h8D^16HDSZjnepF<{$FnBmXJe;glee(h_7V0s-$!!R zg*>hAPo~d-+1JB9)3##x+D@BVd8Sq3?QyV>C<eKm8OAg<^>LA#a6#k##!OWqPye`` zsnoq?<MB>WJk0Ym4ur%rvEw-G)(vj924@H^D*5D<x97GaRW$7x*eJJUS~6A9Zbjdg zzV@=5_v~SfX}sGst2Q+tqC7yoD57MrwrE;4wnLSi)a7g|vHf*+;m_k6?-v|)7+3A` zlDvG5*KP~>_jVSUv0uJ2nBt7^%Pv2;6Oxg1<>rRmaKk+rQp8?)is(_$IWt&z*)3i= zF-Z7KmLc<tw!hB1&-5#76IT1|TtBuK^Ck6m7vdRZ)_(ZWpZiZ$$x$}NGq}|}uY6Gk zT&Hf1`rtmQyBRK0SG+e_JXV#Z=cK|WJU>e(V<<=64*&25hf}cAoP>8g9_i^P6T%d& z#8rDOd2jVimWYfZjQd}cMbYSa{NF5jd-Ph=94!u7G6P#ZOE(ASAd})o-3~e=Qa3X1 zX^+Bs)c&1KbWBOMjL?T9KJhE1*<y(-e9tV^V#3UW{mD7)HqWr*v{wB7y@~11%a4p> z9aG=7T9r*Pt)4RJTMU0!Ncw<;p2xl3A|pjX_H7><Abv`EqqsiB>2Be>!spnN%2*<u z>jjN2*XbmAWMIHdD?58i89&3M+%{ppQn2QQ$;(+gZf90@7HUPpr0)<Xwd<Vx2vF=6 z<h(6_l&+9Td=r{uJ0%{;GdBGZRfQaNh?TLZVJ7;FC^&i`V3N+>poq3(dsB1kA}X$2 z*rkS4Hw_)<llRx<AN4Wo3sQsXRMp!Q|6cl`ewC-L+o_#sk{)zZY?remLL*CB*lj3` z3R>5a8C0j3-2VHU-VarU^0<2DQdP@L8c@43>B&WC?%Os}cRuIKD~g}O->*)VYL$2+ zhJ?P`Y#lRW2z5@5E9WgTIpmE)dG^U>IEk#wPMAMajpHo-+tyk0J^Gho8~~oOK0WlP z_dpm}+qCe6@iYf&E4dtZ%zJCo<H`jxFmT?yL&4acIxLxeT5zQF)ge$qy+Pa$2%nZG zRjkMMH%oB<)?Ima#*2PA`R7T4k5}ttEe$YcLI4mOD#zYiCdjG*-XtA~M&!`Gg84ai zH8J&Vpsr^!aA}rr28L$vmUlf|7{%uEiqOd&OM{Yc)%H80&@;(v9Em@ks{OpzW8UlW zE5ud@zC%5S?yeWSX+&1@W@_TM)KWli=8z!ilZ9g$+(38tcDJoxoDvd~T*g)lo4Ve= zJf%>xrAoTBuc6jgFmx9c95Qxq{+1*4r$hB4Q}NMc9MMf|wSeujNOMU)^a2IhjC=gp zu8QHzwBB*ro9{i(8;1orr}6Nr^8>4lAXRNnAD+7W6a&KCsIaq}U$F%6IovA!_5#D4 z)Av60w+t3+K$FTzfwV5K6IF#e{mQ1KxZ)r)Z40kv!uHV<iY)D&3l^WxTGtArX9fEA zGTLOqoEJ(nFg3#xxRRUS5~bcD9Ue`Vynp<v1q&MzqnTg0?TQ!1Gr)zp^8>4jE^c4P zguX<lRNLqdzrsiq!9zNim7XA^E!HTkt=BRKL(H#%?9$P5vI^~T1X=w9rc+};R`SUN zJ~cH$#nJW0`lsuN^qOlkHna&cLEd7G-0V#c1|?z#f57oMoM_OgiLVw&=#AT&l~srU zOW@y1_9f;J>YyeCZKU?MAE3m5Z4ruRFCK6b99plOc>Di}YDSg|Hxutc;&iDdFB2)m zP%!e|L?#_Ydyrv-q5(VS2eA`8o-?ojnONen&Ear_$x6a#=Fng&MCWi63L@>WK9I^a z7>YjV5ngV*;tS^N7rB$5%Yj5r-akSd?lL)Bql07oH*j1aYl+E!ALUjUWQ9YyvNlFo z3C#^GbLLOieFg{71~teQu5=<EcXX&q1|+@4*RJjo_v23Z73{Q)e|I@33q%krc7s2_ zJ1Qn@W3SeigU;SJ?>*V&_-73o#b4|F7n<!XU;!3Pl^0CWN==D<xRKe2F0Y>2un;r^ zn#7m8F~y)DWE0ki^DxV$955roE_;tu)QA;-O*GU^s7HZa?@|3Tz48hgz`*gz%Sbka zKo9$dsVB`9gjwOeYfW_tQ>Q~`>m91W)16eMgv2Oul>*{3rkOwqFe-Rky>FPn_vI$; zTcYJxkotpk0r4n$E$whBN;;Gi6B$nmCdCOg-m6n&%0yUfr7+p3C*$c*Du^RjuEP5J zpdk1c*2wgcS2Ku|oi2NBqpWoXynx;9@>NIlBWy*IL{{A)X4Y#4V~0()w(FOmfhTor zjJ5l8mNeO3P($G!J<CaHK|^2DcxtlU(u%Q60BbAVkX8_DWB>E${K3Kd0Sz*T^lvi^ zNT{l$O6vJ6tS1a+7J+EG*&r%88#5QmS_^<ubDxa>ktY;Mdz|&nvpR-`qNHK(tu#un zKvCW1D;9-jwpk^heEXi~g&%DlyE;^Vm9>t~Q5l4?M;Inn8439U^<Jkz=zj@U{%Ya2 zC@9tpDTH$KR>1<JuSlM9kSn|<am`nKlo$Rqig{C&iO7$vz6)aF&&O9aFCci|QC=K< zF514{x6NDg)*8LYj)cgxWg@XEtVAz^nOX?r!X{TI8h=5VKihaeyUIyzRm9TH&P5I% zSDo6G;WZzM;3>O0k2HB@Wp$-frP8XTJkjK7LKU8_XKGxgx>YUN9mdVQAxp~K>~OHw zC>r!Y{IEQEM}aL<X8wDVJ`bf)`BBWH56k7r`-u@(G7BIr{)jjXkYG_xT52a+uY5AA zW^IE$@8PDA&$^a<CR6)!Yk(u~c`604@}M6ceW?4nB$A=Weu+JEN#%1SsyDnq=6+A1 zqk2g-bID%RE<(=N2LCv+Xz7+aJ^jxv?4fYjP<uM+vuGO3#-h+uyS8;d{S&;dX1cXB zgQsoS*jiR4Z(+{^emMNkPWG^NdY;$9@wOg%xnL=}zmfz9(A6fZh1jxOmD{*`=6VrB z7e)sjQ2D(Ek5Vj!#`(e?*wZlYuT^FunT{8j?9$s^egOZAk>xo^JY5@czVo_AW==AJ zdX;9XCYWkcmqTX0JIYSha^d}H@W)?La{;6Jt9?^7As+N*?QvBh?QN%p76j!498&Xv zb@jmvcl4g;JH(g|3tcqSo>toNvV&3gSBd7dLmxHuyad}{JMYX=>B}&eydA#El*&~4 zgPiSew)g>sX5!j<4QbunNI7t#sjFWFzlm#2noz2s{gsE%=x3%9%5Y(NuJ-q-%a6ng z+F!nEPc<s4Vdp%H>lxoSC1Cua2$3@{*LM<g7}4Kk3Jq{LzL(BYgnVYjrm~ON*bg`f z?(^-xs7CM!@N2zMH9XYYWD41WdQ!YX5`Q4#Y8X%naUyWg1*r?IWKwBKj2urcW_t#q zL}4r?tzp9SYb`_jtjM@~<&T3&oajfhJDr`Q^MTW|IjzIfh!(mjeFC291g(+=Q}#1L ziNu(Irf9LBu%hSx3SFK-ogJd{87()deG*Xsp&!&!VU2{dEfN{o{nQPw5*}e*s7qW7 zYGW~#n}rFRa4|;3Qa&TzD-R=S#hO;psU}Vq5l{IQ_0muL;j^KAe{;Y@Q%%1LVaV*2 zl@iTQSW1Jgb7K5@N;N=(p1ZTsgM4^<ZY$~*x|#qOvXrk5^Tos;`0%)L_sD~4jRCW5 z=a<aIkpgd7Ubg+hvt}{sP3W1b`!+{49a<XVDIF#ld9Z<R9hXn1X6ilY@FQmd#QY2m zCsBa(`K+@<MvT<=@I(PFEj|3b@({V+eaC}gx!atLpi9(9-9~AMON;6=>hc<P766G6 zz1mSl(8^t-eA?Pc>Y~<AVR}Q(ny@MKACCTt5Q!R?#%A6`QxOpI4ZDmI8Na|=t99?+ zUJjU6`7afur|c*a6OkTYsDEhGSRPQ6j@_=UZr3xoj;YWla*71u44er}jUr44i7jTt zW-YAy4#L^nnfyS;%tFkdt`x3aeM^c48j7B%FZ(H>+tI<1s5khv9G&Tqi6jsr^K(V@ zbs7R4PWiH`Bztbj^>Fe=O{6nX@?x2LOYEUv1D}b>1IlHm__%X`jQTfiZdF?1mZl@2 z)M9V%g9!%S%viouQA0$&EW4ka>z4ir;a+*@q4AydXSELiO^T`j9)XB^#N+@4kC={a z!Qf{?4yxB1&ry_V0b_&0j$<igum%7{#kXvG@&Q4&`!7$n23IhLK-xHoCkF!<({bCC zr=C!>txe{z0os(069zwFO>W%pR%IQe1sgGqb=@*$J{T0Vb0_$$9Awp0r0E2$8C;h> zMx>HYXK4ZxD+hzRX5h1jlQaZI$vdyK<A{@Di&;(g<-g{2ia$7ydtH7ijQUeBoTg(E zZL;iKs6V_Z0bn$jUg6SQW#j}Pcand+jHsheL#;R8yBq<UgjZD#@)JVeo;m18<z^}W zY=9QD)fS{I6gDV>VxReXbIUl$Wamrsn2=W^7XlvJQrlieHetY?=hy{D(xA&mmpv&W zimo|JRl9XxG>GsSr}T-i#M9}s#Uyq&nL~$iLS?+X`(=Sd<}&;+!4PBuQu)uxZRq?^ z>@$4W-^gfky=e^X+H_Y_M}&nfHAp7?4ta3=!61kuq%vMeD*f&S1DHx!!6!&TLAzg= zpQNuzPcwnt<LOdOTU8bJSMK=0@k*T8C%!;xKDArVY9RGIi~B%OJQAAP%%~t&ATFZ! zsF@5(+?%&XJ{SX*_*~3$v?B;~D%*`{<0-gEF)yhUvv_|Aw36~5YG#{*Y+<749p8kW zWa|H<<xeZ&Gx+@H3g61(MYv2MJzo3YS9S1sWtJ}UM7U5;(DoPRkX)zW8AOGjcs1Tt zPvARo6G?3Lc9tSq+nj#Ih8ubG??5c~P@}&YC=_-DMNvTQybOa%B155DUzkI;FY4!h zfF?3(nf0LbN&vL5;=?FcM5yryMCw~6VjQ{Sd&hRQ6A?16H>sxI8@>}v4mFZ>T&72~ zU$SXSdkG6BfVLjn+dxXESkTVg@^nS|Q+l_!BVf?7B^#aT=oURt;u>q#&kiB<KcicL zcFAkWR&zHk`sgPt^znHaZc&AA+9!Gk=oQqHPzvdl)?!Ue5@x`&`Ph8ArWzO{7r`qQ zATm8&soi(XtP~81KLvEG=n-WLo9&{%Wa^~f06dBrkj7t(>kkL2VQs0h>Svd~17Ly) zL#8pp*1WrE92GTo`Mel`@#9le6q+f0JeHgqJr-6%1{G<6%!8UxM8`1B=u#HK3E+0% zj{nd*7^%FdH1xz@7cWKK$Di{atxIr=lBMIe4+ol!JJD{l7*@Lq%^uUfF!;MlI5#W@ zVgGzRt9;2!qQG$+cLA$ds@pkd?ZS+duhqF5s&kk~n6!UrOoNqgd8xp(i}>VM_GfcL zdQ(-c{&6L9?Hu#f@I7sVzp(NonTELKb6!kb!>BJxR-CqbEG)`LVd@V%#jf6*j$RAJ zR)rQ9qY)1ub6cD0Qq)`<s~wCNip`WJ_adLEm&$uOQ}UJCk`q!zU+!s6*Lc7^&xvze zJ^1-bSFWXZ+APY;iu0z)lk5LxT<*;S*IR$&QBLZ;4Ng%jPoK(iUMSuN#9vmweE^90 zDQb4-{qFV)#XHKAdu*vs?d3e3y#etevxDr1(@s3<v+SqAeNTkUFyfgz7PL(^ERNTH z-g?tc-?x^EqNkBXThfv|$l1c`q7NX)?>73(O3H%syQ2<s@2~zl_%JR0J8@FDLyjFf zDpF%sIfER^t6VOltv>wQ1D3Lv%Qxx`(O1Ay`*NA+0N%1ES|W5AOP#p<md{n;KG+#! zZn^Qg{};UC2&vg>uEuf+xZZKp6j7!oE3<4dcvjTEyi(I1*A#2!s{Y1?nFV^ZIlt_? zgDeM~N*8YC(ZRKcQ_oQT`8-~Ec|MJhg5?BrJOyY5u3^8)<!*wV6TcO+-UlzwH_gvI zyB{KPZ=9dYTUhQDT>iBqVvug>ns=+z-sg!4yG+~5I&-P4c96ZPwv8myw0Yao`WC$q z^4>)DsEpp^{lW@W#zC;%LuT{9)xN8Md&9i8XMyrtU-j3RT)$0-OA;-~X(LHW8I*D! zY-EP+wHP`&M@G7TFAR*l8tYtH5P}+6A1EU&<Z8K&8SjUTPL}0sT{*PR4EYo%2uJd} zupi0K6s}<VE;hD92fBFcV!xShG^Gp8?_=awrt=Py&mJySl;Oqwa+y$c(5XLv*Jq(m zwkX{_;;=lrC^9o{Va@PN^Xd|3@1ajSBt6P?P*VBOKyNnj*^rgGLTx~&>j-_@cRu|G zoxS?k6r$wuW`p|klOw;pv($@vU5F37$JV4CRKwiOYxFrzDRch)?mfHL8lK#`TV<VQ zZVtZHu$X!+{h-e6UjN4U7>872>29Syg4FYMf5$pI4i#(voRuhQ7*5KK@Jr+Je@|I$ zh3>j)!duIDDjJM8$ep<VG0)V&8r}R>GcZ+GsT~g5+B;extKt#rTKj{<PX#k?c`7?7 zIYP=yVnk=TuWM(nctjcEBh-r$?MIj%Fh_SGMOsBcmRnK67mL*&kB%V|w|&$P4&Mr* zM^-w=fAx)Mr+c~)UG)3c22+1b+FKG!FZRJnCb$2p*jVg@nsX_+WM<njwA>xl>i16c zwc<g$hycvVEB&llSGCX;zv^pV{LSrDrh!(i)$ENY1LRsO>CExO$zEyRGk519adPEr z8NX#5tQC5CIc#A!-}jnF0lqTfcC}qoufxZ-kPET1XlZm>SVSJVM0@Tz8CSh>d1)Oj z^x)Y&wOD!NJp_f=C3%EtFP6I9Ui~V2T?&P`amu>~Kb5-q;9T!2dvRE&M*N!wYiG=* zkRR%~eRi=bGFAO>(bocVlv}X1(8C_wy{#3hvAWy2;Cr#}nK3wX_<P^#-ArcnTFzwV z?Cdw-U3-2128SVV-eDEVS;*n@v-q3i^6d-S92a&|*j=&v!_h}A5@Xn1{u=!=*oo8w zHOJrGKemY;sAxjVZWfJg>U#{S0Xce7EpaZ#ILdZEBAR0dyJy#MRwJx5GiT<giQr7r zjzrtS<_xD#V8?b?5)ihX^`rguEu;N}vzdK^^4oH4``UvHwy*A+lZ?~;*rl%yV(YRl zGuhu1!SeqW5V`g5tOeIg7pi%>GoHrROjm7QH)@6G7rS5Qdo@3>s~-ppzok^2sS~j+ z6t+0zoJ8BcL%qt?QrQ^6t6mjR_i3>(x7>C2xEAzU^-jqrzIQQl+q2q)9DY#ANat<j z{(_(S<j%R*j$wzow%UwGdV@&s3a9+!9lt#G(F*^J4;57*`|3>F%JqIT{v<};XlU?G zCi^M9>%)$g2^_BV=}`S{^zzJ@y<XaTzy4Pvp5O#7x`lA%*>aT9XVF96%h+Ek%&QMm z<rJ!n5f8biUj|)8oh3i@r~dMpxW4Rm6@?aGKoYnXdtF6)`~JE8_~kQ)@@eQ~$A|gB zbX9msrzNeuo#_4z_1*dO?HlL@HpY%+EW>mj_EoqYTEs*Ca{s?Og<KxvEObuIk#E20 zkX}XX$nyC%T2HzW)UhDN$7tdT%txb;dW{J47flc?@cYUPl-T^umqmKoW+b!1a}aXK zEC&5z>r*=-?f10J^xJ4v%-l4lz`6vf1OY)7L*+&Cu=&a7bGhbscGl`B%o3B=<FQ+W z{eP@}*$;F(jMbs3%4~auJJ6ySoDdO^XVr$2KmRF`!dse?p+XZ-o~)CJ<47SvKZXIk zBguy%IR44MM&4dCS?b)Hn2FiXFX#z!qk}=~%r`0GaV$%B2_xqBn;xXHI@L#N{>Ibs zUw4mGC`pb}T(!Nd#L8{?a>AFxXSDgQpfOv8cRSMADON0?JY|2yT`<L<QY4F5VgL6y zTG+)C=ZED*+=A9@75+8%)LI5VbbUUKzJtRkLXEGXd3nW_d}!R)DdSPP55$%gyM!Z! zECW`cPB|j0d~B%@iG1^nYrE-eF?4+%$$e}`iuO*ZZXLJGD<*cFK4dma)PV9N*NDs> zqvKp9reu#T;vEEQ%9^&qdk!c-$|Fb%!$+d+Ca92)_&q0Np(>O25Gz#5^rLfj_?D+w z&%YelNOsj{r~U)9zgMVi*1w2_TY~40b^IL&?zT}MR#;ZzL_yrAcN+syk9|AUKXvUZ z9ap#lq~WDBhFK*L-KZ^=gCISb9A$1UQ@xc(9sMVb|Js;mwxRA3r{yHM_w)oQvpB`) zuonQr6qfZiv@=@KOew6jx+2k_-n%07w!zS3W~lj~_S<;{F#aer>@jYb7(*b@Ww<KN z?JElpnb&lhM}||#gnyn4Q)+TktH+o@KzVAJklm89#*8YHNv!BNW6~pl?W<<e7zuMl zKjFx5<%YU44Q)XF--ovBt}|d`%JXAlb!h<P2<E9NEzM{ZD4|?BTkIJkO?=*wnM#Gx zuTkYFhrSv%8=}Jpl5T_dK#c<$Bp<KLA1_|CKM5!$&kVaAAxaSXEHgjlIe+)|3Ga20 zw6Kz@5z8~36TlLltG4PM7>i?d=WK+lHA$q4MnzO1Td}jitzoA~zE?y*DifPmQCwFD z^Nb7GGydzQ)8pcLDvO%O@pA8}S+qf|P97_`Axf?~NS^`6JU=*xP~vZz(!Qtm-O0bc zup-=lU$lKQeow|9dO=F$Wjw2D(yJJ+%)7HL%&rKA#Bc@Ux=>N%brC=`ojgAw*xma8 zKz;shD}#|Gv?wn7i6);w8VV`tn`Qme)}IxuMarS3tEs3?VE#q)c93vXaEHmyf7+`W z4n!ySS{Akv&dTdLD*%wU+<N*AkuMRhdkjo0F@P}Adw1(cU@nu2{;?qqGEoM!25Xg* zeL#8EX30N$s3M_RsR|_=8OpvDK@_dP?_{UJK`6|C0Wv_R&cv0B^qkZ-_4V%y8N;8d zL2;kD2k*#LXlT1%AAQWTiHn#1EH&`y&4m{~GUs(@qsRS`ZV;L>kutYL{c!O+FkeEQ zElqN2c#FhCw^Pm!Q0T_~6z5q>*+t&iMvJ5!+3E~%EwfGJtHhrLT{5zS75#{D7#GS& zM|$qn`_)cpE49nN0Fx;hrH-Ut83eDnvN?{QP>qzn`rgt78iA)K*;|wJdLyq(9Ax97 z)l!9mHR<_KMfF(=<?{;(9!JU9HBhPP@JN9RCRP@6uDO=i)I^q;<hMu-g~dcwty|R> z62=8Q9MNEcvhF&?<~$+ZN8NL*7NpRv(8yG*C16ib(K=IoVZ!6KLx=;Wz$BBwfp`#_ z0+BMWyJ`AganRAakEQ8lHB*J+p|1o>;PfCHk4bHP&m@t?E}+55T2dl}!SO5xsPMf5 zeFbI~C%{?v6C)=>uru;FxEJ4GVx=+Xepxt5UPd(JgSt6xNlkZ^ptxas&j@s&dhL}- z^`#wn4C9r!hmJj1Y$GeUElqQc?QNj+a)2$KLN%0<C=pNd<A0K?Vq&ELzP3eH&C7x2 zVINO6=9Q!sip!X;L)ojrhA}+j+oVZI%ngLFrOsz81Z=ZYR^~)`CE)<uul%?bR1|}W zf}~*4PQrUM)BgtdYZJrQ<cq2ld95t<o935cg&1XD-BkBqW4AwK#C>knJ+NL<hLxn| zNUCY$*x5zB+glmEP!Q~sosR^+=yT#AoAo(@4wtX4{gjyx52%ezl{1i8a}z31<#%C% zu%)UlhK2|e>3U9_PWf%-zMX}yB-BY#eIyqmab*BM{^-NQ$yVV3i}Huti|o0TlrC#0 z%kswOu?oOj2#M{5p1of`f>u)6{BhA5ZiR709DYHz&Ho#FUJhKwb`n}J6s=b|opnjG zQDr*rVa5@4-H%&MJA>E3+xR#*tQ5#ygIBLq>)l-5_9;aeE0ZWrE$gd1nYZJDLEOSl z$u`&44y|phHcU3Q(=99a67@W5ZM|48O7o(qT%l}vYo|_J0s3J#(0i`<)G%$aqM-Xl zGKH~biww)3z2wBp(RDvA^WZ^KBfSHHTLB#-eJ}WCRm1dkSFfhZOs19Dj<%AZjXmvK z4Wcy8&o{HG9PVIiDQCuo#Gbfyme3X2u&IayD$$C>>(?XaPL*Bl4|g|i?oD%zZr5&_ zp7RZh`C3O|N?*LHlin64@KEa(!;k5$i_dOO>HZX+*((dWxm(=3!H2ff&M>8!Y%a-p z5}!qUZEM!L{WfPeqQgH`Wc_x8mp}IjecZ^`fBb*RQE-{@HNCCL$S3^q_xP@6m_SmB zk5Q2dK>CKi_Z6<p^_N^`1KE5ubH#zu+6z^g>Q{UF4(z&>seS6>mcilQgsS!9MIIM9 z&g6V=E`jOGY__LZu)5YIW{kV`ws^NjfyTuK=)?DZh-~u;AxGy5e%eHv#MV%CecZ2{ zmvA;pYn74jtx#Snk{i3fCoCIp4%ncl{Q&o=VFY<)eUFc|YIlNst)<22in7|jgweL$ zv2U$V>s_{VoWqrnR(jok3GDT|vjMHpgIzXV&+W0ORw%_T+j7e5{)|@oxBn83_|8fD zSCr-dB~Xb_<{8;Bk$A`|1oWX*)aOR6OoZi4N><g!r9L3l-|i&H+~*m1t7h1SbL9Qf zt|JSdJW_lyEx(Sr+Rt{{?DHV%#r1F+H22V4spy{T@r769o_+LV^=i?S{_wXW^Tcll zQt#iIdEpqd9PG#ZqME2^Azkeboc!nkt;D!ACJBD~>51@Ln)h0f-Bjodu$jEQ%mx8$ z&l6HAJ&yM%^2P(ymj=d(XI9oF%gQVC6w2+7f18^V!PR_xirzMSB(gcXqT=QI_qM%! zYwPe2Y}NvJH#9c2!d)Dk?Xa2@JGIbZ;%~&%NbadO{YI+$J_Y{;#7>exHqSO+o+Q`y z)iYYo4IJ#7z5z|Lwwde5%&jy}PrG~{*}LoM4-1|Rzv43YiEnDl(t}G&Q@iyuMd98W z>g8HdW^5vT|72=2ly*n-t0dKFucBzH+KF#`eDZ!?E!wF{s!tt@Mz_6}7s(aBJ(m9x zJt)-QJp_D7qOT{KBi4y`tl3f}SZKYZ+rlFVb<(u7;AEO_b}Qh13$1u#+r%o<#D8>& zt(QzS*_8cxyPDGk&K>~wwyG-EuWcd-iye(xWKT|2^55DM8O8`$Jwh|FRbJgrBV}Cc z`~xVEuzetF)M#<cyDz8?*wa^Kzig9ZIWlegDb%nC9~$UwgYaC*v&st!e`ePUuvTSH zeuz~l?o6L%Rcusc(pQIj%UpL3RzC!OV^wo+tG>Nz-D4>8%Q4N&w!@#OFwl`ow-ap0 zWqgI)Ipn4hwOu2J;vdtw(R^s0`sfjqMMB1f-WDG>34QuvUuv0-MxfP>EBCoctgiuY zAD<wOcdRc>t=;<&<?N(KkILuMgL^Oy@No<FuFlkyUO#y(Pk-0*<%JzSZWi{}!g>8i zz_!mrFV>Q2ULpHg)mUKUDehR{3q1P~E!#BgeJO}ujQ<_gi6Uo}Yz_ar@f-D8I+cR+ zm?u44M9Fj9kBkVuJINfFw&CRvd=I#Ftk^fcxO=qzW5w>bJ~BPw_naohX8X4#N;KFd zgQcD-hBAHv#ZKe;pSa}x1PbhoBezkd(bjYKTG|I%u!m-uaf692>Kx4q)M!SF-LDk$ z^cdEOa53}pZM78)TToCmdMj2kLp;$Q<^3VH{nRSo!6IhM#F{*v(WLIX<ZkhAo4~!? z7s;fFKb6a(97&K?*Uha0Ne|htNbKiEPT(o#V*~b&+q$@&EHoNhII9AFE$U*v+)k7W zQElpCKa`h~Mp`Ijf3eUQv^F|A`!VVvcNSk_Ejm?f1_b)-4)Sqsy*H&oVW5^ezx{V2 zc+M53edfmtJ!LB=mXtzUh$H)2Y2wqomgq$jQP|%T^o&!+k3XOCb|exx1}B&Fg5QG_ zLssO+tJ;DiLVbH+XZe1FtLFxAO-ZgLF?N0}-1rRGz=t=YIDj#cRdq<I_9V%RW@N85 z7A|28@Rz-wr)8J~;<w`n*|-o>`h?;?Ys;pxEupvtCdJH5y!-_JlgBdD=#t#W`UIR$ z9)D^TeiQW*NR+ggRXTEI;G*RDvxFTe5c=FQBEChAP-(FfX1!6TawH)1d-nHwrolXz zuaLVeOs_09<xo&mi;4JJKX5+zG2~7eF^Fk|kK3SDL*5z^CT*}fg8S3K1S?Tl`G^~a zb-=(0Nez-Fb$POGys#DbkH4Gyo!6%;>P4@C207f!Slc99QtUVHycBtUT^UkcOP@Sh zf8UgD)rrI|{?1GFO;E=4ra55`hUw|iAeD&BJ1@1ZTj#>jPNwfTdz1IM`NFyW*SV(h z0$tV@$=Jm-A|*U>B}w&q6Zu3vJ6m5MgGxx{zKWc0axkR-w*KR&$m}3P7m1`fBEn7j z7GYWQtwCMPKnvPx$m3j&)OgPQ#uOJ!6KBgGBInl%r+Uc9g;S}KR%j=fk5xz7*+^f8 ztBWC}etpZ>{1`}q!LlHEB#?jFd^6_uUn`3Ll*QGHVZ+=nKtifzV0sJ-amKZv_$R_c zM@Biz#ag0FuBOeB@l5!>qpkNiMqHQ;8rz!s3FMbQlXng%KwD39l>GR#w*hTEE6-AH z3V8s?e|cfgUlbla1MIl@y!;a!_<1DXtIsH>TprDF;4;j>B^Cz-I>jhvB7Mv}{hp#a zD9;3k*Jlx;I;dJ5CzzQc3kspH*F>tP*HEyKen(vt8S%T3$u%OSF>W>sH~y7qp^5&e z@)9*7i8<NdM9kh&$MK)hUIKDwp|0Ci_a+-EifV9nh|phc^hbp>x&+&g>9&9+G~pxm zLYM(5P2<vOr*m)R(;qC{^E-MZUEDMVXxH&m`F5l<CeGfx=a1sQn}}sO-f<_*Hz!4! zh>fMptb5rRvZA7BtQC`dT<6BmCD$Wn-0O52&Asl(`|yg+2q1B#Yna9m#75IZ{MFbG z!gxpeWFjr$=%1^&37u2`Ch*@9f|w!dlK#B84lK`-U8`?Q>7;UQ=XUtcKj={cydA8e zQBlYMv6_3i;sZ{6G|e;SzteiA-5h1W)!fL;<xYz8K_S|AWHYUvJ4sG@(?6nVMuzq} z;@ps1P{cdG&N6r3Kd0Lw!tIDeQ(%-{Vxi&OO1m#%a47ffh-+;!vE2hg4?O9p$aMzf zN72qA^KhqB=oRJ<m-RdA<krpaZfK8+o@c%q=5bR77(SKjp`2%Z3so1ZxOEh;pa%1W zD4B<$dX3~6(1-#cMLeG$mm?*6pOl6df{kT=+o06WD^I?P^ZVGtS)eu#PKFAD#7=Gg zO$-`o<{PFr^sC#;Nyfgpa_NNW%3Xbw!;@qo07oC8_b6Rl-<0&Y;9;_MfV-XUAh0GC zJec<CG~3JF%{`0o{7sP`y;2yPeO{#M<U^4Uta6$r7lo1#yDmmar!$6tJ?|*JiOo8# zfITHO^`jO&4#27*;nPt~e4DhGK;qvG|G^NT;9DgBdCw%*N&5ym+_4vaSfrz292v<B zau<2uK}W;c8s+X1)g<jparLZm<e)nLD=<rHtli_DfFsD{Z(V|*SU?QWWE`32ULcxG zQMh}r_7pJ9CR=7lUw}O(?x@kX@g*>*icPV!q6m8E#HgO@QB8>_KU$Y#+pzO7#%|gu z2PROMz<8)&cU+<@O{7J?F2**crXIJSz^m$CYCA9Zf%2+Ett08%eYBos{ac^C$R|^b zLfP%Fq@1F-pFmM{d^wMq>B(!%9>4Sj15K<v!td34D-QiW;VLxPmYKiEQGck?$jg{m z#$uTfeeX_LcGETnXVCtrL_0Q$8(N_MfP>pP2{?l0O4+FS@vbl*e-hfj;_>vr1G)zA znRSa*Zka_a<pRSplm3_pH*2u1z&@o$T}-UXf)^G6&LB&d^P*uU(p*qRcsJH@*dHR? z5=;TgQv|;xk%dPfIE9KPy}e6tfs`uNRwz}^2GXpYOFl){0GJ87tPEQR##r(qKejIh zja1d7!0}@&d-XMtj#^4(Pgvx8GW`}}{HzTCFW}llwUBp#CM042S9vU-6P!Oz@S>5k z)Rrc4V*N%2%pLdIp)(O=aKm6_?G>^jATEE1@-R6+M>rNI_ua*~jnT7DB0o%t-v0XT z4iATbGYI%O3&{jx23^;}Jir;GUaIBHW|8`+|G<ABU}qAXK<@%lxT<p>eL_K*?86vk zD*(n*zT-1}y*z7tn%b}Ou6ST}Y|R>QIN_qx$08+Ls%CF}KnB=9_afgt6oTw%@~KHT zHu$fA+SB-3cq0K!Gc`X$<Y98Nz)vV&uq8(bh7Fe{<WP#^12=66MD5%dm6{4(Q5jyo zku8_OrAl(yNgf8iY1i>TNivOhYkRZ7{m$)^cKVyPO#_0!oy{aRbsC&>8sP$T*g^>D z#gnf0nE#67gQFC2sir%W6>QIVF}qMFh4fSAjhQHA-npq6@4s<MJ^?m#Z`eKCdPx+> zuocJmV=)|K%~0?uX&*&$ynHE*Fb6}}OTexVEhuzOgogdBY+!FV$Qp9qHhX~#be5@F zKMTk@kPHK6oBl*otth#Rwi9jdH(OCQzcj)xV{%(jP?W#6H+w$<XOUGETNyJri>l}1 z`wuEV^a95gN=B`?p%#p_cP5m)JDlF-z(G&AkgPrgr>k4bbkWzn;L`vLjT_M$tr(_x zcMmkC!&h+!1{u6;ofIP@`mBl!+tEDV1y9lwHM2(lE;m+w8W@#j`?2$7o*t`*O%Wqr zcrt{vV2y#KAhv1qKu77p#qSMtGC<Zog`P>I*z-MKp1}i(vk{RK{{aTU(<ws>j91*+ z7w0@rr=$;?gzgBXxD-Fe7w3?lkFx6biKWk!&d84|4v0}|{O*60=a_FqiYu8X6I4C? zkxeTB`JMH{zUi>x2_Y-0ze=m_Me<;}zlpECW!7X!i;0-;LYpZ|H8SI~AzvDzH<tUK z7l0iW+h5f43;;R*kEgGWi?Vy(rn@9W5O9$OL0EDr7gPkK8ziL!q{}6g5&;oGa_R0A z5$WzFBqanTrKJT$-*fNp`M&?n=iKL<MR%BM=9+76U<wlrtf^7fOf=nWNjI`INrrG5 z=%kD2Yu)cq4&k7gPS)0qZ~+8OsT4g~r!<iUX9XU&8~9>jiFj#B&C*nq*1@+#>wT=T zIscv7szhcu?T292oqt?-@8YuGloUC%KTuW*ktjTy#ZNh+JGbS0;t9VY5s!EqZw;1t zX9)XhalsMxsqvj>gh}8?sa?jeXKvtGb;@O6-*@oLSk6B_d6wf1bfCB-u~%FH+!BA7 zF8qR9m_y-u(roUV^)Jx`&gQzmPi)Q4U{I>N?lRql8F&Omi4>oqH!9u0BXr$n9R)$w z+dur0aVkm|#2yWkj+v2oU})**k?r2xWuS>QXD}AN3(_Fu$@{xGah)J;ROcicO435P zt}|vT3;Y~mhCwU|sadGIO9IH=`j<<tTw#XD_KS~~p6?+@dU&MBxBgOWz&tq6{cgnH z<g+)bBBM(ad{^PPHRTV9M3)*l(J$57H@~KbPKcI+)uO1YQ|KjhLBmTmoUO{U`*v-L zksj=Hv3{t&3=|D-hmZ}PYEd+q{YEY$99KX$ul^<C;-wn0{OR`x<^cqExr3&Dxfin% zcg2z_PYHi^#M~9J!P7e#4xjwCdJT?S5kFijvfrJr3Ae$sRQKf(PTSV1p&;SVmAAk< z63a}oMbbDSYFLhd^mGUZfxfZq8`%i58;^2S8fqmg%r?mDWu|!y!Z|M778yJD2N90C zRU9xBlr5H5(8YEI_}o;o!8#9=*!bsoZ|T!^!jU7|bvNj9U72X|&AqmNsp1TQ|IOBo z?@f=5hzKgOTg}jtHp<3C$^zBj;P#eD!G1CrHe&G<3y;ZcrecoTBUQ@_8<)&r>0$x< zCK=>Cd~dBOiN&oT5z0xGp4-yCtoh8vd5)*3tDfn!JTpkviAn*hK&$t`(&a9c{FV6g zL|grp-C{D~6O@w<!z4A9c4|@=L|erMA8ySNaj_KFdZZZp<Bv&c_0_F3bQqR<WNTpV zj9WL;jNr0AAv1RxZEjV^AYieHX$J2cl2xRL35u(EuH5gJ0fv2Fsncg)DiR$;uX^dN zAWvjAvv(1L=O<d+jiB~pzK;{C?CJP;(>t`uOQfqTH2qPxR3zxT5U?j1%%?ABZ^uV~ z6OD!^t52__ga0>poBs_0cPbLo{Sj6dff!FD%~5G<#JTC9q`E?r>v~SoL;su!`VWzt zhQ2MYRCLGIy)G}&PAOu9&KfF|Gs0Oah(f+6q5N@y^taR%(kA~5Ud6CKY#yioz}M31 z63CQXm_6(maG8eb^7GE;dqS5cQh3`_gMR~@H#(8ud;F1Q4PBV6UaI`v7ZYP;3O-4D zm#kp|4_e%|dXJWrQdhWc^mY0zTGFM&h$t+MY3bj~R;eW1<;6#?)CDd~H0hm3i7EHL z!eLV2@1I}-f_SW$XtFO_g~PqW^VPQ=@7>14Jshm3AW;Qx<xXJifP=8hFY>BH5hT+6 z)*()8bnGCsB#jDEuea+lgV0i%+{<j&Cwwyr*TdHNnOp2NuvWG_mF)i#IIN`%#hd6~ zm^vGGn%qb`<iSACvynP_Hlx;EO=<gp%7ULMY?6W5X$K8So!0MP2M)+~#YxTRJk#Jg zn`&sc2G6s}E>3Jl;uGOuRrw*cxb<myqCBwmdJLj%Mt&&on?C#={3iNMQ>O#iUEWAF zEEC%Y))d}2UhQIeA&3I2`5qfmh~OEpYIXnKv)}INPJFUetD2H2%^;DD2pJgvqH+zS ztac4gKI1|7RN~PyR*8*0-CMR8-RorLxZ_mk<NYNNR4sN!7u-+*RXFGGZ3RqZ6M#Fs zsk)Xvv@Sv!MMxlgR7NCv4*;0K`Nrw(`-=iFr1*B)?kxzcyc<omb=krGBs0wO^>Z1n zs-SNSG9M2m6AZH#uX`r_C7~`i%GM}<k@@N_ja{&GQG34~#^+x1B%ZfMl~-%|%TI(` z*<=}P`>*53Csis)KKYqoG0M*A-GY{1uyk34?x`rT`_D7-$;gWL6<rjB9t;4d8XO&# zdan^}<@4`U^|HhcY6F})QTiRiF(RT>RQG<g5)VSDFDWluAKkA8F=nD}`M_ft{MOgJ z(~ox20j{9I{U9VIG>I3i6$x?)62*cwExCHh1<fTm^5PA3igv<B+aR^CDNL<q1VcfV zCnjsdEG-H{a9D)g6VI&KZz<8X$aa$xH-GU9;2ncjYwEWFK;_qutPG@egU>WMOGAAj z0f5%NCFnjxIix)15^iiA=<bAtl!#1mH1*E~04gGg@TU{uHk?naOAnr16%8D`&7qJs z@#itIPcV3@U?w;BLJ9`ZMQ1D`Gv11cbgmdvv7AyXF&P_|P5JbgA^z1T067LR!xD^m z0FFWrMNR7Q663PfxAb$yF~VFM;HlBsLrp7lt(B~}?BVUNBdo+wX-s%@b40vN*c|tr z;UUXl^K&&ozFdsCjL#`R#Z${<Ylm8v&QG$~B)qxjv;Dw3igT|Yv2j7zq#?a7#S>%* zqRuR5EW#`_0pv9EZU0cLS3uo7bvQ`n1KbL4QUZTF8LF@KhN~hm>!UWnO$<<GqU(ub zs^+*Jef)3jHRa8Nr$QAy(xU#2>E#B`y7dLmnuxcUD0rEtiVEBw{-jTn@$R66a%YeJ zU4F=qgptgw{vCDp&wLRvCG#77GALfc&xk;uCd>{etcN#Z<jMSH4<<XwD+eZB(j+pl zkGdfEjH*i+=SYdypkbW~Vu?IMeIfXaU_j?cCcRdpVV#`H$umVY5PUWU?<^co%TuLc zO@6rc6|tE{5axOCs~J73miSI3=$P1S#pym&D{<ZV)4hkxc@__V=l3~ceTEs28#iQg z<R<GDH}3dI{<fH1Jx&40c=PIc8im(bDM4neFY#F26HD)0(?5NL2TG&T`?q<fb0H$f z?|ct&e~}aEvNk*QhPHzfW)x`bP-99HAJo2PQ}me3JCj>MwT+bUVRBfjKYG{pyo_ME zUBj+yh7BU`Qa9UP^G%`#2$oaIUz^#c%DO<6Q>_VN&dLkVU5n>1#`^Tp@(NwV-7Un{ zfN6pjEM{x0P(|%+-x4nkvCoS?x7Gs$D<KZ=xxsBE%XDx?0e6ZyBp=UR$CK*R+Wgg; z6R4ly7T1P%zV(<1&}`Io=HlsA(E;j>FutlGYE~-=`tpa+$Gl;m41+LNKAu``dE<ev z*Cda%8^f#(9LS06zQ1EEl-x{|rQ2$})&VRXlK(??@JnnN^{2)YlYfN|M5~V;2bAmg z1o=VkQW{q3ZicPYkIIAWu}*a6<dl+ZJpdA+TNS&;Xx3(46k)&khl^Ux&Ue%f^=Vqw z!3YOhFV=Lxv2DkoXYH`%6f;8?vjV7%_!y{N<(cC_ZM1oCDdCF*<yZP+@?Q9ci-#&G zi9YyFT7wF-5-jKEW)XZKfLK9(*6GyQ^=Ma6Czal6=}KW8k>b9g?75|HtNch0EEYcr zjl%d8r-S0kc)0Y+aC%SgopU6!;nT?JmskfH&@V*UVrwe2$L+Q;sFd2f$msnH8G(H~ zSulw6oqnDs1C`uAj!YT?`9R%;2_ZZ|2R1zcep142OchAq!wGx#H72!-rCX_+6Q;#_ z=-0jDrUZONioxOeF9B3M;3l4|_ON_W1v`zroy)q?Tb24bVQ#F&dZJ1HpWB;~W5e7% z^(rMe2yTh4>I|?<UZw5sTNTem9QkB&qG9rd+u?TwY!<j9M)~{?Sql#mis`zE3(hu4 zkJ>duG-+<U8(4NR{6s={pzYA({CWc-_@=!uzm@|MLVlI|i9NM=6RJydLocQjzd_87 zK1<L2vi1mJOn6`=IQ!@|YMU$2L?OW3<igjJ6qL$jz1}FM^*9;6DjX)SrcLG}RS~G7 z`Y`*QwKk0gGWB*m-*kYQDnvIRlNqCSl@z#$cV{*3e;nWDQ4E(TsH<W*Qq$%*UP5GW zs+Ij{6t)caLHnjP8wLgZsr%Al>y`g2ztWI@R-2?MD9F~|{n`-~1q4+37gqRoT?&=v z!Ow0Ck<EsSa11)qkatHih^pDuv##1+jqrpo>?Tv@0>kfx%#eBXBN$bwb-o2@tSL;x z@gXk$`WT0XPp}kM+W6V7l^^&us*bgbv}{AB8+A#a-B=;OcQ;z;+#(KvIKHOAL>q}4 zt0mVW^57^f<c%NKA|}RtQ(K53j;|>X5rYtli?T&tLlYOoh^!2K02%F<I_BZoyL6-w zNF8lZ@8m+cPVOCJzjz3(CPK<SjR8aF_MIDcF-V<OTcj4*OvVj9Knhkj!f!R?gB(c# z)23WpLz9GyyZ!A)->V-GDX57Ufj{7(1(|XyIZw<fZ5JH&#QVn==_cVMTpjbW#uuG{ zw3BQ;P7^ny&Hzl@Q-`nYn1P@_5Y4CVc=Qdy4R|=+o$dupNdb!}Z9W3=LTDp=P&t<9 zB)eSRlb*IJn!rKXJZ(B0A`7*-gfcs1;|w1F_OF^$cMh)kNy;7&eWj(0qb_4ZBH*qB z-KA4ZxCJQT@>O;N5{2o};8UGW3(|Ev0Q=XhcKYBceY*gRUM;jP`HXZ0kbFBbEFxSG zAAIZSwh+f215EHT`Pf+#P&S2~ORd!zP$Qr;RxSx~P2~WjI@j#X|M>&nhk!ryKrqGr zCsd9ZEqoir1PC!y1zD~gI!gJ62@?VbtIM601gs;oIj+K1SHZ!nmjEe^?9cXEV1Nt! zImmp`a6PLe{uaeeueNeG1O1o{z`UV$Q!C^|Rsfq<Wb@_o^8MZ-jVKMij~mYG4sW^v zTW9U}?gfACZowq5J>S<{+e!EgEKzCGs!4izaEfQmz>Ur`UC9re8*!TZWUQTDs1X%2 zSg~zVI2AALt>dM!cSPyvh6XfZaoAY}1_hCbiS@b@XcllJrfjXazvoyTud_kwUh)}D z4<~Zm8F!u*+gft*Zc7D$Csuyh&J(>Misf(`9g|Z+5qu!N6v2J`qqLzu0-X${qv{eT zOj*C|Jn=u0Z|l>K@G{Uz&@pk@M>lYQMnXqPuibVN#PUrpG}pevvN*jQMRl|wF1eWn z*bY3E?wH`Dadl+cxZ+I;^_jK<I1X7lxoG<I%Dhai!_;6gC2Ql%YgmTQ^PFQ@SUOmW zH;t^YaqEn<o4rS3M~oQiGR@IU2O~s7+d!XbXLHqc*b>4h{E-t2D+r@lX){vH-{t{| zZ)-0)vO}s2aOuKkG7lh%Pe9L=-P4BXN)>1z&~ZDcGlKq4&|2U>6da<0j|Vw#9K{6( z83yIQ_n~Z10}hkBkma`uI6IgwrHA~!>YM;_k0^1xksZo!RGITS(%&fzf-Ce{n^CDy z|3I~Dg>L;jJ`|0_#S2M9QTMt8V9JRnq7B&cI|^lP84t>D_++1(J<hH3?2j&VB>18= z){N`b65@cmI~p_-+<$}xr^UM`(ntuL%$TqAVcs2rc2Y0Bx$}ksmbzOD<QT>QfD|1w zT5W=lZ8eog2_5-0V!6IqqUg<Nu&88Pz3U{!dyY(vcbWWnR18=`t=;z$rQ{IPCw{hz zw|*)57u@oRdAi>q14{*e)Oh_c0-}O7>bFJ%==H&(7Qgh2>g$idAj@!V4=~~Z{lyfQ zRo_u{s2lm!*+nMuy$Vuz{Aj{D%95EI{BmeyUMpPBw*j{#Q54rBapHrJQ?}m=c_1!W zcUyR79P1_dMJgPi-$Y^%X($7CO}_#sB{9v~ugYle<E%DOQQnvMK4P>%T++4|)X#3V z(>f{8uggV3{q(ZaZQb^<gAm~O(wO(G<c(G0Njt4g5<k3t@IdK1VlDl>Coz`pi+bgf zK3uFx!!|w^W{26nKK?S~XR$<i3+i2wHVvDR$M7uXZkrb`;df&>)I}2Fg?gzj4V#|w zx9PX0`5`~^9i=A`okRR2)Y}0EM2rYW!tbcvzo(1fFZ}`UStE~@7lP<I&!wd9r_Yr@ z1QIT3+!EP;P78v9TIKBfqZB4S*&-i)UuP&)8Fc@;vz*_8$0@^nnS?@zMi>SiYo&-6 zRf8ORT&cxUVEqaViZ=RORt%oZ*RgQ6*xSGb)lqAo_kx2VmUu|(w}Y`ZNTUsxT+Y$& zGKvgiCtVafsO<LU#a~xH4wkM_*Q|F{N+H}zr@FB_kg$U-rQYoHiZ+FLdXi5nyv%52 zcncL%g8Qj|bg;z~H>#H@_ZZLH0P+5#SNjFwR<{`QEnBMfb^dF`U4FlM3pjg)wRpty z?su8PdXTJ%aj!}LBm4|5KgSa*a^ZVhk~6bf@%!>8;nIEDJX2-?B;M)GjuUckK+d^T zYrid7;DbO4=XE$H^E)S~ruEwTrXGyfQ^d+pgr-(yJ2TNFi1BA3xFKwcx{WA{fUxOl z+uCanMR2fOydjqsYFP0NJzqH?tfq)fGtA?GyN->6g4<-D(Ug}9lv_C0Q=0mujg%A> zlgS9l(Km(OcC~|osNLYD+8ptK@>uV&?Ta3wSWxh&kv1&(fwt5Kg!=p!dkJ1pKP4Fc z7PZF#+hfDuM!pUKrBa^bmY4@Z3Oq}(jbuG>2hYk)DxZ(+#PtGDx;`0?YN*Wvcc_Sy z{OzXD^%zke3%5Q0dzbYo;Qy+o24<JAc}$_<#!G5!FPb`BavER}O^TYKIon-q3rgLb z;qF%fn3T6{V<{!nMADzB>3%j2zUYzxMSMfhyTVIc@26|QrZ4BrHYolO(D`<iwH(}u zQ|bhjcE$XpJvHPZCsx)@c1QMb$VXrnL$9!%gf7BH*VSc)FN~65KVG0P&smTD$Ynfc zr=v;Bmi}_H%Z{DSt&~|NCop_Uk^9?+S<Aun=q5n!mHHpbGEza*o}*}++!0JG#H^nn zVSTdXPS|Vneu8qBCe(+OAAsbmVck$#U*-VpT{*3`w~wbV1v*HtXZ~E{K#Ky6GxedH zff9iTS3{P~t)nqABAfAGHD!eHtL9CJrx{pu!TFEbd%eBJc<(D5S$MPDg<cUDey9Mk zCWS>f49S2AtRe^;!a?aS$!7h>=}op=en|y)5R?_H_&7vZGr<JCGg;p<K+VdpQV5;< zOK_O7+#ee`0~|mUAEq2{p&^cD+xU<8Iwg}1$abEZAb+n54<TDv?d~0l*p(YorUpyD zZ@s@K90->jMY2vt8q{3-jATt1jdz~DHi67Y_#E@VP@yzfy7YjKTey$U-Gg#cWH(|U zkcs<xan}~T+=a*&39Sbon}6EdwZ5)$$<`>d$uGFmFRAsww#`<HI>9Y_q%`k<$j&RW z);&AM>0#`N7R2e5uau+qU%F9|C|G9|!HziMQaq6`0rPt9388qfsx?8p+Ix?;IJC4X zFC(kfGwArxthqWDX9V=`W1C3-N9nl?1Rp9&5%<gZJ(IHz2Hf7P*&F5c94gk0pzNtT zQbV}7>G-Q3wV#x5-2}TtyhI+^T{ln!mux4xwstK8^03C(nV6xcfbYrKaJk{H@AO`& zArdvNR~X431ELPG%j<f`5)X*FlKEMq_1`jmfSl86@H>@xt*Sf5UFZByW<8e6+<|N^ z;eDHcISEARmFl_`C&Wj-h`dzC<ac!4Z-@9ib&q3v^MDPAy5m2NB&@Z*HUpORyr~?c zl>kXNv2|2AFW?3~y1Me?=OVz(#Cm2(_o@TdmgMnopF|lA;2Mf$c67<w5JwZP=_%@3 z83))Dt=-|ntDr?qlfPJL(#6t{4>7-!>vIk=fcafj>Z+#FfhYf?{helHIY9U}44g3P zGGDirILHH3V7^esdFReGrEWM(t7Q5qf*ml(V`MFr^G4T(K-J0h4A0*saaH8aEh<H^ z&tf}%q6r%i1?OGmBq_V%DfqHaZ?qV^XJHhud=u@pwP<@O+6@!3QHLy*H{-JFCS=s5 z{#l7kC*;d_2k*SNS$qBcG2s648ta~4@l<-?2ewl0*gUM|)c{x7+rD_F`H&4*#A_DS z4=9Cz#iK9G{rMf1%lle(tu1Q>d^oCwc{<{JQKK8mdh3;H_pw>;I=!G<0wjJo6zR9Z z2AGOSmtonu98mM5D|uh{$b|A<{%@Dud7C5$>>2av{po9J;0~?(-EvkMYcQxk9&({+ zmFb`j>Lr6U*R@~DSU#_u$vqkBRyIDX^fG|>ylJu{#a3*L;W#l(S-O1YN4V7@^7h_u z0?QuIF;XaH{dxuB^WyUa4DJ>KG|CNcVIg)00K{}>Vm=B2AUU<YQ++G|eV_HHP}n5g zDwm)tA7bh5Sh}snBk?4Xe4^hCV)Miou6D8{42}xN@~kmxQ|DhR&TCw3tB`}pyhNE7 zY4?h4vZ<cx6ggwkw)Jk|clN7EUw>o<VbC<HRD%jgA3S1e+qWgWhl0MXUVl6t0kxYB zH5QKEfRZS-^qG3Spfhln4+0KLkFbpb&!JbC&)2aPR%2vO3W^pEBF4~*ocl6>%Tp|U zai)!DMAZMY{%RIS4@Bj$%0Cz4g8;}AEoUizi$kG_$!^4{$RJb{^q3ICd;<YuYf#Im z#35(y5(aQ21+y(~Rj7&WGBy5ek9Bw~Wycv2>S27z1krW_{yvY)i~x$1W`_2`?VXF+ z0p5H`Y}tK>1tBE|J8uGr%k$GJ!KpQt7zK!?<4g354d0IQNmpaVK_W`w%?k*G__wEh zv6T@f(zoY=9IA}?D}1fZ%ejC@q=-4a>3;yhkb`$>=l{q&o)}?IPR?dL0=<pr6@c_r z9^D8UHG>F2y$*vD^n^G>`BvW?YC~fi+Nbgni}xTG(I2}r$a12N&2=5jdbFTgT7Rp< z?*G_4cwNuFaBWSZS5^C#0(*mc&*=7zC@<lGvbo)5zN_6lr+kMtCDCbw2MFgZ)1S({ zQz5_74IIS$J30g-s0UA8zUHQGfXSCsxsMQvIx=<_*__oq)tC1J!l4;Oi-6@#MsUTL zP|$2HA8Uw0fDyTi#X4MEF+hjTYtP72n_@Zd{it#L`~t~=L(7EeC`(_W#sEydTJaJe zZQW1|VUbAYU%@me=P1|ySsXkm2wIfy?VE8Sm+|`d{5>fOL;~9cImhb&i4a$tYfhw` zhj1KBEyh*~Nr1G*@$+==b!?O>d(|uQNGuKnETh0XhTq_K!B63=I*qv<WE<4nmVBev zIV$Ye2Os+Lc9xO*gK$`o_{TdV^!u`cp$YN~Q3i0kWGCTQ;18)#ti~m}1g1FDJVOy3 zX4_@34bty7-@`{~<=n4GseGH}0Mjh(xww=OE)A9~r4(neD_Ihc?bAE@pi7$Y6h<vj zZJ!`BEfM>`pmt<KQur$}b2{b3EM@rL`8m;H@nQE46#*&nVR_QWrjcf&p&6;zK79fE zFa|wDEYGR;GpoXIo)s3FkoEa(b`0+KZU|7E)Ba!rs7N<}FzV#9HrVi;1vF76&3e^D zJ_DfAv0nf6g!~%|P4PQ2`bMP%1WXw9^!D=2Fi&KAl!Rx@)qVtWtE|75`!N8UiZ2Lh zj5f|aaIbz5$>?1OlsNo%jWJFi!vIjpZF`>;U_t3mAEvE+oZg|nUh6{^-{-+wr0In) z4a}NS=7U%<LU)ZTh%FI&A@_&6G#g@-_N|?u>Ey0;|Is8Hj;byV55tBul2BQfrf79( z_Y0d*YKT;s+%BUtP7z-Oos7j?-fbau1(u}CM1plbDu8J2It@z6mZvuXJi%fd9WZN5 z0JGdpOm3(>csxN5q$EwEfwkb<wz6?C(u}0(16;p8YRUBq<RmtRK5BAUkyWDozj&<3 zYM=jX8f3U6I?_o*O%9O6YL~mK7D%jU%3CLE{=k5Cibix&uvUj;C@82-%tux<OBGNp z%5}c-UFrE6-EgFf=smTjn-Cho+YV&SAvBsEtM|JtloT9Tb~jog^|hv+9ouk`uBZ)> z4KDR7Ci^eI($cV)yN#)S(od1S{#3(q#5uu^8IE64`1`HKdN#25ckAAy5Qov4-nDBN zbKN{uHMO~G7i25$?1dlmkzEw#boTc-u6!fA=vdU&9S2$BrC25{LFwOOaj)F^DaxhI z(x%el-TNs%MWwOXET*VrdGu3s%M($4X6!`32!?}xw_-y59)r{ZbMNSf_2CFmba(`x zn_>HsdwmbZbN5+j@|DV&{WNekKu!46j`rwNEND?$HVV&9JtWx(3SwAxmQ{j*hZ5(# z0oO%93vxOwsnDJPc$wZzZr8&o;AQ27H=Z__(rn<MsdT5`qF#h2fwttMD=$vOFo^0G z;XyfQi#>!WSq0H1GSm3+(S}+NW>`9w$99LwlI%0ps)~L^pfgjiOzZ>OlJAC;oNWEI zCiea@h~mh26l|2wXOPKG=1%2Sux!QPN=Rt?Ua)L!b%A|~MbY^n@!=<l)t%}aHaGff zndR!NjT4^3sLL8oI(w1-keM@`iDxpFgajy?Gl@}N{_tSruhiFmS1>7J;`qUqc3QT4 zx>6IuG$cBUa&NfDB!N-+(q5La9SPG~6A1YBk%S1Z!*Og3j;T?nry#H)7(86Yu!nn5 z5QI#x-1jw!uz}HFE_Q`FD6Y`JX<}W@*X{=12iF-4C%-bM??l2ZZ4X`?tJ32Kf17&o zf>kh>f?(Au*DBz;>0ZdQ-|C|;zyA5$K)&2BFJl7dxqdigMngR1me?xE?W@3|*K^d6 z8REg%tumV#B^WViM)Sb-)K<VVfUatR&|S^hXG?Uy=V;iB=GDo`G5tECRT}J#LpaNo z5@XD0iq;lt8IO)*?e{{YN_8H}r3m(YS{I6_b$nFo=wt&HD@{bfJ5W3syCZv&nB^aV zFPcUy&bpSB1_TB_Ij^w+r9}h`d9JzCmfY-#4Dwj0%Q|P%1Wjq>#K8O)rjWA~XlunI zFp-e63}%kKn*H}S8bp`5-#>01t!6@bbD7t;Th_G^+;O%+PlTwbdN%^5^>vP+>nTF1 z2LUv|PqxpDX+R77#L-z{o#~q3Mi9*BtCN|Fe7vYJtoug~Ixu)oGBaMZhsVy~S5JGi z<_LY(L^s*xo=t#9;jeVIX!%ar**GbLZh%&y)kCGDlypA%lIPoWQMO*-dBRq-XW*zk zu*&VcS_WDE#4Qe>m+2-Nno!h5!0M8wVHing6Z8f<@}+ln8WM+A)ef{1E9W8&b8pg( zy_b0}9<l<Kf(0`#{$^t%O^oyINWe_4EfBu6PC1xHL6Qf@d7EKbDAqXW8$Dk)rXmG# zLEJ{Ip6(dHmfn~21C0;BcsE#a&l8)@lqe}+v~NJ^jM3|7eLGM=eXF9{<t0`DTJq3C zSd%ak<TC-S0dvjL5A5(vuEc4mbb=LRk(s8MHV8J7VL|Um-#({_ngXz7CSmsF#&@Rv z(9{>h&#o(q#S)+782Yb!S-2Di%a*KHB@4eYKhULI)i!RR_s@MkgJhcsvvroqvB5_f zZI5hNX`Fp?z248!wC{FAt0oy+#K4qZ5AT(XeNZrePxCq_OMk7$$BRi;dzStcou2wT z1o~8~kr@_^QmR;&k?g#?kJT)YCGR$;HhTt9C?I|+$3rgTIJWxe_QeRUf-28O?IIq) zl=;ag$!^Oa7L^R|eY>RyRu<wn0?|jZ5eQ;}_lX>(>Wq-;0i|@>Bm}7>q#dU`Uy%U+ zA-gxl{R##SmW_Np4~C#fq->MNw{x7n+uu=1*3X>+!l9ksWM>O@2eRe#<ID^rTor&q zUQdRv+L<R>0IVs#UE1_o0X)qwtS{@uWViuHD)Op#IzU$gSTRsH+ZHB%R?M!hkZu&| z_&epY{=AScqRY|il3ldCO9U)W&N6p+YJ&@k6*m}<alq4wDC5%6=8oPw0Jn@HMxU^P z?>E0-chfd}8GNZVw7zEZ*yi=s4W-t67bX}-j=i+VQ7J^YOlFqUHGYQRC}Rg7Ehm-; zilv04riPESklw)|hG~Fh7PPOCF3}%HdeU!c^ny}4(9ad~R2B<L!!DkEijY8N%Dy%b zAp(49dd#c^?K=$wG6dD0nl4WcYADLr8Q%SV^oO|U=1WZQxN(Y{$%Ft?7_mgpXBnZD z7(mwDE%bd{Ul*HyEv(O`<#Xihq++fWO{VVt(|rtzgy_3M%X8_V*&t+;1t9Etb?*-z zekSkCOBMg-Xqf4v@XYd5#QKrPJMzw2znMq-EY$Ttp4X6kq46#EY8X#l8>-*Z+)m+{ zk-pHUugknw!!{EC?UX}mPUW(tr!pyK#wws87#2g7cu0$ta|V9+QX!B4#G5HsU((x3 zRAxg}QdE*lrW%yb-rp>@mtfL@dXdb3#tw3!USz40rLCD2)EhYOp15%z<irLt&E?!! zVV0bEg+u|kEMMuY<&grE{hqAe_o{}3R%H&SvOst@A)xG_mt#X*+xAD$F3;pIAnQcr zG?ST0YLOYbrai7$ZJySKo^`9MO2ngVs;yn(imhu6$P7K)w>nIF$Gq|dzMf^Dav-KI zYm2%i>I8@$hL`+P5lyelEVHQu+~X&#qph+vw>73hCC0L7k+!7?_T-PpY!c))08C z%Wg+5y!S3xT6h?KnKGCWWgGOPm3u0s!U3xoUy0A%!+H-LW?nQ=h7e>m$B#Xq-h@(L z{WB#!oIp@fO||$dhT9uOX_hR;I?Y4k34=GPljXcIKn$U0A`*_Mcnu1nc6f^6WD2&r zn(T;1|F{X1fW^_tZ!8zH3jkT8{tLYB@=;LSdkPZEj8P-gP+QLNJ>47|c2V9&TXC!a zg30=3iiP(lP~})V&U|sX!wD)N-P#8QDPI+_RTHyhnY)1&B(!Ts2a02PJ2hgHwJ?ac z<A>)OiePUcXH-AJD)SN}ZVmm9w@ZJ|K2i*oOu;cnqxhgiv#Jnf8cjd$y#tgLhKqgd zwU8Q<Q1@~NGv5h%Lc&AU8<^K~1mJtu9bvm32r2U>NdzU=%XNWFreibvANfOw-{qgX z<0}JEcMi&oEt$aY2ugt-Edxva5uVEK^MnAk_?G0EZ0!SPh}=z#?RrceScP-`XL~H? z10Z+D{d3gru0NFo>W2=edvx<Erp<W2m&0i;Z%g{hcgw!eEHdgR=pK2CsFB4|cZYjy z!ne8{0g1QuK1alpyIib3mK6DwWp6G#)U=-<U-dTgfGkOZB_Qewm|v=$xqT)_2PF=q zXH&9VDssalU>DyF<U`o$+8gEg+;3rZdCrn|==-s{Jj2&Bc0{cc-yvn*k=;wQ@8fYE zCOB?&p+TJ8O{iiz@EGoseNzM>lj7B@o9cj>$#vaxuPb>(q`Y_hGv6AU1gpGSuEpXE z?#h<%TS9EqAqotP!-{4Yr1EEp-JwS2@=wAgEGttW$CHj#<h>I(zd_p$NA{{aEZIv` z6#=Ep2Z|w9`^8vx@R9ANXA-w@YAGh?t-{Ss`=Z6m6hyTioN@}MY{oaBh_<q$P3=oz zxj>s&?7mTIsfO>Z6W`{;LZCxAsojtoV-=&jyvkf`Ep0ishpQ%`^{Nhra#5&u&W^$S zY7sx;hFg3^W*DF^=~C#@5RfSE{YFX;rt5&7YSQ=|lWD>7gr#`p{j3QeqX9yT=c6C$ z{Tr+-PiVK!PAM}I0vCP>XS5F*R6jw?Pi7xWqCnkr((9Z{9SCxcsn%~HaDYz)rKMjr zMPCVA0hz6ub<-$0T_*yWd)(m4%GeK-eJw*xF|+z_fZ%Sd(Gy{nh9&)sX0KxwTlLcM zZ9>Kx{p|@MU7oGRv%V-$uXSGt{qbN0<l9%tqm%nt_CEl(_V{wept%$1>O9|@Zs-s| z9G*k%-!FFA5QCQ-Znhr=F?b9T>!hCjv{hhHRW3E!@|1EV4eZ=1JY!t9YLNyMU?ie3 z!Dex7J-slS(kYsM!$}XYbR`e6n&2|w?&`W@N(`45@5JFi;AgPn)5!J>++<1K>r#3P zF#6ZfbN&L7^iaVhXu8xS0u@YR);=P{P!hA*Gk-DB56YwBvDi{$(17kgKk6j<pCXU2 zVi~PF3QBZ>GOh{1l)fWu8q@O`A04>a4_3>s>iSh+9cG$2TvR6f3g}vQC^qn~GD2PJ zz`A{N4XA876|{OzlGTuerSOjV?*wp-0uByeVDs_gWULbJuym*3|E#$${pqN14)@A} zpXtoI!ar9<fc8$SHLD`!>|Ox|aD3y&0(X7o0WtSL+-q8AJv!zX()WGQ%bP$ThU@4v z^#xmOo1Q^C6AZuE3OOnS=x~W&yj>Ls_ZT&4>}GU8cQ>EPwJe!q8%f^7%fHDWZpI}h z(<lPSWG3}%TYg7CV{aX1MWUL{ydaho1>JWSfn-y9lNsrlOhYi6LZXZsj=4t;V5|5s zo*%r(BlZx891e)A{jeejS#x7s4rn$(VqR^9Hku|&uZ<YBf^Ar5eJrVrSZ07VPcIdR z9g<D@TeCI(IAIgGT1=M~@MKOp49LYDtgQas3p2?c894bKI!aRyIPq89LfQ}@O(vBp z`A)hr7VG*kGI`(@Fc#O&olnLiB0}3N0Hgg!#G)|nJJdW%)gOs)xcJ2clz3P713q4b zs5{4h#o7<k0U(w2^KS29)#7=G+U9!zN;u+eU3=ECsZ8E>YHJ=OXQ*_%mIzWAnskFK zld)8+P~4$zI1$2@LzgSUZ$AQ6!<_r-o*p*U)ynw#Kj!XIrUoNR$^>fbsXBKY(V}}$ zZB3~x#smuj^GoA1%>T400PE+?<z+?%$;7{edl;h6zBJfzPR<nKvD+k_70X;n2W%Wr zNU+-d4Sxi{rcgOUa|lb|c@QSq|3~1Bk8XHeO8^@k7rYp1&3KCdwkVr<c}sMF=8(4F zo7U0vx}0#ZXnA0GouV5cRBftbU}#3bN-{V!c+?f|j)fz9nHGCCTx=WZ#LJ6golyKQ zpbDGc9fza@o&nirSV)7@1xx|bZGJRck*~Gjg;X#J$6j5YSPAE4&_m2I9}p>g^D5o? zn1l6|vP6G$mj(myf>;VwUw4QL)SBg)w9}Zua=nGEtcI!tpyxQn-X8I_BpEc9qJ2f5 zj730PFWkdnfI>JP5Wz;<jB1wVDtW66urmf`o0g6~foeZ3WmjEnceeC~@S3pjUTdD` z(Ii>!(N>PFH0O$ef|u&;led_WPkJ;N<B{;dEH^i@T<b=$ON=#}s)CnB&uBV2UONva zomaBB;(B<6EH_?Ns%Aq>GYckNCBiU5|1F>H-UIO}t;^rCPeXpB6S2Lw*I<piMR$)l zQ5Yi&=7MN4^xvQ74zTEl{P-k$s|kLi6iA}NJWH;49cF_;S~yk9kNi|HNSlv#=jxIG zM##8V{M7eupia<b&vNg@cA}S9t<e{zn?Re_*ru6RdJJ~ywN{koVkc?8m|8m%fbO*3 zMw@)eeW=r2VQd(Sun!AU32HW+ZE4^MQ9)M2D{W93kRWfWtLO?Cl@%j>ccIQ}*tlSP zTF7U>MMFGY^LGDw7?@+)JSDmdhHbg%BCI{?tMsU#Sz370+-tP?N)ec*<qya`V!}?- zrr!BAn4l~B63<=l%HNK)8l_+<;a8)G?XCnocY_Dwx7FIEz&PQl?DjKyPKJp1t@7Qz z|Hf&>pSg26Lpld1mn?fQF^g>wp!o!fNT2{yC|))Wba+Thez^@2AkzctDEhP>2I?&+ zkC~rmz_>I4>KAWv?KgQ}{3!@9(y3Q5zbJrM0%<FGbZU#ueed5bE4m3HQrW=$A@n;) zx5rT{cuivos2tV=ePU6Fq-evS59?pPx7?)%&R8e-<c_BX<qR0A^`QepwSr$6Ul2m_ zxx;5J^tdYWv4tPF{3tvLVD&(npo(Dgnu`XDy<YCh20qqq&wZIyJjonlvrsuHx<FHx z*LAiiYVYN#4kl~Y{GBrJrGT<wW|#l=O?Mz-DE5f19FBhC4zvyHdYt>oomjbplBoAd z5O`WH-PE!h3QqQ(OwY)qVzmx6Uz<KM07cA2KxR`McG|6ltcnpU<*bt{IeGa>1!<Xj zr%0fQK`PjufPhC}2p7X`uL>rjl<3;(Y(~BpT7lu(trvf^?$O7Cf9!|**8X6!DL-x? z<O+t$o*oQLO(d2&Yrc~tjg5Tl)VNF60mPhc(XL4kAeUh<tQZmy;szD=-LT$78QBXF z>e-1BS|FTrThLgdXd*ZeK^#Aqm&1<N#tlE^$I9;<qCbyNvv`I4%05hFP5hi0y?hsb z2j2VowHD2d#6H>#8J0I8zuHy()9o*;HnG#SE%NKj-+&%ZfBm341Up@Ob7Zj`Gr%7~ zJ>=7Kb-g0_*n#i>wYfSvXN1M1eZN&JwuX2JQ2x4vp3*@T(-4M32s;Tk%D-^~NMm49 zbx#tV@u9YLqNN`^PnV<TRlfJO7Z=pF)*sF=TgP^#Tr~NI)T*%!rL&^CDD3##o+A%i z5zy4gS!6PsF@i?6W#ZT0Z&1YH?|_QR<KI|5D@K53-`au3t}~jqOyL#{kHJ|wt}dIW zD0w#`V_Q~+1QOjfirzdH?iHjVW{`Jz#iT3*7+>%)o<FYeG$GT+WBJNboR2?k5iWzo zxPF)X6iD}`%A4MyS-%Thi|E>l_nu^qwE`NUgX?Gdygf*kDOA%(Oc%*WOt4gi*2-Ho zjxGy04Y7gfk{%@hm(G4pC3Tzsrfcg*%hCTgT}$TEFdNcn8)SN%a%Ra3)k@x7&fhp8 z(f3x;a~e;AcZYzC8Q0LaSf@eqgM6oSvvX7c+bUqY?=R|a_6}q$+|nxEOIWTru`1!Y zAOukGr@zrm1^H0gE5tD0j){+js-<3PSxPscd854jm3)-<^$5_^+4(;+cTof4Iqt7= z67wS9VM>&XHy<g`+pUYu7V<V7G4?sV%~xPxdOXPTvReS?>BiBx0x_U2%Gd6B_2_VC zomaj(Htf54FgRQ8IFtSp=)=K$?fZ89|IOEO@22<f6k_LVlXMd`ys@&oMTZ^fn?MD{ zo5EaIWlrBKfIL){+-Kv26b>=wU%n|pttB(LD~ZZLMKxG*qH_8aPWBdri<YOEqQo40 zpvs1=IeeYhf~J@ftrf3k9&z^&`#iU=;KPQO4nlP`peyhH6M9<#sQ<)(=w&jFiyMQg zvH|`z9o1~YaSwo#IllFW%0smDKqpM`Y-o9Hc;+A-2;r*4O|%Q(@<1G?s$o(&f}OD~ z$h*rZqUpq`kwMR@TV<T+dXNHk^5oT=$-yBRol=u;vhg5xtX5Cm72fBN?+b=xo7d&Z z_D39mRU9=FARB`nt4(Z_+GkA$GQCg}GhQ3IDqz)axp*VvSAgKpy~6l?#Z55u=q40+ zvM9V%!HWj(Wq9>Aoftc?KXij(Nf8o@Z_asJWscH7ll%9lFNkzOjL<E=?>&fB+;xrm z8+`_GM#tOF(3)y28<JA_IdZMhfH_<(v-;wt2p|d;8$hAnq6Qyya>9@e(~aA_5Fqhq z54Nad72-wLdssaBu!=jh;3G-Ka#iqNhTJ}n8(1_NUn}jo0%mFpCC_VFLcS0zr<-!` zMRh?lwbCQ%WQJ{;FsjYcnnxm7UHFsf1q_<$&l@~<%TfCTgANFa4(JA#da+P+btj+v z9a*<ZqWHK_P%T-v)qSR35e5q13S_!rDBNTMG!SDk+9h0Epd5~sA)~X<XNUYm(c*Uo zR;QU;YD}a9f{aF)tn3jMR3^g1@32GD1W{K7HaQ`+-Qejlj;AUSABZ;wUnSk^f!c07 z+j^|P5j#zgTxDXm336t>`GEPSVW5IHs=7lgeD@}HR)6DR?GAXY!(5{}i~>VjTSwUe z@Sxe0UoV3kLZC_CzXK<+5dOsf-W0CQ{{uvKBH(?xOKb^1EnZ?Ug?EVy4n*@u?|Cpm zQ?;r`?<D>=Ra^09xZ^4Dh$N79Pc@hjqj%d8kPIA-mi$D_4;CjU(8j*FkO<uQ!(LzW zi~kdW@3O?7jY*Qy<jfp>6~$`66S@Chg|ZZ#1Ht>w4q!x}z^BzL#aIUn5Crr2P0i7O z34+|wBQ`VjSx(qB#3vg;O-PU_{<SFsL4uvS_G@;e_OwJ?0b0Anw%9wsBm|g5FO|DM zdk9U|mM)Dv#ZF#tR2=?KZI`HdUF4PfLrKzd1KM{@>VKNQ=iJ+T{Nzf%_%=*dIR58p zp6DJepzG{s*u}6zwJUehrq`jNTCoxgttXqb!ZWRg(3sT`NM)z<;%fwcKp4>2#h9r) z8=l|=TD|ap%64xHuo}DF0r!ldFfhz2==fq&n6U&VTVfzS@1Ax5lf9KeWR8h5-kc=s zJn~rd2}MMwML?m&rLNR{cbHeUy8U2Dg9bvOD_5D%u(i?M`Yh@^;6&-S)ZV=tfCf+| z%8qVuLN244^nC9&<T9y;A1C>ttR-a)Lx*8|s0R(8M0n^oVEk#=66&m9=78CO{Edh6 zs0D6$5^8}Fj~kvrSGhpivUW$}y6*d<V=|pNb>7(yF-fP+|3+$kPCNzH+kMTk@&@v7 zIG4=rI%G!tKhYOX5N%hQ9`n~a+7!@m;o*UjC-a0`395Qx3$Mn>CymYi9=6kMGZxc{ z)}&-IaDxhIZfk4k>D1^b5ZHNR9LDuxA7{sGU6fm6WNHIg@x4tn3OQ1aTpJz|ZM8EC z?eyyHVFeC0n}|fj$24dr@{x^H8iFDdRM~E1bAAc`3GI|-sn&SF8yki#GrO55fXt4F zhnh^N5R%zltVx&hKr%c207)?=7NCz>HyT<1O-P*0`>w|5*-u%}0OjD~=i?BZB-}&3 zYr@f}&?M+Z4<_0leXtX?U;4^!$h5=%8>mH#*3M!{JK@@m>-gB}2(Q9p_Z2j=)F|mc z&k4XuqR3#@zb^!uSdwd)^$>t2mc~~+l<+M<iBnw261hME<a0zmjXipH#O|$-)K0Hf z4lOqhbP-QNr5`u8T;m7q+@$tOzfe0UrHmfkKDP6Dv;j>l32-;mB>{Ri$Fi}CG?fcd zWKN7->SRE9E}Us$J{eThyelVjcF?TA#23bk1_)JtrG7eFQpV~VSR|*(pdvcv{@im~ ztQ@#<E&;KU4!zxBj@~Hg|3+!O=y|ud?m=1Z=2cMTf#KQ(rn&gn2<l8IXPI+t{lm*x z)!zDv{ocPJ+$P$Tjr~n}z_+FNy0&>TNdRj?xXfII$pEtIbdsrU7X)qaw1|m+30q6h zt^q!7P4t4`e_A_@fLlM~uv$CQ&Ms5z7%k@8vZOthuJbcFBdr}Oy$21_8Z)n2O9&>E z0-COL>;80uy9bcq8N_6QmuUP)*nMuXpoC@`qt!N#3Q~#=AOa<&W~O5R5;?@req*=- zjQr#Wc~QX-W0&Bwt%^y?5d*s>4U*$9)(Y6SgQQEot)quJ1r@1de5zP&jqU!rpdv)U zl-_3)vF{!SVyzg18&o0>guv(kiC*a6w@up=PaJ0(^>A^uulVn`oP`tm?|;1pLvnIn z3!#_s=~?tQ)T8(BSfr6L=w7T5;Fq{{`^Spn9#=ft#x6^(_0InDGydA?$M$RKa<wxl zuTN8zl5rI)B;J^FX!g5J*#^<<>~4|vhyK%RqG;~3I^FgnLQG?_hufQMR_5iDPUA~X zn4U2@t}fgfQedWRCKtc_J)bF@LA032FX^koo^61?uT!yjJ*rtFQl~1|7d_4L%$TTr zw!O(Hl7pBXfBl-*^=HYqE*vTy-FAr<r~D>i-7LjR?M-^aw><QN9Ho9%CeDOEn!u5v zY98|57!OgZ79?I|C=+X7gLlWKW#ZopwR7LS(jR9(;zvdEE~8xj#;n<T?gkF~WX{;- z?N9p%B#HO;@1^>G&RAdxehpj)QKZK#u>8XanK#X*k)fIRVQQH(PD>(@nfU#ruLvK- z8g95xdj`cm{Zgecs+PBblQW&VS9F?!9K~JDw+L3CR?7iv`e<w0RJ8Iau41L_=#3Y` z8Q`ABE`vADzZdfT8W}!*KJQI;EJnB#<?L@!EsOz{bYQuzA0F0PoAq@U7z1mHT#Qbe z1mfx)^*Vy~w-n6Z-*ZY0aZNnr?~3YB*Zo<Mc&{11!FcpBSnSDs%Tz-IM0>6^jmEyd z;i-k+9+gBIKEFxVRji@=Q&3-`tfK#Sx`QS-Hoe_<H#ew=@7L9*PqTO<`Mj8r@cpVs zJZT!>o2~Jc6jDL$!;wGqTC_TC7n>+(q)$vm)~}zL5s~DaPG$wZ`zM62tcdFNTupq~ zyJQ=r#b>qEbG>lPHfXHW>&NZYQ&|hJwMRloetw>_k%H#&8h<l92^<R3sj#TO&mHbQ zZW|Q4rIVAeSNItmcN%)zmB~E;JgMC5j>>K6Gm~5|;YgZcpQAbM@n)b=Cv4(<?%?pN zC5ijCZZQ8r6-66Zv}N|Tf_qRCADKUlpK)*u<2n?eIRiFhsm23RI6cz_WS0~yy*l*T zs}n<&kQFPz_J6=yp!>?symn!#XC|r52DUzzQF-oF1q0-3;=i6yB>D@go0p(7OlI=E z!QyN2k54z<>G#0}Ei8I6ENa<RIPV%;ygPK_HwA(?|5BB;ygh2g3sdu*OE4WkS>P%b za*tMW_&PuPA%Ohh<#WNdiYqU4K}MrUS&_(yDyaQ@#sIs=ycB4bO?u1#%io%>Vm>sW zssuYi2($L<X6b=tf)bq71_}<mFs<#zVvnCohB$~CEdkpE6>y|mI#clbyAog>Z@Ja5 zNOwdoJHhv7Q>ZoHMu$1y`dRs9IutPslO}K8ruT51<&2od+m{|D-Fe5FU*F!OrNSWr zd-e5)IO8+J!Itjbnw=){<}yxo^e3Fsmi8uthc+7hucdyRfybENfBs5XQW7opkd@6+ zqE3WbsPOky>(W|A*@}C>uV2Tu6UTg?mp?^w*-5pZx6>Fm{(i{I{l?d=X;JaMb`ddZ zpDU(XQ?M>2S*BUH(lUgTGtt^VVu*DvuYxxHJA1&n6n9hpkuog|JpjH}Li=WLdaVg; z)Ol$-Gnb|a*2?~_{AQPQ`LzW{@*13?Pj1KmfFmQOn!8jH|ICPDS9;?N9;jyz;jrts za9GE6x&HBDf=!xyb+P#{04xIX)R_xe@V<wp6takLOpS>q72c@wPr)z83&WpZ{c{)w zwr<j&jiV3@u;TS2wfi@9{X}U4w(0j87KolGg}CNmB;;iYggH9umCM+kE90fbeS5?| zu+7-tBykNoWTag1#nTNOahj^QjNn27i`+Y){m)k|U<v4#_-#uE0qeDQhb(Txbxj}% zbZt4M_#M|r0KRL%YqcTdanw7eCk!wGnSU#y(&b~nev{F_>x6HtbpVI5nc;S#Ts`Ka z4kU~%slReNZdViQPpmT9#sl8K;W*FeXHK}s3~?GB%0Cob-DSpJRkk!}`+@J60lD`& zHhmWp2sRei+`U1)-d2T^Gdb_$X_0mhtd(yPs<OY$KbSNO8XGz)+oqEaB-A4!^ik{i zHzn3fr8WgWi~C%f?@XvWv#pklOHa$HZdO2=Bl&rg-bkHL*KqT2yRZK#874ENWaz_l z{Z<h%6SBfe-N?KAHt%pW9&}8KZ#CLk5lT~ny@M4PjFs4L(!$Ca<%9EsggMeep{aO5 z@mSyN6(2e^v!a5;(yvA1YQo+*tuZ%dUmD!*n5^?#2>iz%RY9Pe>@?i+R{z&NC-T^o ze%6koOAochiTq*vNRs%a3=jCJbeBth5Rm184aEtUTLCtI35YWYNYKrBEsjTvDnVnL z18=PRKj2t{4Nq1Beh%ROl_8~3HI{66G^Hm^O5@i+HDtI%AWaH0WbKY#?DpruRV>04 zd@!=s8V|7I@7l)u6h*LBR(E(yPCXy28DhAIZSH2+@!;~fc1ENLSqRkM?;RR<<#H4H zvQTy&JCO9jk2HN~b?1;|R;y@A?VD*~Wu`xOh!>sIOv(74`hLdie!B!CFW+C4tg&3u z{AQXje6*98!%UX`(7SB?Z*Mg#;Rs>bt~5ES?dnt#-iC>s#bxUqHO*kp9Me@7-Jn#m zbb%8+kt-;cBp1VA&l*#g@(0|{v|R<<DrdSk0%J9E(!uxN=wGqUfp6QeC4eNEUa4a| zYzPj80@h5ap2EiDa>r|6guPy79WH}K3x==W=gFa{Em<Sdv3?zBVxm7f>MqX@-4 zcKueo_XFG$hCwv9sy|%!SPD*V#ZyLt+0^Tw_QCB_Ex(hu|A6?Gz&~3nf9Q5g61ePp z=}AWPw;o+^(d}eaCxKvf`5!T9!pl@@J&tl@xSH~A6^2%$2%Xpg;!1tyz`b-t|0@0r zf6M%%GHML4;DV)%KLo~rB|Wu!%b!5_yuD6|axZy{L;_ekq<uUw1epQfOse4GX%?Yr zz$&#K4u~VQr{}rxY1+ZN@?pOfb80-bDf!y;Q!V3m@G*6zA6IA(g(NP(>X3Kdn*Zuu zh8r0K5t&12AImCJRKb^A^_zo@e!VFk#G#2I)alQ!DnVKfk4%juMu5-2zhWkG5c?Sf zu>$ei^!GoGzG(p)wSSbPy@uSAH__MnQnYX!xZIGyzvP?TV=6ceM&+hgY&w~!fk$Sr z<%hB3%QyVs3|a`*9znFz1Mf3AzfE>`v9(v52X~dCFS_IhG0}pg_3<o)E=lFFp9gKj zfW{Y9;lPd&qWbu23lD_@(NXdl{vF@!LN<r=Dhmt+yP8M~t0s1d)Q@zZ@T9bA)x6D% zyZ-#zCBX#a=ET8PXt5Z*nnh{w<yF_bFWTTDRp(6RyF~?-7bY)-1R@sP+jscDDb&ZP zE(b9NHH(suR_wR=yn$7oe7%oDvn93;*te4-(tdEf325dHjjp*I3|{cqC<T7;Wzv{D z&Q*#9V3LjBuWhSq8^D3k=21SdHX;;+g9&Y4cx)u!zJ?G@!hWrP_f8&gm<T<N<xM*} zutB-5-1rqegs2Pp^7G=aptyzC+}q1;f3pERCYkayuYC3wL`7BgBm+g4NjJy`%N8(= z{i4FMAaEWJHss_{8(9P&u?#tx3Eg~<z$A86%CLN)bEbb?q;3RnLt0SyMH_j3U3-Iu z_?;PZvWZ9EOpAN(&rsqx3b|G4jfgyo)lN!iBu_7!ggZ`NsXhh2ZiM${rEt#}!95SU z`jWphQ>4FUs5vv$&8EWNP)&>}zr0r3)83%hC*gdGza!#SIUWCd;zg$flN4k5kk?TL z$cJzSva7C#vskQ`0Ypm~EoSI9fK!9iD5LU@#ZlTBNQg9Wea$&NKn_K-<=aAyI0tDL zuebMs?<#{b$gFS>n^*kJZOOoq3ddd_83$=7uktu6m+rvkRVueP&M_|_-0ar1W=ur_ ztF)TV;&mD}L-)3D-4{lK53$^6e8WsB$<cAE5HFW~CHdhAo1apMQ->-M&rBFP?wd&= z!BKaVuqdtRP{RDmU6pJm(n{~t*}p&Wih=c#Y{0O4?AC(`#sTBjAM6X!1T#7<Z&Sh- z;_O8@I_fOrE~{qoN0i+v^v#nA`~CNx09#OxPUg;P1Z)bNPfV@bUvTILR!`%v8YR|m zz&aT;X}dr`U;PJ5iJ}B&OE~bD4%vHG@}z)c^e1ebFT+M46g+v-q|pI|rXnHp)7=Ir z%;>i@R^`;Ir?x83aLcE;;(?PTA8$0hTnNQFW6IK4?gRB4fO7hZYklquARdLmZ?nCj z__Le`7WHNXP_O<E`p{H)=4q<n?y4r4n2~JHZBA7pUFEigHfh51BsIV7{F3bxI}DFS zmurTYcjBM9-<2YNrd4ze`Ba-38o)~c!G<BPC)Ol~AK3OP;sNTF<(qm^ws?<Eic5bQ z?adm27WGOlL}O<hVi%<yw)|%08iS&*@O%PW3OqcRfZYde_kKsvI8eL*b|}_WTr7eY zc!7gcgvps^e9$ou!8`judS8IZptv7e5x4OR3w(9nDigT@zO*pC(p$4LY5N*@{ra6b zOL4zNS-farHWwnbH{oUxuQc$&H0YK!mo^%p7&UnK1OJaNEm8=<J57NKCej4)sKq3U z=}q&G^mwpUkM+6D19PvDyAb&OKdP=Ws;Z`K(<ul@cY~5jh;+AfNryB@cY}a{0!j(e zEhQo$AT13N(v2WpN+|v9Jv_c^eg9_7p1C{boU^YvHJ8mhaPxCzkj~^YP|kq}BwgNa zv}A@#TRhv9P4~W92Ff=njshR1pJoP_>DZo*TKtcsxkB7h8=xCmQAePT$GpzLhJMrW zro&%}n9e}U_+|@!u$^3@e3P-ewAp4VjzC)1807eK%f}k+G8Sn)^wr<thctRhA#UXr zyF1nRg+;CPYU7NUJpvxlk5RuR-P<|F_VJ*fuRvNiPjwQc#+cDrGW;+{!LI*S+ypTf z_0zJK@l#uuap*vFU&_@%3=|_Pk+)wmpo4l(&*I#(9u_ug=;$T*MD#p82^_QM??sla zHbAB9jgexX02G(O&%f|~2>HSkRtnWD(~6UAt~5y~Qc8W+Q|KGF453Q*{EWMV@b?36 zQHYY_Jk5MX^Udiza1^ut{Q%tWKHN!}$v}PQFMf%ix8I7g<~u^-{X4|I>4eB56ZZks zy%f>Sf1jYFLOrWs`El6mPx(j8G@q02+WvY@FAnvlZQh-kLVN^hWUm#dGX2C98nKOE zOk1zkklun;PLw)FaVvd>I$D7sF)yjlzECGL_7J4p3&gU680DNWvCtt`+=YAWc;!dF z*Xu(Y(3m5ob8nFoiUL|+P9e?Y3Z5j$@x~@qUl~E0+w@zl*9LJVs8);Qnw%I0JysU- zG_LOgN1_O)W-Sm*Bc(m9$uOD3Dt`OeFBoA(PzZjr>4-U3&W27$+EW8fqF<oL?&LL` z_Y%)lkmT{wH7!<&=DxTSh-VTc$4X=htQ03?OI3KK_7D{Z=IWrmf6pd!FMPB^PO8FY z$bYhXDsVi0`<MI&M6%Vs%XO!yFJGxO>g#$mOD<x@2AI|M*mO~P5%G54Qu3f7zaFQV zpX%r>RmNxyIV>`q>hRI(Y!pVL+-|AqrB)U9-kucjz6!*fTA9}CuVO}{R!VB8iRv=< zB~R1Xv=L?^xJF1lW4ZRgeO*Ma=BPq{+~PWkkev8kOm(cGhP*Y}_|%gRxl(vjO#77t z(=lHESdi|TWpbxcj>g>!)3$mVBR^{Nbd~9{zCt>-w^uw!7fB)11m(e^aai-}?$#VQ z;zelIwo*zQ`fur3Cf{h{_l}C*5#KWx^9q;D58TF661hSyR{2(z=h|r0lH;S-V%pB2 zPe0OrR5Cf)?JjK>kH+d1K~7RiqW&U4=0SKJT4F7-mXB{4=?6s1A^XTUvh1mICCdjN zH5hS|(cY#;avKXzHK;zH=nNy{%JlzfV^JOxnOpu-Y34`xC<%#Q@ug;&vFcuiT^^!_ zQ$1Ql2P*$%v2cDmTEym^-|W}uVfCqDdIbc2e~Yu4H0tEp2dl2T-wbLzR5ARLgjKVC z{Y3JxCH39(MNq&I8a5wNOIG!^xP0qeF-D^Bg*OcyB6c4AL1Te8JsqM_9=%$5$RB1& zBpq3qY=7(`JV~SpSs7ZNzOF(-qaMoiR@)Sue5U8FblylJ4K$qMBjHP!pQ(+il5?@! z?vSRBXCB4e`YCyUQtJm@>;yk_?>pXMlp(1?VByYL9qA@C!pLlW^|`R~ZUdfhNLTNt z7<G4u)`XY8js`qPpcLNMga;f>Vha#;_BKrR={pkolB^`5ENXkBTfblm;-l%E41mHP zN4bQ+*Exn5nZZYU$;;p&kxvtMH0M)P6>(x>)`AU48d2HZqkMP}J0G<p2a9#oPed#Q z4=6?I3h-ng;s+&97m6Sq$**E|c3nmZB<mpoDIfl>&4oAWc<vn~!8(4%SC9EmN3;(c z7a8!}3;jk$=<l%mkW^@Mf6gcYA+9P#UI7Ws&k2iIVP(S?xB9@~QxvKvP*$$)A*t+0 zxI-(ZRx`QsjgHQR?H?ln1&u42Jupy7W!WSl>w|^m@baSo!v6Ni%z=a@P7fCTB}ofU z;Vl$S`9*W0sG9B+9n!;t?C$8nM?i+P*q^$<&ilkfgOL6^OB4olALNxVU{{$Bu>=6) zFy5~ZRD*4g8T(znrAG}SLF?moYyt}r+}S*hdbdGM#~FzTowKe8wn>DMYKbar!^8^S zqZ_fB4tCeU@}I%`{P3W)=tV;wYeT$<KnU3)bng`&tj4O?$WsJZ=E{PA=}fTf!Ub93 zhGq8YAas!?WH7{ax;$bi7SR88P~U#%l>@WHr6LWC2PUuQtwjTt_#!`F4wDdnFEC3C z2B%3JxRADT&PWW+s0lYLDWB}>K8H67y#3^(4O@q{tLx2wj(k(9>n9DO;-lb9YA&{A zSXR!oApHw?J!zt)+B10l(o%(CKk%Qv7{xabRw@CmjgR4ddY>2fAxEaSVy4H?sN6Vm z$*XQ85Z>5o|D#zAQ0S<U%{O@pM{?!FxeqrizO#hq3Dk7x+;`}k?ih%X=vVfVAki;< zd=|naLqf3hkalJ!{&6!N9^&<n(}d|&Y(#u-zRDm={!>YDl#?=rs{v*cj2FSjam`Rt zrgEW3)7A;wQ$qA;kr*Dj`**`n#ls1jAlq^uh6iJOaVn)B)M*=lcyM!AeuQ3JgI1Q4 z3voV8fHt>U4zY*<(gRiV41-hc@VX_>+*UR)Ig>b}GLzH_Pnsqe)`)@4b;-vJ9CQOP z9j>gFJRdv!O!4tyT<a-V)~P+05Xi<nuoPc=C5nIo@m@gzA+Sy%ib}C0Rxso2Gye~) zfJghA7E5b*%2Zu(m(t-kB=bw3-@90V*Xvh|ys8}fVRwpM16XY_&DJ0mpMQT#aKD8O zvuM2(OdbR*^qkpW{>Qhq3sVQ$)PBcXs3s2`kkL>d6#&f6<O}|@srxDIV1}<Z(yu=z zyOuC}+PQ)5NZ@}rPlC-h-9+r^;5!E~72^7mZ(-XPR`>8{!Ox5B76P#WS>0|YmP`5$ z*gOY9=SPHqMW4|lzn74*Q`WRWJ|GATleTeyaco|5m~etcYbvDjf~qfJ|FxF5<82Dx zw}Q>%DD;#BsKY8|^XNZy-(Hltpn%lje~`>8KmqjrF<J-ZD{$~w`f(W!{CwD52HOcx zUqERR?gN2v)K<gmgcQmWKlK~s9~0jR9(gH5us8jdodS7(Ff4}YpO3%z0PoM<*98Ae z9bipvtH%grn;dsaSbJp-dtjr+;SN-vpy{zEQ5{iOfN8@?g~}l_ExI!jYRk|-JIuZ{ zZd_X->^ESg>>n7#YmEy6gKS$j-|RK8YG%_0yvrNu!^x0N6`Tpq2NI~G1RxN(&swkS zK}7gppQRp=lR!boO2ILcdgJL5uEamKOb~PE-C|=@b~DI}d{S;_4l+QV<Q5m=PcT95 zAEBSwfWJz*_vJtMbPte(R;hPC;Q`tT-2alx0iHg&txNY`TqlXCz6N_MRrZt)b)>zK zgcg<Q{<-?|%9}foRM~}ZiFv0hUIo`nK=A>=TEy5#$osRy5g@y0ot$0eBR_{OeT2e! zm19<XTfCYW3240KZ&Vu_4==(CnXy<LXJ&-$Pn6#D(VrjcVYn*U+S0|KaK&?o&Ao#o z?%)Yt0GKHzG%6s2ea&4C{j)qAT-h>5G2YNNM3vT@o!jLfZ~1<P8jqtvx)P}V{cp(k zKpDj{xIO9XYjm50Ap3dl;v6)Gbb+=<cn%dosNoWp)M#zrdPNNpG;Q+Vbpc`f{b{!- zsMOsl<xZe`t^K?-1a*4)W%f4Ii?GsLOUvl=Ey#|Xp&nAhb^9^sDmoq&xgL?Sw;?(4 zisu|u;CeYWJ6{7eOejj%Hbu+V!=WAa&PYZXRd!U};~mff%Ei>7Xjs|5#<W(r@XDni zpG$FA64S1Zat2U`)u{&qpq51JjP#DA#xoD@4-3h61tZ{ndfNV-96)eqI=AvqiNFxP zan!bt8^>S<iPNIBe9*B3k)_f~E#?8xECv&G57@~>u<!!NK69a;$7doB%g*g*|AGe= zB;KNjp?u`0@8@_90YsVK9T#A)<RW!G7cX$BqOUStK*3kI4yYXzU~gjM0Pl8N8!DLG zy$^@LfW2`OFT@;~h5O@bge*)Tcssfyf6xeoG*yE9j4>3KNc4DaHe$AJBp}HT+n(j` zdO!<`31(&sv7qAymWt`0RdanVBB0TLfoS+qI2uT)#+l{(4~amU=p7&NGs!SGsh5`4 zBmPaJ@z1+t=3-BG+>v*11qT~vZ5?LEy_<|C6pN{DiiP$Zvmce<{3n5UjbV(!>^7jv zKX97E*I;;?#1(;MxKB)Pn`jbPm_5o-9-S7%x#5mhxH}3ekmu@6fx>^&WpmU@f}x=h z?V4WQFJ3y)oU0fr*#SBCV}MUZB&-!8C4iOusdiKZV`G5i(%!10W@@0zyjz%Lrt0p5 z($=nYKxM?YAcLAS5hp#kc)7a(xMJs>(m!TX#4dDH5;yIx>5Oj_5d)AgzD@HcHro^~ zUfx>ya1Oa5u?!dd$X$d9h<7lr%l{|CDkXQUJ0);{c;21)f1(XsY)amU#@Actm_`Cs zjO^h2Pnc9fQ2P$;Gj5rg%7ThMVTgm%L(tF<IJ%Y6gpLnTkkCIc&(nY&y7Xv;bMqXb zFMOa4bBbE%@!=S~aLC|FWEot#JU*^@QV@YkRFxE0_z^B$!yl77+(9*~GAyO~21>e6 zQE@k@iMOKgN9Nu~vN*F`iEO4m0($<z?rsLnbZNdsJ6}^7xDJi7MP(*csy?1cAjFT# z#K8h?cT2W$3$bt&tEe`ZPvNb&1G(+qvA~`jCg2zQa8Z?<AOdzpw#L`qyYR{d^f&hu zLDBsC8)_%aG$6{+YiQ<gTKdTtSN=INU>TaC{~eZ%d-Kaa9Uz$Ae?ks=Qt)Z+VFqZ2 z0&4xKyvc|lUdEb5DFEq>3y}grV1>sKL+oHjYrpzk9%zR|09xAL*O{qM!wvN%uG*G$ zE27S9EuNvmh8hPnBgpfC2JAG(woh17%r@*nOAog0VIS?F2151qaV7!XO|w+DsctmN zJo)k!n6>VEk@-#gdr;FS4@9nANL2#8DWI#IIUolF)zU~U{w5m}r1=<HWP#AXRR5t) zBmZBj8?yaz#{goKQv_VJf<D-TDJB1|5fVa;zagL5){hF-IrvzgY|sFI8PkDMDB&jg zv?W~*GjGC2!KDcSP!ajlmC!s4LiepkG3E_(Eeo~^`hUz5h4%k3XJ^|^iUcRET&wTQ zPz*t4*}8nOR{r7urK+Vx4pbRvuHhoAy8b57qza@*PXu9CB4%+o&H)>|9WFK31U4`q zTuZ<e2i%>@j$izj-mSZ`i22BPutJe7B)AM9<3eXvKYiM7gB0H4Q9C!61N+~g$I?=v zLAi@WUm4^;xGBI1IK?0c-DG#Mgh1m*UMHF7EZn?R<GNr^a*Cd68Yo6JgK0;h?g&UA zMA)`@@)=lxCk1P`6a^7N;OI0p+7TYbCj`;oS=Lem987B+W8Da1j8w;paFd%AL~5zj zip@eahN|gbW`lpV#mFs2q1y$jKJIv*U(3>(2qK0@#%a+1*0}e@Qp1TR3GMG!vlRWH z3X;6(RL``-HJXE3tCK5@ROK?lsIUS=t2MfvNgBPAHoNj|^iL+&5-n=z<=c_;lXd>7 zwasp4XC>lXT<Y<9%-{7+tz#yfBvY~`^?bH;W_`(YzNMU{NK<3gUpO(n(4xB{cr8tt zt-+NWujDZJ+xm=U6|0~R{ZbBzFk~}`@z5ib(RTaoyGK{2cdnulwCzyycq(p<WnXUY zIXxOixO~K`7WO$fBICWSDb-TmmB|%W+xwwLCgB&Y)dcAlE%W(*Y}MXhQv`(%^Vun3 z;_{m6qkq6X<?r~8V6<r%#FuDd;7xv;p1b_{h<)PrMpWx|iq!Woxii-sr0@FNhu)&E zf1rmBeTcMRu;W{mKq*X8W2{pO7}%F5l&EN-M!R@HH57uTylvVa!t<)w7TO(l(a?`h zEp4igIhpAYmHWqb>9EvnVQ4>-(x$Z<S2n=lGig=Un-ib=C6WRc+4GuU>0zw<!tEKg z`+XW4K5nV$*hFt9B(p+U(j-r`URDwQp7cB$2*Xfjqhmj7VzbfgWL3&Jt%=C`tQKO+ zVYuM3PvW54$r?j<o;&UMH$KFck3AtpJ%nH9Wfke3dyB_&m|S~K?wv4NKQ`GXSPa`n zr9CjYoW*gEdWm=NTuh!{Oubsj?o~(h&r7;<c&g$~gToT6v2&@n@xY~kVv*<7)>EzU z_@NWZwQptF<lN<9SE%34+o@;nw@b2Wv6-ktx-{P5D{W@lcu&Q^j&fRvc`h&<Y)eJy zuqyk@GVS|^2z(K14Tm!4vw7!>tAmbKYtNUJgaZ~OGcK^Xr)Em!1#EOXNtKo#=dt|) zF0}ee{SMXFt{Ve8=P=KK;}J}b{Vn$xc6FZl%W&t84|ATtg0p@^&!M{(o`)Yak1$Pr zH;~ms-%qK|6F8S#5nf+ZCA6W_B0GHd;{aO-e@dmr|2D$jJu-{C4lbftb2<;Y^y5(< zjd54law952cU~_H`QU}vG6~<KQT+8dZIbH+-=GDnr?&THgur6?g|o#w^gG$4JKOUj zgORtCOX(VO@z)SArsUrubPm6lBUodPZ@tG|9=^D}N5xa|9wNEC@fu4CkW9z;;5pdS z)sDi7iv9CDy<u@{HF4m}J{Roj7W0<{!{V+bgdi3N=?-JarfuFL6wtoA+(09N>G8V+ zHobD>R}MCRTV1z7F8b6|AIC2;k=*g=iWw4N$GY*uAz;N~M}CWhkAsnzYPU+hTJ`$( zv{sW7i(qV-x+9w-B}y$iv2X~CgxHEy@7`~T{W$mnb0i>9%=bnWrv%GVx_2C1Pi{5! zxd(QHV=3F4ZM_sOdg-VF@wI_K{e7^?ZDQ9X17)a{>h}?&bMyz--af%bDuq<hKO#LI zhQ9AYVGU>heRv2|KDuWeIMUMv&z-6?9s&_xOt;uHxjcS}sfIPdHp}vh7U{d<+p6yN z?jG4LXdRD|*Z!&`YGDl?`Oye|?T~q5=dfw=whK^Mp*w+4mY<(l{Ql18@1GfgY5wBs zT9Hq#LgtEZT>Co<0*5ouejd5H(>$?&n`iw?88EfOfFPr!ryi72=7$`{eITW})95u{ z+WYUsDOY|UPxxTCE{<I2foEKd*u+aMSR}fPog=jyb@aoVG#V->{#>}b)`oc&2jOb> ze@T0SYQmt{WqM-Jw$`<To#V2S{<hr9Oa{t+syg9jYX>Qdg92{Hy--)czf1b_ByUyf z{l6Llf*qESt1CrH@pi4A88W~Wcee&aK#|}aR6zJ<`Ji30hnO5Uz5nU+B<H;ik%5&A z)4KN|B$5+2pYE%kVU-}xHp>1{Mi8lg?h=UIj7M$QfaU9|V1oZvM>51n^z*!Dr3A!j zqcI<0?^{gey~+TUjpg-cPu<Lsgjsmm8^Y0{mF>$)o!I2F@udEDFXjT~-+{{Wo6;+q zjCU?jW+!S+@qYY^+5H#VmuKdMY3(>K+DO70?U1dS%UkbNHW@hwG`Yy~)1aAPd9|CE z{E$GY{iES@&(zVO!|b47`0%+GFYwP-^$-1|(+&_5Y4#GoNWl1m$gwWjqz$P*9c4Z5 zM;3IroRrM}=_Z>tgyvYu{}O@XGfbzu{-ukL7|jGjQ<Gf;loP#_CwhxLR_#mJZ5#%T zaGl_fe6KsKYC(Z<$Mm}GdKH&b%TynmE28@Opy`7yjVWGBeg~w4`#SB1Ilqiv*;C$X z#_PNFw(2D^&f5uzwyH_wR}RF`+#WxhaV8M%f~7HzbdnVk<mk*Y=Z845JJs8QeA{>) zBjj+uVCnZ)Y-CKu20f+wI4+`IIGCB?)z98YgkEflwJYt=CHch&7xcBT4weZMNw~W4 z_yx(tOW*x9b~$_h){}FiqCzwJPZ&p$9plA*v(axqmtAY2QC3Ttxoo@ST)u}2U`DQ= z=-2X3W2#7$RqY~m18p+NTd2-GicuPcTi4T1H_2m3-1xu$`P54=d2l8ccI&T0)$<c6 zLH^$0A!a3;=?@jDAFizvn5xdB_sfs39k#?LL~))h9MP0sy+gcroX{j+pyvD6W{GW! zJIFW1PLpqaQ!~OkJ#!HL8RIH!5d3%%B%Zo*O4Z143wbDOk=~6vPqv@rNAT%ka3}eI zYh>#hsp(Y%k{fSadxJx_#NnThuldr@keipYKWg2*7}3kjH@lk|%OrkJ^BVPaoib&~ zl~xI(V!@PDjz$!pns&zLoT!2UedO^nBZb8)2}|4LOEGVa>)W{BzJx3ey0?xjKh#=Q zCObMv!WAG6b}qCI_$7f(sU$97LB+W^j@XTb_wBg|rmB1EN7?rKaXqV=d*y`v1^12{ z3l!P4(QJZk_$~XWWiFA(X`j8Vv%9#B;l@)WevR<HNILOs>F>IP&MP)~dTrl(7^3F9 z&PJO*^&=@s&OXT^PtH>}%_OWy25s0ZDNfD3M}5S3R1}9+Axcc*#NeEYt!;A2;3Q1n z-PzoXcKqc}C?(1H*>7IIlT1pIhGzD#@6JCJx_@#dofmqx2`3UHNh41rJ=E3jajKXI z)^1z(Ulps2k9p$%*q}`Q%skWlSQYxgWE{JvNptX__!h^|XPc(MbxuyU<I8tzbq<ET zBV^Uic%}0T9qZRlSNfR>?1RuR9MR(S>sd^^*YE062d^jPCPiehXq1G%NXxg)^{_tY zj2V!WZ)@*itxgkWPmK>Pc&+|+y0*_=5=%!`*6ev$fRH5C1T-=|e13<Js@D(!i*DAl zpH=V;9$^Ku=#>1(>*v|dRjs+xgj5O`Se}t*Ub!3cl4=<9vsLUeas;969i=rzYVM5K zh^#wAyYU+CjC8E5qP4H`;Z>FV_F_r7444u*rHs`~V0o$iuBRrv$lbHiC@PoX36^+H zTm9mFK>pI)KtLUS_?KODTMDpVHs~Y)&yYpA_7&l&_@5nRCy=O$2v{kDL865)BUa&y z-%8Q3j40u0W(p~HVd(>P$h8t--pkDf=S_gb<A6SCh`c=_tMI!+_ZxZoZ;KQHmPdvf zg%4m6C4amzy$xuEt{5BU!Q^d@?w<Qb$0o`kU^&J;-V%q!IB+qULxz{3x~yZF0E6{K z#Pk8(R-bs-69ZcAzbqV~XRi<lUH!eQAblZu+&eLu!1{>@tsWVe7VVz3O9=doPPNF* z35fq=uJa?Lq|S@-!}lW+K>RQ?4i*l^M3}_dU#EN;Yj~KW(VdoG80`uf?In9<!ci&C zPuL#sN~hSOW1q4hgwHje3>CmikXBn@Z+7taLHsJsom5A4sR1d=_i6cDnXIrY3vt90 zOs>^oGu}R256a$<e}bA_d3CVC3HZP9L06!716H<@qS_je!kj+un<P~~ii7!=`I5>u z$b`bf&%B${VYv*`*y402T~I2*v192+NH<?C^9b^dR+HcBXBl~v&}^h{0E<X#Fucj| z&!vp-OB9%@fXJXSWEo17Y`n3hS`iRAXz%Fq(P!0Q>4=$>;;Ff$VN!1hbro*=<ieyL z{kf+QHv@)F0TzCcSLF~{=Nd-N9|2bk7KJO^z6MOKt#Ik6+Qi}gg+}`lf*4^i|HME- zX~Ed%K$IPOp^RJx%R9mCSDH_dfk#xnt}#%>1E#WK`?7*JMz0QPNpsf&rg%%RFE3d2 z3f@2W%qX8+M$VNWSaR3c(hX$ASHdrH<n$r%<0+J6=2yP~{~u_v3^-a6uqY+rEb(14 z*{~|sCttHH65^hJTg;U=)6MH!0r7GYcjxsp67w~N5cNtUDkWbPA|4vdm+b{5aobP^ z(y{V#j?caX>DCiG+UD^4bt~(R!ID&idU$FaimX|tPeBNn)YGqSQ3Ba@(j^Q1wju9q z;@IDeTG=ayl&9u?9kUHqp`c%L4lu(ECZ1>ITOV>CLiE0_8jL-MS(xdZE9S3j-G?Wo zo1X~0eJn~q8cvDaFAUX(WF)9SeB^<CZeZlbLZ$g8*bC@De}>>$1vBAhu<mh%_?i+X zFYH#`h%6I2iv*^uNNgJ80gK@7zxqHFMlMHT=v6$3K}3Ce!1O9)0KG@1__pmlR?sAB z%>8?+V5?#{CnMEh$ADa@-&C#1JRoeg@YaA1P?6pdOiBS%0-O7zfwtTi6H$yeiG5O6 zVQ^!CfX>fYU;|+aT0$mF4k>SmDn8-JlmccK{tmq_K2x(3p5`vgS>Xb#1zFf>MPx&P zp*3RI9s(h~*&A5Q6XH;;OG;GCKoW@9$CY%$#t7A<Ah8BJ*D5}}?*aCS)-h7R#sogi zCE`#J`}P(#(Nn%V3Xg%OL+u9_^GzJVtg+Ucgb1xX%3c)&ERp1%y-&b-J9A#6k&Xkf z+5aTZzHa_)3J1&7ocFEG7#2wQxo)2`&(X;suwq=c+VLyIc<)&33Td;~2Ta;Wo+t1# zLT+7(ommi*pCRso^;G-3ZZWzh46OZUKSjPy9z?_3#wTXz#{`HQf24o&$6mS6bGw9o z`G<}mEOy)n6&pNafk{^5b@I_k!KT%%hOi@2OkhfOM4`AFqu0r$<^iJ%2N=A$v9uu_ z-)|NX;}<Z29J?XR{77Ly*YK5)vhNr%Y$wSvZxVE1#ott}#ci-M-pgDjqaD|fk(?81 zC72ZCxB*vp|Hd?TJT!Py=&G{Ms0$-dH})|uBP2lRJNo*`7+yDO==omy*2G~x@*800 zwA2rbRR8&!uNi%_k{ihFxqtp9*1=+pp$(_~ML-s$IZ__p)W!({kcD}oLLAC;Ah%_U zLB2ZljT?|AMFCkGNb?u{c|z9=<eavQK?>49Z3<MkFeqK=GmYoP{zSzi0VepfP8oFh z!4MfQFSC_ri<ozByvgt_mv8wAHY!kH)?bquU!xTY`9}P%0j}6whE%BNt}j3O1>$YZ zqCW+6$ODQm<#?cuzO3xhcWN8%fcn62$>4`Uux>Wlf6SL!;kH~AUT}M4kNq?#IAzVe z<&vO5#WdBu0y+6O4^jV4=CLze#acugp&}2}g4ZSm7f`d(K)`AtX_-?2rNwig@DPxe zxPxUsFE|PF<8IMZDz7u$ScY2^|MXlqyqnH_@cZITKhN^iwR;TjUh+TwP7Hc*2r<hu zP~5)M-LZXDGXj%Y5@Zj044x`98m2(S73%2Ke|+<ft1GGD_TaIXQInYtI6AK=sFX91 z`U*hxmH0#-AC$cVn~;0AK~Gqk!;WDU)F4J=T@75W0B06f&z}}<5b+)-v%j^=Km#xP z!ExV}FM#%^cLh|S9bDW|4TLPqkEB-ou;}eXKlTx96L&(s31CO)95Fb!pnr!HOiQaa zoqz_Pqdl0ek4y^eSN+mI6SA%YYBqE~YtU%zO=$i6<)QHI5Cor&QN{aXEKCu&BX}Y+ zssmxpgKEDEV&p}_y?>%_Lf_&LA|`~$DxWL%=2-?5k|FAEk8=e!5z#<==n|fA0AF%4 z8{4VEw#^=R6mWBNi>JR&02M!N1gt%deW;;+g;Txq@?2_EDia9M@YJW^_?`w3z|1GG zE(bnG;H}S(CIh$E@v7T9G*ibu<w6Me3t7%*AO*V$i3jAMmI}q~pIzaiaYt}S+9siz zygBzpFP=rf+n4<>${s*|mq5T$?_5l%4F}0R_a&d!@f%P>Z@xBpf{hUvFtH+K9R;}T zRG+On0(%n<q0gBC6%<gHCH!ILcO^Jl?gnZ6nQTa7R{l0ONEL6AC$>aj*QDlN9&np+ zORNTnr(5ra4@qU@`j`>I*$X)2<KVv9)=5%kEAHEK2QCAxZfY5CfXRKf*Gr{g({Wcx zydeSfD()9yLF%v|vKAAUu~4-^2?%uq7F*Iy+FMZa&NUv!W$}Z}6c*Z=zyay^qIbYx z28Agett6ZBu2eRb-dD}jx9Sz3^6`r5S}{V^^m8M&()94$B}piB@_abT3=<>`_cE$` z)0JTc`<FFQuMGM+obhw@;a~LSo4v0WsM_HsBKh;9SOqAl&Lb=e+Z?13KN;EzGW@D3 z;8OpxJ$}9-pJiP=_9-1gxSbpGi5b+MWa*gCt63mAOBE(B(`0=A(tt8)J@g7nb)0@k zv*DAi7g>PO+|Ehx8m590A}i&*c4YOtNVv+ieY2+B?BRzMr_yHg514{G^k?JkcM}HO zx8VH;HXf0mZUhz33V?nuf?5V&vhdpuAXCl5$MaCXY%`+!MPEz`9?r(d*)_Q=R6<HB zad(o)f(q;G`zul!s<6%^!mlb!5<x3xab#C;yD9PPfA5xn1B;~p%Cm2PTiLW{qWcyl ztd8Vv`6*O>)xarU<cA?H+^+nl0(Z}nAVPCaZ5=DTfOWr+D5J<!1RTC+VvV%6b7Zhy z8MP!~@4(^ZKtw|YI;O%j+pCEcb|GckjQagtCk;XIZ@$a&dLa}p{&kZR64HWcHx^<p zB;F(<V3n&P3T|JDo>rJ`2e~4>-<`m}4kBN8>OB3Z6`W|oKS_Cc-!MD=_2E5GrI<Ni z*a~)5&JmyPO{{ZcxujGiP@CQapS1>8+CzCPiMC(nQ}9X3xlx$t*`y`d;wxgER>xoo z@h(y1xry}<b&MYE)T4O#SU?(1=9HX(dy{rH#^qGD{)k|js0DoxI)|djE^DrLQo)*D z5*M<>$C^Uv)z-wNXGlx+ybbC-=bo|YLJ6=#Q0oNTM&s>3?U`OIDuYFy)C>svgA0u7 zv^x?1U#7GB2F6359N_p_HXA3&M|=q^LeS{@rppu@a^s12W?}%*b*^$NxJ~;_#VXRz z3wA>+XCbH=E%eC&-fuShemE0YX&~DD5jbD9>ul5Hr*Z?5<P~~6H_<|j+o%9CJw*M5 zut3y5?e8HQzp)Ajxh22BOxL7>3cQ>1!(M@_2GS2ofI$|$K9FDKjx_(y6o&OrQg6-_ z&z=f-y$b^p2*=%%O5=D5``>of{Ll>YgbkKe)_2xbrJ^McO_cZ7h3z8Zmx6aEP-t4p z=H359j`OvOHS+LXZelbUrdI#_UC6awp@q^b6BvRhsu<qRwPJVdckTVIpU*$F%kEN$ zxa4JB+RxMKajc>bYxwlZF~Wqdw+p11@K4!7g_#sz@u7FM@Hw6P#&)HlH`hIR*?ZtK zlh%1PIBdX-t9>o2O;Dh*lcRPW%9m^u<R^J4$~0rg?)OEy;Aq2@qYyW8Z7f_r$s>D2 z-GO%`9n(%r37s5UfR`k?#-<qg6%%FJXE&;}ZE*|K$TfY_xLUzsJ%$lIqMonTVPD(q zNU5=cf}%9?HKH#?h_D84|31ce=aI3yU}NgDQ{!cY#_mZ@!{o+e5Z)r4xmCXv<cW2p zd!8k|n%XCLY$9RCt}`*IKq2eK&?a!Yy*56>txt+6i(Yzg9~zNkcKx72Tb%erJz}hc zzmiwzr@Hkd&Xm+*|F(agwMeMh7d6(L+RS5&2$$WLt&sx9Hk?b=Yi+Z`F#^Y|j9px> zMdzKj)a74xthnlSzL%rBuSg#|H2I0TO_WEbLibw{Ok&Q<uC5#=@vAkHr{kzhj%t5X zgvX~>8>Q6bnbdViD!x(JDXNyeQp=-~kY`?_s5H<`pD{DG{XhE-W=?f)g=^$~9Q{_> zZ@rj2FrjDL5%wdA+r&A`QgSB??Rkqb`yZTPy*(RO&&KY>Pj-bbBGUJx>e5dyloyKT z3lwkDyl&ATz{4v}%;zi_&7N{zm}kz)*sZlGJ3UkTPEQs%uA&%a9jr)~a<TdJg{9!4 z{U)8y{(K=XIc7uc2Hk05o)Qx;!O=Op2%`z^(fcUb(7=>}NJbg@XTi`gt{`%?E}@!? z9P^ASnVKNxQfemq^;1O2H=PXmJmgV0cyE*ft)grgYsKX&);<Qr$eN=+U+wz1(`&={ zBqV<B_ux75tFP1;y&@^*$u^9*;_}qgcT<R7eZ|M<)wHdlv|*eMj{o9ZkwWt7E9R@G zj^qR>?u>}k=3HFi1Dep4<_O$DwnxkD=kt6B0|ETvd548p=w9!YOR!GQYnP9PuDO06 zy!QUP<t=-kaPW$xIiWh|KDwOSMB${YqYZoO2ygYwF9`~AC1+laxayrEoL58wX_&+w z^VK;Y(d1U9KW}?D+EfMSZ3=e%&gAFiU$XJNSnz(+>g4_<iZpu7-f+1TC9s2Za4gpz zo!~Ey1#ixWFXZ^}fdAq8Q<ejYE*llmAil6xC(^vT<My3|NG)bBt&;yNxS|B+SzP<O zlc!yf4ZrwltEzDCqt^S8ecMFGi+gU}Q?Y3@huAGVxawC$HTJg2BH$}T`EO)u5+@8( zR(Bsgz!-cZx$u}#u5jf7k?3hJQ?oJ)G-gL4ddi?rx$#QeiqkM7oPByaoRUmB1C`nF znNsYn+;55H6TB>P5fwZ!8@x|ZA5e;FDl)phjwH`g&b^{Bc{SN3dk0PDUE-Ky{Tg=J z7v0ob!|av~Cq=B~je$SXgs$~Tj2MztQg@P2iW4=0uE!Dr!He5R>v8sFB1Oho2G*A( z_GJN2`0WqH>}S)a|KF?F;yG_ct`=y^SwnNwp6;)mJYkDgJF@QJ!I5cmCsgDMuKxZa zRs6HyufdC{Qa3)@om)p@$;<YlMXK3#j2%PqB}ncD6ilpsF(}<z&vQHaStX*_`E80+ zSOo`S2oj6kL`MX3Hva15qgd{Sf7^DK9B~njBS?Iaz3^HvC;Jbdjl>h;(WfCQtQ)Uz z^wo5dY?=&N2#>y@aj?1+)dqaWL~W{(!0zMg4CKjB8}_59&Q2$*`ke$VAB2{362ADG zeFw|eD~vdCCERLZ99rH4Er0(VJ8R>4=)z()D%07(h7!c-p@5CVBTDYg2xu!6Zj4*% zfGUbNN}vCDT-m*k?2O_`__8bfcj|f{rUgvS^)6JF*+q2g*dMFv{@wI8Ev$4{+02N% zw0=T0M5mMLZX!&$xA&ko%Zua{U!zA7r!d}xp2g$+e%Cqm*E*@`)4$YNHRWUy%~QJ+ z*H4tF*Y#@U583CJzA)U^B~R;3&A^LNF2p#qW|y(3Z=A>w9ZRb?<@aRqb-T6Zddc(s zVCdFMy-lu>zfJYY{4L1`B0Z`W@B2oZe;y=1V9SV#c%OBLi|^s!9i0lQSdYH@0#6&) zTtaJv_$~coug@o?OYnlCQfpe;7ilhyEjE6~F;JS=8HmP6ZZ78;KmU35hy3Yfp+~?+ zWoDcsi-y2=BhN@`%AMBpFqAKQ6grjZ*avja%XKhH#8byi@~>=_>E^QLmJ;|eaD(WI zaU}LQ%%UZI+-fKEtFL3|d*n*Yv2!InpZGb_R$ZYkLtlG8=u|u1=nc^qk-%riG-C^y zt<8|BQOv%wiod=jQNDlVm;N{hM@l7Ci<n-+J4#EGU_`A(LEhbawQ4}6Jvu-pGg|z( zu7p*b*>e)1daCS?P)yD_FM{Q+r<DSlxX)<N8(*WOF)4P<YopYZC&XUiiUq@F`n(9c z@3^OM(Blgfm*W=prjvW841%*HbAIFJ2o_eLyUx|kp0&!l4VebKeo2R{b@HSbNqNuE zie`70xCFt((?eg%Z!-K|>qD{M4)o~7D+lb4gE?rbH&py?N8D>&tD*3qMaR$!t5~kl zA6<BzgH_p<r&zIy;4!w;uAFulhWvBEPHrNwaOk+FtkU}@=HTNuxf2;D<nE(c)QJS@ zO}KeE8^vtCYJEre84otf#6(z$*qes(a)QSde{w{Z+OP_$oPQ6~Xwn_#2#bYBv;JdJ zyo6Nimzxsr>wJE=h2dU&4;wA+e(8ml@wW)Qpm!fNYedbR^0WxO({Uv_3oQd06?oKP zu!Z4XLM!*y`TfFF;E`=db2=K;IrP*ZCGT`t%{?6+TPr%KL-ZY8WXKb9$<}DEn<acv zN=BL0uMZ#id`LMzxMx+$`_X;lSLnnn$79231MPH`pG=d#pD}-!+p7|fN>)x`KF!MH zs5d_rA55ST`7(O`VB_J%7u)_v)~*-w7CE-{4zb4^^D-7WkBDcnEVFkL&mPJI#jL2J zf9=xz!GxRmUeDb7_wG|OLNq^`W8~U)1L7b19a=Hkv6c;a&Uz*G(Lc`=`vYjyx2;dl zal@|UcZODX#uuN`aaeyya}YV5U%1ZvgqC<h+^j>k;LH8Zf8>c*Urc+(i*Np^G~ZT_ zGNz<<=!jxMzdo1_`mvm*Q+81EG_Q({H#w=o$A>da;thJpNXYW!at9F=H|^NL^=}ag z^jlOPRJBZtbqs`u5v96I?+}p&XnvF349;1piNbmrvR$z=0?jMrKC<QR%kPL+XbRBE zrB&FvmKVk!B6|F%x{=4tH42UVZDE{f^?3XmGP1npvz-M~`8%PA`971Jl)U+5Z3v?~ zzl%I6MiWgDY@bXwTI1NNa0gf0tRCNq;Jr&Kt}k>k_*g29=oW4Hm)}QKbdRyo;`zHT z{jKB=AtEnEd;j3rnnN2tC9beYrIFpDmBINGXd$l|hM2z*-%cFC`;b(8#;upBGoBe8 zS-zW7>)SK=Ptd~-CdFB@(If80_}-6S#p|tKkShkOW)JA*64tYY@;RrUE^7z&=AfsU zINT@FS!(JtAXGK@sCE{8g_X*?q^eP)yt90sMbcaNke^E+GtpoByF(!fb@;SMd=W<~ zPk=%T-fe{~=yK9+Ud;;8%(9aEk1a9~6?EBtlv^|lI%e#U8Q;EE&ao+bBM?}`9#sE@ zy!2L6&fECqjA7Xwj?+))_V&q_*KX_&qNbEC?QxQdgT}J1oou$w-S)1pb&pLFPouR& zHEx^KH-rw9ja{=^N7SmBdf2DglwV&k@O*Yr`1xU_-2>x5DZmI{XR)a0Y%llL+mBy~ zB<0pRWZCbqjbA^}i+Z@e&>a83w2MF}3tzhj<zu~z{&S;QnoiqXOZL5$u5febW%6(t z<*cS`RLj<vOw=?QjN}v_@9Rms7fe&!Med59a!?@uF}L|55+ypRWcd2q3*K`>e~tVL z88V!?jNONYi%;$Bt@f80aN>J->jZnc0&u$BpRJCM&Tvm(T?R2`4rinJ(|9USu-d1d zSre$pXo@FlxkT|xUoVn{L?q^|TJwzBNya%+#NI7^+K%_b&K#%Yh1-rmm7RGkt9^vS z<1f&R?OEj2p+c!b_ur<rx9udv^^Xl7mq~C0=rM^s;kz$K7<5f$#<v+fCVWSJ=36<x zS8rX*p=_LenyRB5`_`~1wUa=JwCzM!_0U&}*sUbDZ^Wy`?mIK2FSmAcE_Qp>H>W<| zCmlblsSUHG@p(u&zO`>fT|HD!5o^~!`K4{OSZ{K?Fm^gJK2LHo>E{Ptt)HXnmraGV z^7J~V@*3z>qs?3o>Ion)3!8;?%PdJY;?ao6rz3aQqu<*+o8!K0opeFGuS-*olB1A$ z^IhH<^mq(CTE9=uwzv1BL3Azp56|@2^*fiu)B7KYwUt_CC^#m*Bv&T=8Hn)Sexj^w zz$JxMStR{~CVu7?Nj=;6zRLBZjS2d|*=A$EKwD!H{?F~x3uMiuw}gV_aFVv`Mv9!( zHZ$*^7ZDsjEt9si+ZhQGqvdFRhAqbJn7npWw=Zlh(wy&3iT8jq`45|)Nb`sXB`Gxi zErTi2T<A_o3ys@rf~glb1#t<<aJP3aI_qqM0yKh;b9%Srs$0bFwkihJZ=&vUsbN(% z56=I}JX4-Z9e5yqk^kON*y*+64(Io&Klaltd1V>1KjqSFAFWX%A2h3!4e&gAl(GEo zY`@L+_+jyytbPA#U_bWIXD?-o@z^ky28%%3z@Al`>U+ceO8pOIwo&EM#!EyQ^eyUz zIZ`x*#8ps-JA@VzRbq8}pT4===aGq*%)x&%TA1ysmcozy#}2`##aoVXjmun(`SxpD z%vaa39JZhA;#R%Jl{$GRtBTV|oH#x)r!wXjpL&;&od0RkcKqQ`wg09&!p5rQ@7#FL z=Jh0%<+Mi73)W*z`qS&(!0U!hu9`R3?IS*)(%SUoIv;zEM~u()RLaWGP!;2phQBtW zn#q~H>!`=^>ehDl3BSlusji!IS-4x~R;fu-yG6Ob!?eof77_LK?BCdP=%IHRy5Qg! z#X%E~YOPzHGpn7)s$&;P_b2K~k@z(Zb-VPoy~}SEtbKU-kvPG92wF#Gv3t$oO~n^v z`SN(aJVg|&R*d9ee}BqmQKoH;&bU0e9prQoalv_DKz>NvVjSMtuxd7b@9*(F_K7B3 z56XjWZM`z1zQut8NFuRD)<{0P3Rt!P-}b|@z+<eq<Hp_hJZIx<!V~#}a+8{n6pN^@ zEu^;94|OMoI2R+ua$tFwgS@9>(pnyL3G#Gn;luLObj|ge05O;z{`3Hgs97`J82N(6 zUD#sfn9r%MG?F6Y&aCqn%sx!-Txvf)@%2_1G>!I&6#2sUM+oC0qy0=|rH)9t?1Ke` zojUU)LBy**@!MLlDzmqH+YIvRm~57utG%X@mg{7xYvpc7XH40}(7Ejq=IHI8ex$~c z>5iZjVI{`R(#g)*n0lUvUeNk2+9hJ(ljid`lNGCEP@^~VJIY=qAQFDob0H|hI_<SC zXxcLejrMbgZa;i#oI#&BIjxFD{-8(&zt8PMn5)#<_q&oluFfeB$)jT{MSRr)>syRp zKwsfH#8D^ipJxs{c!~>c;+o!5mXLzp&62y_CpTnofs-m!=RB~g9C`*_{Fajc-g@gQ zfVOGZI{f@Bs?(fLHIws-FxH1KTW7}g8yU^eYV-9Yx8Y~XHZg6<hs}1>%#Oo9$rdE7 zu2;#d1eSis30&ulKO2mz)!s4Ew|l0m5|<uXKhv!I&ALG;iCUDNE6VWF;P`5CziR&5 zTY`dSoS;H7d*{^*kssQF#`mt@EgkKP;3L@Jd>Utu@#eWhFcX;NSA)l8E-88V#ji;4 zN0XAgjTt6R?3TK{pt06_aqP=P(rmf%5OHj_YmGSlu;ALY)tC6bW8^5+zBrWk%qP+w z9dx&jPHgd7l}kto3GQ3nf(O{wn>sk3CbDx-NrVXU<;o@eZ>bP(KQM$TocMWr<BHhf zAnA9{@nmg6jIzgj^OgP%eK(8u#l$iErE<kGu{QdpaKo`VS!($9f?(ggGll|-AX*+B zx9;-v`x$B*48gVjTagDEBi`KPp|Nx_9dvUv8V%^=NDb$*i0vy#N#_)!9(f4MRylL! zw>E3lgPqUamt!N`r&+}-Q&gs2L}WAIj5=eeeEVKUHmaS6(AH<ULBNYbWg8bERr%<F zYYm+dFBV=P<zbqY{GXRdC_E;ohEfeygtUz3$CTmF?(z|5jK$x#(t{(i^OTqs!_+Po zk-OiR2FZwT;e8i-DE?IXd!YZXjoZhD3B2ph*!!wMc+#46ss;}Y&Ke&4QhW7e@no6h zjE2fb`H&#CW^%~-)`4fdLL`c!h)gCyP*U8U^_`{^Bkkp<gyztvD+XUKiYI&D6uwpo zW=b3x^P7*=NWHN15cb+=+WCh6;R9{*_^+~@KUM0wS?N;jr7NeB`<8iJ*WST;I^8_s z3;lZ>hj$S_mc_1PG^Jz+i25_aL~(t&gicOA)51haj*LBh+ee+@F+Mc5OR8VN@89y~ z9QK<&O`<1ZfEci6|Ec=aU6Bz65&hG$(9cQX#*G9K-S>wiC2X-Lf`~32W_?c{jE}mw zHmLOf>bgI+yb-jRH4-TCfn~^e?RZptdE(6W?BPG4@<p%q6$M)SbP`vx73YGYjOiAK z@D`Kw^_C$FQl=tV8KxgTd^Rj2iL?fRk6otT(Mlo_%gQWm#2i#TM`3>z-8$GWRPY?7 z>}7OoVOa7hCZ-+*QHbJc413{*<k}KSg)b3>BtWK`ocnUZs2%5AmlRI`4+-wFx(@~) zQ>@7W+y`}6&I3XZ5ig;9bG+La1H*@2EHr$=hWAz0?j%$IR7izS94A=5FT@uIz($D% zXdV#1<Rz3<>vm#*ob@u#bOO-O66@3&fY#E}@Q-^55J}aYm-k?jrjtiDq5z+4YPS4* z3&=7SJ-q|4R8Rw|09>lb6NCUsvl@~hh|2*=+1ehJ;z)Q|8>YjfCEhrgrZ)x8@gta~ zn%%}u6A%+;sOTL)(qP6-vIFRHl}Ga<2uo#&$TIBgqhkO-QSE!OA%JmkC8ZO92;W9* zp$5R~7!IRkfbT->9LI(R6n{mkmSc$k&D!#_p8yzzX2>Q83{_bZSo;PR<?Y#Ws}oGS zb?c%T#!(Zei|e#34#E2n-x=1+d)dH%;-$;-wcCK!REdSW*IjjJj@D|C4jbN@{dn&= zjNC?VX}dq{^Z-_j+wrH{C>41k#6e(s*#HwrZ@Dd!-<SyFXz`d$?g2#Gv*UUZ$jp|g zES24UN&@g!=Zf!|qX~@wIBr@$s|2=wA41MAnuNJ#|7>i>2CDN=k*`8dJ6S@J#G>&9 z@NjoQy^Nz(P8h5#>=l~<VS6Fa8VA*5UtcO1GVNPHsRG!h+f~~_KA~*I{9^03#16AP zSe4TLPvV1q4_08D&qm#u?kUiPl?Nz_og8<SVRF)13ytZy*pJ$2L~4ar0at&Tloa<! zG{B){YW)V1ET|d#-`4gIy23z`TjUQtks$yW+O>*d!H_zvnCa39az2X}Fz1H+Yj)t= zb`CFj)em_PIKGu<#PlJ&u62F*c>?4e%=A{SI?H4bGVi;6<W=`_;boSM&nSRRXv)vy zGmYh747^`KQ$L8riM{b82q!a)A25b7AoL`}9q$If`Sr^g5!kfoQh^8<=jvMJI}!mD z2A9r$PQB)BRdFlN@U{0cZq7(<a=l6bPDz{5@Iw@!T2_2H9^PPaY<rC!f$;dK!$|`$ z8=nn+3vZGV_l2{!Vm{4qdv|mM;3*+yAOFWAE`yl;2FB#8mE;167>uFy{<z=<F*Ca^ z$OZW-`Jg34E@cKVRx|B?;{=xZv%5Oo;IR8u6v?pw_BGo{d5jT2PrjYF(Y|t&t&fN9 zvG?9Szz5bkCk{|}cnTnHW%?(dZ@@iCGmErT9RQ~4T&5;BDis8fDQUMUpf@!Teo9V2 zHDdCt4u%#VlTH254+E$iYo5IX+;#wCR;x953$Wbcr8N6OApcU(Qc8Lu{7gytgFQT3 zXMTWM*(51SgdttybH|Ug;{cV6?=!Ds&2GTZr}X@<!DL{0rZ1#x=nTWV$w*#ZfSGAs z?VCmecp|mGuS}GHiu%pQN6}yhE^cl9!y9am!0|xZ+GFwBC6pQW#V4=8<0(aRD5@cs z7GH+snaI1XK^DkvMxQZcoWH?3nl`m(9sLynQbvh&Z~;ccVyq4Kfs5B!IO*nZP*dCs zjh&AH_de~{Z}v1G%%KFI7fW-t4;zGlV?wkn1<>F4ITtlyuy=WM3yN(BmXm!F)g4gY z2~bp-!nIH)!;{VE6(-EDEN_761}o(^DDUHKE2Dncd_qlUWShM(w2~Hyf~}tS1{2gX z68A5X{5el%L8!g37mGW($B>BqDELmX-s9yP*s||Ae!kERw3EN+ZxD>mtLdGrf4IpB z1HbSPhnlDqR-k{3gQ5<)<|z<{JiTbGB6t#W6KhTfk8TV{i*(9SE^k2wLT4bZL8?J1 z3c@Va$er*%Khlu6!7L?r9>eZER!N_I%}aTM=6vl|`=9zV_qHqIjm-emDSK$M>c#-U z7>RaIY|wzIC?+OYaQYy0-yMMv24v$MSlMHNfHn4i7WfqNfwBh{Q9SHk?7#hG&htJn zP;87M^<H5!Khhf7Pi#a~Mjyt^Y6bvEmNs>p4%JuvU{7}g4?p?3_3b}Av%uVO!W{z0 z_@RntY$?UZNgCLQaG0=F%p1vLLa;mN(vT!BktUGUvCpIqK`lAPW$y)0U;!Pn3Xc>G z7-CCn_c{JZ{U%gXz7Fl*ZHPw|2r0y`Cj^PNyU?sJhkp!%i+OUJY6{6WmZ5s;x|Tk3 za|uV;#{&k?3%@^%NQ7XO5R&<&a+PZ~+&evm2a-TJ9eej|F$L~dpFK|<so;tzj!&d? z#tNI%gJ+{bzaHWWrF)HA`XUe%_3&zpT)iL?+QMDOz6%w5=uQ)gb>DE9$j0bJFSz4@ z*34T)1BsQS3XP@mTwkU9R1&~psZ0j=K~tSwE1PVP%7dwRvj*mQpvb@q56=xn{HL&Z zeX|j+hI<bC5{fyeKcy1P28BbgB@AYRaZQ*GZd&=<$RrD-vfpw2hh)~WnRyAT3*92} zbhfS>6Lih;+JXsuLJ_E>i|=0H*TB^o@n>$28>r4DKbYicm_QivYOYQQBIHD3Plw*o z`_1{{KNz6tLZ0>w*q9cH=Yxp@V4m1ZBNzgU<6JgxB@EwA!9#nSo+;RsPRJQa<_qVW zXYLl@K7b}0p-pvU5YNUVF*R$@%PNoyWc_55z_BUUUW_Fm5e}F$dWuTbZG`6#fR`v; zJfi}7!|e))^~L+@_0beauaL(d_a|<SX_tq#gCwIcdV5TScEpz^6-KBpZFE1%M^WQ^ zdRxWx*{=Ex4pAIM^3K<P@IW0EvVS0M)7kbX+uX&}oQwz|8k#po$YvIZemqY8wj>fx zd(y2nnVC6&P*k(iME?&98B*fZVA~`v0Xxn4WLB|!Qg92_iQf8L7@UXfGBZ>ap*kPC zgn}lcMK{0E1!EkY|MV!2=AnUBfNQKgLB99_jsU%E)=hcX!59AbgKzwAhgY$}s2U9e znOimNZ~yBb-?F0VMLgjuA-&}DDQgY5uTP&14H%>sC$1Wt@MMb@;t9mzoJZV#5cUs% zO-Wk!>K{ULMcd?`ACUMxO~B1JU)T5k3w-Hp_ZB<|o=ldjknOrQ@QDT>c$X||mGVGc z+^BVZ$_~oQt_m?zhB?&dpk%i<=3#**p+t|YPk`A!5(uIEvr`Mo-V*Q9UmsB8Lv_7& zz&#|bof9ALgRaZYH4+P*1>j2pp3#f^jdrEOc19xZ{>lX4rWo!VUtWNo^G(-F8?J^j zO9m@MVCUbOAVoopNH|&FFL_&oltW>0W({<L-a>`0<v?nVnS|S(FR=zyA$%7}>lf%d zQy@{L$CdM_93}|MCq<$pV3Ib9vN{beeB*^Jbw=Rkk0o!3pDYkDwO^NEV#MYUduixz zmF@D|rhy-chwi+FmE{u%Q5`8-1HkO;zJ(C?EK(Sk+S)kWYzd5}CsMX>(_`BkWD-Ob z0c!TZ*3Pj5G>ETXnLQwNC5+bje8-@-cOl^g1vArbP~BSLo%!#(A%1Ep3Pd#SHywB~ zpa_^5M<4{3O}G$7IiKNqZMi2y@@x4u`Lx^^cWmmHBlIE<)a-6MtSJ1K=Px&1x^7Mj zh_x&V0DLTtQjxK#^4~=R!#|AzM|a@c1!$3s*OEA48w%PD+lmgp3x+OBO80&PfrdSy z;PxbyY8ZJ12PExctVzBc4#?LsgYl0jfahbm{JEQ>m_ZIxejEX?8E1VjJ8<padu%ek z8@B;~cUoy%A<hWgH*gRz77v{_4p7dHg=t^_c3*gfV;oFo+}P_gNG#}ZUO`-<as%`A zP&$j`_?O;$x8FayF%S<jdOZwsCJ%PDQNAhZ(o2bsE4?6e#>gteZpvFt+x`BRHxO&- zC8^{jR3KZW->~CDIG#U!f9Ev>hn<J#*)Iz?#9UEWD$UOrMqfRH8yj8rJ1>Pea^O$- zX)2c}5UUFb7jYn@hPgR~HuF%RqrsSUwoVv)t&N)B)9||2)Mwys7*m=}72h2K5G-9+ zjwjGLDb%-seJY7J7Ymx)>UpAiP6H(7%`}dKD?4<p&}U0GW~yLt!r}IxR{bBunZ<G8 zrkM|}l)~pd=(?WY-(TO<l*g?lPsu|JH&N!`mn+nRm3Jyagj^j4PElVS+BWwuIwVma z+1V$vTt*hBXeo$%eQ>;(<dbUYqFbzmC03}HYxLn;cboZpjlT>bZ?lw;-*8-7_Yh)< z;b5RU9gC4KJB#x$Z~b05BCab)&4}21pnrH$se7loJnHJ_cFWqO22GKz!S~RSv^dU! zf$`cxCfCQG;;Z>GzE39R2R_rns%Knjr<(genyxacuBKN5rMMNB0;LppcS@nSyL)jc z?p_KMcXxMpcPQ@e?rz2JIs5(Y57x@cJd<QHnIvpGUuTORn?~Wn{GB<7GrZ6cn<95p zYW*d08Fnfn#osA>9Rj~G4f@90gtisR8~p)FN_wlrpfZ`9;pULZi{0d-jeJ0T>HbTY z1HT0^=v^b3H#{PVB3&OrD>DRtg?B?^DT8hE;X!jg=na%jWpm@HP3rYC#GkL^d9uFD zO3l9Yt86kvy}f0mLA|_?NlfbU7QF>CEvU;A)LXn%;u9>`!dncOy~Gj_EYv-)qfDEl z_QN=JujR-;&X#iVE+D_~Yfo1gzlYH5D_Nj^F;{`89OnC+eyCal<>QD?$P|%0e&oW) zgljp!AgeRu5zn@OA?mK3y;oD(B~k2ME-`AN?q}t=I2n^XUJdh9s{Z|hIsY*J$&>t5 zI)HAmdQ!~+qtm^y9SAy%NBun`7r%PzT0AFX;aU#Jnuu)7mv-vy-{G_4b{M~hp>cE1 z%kyHR>GYFI=NZ<%DN%>)EI(f`OabvA7KXeixB=7B?w)p}K$;#UH;>`gxd2rsi*~97 zU{yL+TCN;y8AixX54V>!g%7kAl^ij;>3QHW{xYK!L9o%D984`(Y#FmvJ-45cEvp<V zy$qTf6quAgU#Ut!K|#eGc}EsynQnyZGI}@K`-vwHGz@4E|MgiGr@DNe{`FSlOBR2) z@+AvlPmpY2+mz8~sJE{r^_$-}>k_&5_Eu6^oUFJvUWm$!lMU(R0)AD$-r2jXq}-Y^ z&e`aNtIDkXf{A&LGE$WC)j6wcq0=|0)XSU}Ka}ocVj&MserDG~m+vCi<|8d@VyTVZ zol08^8gf4o%1AZUaqFbs$BoX?;wIs6m~A*%<I}cPUsJ}#jn3~<+^ipw`{6zR<|^Ui zCcLFCJlu~t4|KJax4T<iD!l7lphC=0;JGxQOw8M?Dc<&(Y(lo?qDK}_LK<MsUE`;| z^)txC50fZ;olpO4oy7S`V25Fjmwl4SD{~chz8RLSQhE8Fn`5mCzJ@<3b^Icp>es71 zf6nyIra#7G!g*0s(h@}<hJ9AN86uX9?t%4#DMOEpW~%nma9pzN#y~t96332sJW;d| zu|+t?4iktvV8;^0ze18k9Zbg}^Fwm=0X^zeF8l^3l&*6~!qt)cL@h+5eMk}{ZQijE zxv%0TCR1mZV)$2xhVRS$8q!y@zZf8@A*BQb@2XJ(Rs8w$B9TzK@*zpn;~2|OB&@QV z*xHEZV`pT6lAD-LYjTR-Us7tmFXPDpx=cF{u|(0Li2Z3SJDW4zG|1vj4qv`)>D~Gz zd|A-_kol?`ArSXh=(4~s;ZZ6dX#%3IZ9KNqo{##?Vt%^bG$3bJ#`hIcK{n{G;)k1t z5Uc-xY=~9#`-X2l=<a94$HH)OLd4866^~JI6eZDj5$`AV2G36ieUD7fsvsT@ou=TR z<?M5cCM&i(!boAM1;W~s{>gnqQS!l;F1|CM<dP#CKku0G!Ip(RB2{q<{^gNd?A<nM z{%OT_Y3e9Uh!pwvtu<1&M@MaAO8w6@q_l7W*{BwU;#bl)ue@`IA7yop!?C0IC9`6w ztv)K=zA))Lqa}}WW=x4}Qtx6|UQAqq;=GuvR#-?w$4bVH2(&78%tj%LHZ{NFwQJZv zQsqJx3*@mXy;xvAQ9<oeub!{KK>k&AYe<lR;MS=_hLU4Ei_8GgYn33H^(Iz=0)miO z$`2xz)>S_d%r5}(_qJ{s(;ysLMF^lX1us?yxgif++}Ys-;<jW>j-WHQ^P1GjG4v#= zl;UkK=|_8D9PXPIv<)x)hnI~l8I2@gB%x8E9~pS>6>C22baV6HyD_wG39Z|J>2nZW z>o|Y;*Wb}&>{iD`Xt*mR3i0@#$8p<5a^9JJB#71m(Q|!SAHbM&5N6tamwKnWlz*UQ zU1rfwOHH&w^4#CK(O;MZXAblkIU1`?4?LJyBLmd+4(o?+21(=rC|1o3ME|yb!vsL7 z+#4|S%I<=UIubQ*dS-x&qVCoG9R25=(ox>b*O@t5?|g5;a=BuU3GXig9}-_hnRO|| zqU*z(lD0FxW_g9G9`bvN3kUI681HWgz!Nf9v&P!AU$*13k(Ivln0Xtvkk@iwzraIP zfKU?7`7NS)=*h2i^ww`7|NO{kgbphKG4nm6y<qz?R$#sz-otZ8br)g(8-h>qMaQfK z6Cc&TSEwNF_**I*4Tu@x)s2J(O0VxOuL$tybPRodm=MY&r~i8Jua_}}r=i;ghn{M~ z_XOa5lEqwGTE4K6fhHGuuZng7z%|@AzqNh8{fdY<mV){%FpTp-!j7QYBIXXBxjQ>i zug;JhY-V4<S&oTcy;W!<tvPF<Z2}@4#L-smZrOfh8UZOi;&H;X#l@%h0?Hozd`rl+ zj)uKP@)j0hcv_~AayS)Zb=eE6JI@wht_HP&U!=!6M9Z7W_aUv;ac%WSPN#2pfrUI+ z3!Qy)vH@wxMU{x#2Cuhrq?rk=)*mFzwz_x!3Xn!Lf359vvW07_9_w+8)el|<Q?u;z z&%f+rZ?hniP4VUI7y%c~rVXcb5eYFbWy{f?F^9oYK@?|;1}=<c?nj2!Q|`@utpELv z48lBH`5<RA^MzoWE*O5xwZJtJDwA4eKLi)b17Xl~xI4$q&WyemqA=w55KDvK-Otbl zTI`1AW5Ch!B)iJ`6ZT>vnwWmXbP0Bu&;rt&5*9GNcUGYdl=J~%MSsEeO>Rnfs{RLC z)RF$z>x&M-7BziNZ}}#%zgXLm0r~>N)=}?OB{D5v9z<dAi~XIFztu9XwYmp#9LlyJ zqE9nr^uMbb+tDd_73_Bm^wvXQshjt<czaH03yf)cMju>DSoY|%Iza)^w;Dehg15|? zrsrR!#kvhcYyhDoViEFWKuS=EQObCNpfWP*j}77Y;f(h++;(&ZB+{f~o~8#Z^3v%x z0a=7yi9${NJE6nzIDk96xofu_odw~%cJW7c!TWMxw|%cqN>GYXN^4BCWHtcCWi_W$ zK-NYWj`+(Tm+d?7_d*ngb_o;8;x5{NOp(vhS=R&HC`#61Fm~D_JrA#fYk^NU5NeO6 zf#fqJ=0y9wYJ2c0K|CzN7Slbw)jK5=`K$NG+6=>yJX>D9T2k;r0xZis$w&#RK?|WK zJi7gZNcZVZe9dYgb<jTr%tTm=)I+gAq=OMK;w`Zc(y7MZ*CoqC2%HR2*?vy>QXbgd z)o6SQO@mkv(C2;$4p<~EmtQ&!5o7YIT|?QA$P{kcdhy_6<y7LLyPezsS|L3Sy?QJx z)hC5}2_!6Eb_Y;^I&ttug@BJh_IwcYYd^a!g_{1zy2~*Hv4Ox%&p=2$-r@v!(Ngg& z{=i8wI#2M5Z2L$`A2K?`-3Nr2ASr(tdvzC2*O0+mD6)0q1k~pO`<QL=?c_F)(xGwQ z<PjjY4=1LH`q@10MQI^}v!y+5OFvRV%3-kG5h~*^?xe{HXK8(~`2&t^gNJ9Cs(v;X z3MKg!LjA)LAj(=6p=3!@nlXwg-C`}Cz8;M+R|Xj)_J}JoU{YdIo@wlCot-HJ$48Qz z5hRn7h+`ErVlkYZ(G!FI@)}!sIOu*GleF-(@YqMMe7&$;PTiLpYiHrP8p9VgMfl@- z-W$+xdipo5Y-sCb310DIHpnHPDLvgn_Z0}65eF4>S^5==P*wwP`1W;&+fh~)iz%Hf zrHM8n=s-&_be=!5HYqr@RK_f;2rb@#lZLcqDZdMCTirLw$^OUtIiYR!(ljUeU|+i< zFfFaNM*aTstOo(4*r#}*a=EUkjj*OhSsk~c_GuH%3Y2Gf-U&r4FH-Sn#tDBt?S~AX zz(gzO#7Z8oY9Mv0r$&s3S9}LpXmA){iEF|^*h=8ad5YWrAPsvi8obxqI<Xc$zgWyD zm@3ll@TcBKP1;+Xt066|9HfNRKH}>>$Ur$Pn)Zr|`i30Gdg>60`(|gSKlPy0;O83h zi5|))trP_1Y|#?Z!mqYc1SQP|I{vbgW4*Mp;Oxgzzv+*epmbxP8$Ryk<WxMSBK$!& zYm*A>NKb5HRLPTLIS_D5D}O|j5Dfe*n`YtUoR$$5{JBX|zNKYnQbbu_i_f*Kjj#Zu z$1-tcr$wmty@0mI*ur*LT3SAcsq~tdmru$HitC-w3&iS*9*27BU;-f_4?y+SLkO(j z>B$$6n(nu)+G&u7K*@8<mpj3eFt<io{v%fs#|)&#uO{-|<54>KPi17EYhi>+slMA( zYCM#DlM*k!fsu&0dC^l_EnfX55%aHXZ23$!=8rJmXGfSgK`Ld_9JV6e;P0_a8bwny zwF>?cZ-~0FS6?~1OUD+I?J0g+_B_iNx!F)SKz$imEiyGZ`;9KpO$|?R&5Raor5ngr ztV7$KhE|PYu`W&B2PTdqnsj}6NF)hSYNlBJ*108V;D4S@RAbHBrPK@h5P|p=V>Q?3 zW!_M1$>!VVp^M{tsyuRRqu1LCs*Hb<-HJqnRe!a!VyPC~8EI2r(C3}D`lteSPYB(A zR&Lz=aCNe=IXj`y{nW?@$!WpM+2WU^Xo++CKz<qMi$meynf`H$95mE^k9?ergRznh zk@|Jc0e^ht@CVNA;-mO>dMl^zvHg9Na2Rw0Mdv;L-AID;uVoA$d$lz9@{v^ZmcnsA zV#KqZK6tH+z+iSunnpg7_WMxoHjwVkk7GmXYX~OH{?|KCgM4BUUu|ugW8nuy+@6E{ zSXRTxX#f_HdG#yw;Uxvim_k|2Kn#)h)<!As)0*kjw(Q4LhVEEp040Jf-YNx5T%XS# z76;2}kJaXr1hW*~o|fDJxP?0oo&=aR{psspW*Kb$*WNC5{&pE>U=-`|v9fglo*1NI zreuwtl=n5Zl+7MnVjC&u5Nirfy->k0Hcw{EJQl&MP{TNLY?6L46M=4;gW|pGN)(5g zqH}1JaMMUsO`fm{Wmx`7ty_^!es=laGLQNj&ta$bnHo;w=6o*AVBteWFY)5Yq)cx* zNtd@r*c~cwB(a?Rp%z;qgI6!cPspk3txOzW_QXAr#Dp|zt^1IJT(ODYDQumrwKbA( z#fT(Ne&G)wh(<Ka9sL?#XcpDW?z+VeJtA)>^!c^7Og()ejR5_U;%6RZ7AeP<E!mZ> zzQ90x@Yv}~z56GQFYy#I#v)gBT5mJBp%QIMc=!^K+y@=fGh5%W5#(|z+NMMx&az9f zJ7`|&oRX(@L$nm$WUzN_=OLNkD0Q$ATYmU@n2bq8K;0alce%y!rqarV5V)3nhA?`p zgFp$0E4e_9#UHYK{NdVYmI({PPO0yz0$oXa#TfUkXitC~&%$W=TcJ+TjGSnVLYh=g zZLg(j{&N&DU$Jz7-X3mUuMc}JRt4k0Xa=@wfpQ6wJ{8F=bJ&onRfA9qJf1E`+v_ci zqoMf+b*>Zl5*sL;!|A3kcV)_)kmvok#eSY9STdbEEXBnukm~HU8_=;ksZSVB?bNA0 z<**4&cCgT;-(*jlZ&n~7Ehd(&HL=fpM%WHg$B`0e4O+@17*J!1<Ekmi;>_(ArV39S zpDC5Hcrw0LXI{<a{bqJV!QVbN!yOk(Gt<idk->;QR_(IcdR0g}5=WN0D{~QhbVF+} z;iNwCG)AL@9WP!;qe4FHKrP)k!}^giOrlmKX9T^V&0-pPU4}1Fx(jcKr}Co)$LWbR zO2WXF@HgW}%re>)rH82e%aWPQ`1gti_?j|as?Bm<5>f^CakLfGH7@6j?>UNhimvUB z(4s6Vsoq;bqs?|dupnm1iHB+sl15L%uqcFgwBs~M<uGpkHQJ25RD4nw3T5NBNLR3_ zUnw`D@<nGm{^?1pZ`dBXa<8fuzm2X~_P5wou2hcNF~6Tfxkf%OpZ_D|uBbC1N5ht= z#Eto^mVLHz&NK~8McD^t=|UZXyjT&7Is}PIt`Gj~#z%=_@G2Fny`tE<jR`V&D#JGl z>Y+oQ&<8+qjwI41QK0yFq{1*0Pp!_@^#^`@#dprpK54SKiK$+JhS6BXNrWHxTUOIM zJ+YrOk~t6RFGp<VVUq%kP|kyr$gT0iUgs@zQx8#(S0w@+&rpBWKpW=}jZ@O{|EPkA zVfs$4Y9`nuWxHiKNO<qcfrzNqf*CUE;Op;iq<}#znKeyBp{}N8%yL;3%a!zof`~ZG zO3sh{_RDJ?#NK0gW2xDxXC{Z2H{X#kD?IF>`ohtVBT=k#{m%c#UD;#>tr+j?QqE4^ zRBn0~OGTBrqXcrxo?cFhF{N&LxQwNJa|7MJM^=t~HJ5hxz_I7y!58)dNp7tI?uceX zd3h}dZIOHL-kdX^gCS+p^+?fG$jQ1<rP7ZP&F`{XIE9sAs?K^ZWc^EjoFe>jG<nwy z>w47pc`gn40A=S@3BT&hQh>u~G~b=~2<4zCG38L&o5&INdqb`r)G5cXoU?!f`aV{z z?0ccrDyh~ymT9coMCe#yXp6cjxUz5g(bLkr@7e#k8s8eIIwKgS73omOlc&q&_b}$r zt=;!vybp7fk$RpOiCeXc7sJw@Fwad^tD|QA1(mbLn``r2Za%|xU(eS-@-w={oGkf} zk5l1i=nPGsyav{88~4S@YB5DjK_S(o({HP<rcrLjM-^`xhK@21G`7))$}r9bq)~sB z-1fQ;46Uj;6)Gf?aA(YS+{kXtjt*29h337&95JJdqN85_#PrP_F=}8Nx?w2xAJoE3 zXV{g=6wFBak<P|)pQydX@%ybSniB?BH{HCtGtb=I9Mw)r@D1@I8mCSjzI%OgkDdwO zbRVv#8D8P0MWl7HojG68PSgxE3Z^f>D@cs<=SF->nxR8}gHs=v4CtYekW~}cZ>CDS zLQ3e5-gr`byXB@8q)Gbmp5D>Ku2i9HPNc?i_n`5{yo5UHm`7n_tC$T@$o+&YVPw)i zU-*r1;Sot{AQy63BGj@fwfXdH(yZ4fn|;q`4g0ZpQbe==AojshZa~|$Fl8<Pi%%Ys zNrc;i=?8wWoilX<d*OB*)tmglPT9bE?LQSh1+T*dofh-jN4TV9xs|>F7@1lkRRcGu zBtLSE2N*17MN~q3<Jjc;_X=?4cRDRyY6%+5o-MIDR!y+$n0F0kLDWiyLn)4rIH)2g zOP?hZnbi8TTo_?0%Io~_{EVh>Q2Pb;B~n1_J@-q!P$zT8P!PKq#J-RTyJIn|9J$)+ zO2l1wL0}1>ffnf=>Z}>T{fUM<onS%4XxJ#E_a#^`I*Ez*6XV{?!F@Ph4+fN>lfaC< zF%kn@sL?5UY9iCrH$7Sn?i(5w5jVKdx{&=JKl(2MI@KQNCjIb8-(BlHHsO(G#9`S? z513<8g;-N%i0F&MX~tT~Buvh%z1pnuW7D?hkN;ss_E7Lj=ow;>$u3+`CYArArt^mp z{Sf(WIunfvRm=w6EF2qEn8bi6W#bEed7Lzu%m<MkmDetqpZ7i>%4GX;Er)*xv0FF3 z94(ia@gjV<=7gb;H>md|kpGhy$jTW+F49{r2turGe34jKw_pMx1Yjv9O~1~51j%4R zmB|)v*-!fhLcDH#Nj~Q}r34|w;3y_fE-s(s{v^T*a|Y#~6IK!W|1t8oSwfi1p!2NN ze9!Z8a^_JMWnzsWANFd%@JN@hhr3=-zarF;f`OFNU}yO0rC)q+wlv8&R4Sew3tu>Q z$P}-^9%V83ZItigx0_gxYJyV}$u*j16qgiCh~;cC+)%95{^u8C<Bz;-IAni=LVmuW z`8{(9NA?-?F*-hS@0!q(Iu_-4uC!#>(U2+hlX)f34y`C=%_NyG-w;z}&~2$0391LM zU%+P@edwv?94$&ri|>Pv-*p)z=rL9QQb}7jkUA2f!!Tm^DK^hGat5|d=Wq24;gwjp z4#N+15e{0NcVJHHDh4w`n@*ymCgt$tZ&}ZYV}IJ=v%50>oOwibY}t(8_6N7!-F6ES z1vuFW@;q(yVJ|u`BWMi;?W>_y#%7!rw-df<J2}kMq`5^Irua+;Ue=By2A37hjAim6 zl`o-R4-27iuB#)1PyKE-AM1M5#nMjvZrm5<@+}AOK$Lw8@9hE#&jRe>f|GMfO=L5O zN?;?<xpuqjo5;;TSkW9jUWBFKbDe8Uo3BBYdCc*QPial}w3&)Ffi2y-J@Jci8=h5l z;d|Q1-49(~HaNXkXZOFLu#zYJx_tTd=L<j6)aU7){i?gCUNL86yI0@3bqW!UyrD%V zwI@%5+_G6CVSreU2*x%%y$^~~xf6T-0|s&54eAiid1c_5jTVar17rN|cFedI9kQXd zMl~MqK{9Cr&5G5M+!+sUqU`dN%JLRA9fOF}+}u_k#xPfS>uPfGlI&83F!8yYVrXH< z{gJY=j7U3>@{vcGyRHV92D&Ns?gMUTh#%d1+!B0CjJ}4?Var4C{-R#3fzVr)JEtGI zaMz4H?D4h|mNEG=VIHQh06i{A+pFAbkApxevnq=0uX*}CR}Ht^eux<tqR6B~Kg>TW zyCuT_GQIFzUI4ce@z*#7_KYHFz!RLM<_|*!+Rkwe3QB*-mb>8C!!R`bc&+mSIb;@3 zHMOB8%FMaZoBVa*8ACy*Pfe)d_XOk+I7-%@qFxoBeXt}fMZHdoF?`@lYgOM36+=(_ z8nisD;qvsar7rF7cl}vJ>Vqw#M?4c+O!>WhVwSB0Z@ybelWw{QNkxTG)~nH>a_Hm+ zQ(-@Bbt+thDR9UXW!JFjejqkjMb@*)t<h_yfG>hxZVR&|{+>nSV*$$^CmHQlp)!?7 zmYY$9*fb06Y>|<jQKCwjm|1Se^@+x&c%u99kY*ub1YP-z;7CmoSG)i8VaDo<_wJHo zB*}5=BF?hHfIm;gh%hhv(a-R>VS|^41PjP8?QipgJqf>Y_?^?ynYMaURzGq(pyeBD zyY2PZS3Bon$s*C*onU6WYQoHA*cLJPANXY4d-mcV808_;5)bLBe2o9ll84wa%j%lL zL1y6m)>1fzTvNuYO_fgU-Qd_zrM$#Y#8CoAHug*V1+%f;TmMT|Ix&yo!(7@{8CRB^ zrScmYcXoJ4i%Tt=f8kp5ppk7IGJD~CQgZ&lwarI14D|(wH|CBPtDhvZW;xQ_W<i*> zs#l69ioB7{75P3MJlW=KRR%>-Zr+JZ%$$A7LJT=8X;YqZiOi*2zb<D`HlJtr|3n1T z)!cRv_nRsPzFMPBc-j>{&;8apr-DSJ4Z#mc%!r7SQT~Zg5Wz<E>DDsQLEdIRBdupn zQIM@n&WIuHAzY01O6CF0EKvgEaX-HCYrAYw{1LZ(Lw~y)Wnx|4c^<t(sR55+nxaPp z4q-`5S`~j{+c^|rN$;52azd)|AKkX#E_Kz644o9vs|z*p6=aW{6*`;h<E=qj17Lzr z6D6oSzk;@eRZv~jlb{MNr5)Y)RQ~HKk>#UAV%<e#Ve7(89OE%`2D=8Bqx^Kz#RCw! zriR)eq8X<3&R=g@wY&65g5%(~PK7~AxOTI6d8t5!>T>nj2wic2PDQ<uRdj0+nOoaZ zJez@F$kX!?me&coi>-$W=sk*YYOd0h9<VV94_>%{d41~q^qu(?zn^}sG$G!DDfJF< zcBC#a!N<?Tr4QDZrz({aF0Bh+BeOOs5CA5K8tYgfzzgvFXSHZ+`8Z_oq|`lP-p9g2 ztAazGQ`&m-xAu9pHp*+6vazIISz6P2$MnI6q;+p)Vzzcuy_AUYxA$o@1CN!<-g}#Z zUwaq+wAh%XiO=}2O^mgoP5AF>zVBpw|EB&{SG`V*DRqy&ZM@Q2=)`{(N-G?iWZM^= zt+RrszB0ndQf{W%AIL)*(t&5(Ke2GfgNy6BDj%96@74T3eimAJyO3k^GP{DOe_W+` zek9q*QazKiGNQ&A#-u?dsU9wM84|&o%(q=8@RK{XY}oJeYmq1?fsS{m_9t7RVBIxO ze{B+5AukutXKtsUUgQU({s*1~Hgt|39a0S%dN|@Mk+xZ?_y(#VLY`-Qrkg&dkpwv@ zLaDzzrmW0Tis^gq3Rmt#iGz6_?aq1_0bji*2U2ManQ!y8b81MJ0i#6~sU-b0jS_y+ z64Y>CW*BRLZQ5iP?6QY?i@`5_b#qJtOOp<f)hPY6oH&@AQ+)Qz{e4gk%vxeQ^ODoP z`8;8ITVJ|x^flfS4GD3!IveLQZK6bgs>Y~qu3gQ{S575fJnbug5H@jo^FxG_VJYqE z<<W~KeW4%;U32rdCC((K?3xfRgAXC)6u13fLQoD=_c4kS<SXW)u_#mWgf6|5h;OC& zG-3rp=wuiN>KEuAo(@cHawl&L1mIOXGmFRb%H4}n0%7W#OeQBEo*&tYWGYy3z1DU6 z_Q!rA{TOE85Z@B+*CN6F8y{P2AB2N<{R!sX;;(<y1+Gy{5n7Cqr?hL5y&)q>tc&%p z`WYZ5PpRRj65UE`O=IS&u=G(8ia>`gVUg#>QZMdoeg@{PapTj4zs9Y9|9nlsa)VXg z0mc;6)ctVqhCQXP%hc%q?&L>Ay;wDUa*yRxbG$hFfiTxk#^2^bp9hCb*d(g)d!IRY zVuE<x1Ao9CUif)!2v6`DAzYqe`lW3HTkEgU*hAnds^a)A5x#WG;>-GJ=#2Om2;S3J zRL_HpSM4&GfnzOU=V+Ai!8@0E&yKsCo|pxqbA>CcrLa!gx`C;E{VvT_=SBVWI#}O? zoYS#yycVcX<LApqIbcGT&E<qGb_rwoVA3>2jE45@%nf3|efplYjFxASh=Hm<=I$oE z_s&JK=NM~G-5o7L#9-XLw!2%w9nIxU5h**)D1OPWGlh>o^6tkw$<wM0KK5;+T3#mW z$RetQY-rTu>Q~n^@s}cs-jwu_5qlfDFP1t@>mSnJS&-<P#1T%U?PqFK_kCpd)>xQ% zYTK=1hN1A;D6$~K3nAi`_8eoFqw{mY{+G>Vse|YjHdFH1Z#GIIdAQAw_@JGCS?P@4 z(d=NWs^0XQuxDwii%N;YGlSQ%T%pki?qQ!`ULKU$M%|zz*QjqEZJY-2%Ln)zY~oA( z%7xAzZM8p_9ggTT1L%fG0#?mYG%qSDo5k8>MlscNnV%k|QH!P*c<fiK{TDjDwczNS zgs7FO;mSlV8~Oqbj7;W_Nskm|D_}=>t_f}DxEF)d#A>B7?5tySO4t;pmsEBS0_o#w z<P8p6t<!`oWpl97LL>dD*+dqJzd}f~<`%5O{PY&7`<=vgd5PR>JYr=v)>9ZAI3VFj z-J@$yBw^@lEInY7g-;UNuHtulgfN9fIu!pl*xhNsO`WQ*aEPt0hO1UCfiuSV)5h<H z7K!#g7tth<sZbLV8)nay6A39u70Rxfv2vslx%xAMgmkcG6t9$7=1t5{4Ij4(6Dz(H zN#RrVyl03Px)Bbfdgj3ORhbBkgtjNe@bt~tMb80pY_q8}_Q9%8PEHfu9dm^sM~wk1 zMwU|HXVTn!Z?=Fwe5rSlVnasWLk*6gADvHDitA=;6+V2$){3A1m<Pi$Td%N_$~@-3 zIe2gQPf3NSKn&aZlNKwf_R~*;yl<4mpntb@3B0P`Reil!@ZtAlt=Fp{o`WSKkUh9E zL0xcF%U}o^8WKC49}B7!b95QPMzRVelM>^2cpp)c5HF{9M$O`u6Be7wTXr(@j}IZ2 zNg45Q_O*B0K9F7U^Bpm_GXgscjg(Ybes<D$m798yv)Y4#n~1rf^p{C00-M)%FZLhO z&zrOB#tNv^Mk7!BEtwz4h(aBr##!L;mA1NUzIv@IPBfyU21ves3CheOBSNyh@wfgZ zlHkJFp22&mGXXJOlp2>H;kS~jrQoMvm?;zBFQGBI7F{4_dvs-7fT~G{=0WSNUd-~6 z9PKI8p!cVtM7Pw?UvE)^5_c=^+HbcMZDjmUO-fx)T!&NFd(mPlbo|GTxPT&Os6s^{ znWe&7$DfMbM>DfH3~B7#jzWaTCk=W&`M2maZ|^_Bye}kBYsIlgWSsZagBUM&&R}YQ z2+?Awv9nannmSSzvDl9j6q0~#<?pb^R3=M^_4@ThiH1UddH>njHsiIcEAGsqOtumA zd1K?sRP&I{nLZu`?VW0yXJ7pW|D2y+NmTO*{R)MvjSbtd#_p%H9v;p{jk6+qsC!q! z{nneO`56iI*y!N8m6Gma_CRNa%B8miE5G;g4fJI3U5N%OFBUw$Cb=^Dk8fzr7@Lgh zV>I1z`fAPCn~cOz2rJovAA0IRKbM^#k=<R1)IO%UXo!<NA0UCut<Gxn@uQog%1m0y zbiSM_zHzGQpSxFDZoNGBx3-k?^S<>79IWEC*F=wK^kS*)<E9BZDd@#21mo4mb`e(x zM2zKX>{Gkcxqg@$vI;$w{SQfeAcW=c(KTfYafjQkr3qPL-BHLH#{=la&0}MsMKVw` zl0K`sT=>4j&EI4;mWUu&T31~~)4#gk71jGUxUUG9ITf|&`Lh_U%6Q!MFr^K2MDMG% zFxfrHM)2(?3pfFSui2Ud=$ecI$AbXOvn-G2<gs!jVTs-x3F6qC5V$95hfjXRZqu<E zZgyU;^f<UR*PMenX5iRVt++5uAfRdB-2qXhIBqv-qu{2fL<;Xd72Ld0d+>&X8z0Xu z-zdKUpyeXSCZg{HH$}q3#r}rirs#57M5Y(aF(I@|4+0wx`bzvA6WG2-SLrDaP*b$U zdc80BJ%namVNTerUeX^+y`Dt(viqsf-d{I*TIP-^@X5ZcsLzSXPrhO*&r;^mO7nT1 z>Z~$cKO^w2rNIxq`r<;-E`7kFl8HAlBW5i$;h_|n9me13QyVc;2uWq{(66|Mi`kAb z82zfV5PRLALs58drR94>$aTVxrm(0Y?YsW{)vUiDPe)#>&Y)ABZ8Fnt2&l>R+Wg*~ zcJzGXaLr;mfA03JCvy%Qh8_V8^CdUtP(t2=W{;Z_QA&4u!NM*L2U7fV9gOrATo3Q) z9G41s#(1oHMD{8d-pQC?xIe$Fp)>FjB)cywU3w!rI-Fsa<}{k+FMSJnCrzAQCgOQW zu=)bEwQ-mk*OTg<0aa^9W3$ja7oyVoWSfd!|Jq#gGT9xBpVDJ2gIF?^qH%uSWx3$Z z)9>JQDBPCr-1HM-9NzUTMzdRNcg&GJt7B{-y4G0E25Dxim2s;3_GjB~jbt2=VtcRt z%`<ZSkm_zRYx>aLrV)7QBWCsC%{6k(=&^AvlP@#TAp8I!&Bcm9w<ohCu36TKp_?Gx z03zw>bf@#z83WKT?5YIaZ;fCak;e{RWv&%AYReBQxMpYac`iSP{mA)h-CzUVMXbc# zj<1(P<RmY;#rAd11>JGT&)&P}=MvmCp9oYaYDV*?9(fU%UuxqP{R^}pD$H}%&($8{ z&+@#pWoSk8*pA^VM5sHV4`p3HK8DT2UiFXKd(oKatdPkPH#(1>Sn~{W@6yISOd(rd z$&6q2kD}L}*GFLIBa_hBUt`CbQD|YDGFvpBY){$JF~t$VUu#)GEHrY`&z+)y$IHZC z^p9E_%ccpCmQ_cgU8}6_CCZ<rG~8YKv*CT73NIOr+^lKQ$l~9yo)$;*fl&Na3r?%M z8?tXmI|O{YV`XUYyK>zC;j*#?e+v3Kr_+MK3Ps{zWWSO)ZupZF3*OWp^GTSqTv{5b z?_!Skj{}?h_fi2jDPm3$zOn1?1HX%XeeWc*C1W1K?b%R$N_Li8UpPH4BQ0xBVN^7o zV61>RJ_naXu2d1Gput9^Qa5auxz?8HI%CuhJ7tU1gZzr~PH)RzBRQ%57~A3W9eMjj z;$5x#7WC?qPtf_p2Esc$7)<v4nADr5v6@#kR#?>a*EWSERoic>U-h;$=^gV{ujvoY zV5K}tOpr@^YhDHZ1sMO8<{_gzxC1etSl^pAP{B37LSn2dzlEO4AC0SctZJT9hbVa0 zZ&_%SQk-H2XA*p3ybO9rKS_U#&<q8Iv3?Wq?Tq&r^|!T3o=%(V9P#k?9#$G1%C^r& zZiXxC;aA0X7DGf|-E*NFJJ#xl2qxB6gpWs~5Y##01`WJdB{JpkrQywP72^7D;6=## z6qFr3Jb0}0qK@wQ?N*V8C1shPn|^%i8<xvA_B`(zWvM^r^EM`mw}xVo^_U2<9O0!M zJ4tj6Xwv@MGt#p)6`o|Gv2X2|<eGSIwY8VDd$RI7DX_1NkYyY?!DKhdRqLB8d4;QL zSkeG;tVxk#`6pu{XSsAN8bqo@S6%_vy>glP<fH+NSQGsMTHg^~olM@8g!w;4d*ZM| z*SZ3JkR!Z@-(2UQ9|KvgUHq;r(Ud1xu7Bw1P;wdXWoZfI&C|@rSGi*+x4%02xpPZc zRw!0{)$FZz%j~<NOT-mB%bkl*6X^Z)sV;_(qS}Yk*QfP5{Hz=5GbH2#=gAWqHkt_< zn&V)~xtf5MfPf5dab0qcVo&uL6-4ebq_``JEHW3`WI?zzCokKMz)%}Q)t??kA(S7- zt;VweOXZ(b;pk?OzH0)n!R|&puT)6{tC-LX)f$N2OrBD-OHgA??#95ywK@!?i1M%2 zJT`<|7M{_)wpQC(grOtGey~=H$N90TZv+YN`HDPzMMTS6x+QAx+@Y_o<Ln=ZtjTS% z)1jZVZr&$UmKHv9WMppkN6IibFRKymx2W@qgU7V^`#O^m3==NX!HeVhw<8$lx5xQd z)L48&M{o9@G_lOTL!fQ+j+9;%bP^5ZMkMI-h7o^CV-xoIJal}kQipCJcO97@q8dnI z>bsLmA!gWL_=|{9&pv<O!Xsc~5&bqrbISXm2%-j7hzXto@TLce%<ZL4YOPus^)050 zM2nJtX~sM!teJPe3)`6c643;@agWZD-%Dh!$o_NDKqs-XZt?5E26R`M_^V_F!Rl$< z#@r8Eq!f$(k(dCp$IjTYDVS!9wjR`#*SmG1dqvCz62t*VVJ*BDj2Z!rkpsr-QEG2N z2}fpRBVqPu!Aw!Lwzobxo&;+$maIO<^3EUASfx|LJ-L$_bVlY%o|YF6jqj|5C`tH^ z1}I&Z;`9wU=T7xd(w&SJa5cU-5Rc54m)0hcROBIsN)i1OWE6&zT#U-7R*zM1`B3+Z zeZ^I4JWNoYbj$?KVAGKUwyss?=6+V+El<Ym5qii%yLV$MMT<8%P;tvqY~ix!vqh|C z2H9up>j6nDi8|VNPjg7rqd=8}D0#(-z&fnM)PN6T2Vu!>vlV4>y3jJwSsdm=gbnqd zt=zr0MUmo$m<jyf*Ilo4rgYO_{c`9>>T^blL-2hRVP%nW$rFrpYH?>K43IEN>g@f6 zKU4bTxfzG>E%{&bGAsa_Ftc$7bC#NpzTtqS?T5Xu8)%6%0i-Ex`y}8}V?o^x4ozzR zR;3;$$>UOCpL|^!_)r?IB(cjJCMfO760GK0B0RX*<|e<ZOKdy3bvt=FUxDW({k}kF z74e%bqr0;1F{Xh)u*WObv08x*R4#6+p|tTaCb~fIo8y>nVimnwy~}nM?qdhZ0PZo= zZ^5H$Q%pl`A30Xqw_7+v?MkUFDRxE7D$ban(WFHhSY7Nz*<5Z_2Ky?=x^f9-FbR#} z|6~YF!_x)@l}Sk*HmQRnO7@~Vj($F>Hc{%&8U0<0G&GSw+uORUT4|QFqa|Ct!r5IQ ziT1<8nbjFB8#eSZhEcbblcS8|_FKXfFjp!nYS=dKTb0?P-^P;AA9}o&gVOK~=1A!= zs!Kc#24B6Mnn4bWRohh-b?z8-|AkKD&EH{VTJ=S7wCefI6-|+qM2R89$n|5L=aHk1 z;`P2xG3u~!1=SyD)iTzf=4#UyLJTn$VIp{BE2+U;bL`@FS?yc7*7oSO5^RBSMOs#& z5?R5LO%DPoN2U6wGZAAA_g(i~%FdLwCK>RrKS=!k$tZ94DP=4;C7~!zY?`+wax@E1 zCubsX8;0RrukD{n2YsyT4@s&ev+zu>8?>|LQW;S|)e^<vatXs>F?m6+4T%0Vrn}vz zbyD*TJHGV0*K&dC1@3b})uq8v%gva?jvTwrh<$%et%>aWw9)>Mhvt6)W#7Y1xBDDU zXY;V5VwPN(=4a{Xw$)Q26EOz%viK4h4pu;Os_}%#zK2^&W|y4|NqY{mttWodC)m7X zwb6BuKjP{ib&i>UX^`P5ZCxoy#(VLq;CT5UgX~f@l#@?-<edraF;VejiTdLdD{6Y9 zB)Xx>D^`LL73jVoM_%mU<R3AiM4ghbN~A@L(dkpvVBB^&!SF)YqAv!^#tqSig%V|% z|AghC(rpQy%w{7cVtr1pPuKqZGq~Az(dz2Z88wJAuVYX4YLKjGz;P<&OumM9RAE_# z`(UtvxYvozFt+EeQ`!-GSYmxi*x))!F41dry|nt>a#@e2h}zI$DYnISCradc+w$K) zfaLnULb&bZQ+FZhIzPj{#>@wz8iCe7s^fgA{e)3Q=BNx?c?N?Rjil8J<(tC1^XFHT zXMboV@mxM^W2v^Ye7a(R?hWA_d8%h|{yw2`5_9=i^6XD$-NIW33o&TY%$5+1y`guj zS*Z`<a631NXD8dy<PLj)!NM#WY~u3P#q$Cih}cQtg>~BPfh0GdXJc_{>w(0+Kb`!1 zLLK<7bbU*erpkf<g|TOfCTIZ*8CK1qMYG2CIPstWkG5u)b2W2DlnN`3HVP@pL*lpt zJE#2A6NRbx18g)ijh}-E9mmcJJG0mg83);h<l(g1NpukGY1zOs<bp5;>Jn+i@#caN zh?tgkzFqLgK-CbaXK1RFq#&wg|6uJ=6#^o#pE6tw6J&W!rC-h(^@-u)>#FgIis0uH z`D3AGNVoTWD^#4bAYPsyu3?isf?+i$55J1~Q2O4Da_SaoXflz>9rmKG(#onnKT0>L zl+A{uDmo>IjhJ{VpkQVt)Ckb_(1frQcpDxnCEG0Pv6b-|&=JO$&?{6ZE*G{*L=?XM zL6OQ<rM48pVxzRDS=xJKBQ++dL?736*igNt-M{>;e~7Ogv8#Ify$WB3s4>)f7UMll zl-4VIMKOg$UdPsKqV;SIt<;ZdbDZp%W5Y5n^Pi0y`1Z~l97i56N$3g58aECq^lUhj ziQG}-Jl>ne1Uf%@mrwrY3C)790ctZwrs3KY6EQIFWg)^;xB0OrfUX!?Ci?lFWWJRn zwEj)xb7YK^W|*mhVM$sL8Q+U-oWpXB`ryq@k!q%ujDvS+!jdI+nhjhLl$8DM3|uYL zQW98>2VHHfIjkKUIyyMVQcv#tY1sFvsq`#}46}9Dnuf5UQ%LQ{0H#_Ry;rBMue#1+ zh6b1Cn~4U~_o678ye#G;5g5Z$D6%7d{DzcAkJY7NhuNwG3DU6mqFLi*1lBikO9u<t z+19Sgtp0w7GW=4Wp~?slHFjP=o1YW2kXrtNW1UZ<e|X@N9-Kg+i?b^adaeQQm)FfU zn7FlO^&AC1KS8h5nv7a?Ho=!vHED*@qAFU-Loh*xNq7{b=FFby{yfTw2Yy^4fouN2 z2K11`jZ$nA)<rP*fs3@O@+Ds|QFgJq94fk6)yjp%JpG{Yu};yfJC8e0K0m+I1(&D1 zO|Hq(Ulg<WBZo2;U7?n=nJ__*LNIa<ym*%b<WSCP=~U3EZ~4C^$xD^P)Dz$G6Mb#2 z5(jkm%;OtcL)#LEu{!}AyOv?j5(oM_0cYpO@NAVsEzpeTLoDLbsiwtuX`IgcXU{j` zFKG38jeJ7v>AMup5H=RY<Iej9P7pQ?sdvu16i#Tm$9ui$&$TEJ<$DvTdH=)-Ay$H7 zurQxvQ6PR6ncA{`&3flUpzE=KoxaWO<Okd3Fi4O*aQSf_a)R#ZiY<P>5yj_hcGRB7 zCHxrT0Kr&iAr)ZBE8V8>4md(M><*n)()XQgb3-)q;>;fbFcc$A=9vA)@L3@dMlEI3 zK`4;yD;vl4-I9KLEm5Me8bwl6fOXYN@c^3fw}((Fx&@>(DzJoKZ}-2k?1Y%dtB><l zJ+5j=(n#^ZgD9AaFR`~Lu`~>HB&00qTInrs4R<p=Ok~Y6b!zZ36fF2gSB!gUn&?p# zR0(2XpV@lX=WiQd!pEzFpjDx)d+CVnqy#Kg;cp)AHem;#Q%|UUQC`Ahw-SAN%h7Rj z4m^Aep~jrz1bi135jn{>3{>I2C_O%6<sbxs-tu<ek7E(R`X#I9e2;U@Sd8WYg*uJ% zF-YEj0|Qrq*#;gwmRUEvO@ylq$M1V?-NC7^L_%=^9sl0z;~y|;LGf9UB$OID!>9E; z>jgkz;E0a(7Ybg$vlf?$M*yJulVbE71o2J`wZ_9yX)s*ijjd$`P`J&@g4_v)$98~n z30UE$yMvf?K-9`~0xM{2Yy-a5)8hvZV2$eY6Av)Bx~js_2cF35y`M&ygV04$*~dW% z2_`mZG}SP35Sn}_<ZfnmWB~ZXgDh(=7(T;r>&XNbwNYT|Xbm=|^OD=n8?Y*JK8_bx z`7ai)?*)M@f}s-0Kj<qfO*}vFDok0w4&wu;-my48z;+{Q5BhNM7-yt>9S%sLFU6;n zRd-^*P#x0yV?eXR>x+gSSX3~9$m#&t1T7CcJxTy{@E6jAu{e^)1?s7KcR36!i?Yt_ zf(+0T-uOre=y6=twgWjHWK0SFM?gZVE9*Y52-cNP#)>v`1p!!=-ev4{6`(M}WNRHz zIOM4{Nep&K<ovz2Bolal8@|pB`pWzBc43(A{yYc;D|C^|S0x4%7OAYs0;s-{h|q4Z zrJ*#cO`id0*?*W%2Dt{Yonmfu9hhfxrbCMdmTqHfTtW(HR)`>f1Po1u5=rd=3rH=S z{Yfr@B>|f*@Pzk@0Z2#HF?sj}*4)TOx%vjIFbHyY88CTkU$qF3zMy+FEgMQ5OT_ET zP(Tp@2%8yMrQraqdG5V81Y|kRHCX%?WcK?85P%;vD~5?dKIL$MvXU8W2R70CCcOq5 zZ0d#6WPUhcYQ6iK6yQqBH&;pwV4`Ze<}_om9L&F}36BH|0M)M?%>mdY^U>`m5I(=# zR+=%uqAERSv5|p!CgkTWKc4;7kVb9EDoF+$$}u$myb21ym$tQ}9y8H?_a@1Nu9g|? zzWwwuy8}%QiXypST%cbUj?%XUBi)X#@Am-%bgV!MyL4kaDq~S3F0U`4C{8W;Hi+Bl zlX&C{lI?>|`FuAe%nTOGYF_Dc9M1xX?dMHt`V|Tm%2r-xE)EDyZ-$Bc`wtP4?1E(* zfesI>9f8bv&kYS_5~LHdf{=|k<`M@=qO)w;TT7OS<Pii#Yv_O*(+)xZH+eA9rzLv@ zFy;0O_ff!s(OIMl+n7!)a08mPvAWDyPQwZ;&{CVn0s~OcjC|5PoBUhcKeY?vFYV<l z5Cn5~TK%*2cp$Auam-hUs@s-;&CeC5aex7z@^9?Q$@KwITEXs)?|>tVR$T<Ix^E3Y zcV=t0SL)z0Ix}f4bb+0``{zj~30@}Tc(!^D5CSY~p$bu#IKPhm@+);WDWGQfIF!eh zSh0Y7JyY82lmrFrmR;7(7r<1VI`hA?x42+m_>%N-*z*kz<O=@WfFB`L2q?Mc0Cou7 z47NO_<HHNc@22Sw-a)_)xG+B=fP8cT|AULb6No0XsS-gMU{87z!Zw;s{&Vo{2=&7t z*#9=3Tb#1M@QrsBTP(0m&RfU!ZQc-ocK9um9uV-G^jSFahFcOQpgPOqIu6)+fjqjn zrX}W2kBHyOqKuxEe1ZAjbkEz!z>y))p4-4_4Rr<j%9PM6-x|omO!gNeI)_96#vs@- z_8&~mH(5(N;=iRV^S;6ZK6ouz4KI-=ia~Jgy6+`?*9WYR6PJ)H2sC@9^n0tKF<^Hq zCPl_)K-=mO+keJDl0{ZkBF<Z^B`Hx7KC@IZ)q_PAR!!`Zg3JM>p|wyMR8aVRJ5?#V z(}Mxj;ad_vhXQu_5pb-C0a03<l48@=4Fx!YIL^tAZAb_lUY!&_50wGke<H?hDL}d% z;SLTixBv_P&6r`%FWV+5{UzY_l|ogm0FTECoNqYk0uHaVZBXO}CWmTP!|j7!yobNJ zz2^E3QfMAmEm5Qb_ENuKPs3&_)(3vbd#6`@FbBo2JbCz*o*W1{375tLK-h(m!nhT9 zI6g6##*9G@4e<&Gz3a<_os9QVdLlXGf9LO`mRE`)6cEB6X<Y)HSS4a0fPEa74Ral2 z`VZwKMVS`yKa{2VZRj1;dm`#DFtVQv^`Od_U{JwDk83_v14ua*?x3${fKxSD%`G8N z5!61Le|P_w5D3m+Rx?&hA#ARNkr*ye*Vt*uT|hmV3L*ebhY}+5^iPl7EKuV8aZ}C( zME=EvV-Y$axy1Qu`V40)OA!46dPutH6jTR56@1Bo{8V3#2wcNW^;9$pzJf`Cx0M33 z2K);-aM3yC7y6%i8(T^DB}7b@!blbuDDf<^V<0!`6pH+3NAEhGSW9P@5J>DqM@S?v zs8R7M)1vzaY<%V85am)&Ca==p_q@J?o~)OERd{@om_P&Yr(1I35%ChN@>P<)9Tq4p zwF2-O=Z;{LI@^PW%^2o(vINUF;E{+}^YOsRxAVLYT|@O904TWKHAVHi0t;BOB)|Zc z_t&9RoD|4m`NA0gO$VKI+@o+$pm4ja9QLXJ!=Z{fH9*$-p({cNRGU!vM?s7AaqwzP z6Eia%AGfmv&p;oqS%2!TqY$~l`IW2_K`+>{8wy@qWX|ok@*H^}D9-xpbLz}M=nGH@ zV2p6urUCD;K!!bX**A1ltSboFPUVlCfD9;!%!!qJbzr}V)&d*DMSzo9IDLw7sxATS z#IX5$QNS?j2*j*5$X|g7uApzRbvIDWW8s6;6$X{z`+|O89277C2zKQqu|O=cUV5$~ zf*lxH?)MW4?5GXX^fpqa;z%49C_a<6&1OMAaMkI|M3Go}`s-2%j<*#0y=I9ZfPKjn zX7=I_u;HJU+kirexm@W5DtMq$-uVzR-*mPj3_h@*xC&j+zzQ;}yZnyN9Kj}4$qnX4 z1Dk6?kXrp;jLZzpg2FK`kQmXbanNy}9>~tW?=;u|?|*P*onJi$OfR)?4V(L4z*y!^ zivr`k(jobv?gMn}nSF<w(htDy*DywI60y)818-1c$y)d9?0;z$iZ6x&I1R?S|Gv5V zLV=y1X>*Df!UU@A7tr{vO{2Hp!Fhfry{RR=0_Z<N5Q+AV%zZ#>XKQjdSLXsN704hI zH9aQ<_Axc+LCW3t7o#`HC%@cDp<!D-xImz4m!uFEL;}@x(f-OA$UUWAE><K!-fQn) zK><=!XP~$}IECziU9Nr_CJ3anzocdZv#Q_}cocnEB2ljSUz(PCp>$6I=V4_LFN_+X zQtItzPu1dBAY6kB@Z3SyF;L$lIkR^;0F|_awg<f6G#p-fAXd(A4>%ID$%7^u$hwOW zTDfg%iizMp!FoP)Nf^k$5k{K!t2DmAey<%~5dk(W?0y{o&pUEZ4HbJt;M_^+OJ%ZW zr@f)4Kw<*=E>&IYpyNF^kmM|2w7b9+x3fJ5D<4#YLE$X;Xr(odK?-gv7@e+~hXAQA z?Jd}+?-9DejSO6BYa0UKp7J{Jts}qyHf;QlqmD^fKs6YLjcEne$5`!F%K{*`RJ}Hi zoCb5jNB=~1cC&f$9RNN4X*~{jKl5tl=>=R%wauK*@KrOofP=VH`of(Kc;H-wBl|xO z%-A#!-#!3VQL4B@sCN8!W}Eq1?fz?|?5*ebns<>v^37{R9kv$%sEu|reE@h*=ne-| zZBK(hr!+HvegKaZ7(LLU3OF{!1WfYB*t7XxOV>Gk3P}L0q;ibvA1AO9l>Nlv|DAjR z{+pEls^UPf(H=KG=s5jq&S-oM&H#j7{H)kMD*FZ69!J@i>RV?((8k*>!GGsI6B^$s z(fhxq;Mm@H_kSnXM>I)87=TW+igbMeMTcBNZVN6$G_W~4@k?5&rf4AJ;}gyQH32)e zp?;zH|7pVozx@&1N+qYYGnsG!1`}oc8@!d_CkLN<pYiZ2E$6e(pq6AbOhotraQ&y^ zYn1Yqd9X+N1(8G6!TrK4hV1f}|N4(2!Gb1kZ<Zp^&rn-${17kaaRwue$)E-{z(v^i zgXf+7YFjuEg(V1vOF+D5?*E#j0je+>ujLUc5aXuiFt&V?VIW}L2zTaz0>H#HU5_a# zfdqol8TQG(?K?EE3%RGq2EhDG%Bf&bFBpSh1j@Q>+EnEf%|M0~<7_VB1{Nr2VZ8bO zN57>=Qx}js)4Ib0ZrJRhWMD_(cO)bQ^5Et1L-RKf@)S(uQX(mzlaQLADp2qQ2*r6< zwGUA{TK-BFD3lnv714?qp#>R!LsLncM$Hxe1Nn{ec<VM6n<N~sYdAEL47n>aOi40K zqm4Hdz4_GV8iOTbN>0$dPI0^jE{*NBA@?mdOf#2UD<^CY&8TQC%jOGIh<$C5XV5Q8 z@jEp?sO;DeFh$z-|K35P;e@GJBauol@7C|YAp{+g!pG27Fz#YO1@#urGTHrEOXgzE zw>_v0<}6F@W@N2s?RutYqC^_9-k*9FPpYx94v%J|3)U)`rn;S)$;j^2I4SUAEm0Mg zC?&WiM-~ksGdB*8Bc+SiFp52jL-3O?k(a1*3&s_8964L6>TX(#bxFy0JNRJ#e_Xv~ zTovEfJ}gqwC4zK_bcd86EgjO`9Rkt<(hbtxQqtWe-QA_6v~<INX1>4sdGVZA>$CRC zYwf+)oSA**5L!njzGGY^orxt-vCNx3XQgZP!B+Mvby={sF^S8{MR3(RY8%5WVo%c4 z_{kzEC#7Z@n)=@_Kc`P<Ug`>mlzeL3sQi+B{SP7W9&3zv3<=q8o@y?7`Q(j)YskO= z;p>0hTxG42xXk72k%+1u8{+A@!^9XRm*Q&z{6Tyq(uX0XsCS$0D*i&n(SbCa4tX5s z<pz4n=7lG@dRw~FIzNdA3v^8AvD_2}r5R3@EPhO|)$(4v#tK`m<=raZfrn%I+213| z%UxOe$MCjnL<Dii*s4KH-pKgNXKbym)}N1Af>9|((Ks@n<M&^hcjj*E|E{Ciq$A-( zgvHzY%0eHN;^*VRUu^Eyor2>hhzb8yN>!+Y$F)I&vtB>~B$&d0KQ;r2tyHuq*J+|* z*olp)5m(T_Tx%}M;oP8tBg^Nh-sx;lGJMO(*K%=Vs5?eH%W<^6lxT4`!8EO7S*s_9 zRtn$SmY<ekPCR)gi3+SW)66x(mB=Gh2g41dJO@j%x!C48TH+-L^4bfKWc*h3k$c7{ zA2Zvp<#R{Vw=^cF4OQQ0X+Fb=RHRBQl5H<W$7)0X9*EfZk8q=`MNR<!1}?Y8ZNet< zsw%Bbu^6>zYRxj4|9&n_qC(f?wwOx&i+xuG%p>a!jsAcRdyT|BL84Z2hm-|2@H0Av zPnO(GXK9ZS{5>v3`>F-CI*gX{&;u!?SkaN6-=BA>@g5MGzHh;&xW7R2;w%kv@qIzc zZS4)S|Az|m4~F1kbo5EHS+t_hA4)aig^uf$STw;i|5LOL7kRFGV_9Xm4VN+Owy^kr z7Pn@v1>U;ntquGTaH9Fcw>5l<X;1OaFCJYo@10Y;x9sm;BDvMN%BjWeIPOZZ=4H|F z>N%pmIYT<#f~dHH;qT@}w4S3H&J)?!CUFXMo^`-aP#BKmFrovCII;)5SDioY7(NdB zR%4*TSF6q>YIm^wt)ibz=n88$!1r6g-Cs7)5MW0gI0#kY-CKR(bwP(<#`)$@pa(qm zgHglpoD3fOxoqXG!2pjY=`8NLaslUePO;y_T?`c_$w5bq%w?5iq4U^WYIOwQ+2@h^ zzuvVKyYvRL7l^1{kj(dS>)c9^L(KP+q!&CLgaaJ#_Wkdy_Nt1d4By-Pdf-43@1GCu zi<Ot{;E^QH<irT*xKhTdBY7YYNR*Ddm!~M-TjMe{*V$N;?TN_R<bB+Xi`~LjN$?0& zZMFQaCE)I}o`S%f+9E5t8F1!kQJ@IrP(8I8hzr+q*O?d$@Isf(S`a*tw0Eo(?JU!; z0UT1wA6oQrE*SyIGVRUxFsW@-6&ZEE2y*7~Nk|n2Xg{@9YR=r*FUYMHlx-=Ov3B^X zP@t$(*~-2os##7#u6#nEO0(E)e!TVeW3!_Kn8q{0f1VIb7KrAj2c_yY^WVjX?UF_q zR3hYZa4QLgcwS52Zx${YN*$aD8jWf&#A!0{+=t>)#L|n6BG0nFPWntj(%%q{+eaH? zGV%&%IJWn2wc=M6QD{f^ne$R#=K=o<2}+g|4wRzVxK`)+s0saFja=6ksLn*QT^ysY zONmmzpVtM(QFZ8fU_Po|&pSH0!<k>c&ubELb4z%pS^KN^z9`UuzkPvji8V@#Q=`*v zcF=iv^?kD=e(56O*$T!K+p|(j1EW<Iiw<$G3v0gZw;o4fKB|ostYcGlR&X_o0p;m> z$Kl=iW5XLt*27D@2iZ1e!^L{@N%HcRRS2m9BCLJozge`bNQcANmIa^H&;>@^$R3{x zR_?N6R`=CD*&t|Ab@hHNu1;~*4sxqSB|O`^q%J;7rK(=Rm$ZL6Q}U`dsWakwxhujq z73GGDMp!L>L^yIilCcokxLCj0bHBIXZ1kh*(=YSBk4b8C6|C=cO0y1|S<9;oj0BWC zkq+^Z&MXPJOl|zv=&f&2rwbUkD^D4@JP{1b6z*sarC_#IxUGk6(sAAzO_`Bx&o|@f zLd2XE*Tg8Rp>BFtFaK8uuEj>?;o>jzNfK}^x#>pTEt=Ft2BxDHl~wo_<3*cvHa~<2 zekO|1(yj{G>&}#$9&0wR1T@SRhuXgCe(iS=T_iF4#oeY**W%jqRljvt@3M)$Ft!`& zvQH%3q|}3lB}ye(h>w<q+R}5)FK@o9?!A-XiGm+MYKXVA(;*E8g`SFEpZLK`K3|q< z623{zp=7O(x%PWKFm~EdrfYV~pR1dHrzNpLaD1D+QqB4vo@PJl#63{pvRv`mOnV6b zN&&BU{tPqWloNq>9im9Pg;5i(Q~j{PUve9nxj$Zh>HPcXzl-K)-I%9J+9GzbjS$Lz zYq&i7vR5_=0}OpDEB=Xfh<@R1rjeZ)x<s<-p|Cf~^##xLKr2Yer7l}qA)_?}B!)Q6 zDGo<<5}IIL=}z00cY3Oip8b7mH;DNQB;;!GmD0>sDi6}XyqnT!$n!6(ouCb@pU5{? zu6sKaG;4SHm-qsn`Al|*F?DzPr*8DmmqC{4tl^$rh5xQ&(lHQNx~Vgs3RVh!y<V@j zro<(AO5KaeAQw1`R6;$`3pz$N3_h0Oee_rpK@PlT`mrH6P}jsM@>Ol}B=dNMs2_7J z;>$hXo!Oj8@Ua~4qhD~3EdIL3_egIYzB+!9ukw>8u@CvmM#0BoypI8Czz-z5<t2C@ zxBNsNUauEW6F+qu$Z`c<um0G`+{KL#BKJbrzRoKlGWzJmc{e8{Q06w1kWAmG7{$tw z;=pHfEZ|sptjZAi%h)}DLz$6XL1ud0_pHSFl>O&xs>ua*jh{JU4x=`a%F7P@Vi!qw zt(f{wFJ8sAk0;0-FVp;?t~<@uLB-?<5%aIx+oqI}&Ttk_H5#Y-(OK^~UWb8qhn;** zA?#813u#dn!S`)v@VFPx#!yZC;HWuYu{fEm>O7)NLMLO?Nr@KWc&HSW${V&2asSKR z;rNSSnV(qM@vE)X{{3$VlUK7lZ5A0vec!GGk004+-y=m!a#Pe$t>+m9%M4;=k6g9N zyrYQyV(cCi6KfGAhm=~AYO$4<t@cC4F1*^oNpjiSKw5re9#`04UnkFIF&Jek{Ud%w zP9ImiLm$Perl0sw_;RJX>7&2VMe`JoFW;*n|E$j?=ecUrW*zHJJ6H%MGVxp-8<Aoy zbydrYs|kFsP8J6R9K5NI)EO?}ol1wF^_wV4S;XxM7ReL{pz7R_OL!rFY87TpV3$o6 zzG&Z;{9>8hOCxqtfGIWpZ~B=x*Yj7oW<GE;qy8pFl=_X&q{)Ozxphdmo>w`IZ+A~O zS;Gwvp$j8D&)McOpw!oO1=Ki9UEP2eDd!<(!!e?S7x#*Hv|N+LeXv|6=myApfXpz9 z@{RcB1|v)(RPa?=s~U@iS?C4TUw3O8GD4U$3V(&?vSsv@plM8DBzNK_&ToJ;IUwC1 zP5B34OJf1-y|KRwFOu*m{jnQ*yqCMhzem&fs1;@M&A(v7qQZ=KYw(Zg2%_=X+#8y) zhD#2?Hoh2hPKY2wQGDJm?N(lab&XB+uWh|9GlGm>@p-1u>&QwhV@fH7zrtHP)^H0U z*f00h?uZF}NGW~Ztjg%y<Er@YzT*_v*lSJ`zwv-k`59|LQ|D-Zl_|S$obDPcd~SV@ z&`sDQD&@F2Y!Us5ITR`3;o|v?F!0`bmrFINEF|097);m^R>Z0XO@3aPcn89ArSimq zTHtL4Y`S(XE9WF)gkC?|5=zo|Nf|R7d93;v!rkMUOyo0EUol0@TGEahwTirbIwvYx zkVL|NH~sF=KmY&tm4f_5T~XAV3GD6<n97+r%Sj1jk&P9wrI>{(*#_bk6}(Ec@deh= z97->Q{ZhuFEZozDSuD@DSk3jCr?luz5@L3b{aBRz=#)i5@Kmkl?r*Et6kIa+=%dt! z!+yjjTYk!Lr(Z5ds>e*`->Rlz2Ol`@c1BIpD2aT0(!~0}N9Z3U@VFBy8hLJbmwl|Y zTOCN*8v8JAwT>o4v`cj<lIi$+5}9Z>{V-^CceZSpXjkVT$m?nQmprQ_`ssoy|9SmU z-Ab^c<G0<r$j@W`AR#YLtBjVr{cP$u<uDE+n6DU9us}{!Dcb{@_=u1IZDi2n#IR{A zizPbC1DR9Zv+xvHnM^C0?0S4o#3Zkirl?YIsFmhysSV;BrZW)(oT}sJ`R}@h9pw$C ziS%y+sW#}0KthWIrW};_?6h>$gE%G_IL-RNkh|M!GfMFhdjyi5l5%BiC@gtiFLa1V z&sz!vBbL~gK<z8!={Rp~DlLzIIQfDTK*S9lll83%tHin6AExZ0N})X85v3&Ig6eqI z?C$X5BQW`6S1rqEU;^d03APM?vw*y{E8rdp$nv(TGoXcpi1^<zgM{~W-IO~x^wb)E zrUDYA7e9VKu~<eDGCcf5m)n9cNv~Ilgc6Zyojd3NV_U<){tAH3@~}RFO9!P-acc+* zsPX;#sny|^2*M;V@t5}{7~7R>12-H<G|$q+L&=5>eJP3rQxja1UyM?8<k#Pg8f)Sp z0EOBYRa&{h*qB*I)t`YJQa7znVW2d#8Lb06WHF+I3xLn{dHK0~3fps#6(!KrPYM#u zL#Mpdp#HH^x%UewTaBzM3Mk2QAqU#<lnr?4Oc#SnL?}qp%QQG(Om<ChUlo$6S$4z# ziOdt-TX(43k3R+&SuEvvf7iw~h+u<bdabSA@{rK}Zu1f(6uN0i3~~g^6&+|mukxo1 zjR?8hKZ5d(e-hOof}u4NYowBZgw@{rvH&n6t(&b-sMvX-Xhh*4r(ABJ3Li+VYqGuU zfecE`OonCw7F44z;IM;)thP&6Q9+JN3Xd?v;UnUn2f#LdCNL%VdzBT8#CvRa_Blwb zso#C@0c}gKmYfiQYV}XN4SZmbdXLjl#W;wu+hlsgQM+-*m-5<XxtI@*WPe>Ybq6lG zv2(o9;XIR*J-+r|?|c|{LE2x;3<p>bkIWg`I^~cgV&?1^)kY?)Q07(A8SpTQ2lKpk z8<B_tWF4>Uu#LX=={SF=ui+Ky6-4xXoS3uH832uC4ln(D`}&FK#jQyycG&lmF`ulp zrK5Qw$bw{;hy8oTz`uYEZ|}WR3*&po4|D~}#ARk)kW&v&opnjjp4Uy{GeJnD3=RD~ zB^1X_Jm1{IRWKOI)uP}|ld~^ihi#N+26ZRngZ}MJo0fCz$r#M*9U{^%VPHC~Q$CN4 zP*50)EwRO^>|ojtC!?IBfSgYjGv3VxF$kBP?sU|?#=Ey8_C9%}S8INkKv-mdJ(-BU z`hxhbl~}$3BVZd<)Y)p1hnWE4({+CoQiS4@K0esq>+bx)Q`W)$J42)a`2WlEF_>f+ zQYHW5To%Y<!0H$i1O&6g$UYy0jZU^rW0NlVEdZEWw0UEOy5S_zJM~+$nRoUVIpXI) zbb3j)FVsk}L_s9EIvs%9wLE0<hm-r-F3y13x8ZMNU%9_SSyoLsm9;6#q->x|uW5er zB8MDE>(r(9fNQ!o0k^|#4wc2T>v?^H`zZzJ^4O{>srczV6r8u9bT<^-6%qeB8>H)$ z7_{;RguFbIY4Bnh36ksoLWvR$!glrGP)Y{KeQ&MM$<zboswUg_nC2V<fzps!QCsne zLGT`HG;P0zAestI)RKT^iTDL2_i+j^L8<86g23QCucF^IH1SYEK&+gWz(4^o4jO5D zib69&t*)>MEM|Y&ugdpE(7tuJD%-GnJ@SKRSAUmT=Jd}VVE{OxNYyyqa3J+mCMV$5 z`WQ@68g;G&6f?%rpG;^<JRJD4YRe<CTgbVq-|jNfxk1B%4tGleAi$KH>+FrUzaWsz zXbOG`gHh|IXaBN*5?Qz-+IA13iv`zl`LT9JF$On)P5%p?)3<XTD?q#-oEu6c@5Mo_ zwx}VLvUj;qiSKNh8j_)OXH%2CAhgQ~dE@sP#h}?7<`BOp3e9@+-yCJ=u^^6o%>t20 z@(}Y^`rr_>PQ6Jv122_Z)V~8h(JI_>Z$JN#0H?}a*jnAz$0!hl_9rLZ#e#%D5^$0} zZvE*SAlk(O7k;w}@>qZLf9%__s!U*0$4_XpEmqQ|M+9ZPlp+m_!M)#eRt;7M!g;}B ztKm`=iZC@T%{=|m)f^aBFi-Wv=j=mDXxqubN`wRtrt-|McK(e6T~cP?&fLtv4G?^Q zoA{v)?NcKUyoZlOB*2z+?9IUh?1CTlcaPCqf7Lob6}Iy}&4ilzzSw9Qw4ptma&0bZ zLCBStt0!Nkkz)X>wEVJdl?B-5QKd-<+deab3zu|#^&Ql2n;DZtn~NiZfK4zFcnLBr zI4F2b3>*vqI9Icm+3%?aL>#sCcM<WL`~K&*XUc56Dd`N1dW3O%^7g+3a;`i+AC`ip zoRz!vDug=q#w&tTpi!#*u4_NMf;!dQ{#vNB1SJ?&yOZhtdoWKuYjqyYC~+{)wOt1p zDv0+}_t*<S3r(GMO2WRB2wKfOUVWrcb0sAeAnFCz=uVUN|CjItSGLpQMmE5edw99U z3iMuv2bcEann*Z)4&bt7-1iq9Xqoi#&$&0$>Xyp|OE>VrP)HRkcW}YMCwsf>D!?sl zf>&SK!+#Yve#PaNap^@5#3$>`ERPNK308Z;xm9GS{vxRhH7>SL5#>9N&42*q)o*9U z+9orWe}mC93&Wbr@11~f&1X<R`=*|k@d!WYuYE1g2EstFeX@odYQ;~bGg@Wwe&C#3 z`>n&u3A$T5zYm82D}oqc{pWuL&Maw9{Vyt+c8^Xv;5tSUQJZIIc7nI~6!cXxs;6ZL zUVkNsSxx+}MoLzZx=?!^ki|fU06Ysnwjf4wH3wJqWBjD!|B(J6MVts-;V=n0s6e~n zF0%N4<t6*O6oiWk@u~=k1pz6$-#0{Ec$#J^U<C!MvyYe{h`e&GpbIZS)9Iu@5b#yb zx#%O1c$`p+`VgKH3*#D|yvr~*C<JZWE{#*#5GD@ZXFQ--+tS&Hwg){}diSw(>K>5i zHTTO(JDod;qwiM;Y&YI)E%%(K1+>z(9B1vpw(GETyX80rHLvnkosj(-O=#}CZan+! z)G7>m8nb`W^#nI9CsKm{o6}=qWsaWZV&HK5x;-}37sDTp9ibUb_BYp|ju+a}1jfE; zaL)~<>+dE;lnq?e;Dg(k>{ZnIJ>c=%GVxa$whU;$@@QYmgIMqXIWUaYI_Tg2tIbmd zHcgnu6AxR1|5Z5i*py8zQ3OhHV7|}hzpDy|o8j7eDAac~1lp0Hy@z+laAn09K(9iT zZm-qpD~CEQ;g+DPFEIS#q5m!LFcH|T)@;2=pj}j8>JD2B3P6oSeVYnwJE=$e4^dWI zWMIpfdp%DV3<X+q>ZS_q(d5)@|CjDD)vO29ciL`8SG3;~By;rPCJ0>rlT5SP>Nt-n zn!19v;<4sDgZ~;;)XpUzq#$6gHUONSa{OQm$Dn&l1GvTPVW|2)uWNW0JA0Ji)rU|! zwUKApJ8B@9Z!PK5#6FJlWGt<=*PE448dub1TYH2(KX{HR&N)S`Y9Z$S@%Roo*j?M- z(30%vuy>q4ZbcrGfi>U%QpOJt)m%-}q46Em%-WXNK10*_`$8dx7#j$E(cSSM90+5< zkWc@YY{XCMa)@WQ|EZi9Qr+2^<A5&V*1S9T%L@E}M(|-4b;){O4(Kb!QT>N@+GF<+ zj$xTRQBX|Yd@d+yE_-gBlgFsVUH{uDH_CO^z-eQ=GRWw{3Zcxd75IOS`w;TF+zLoo z9=;LlRsGj??L!}+RGD`y*8gj4UvB5>p~Tv`Q=EvvGETvo7&Y+9K&^Iduc?F;WD+x# zHvgCHi(Bo+_iNzwT=v+jOK^6;c{+}L{og{mWq@^RgtEnRZU4*(s@|kA6-)ou?DyIn z>Hf2J*V?%HEDYL&EdQvaxr{uUdKY)D`(K0pdq}KJ9FWXG4a@Mb9MfpOT7)R`WSKbk z3Ca-bT|NSsjlEyPc&wEjbcx}8U$q9DZ{is~&)G4(ZLv6?Frdp-qMHNWzLvMk%xhC4 zTvRWU9VPv%=NBomQ<m*<j9&URHT-rKo#Qxzg%D+&WvRF&3mqj*G#d|mxO*EN^Gw5? zH`P>lvjx@2%ry5zFUv&v6NN<eh5HRR69<D{@P`y)k)Ifw!W*J-rt-<RU5(=(gC`kC z{>((9M6fHSY;Cc-><DiN$C;`q$x-1EsO50%2jVI_X4_LrvD+-qMlf&~a<?Ym>mSlF zaTs5i3P<FHHI9FuNv*`$9{nRMqH33*+UO97f~L``!F@Tb2nijvQr<OyLK-ud);pjO z0^0m!SU@k5oLykTJaJb*V9DzFr5*(|cB`W~6$=_*)s{Ty0Ea{|Ql=t6jv@xhY!XOJ z#LJoYfI~GI<DR@I5pZ&LDT2sp{U8fYV4PtKbh0@2BDoLbOuH!+lj0Fnxqqsb%QFW< z%9kuv&4uDdh$TRS;+}k;=9Ci9`&!P9EmH4G2<T-%$x{Uz)QdfQ%=geX@86Rh^-q_F z^jcIf+8{mT<@YzqpfoMpc1$oxSSax5KSPN?Nn4$MJw=fKvWo2MeKH_IvsRoN3WYV> z*B9j6ta(0>K{0)~5cYwbgq$>LI%XTp5}sJG%U*I2H0t6Su$=q_eudoNBdq*m!xvjt z4mTfDY*sLltGx$*q_K)ev#rJ)Ay+qViWXNXVEEjgb|_>*v(y*6R$^&=z(!m`F~|9z z>gp}djLv4>r=l6QR^?Z@Y{A`?`uI3&ckw@Zf?>M)Ys#N=HN&~LRaQt$!-Ze%=Tzb2 zh}0;VqJG+w^^OIe<vElhg*S2k3`yHsp>O582#ekv`_{mHTX600)JkD>YcthO^CR0b z*+<idul~ty*BiJ|A*|bPfGUPUD`1DdE28u3u{r+kmDiM)7I}e59QS*!fx5F29s{4V zOwG~_4U=Eb$C7vV2dm58<PQ6w<ipR3?K$nWx9Jwi<tr+E^xYHRiKJ7`bk{#$Zek6l zi|r$7m|uf0jZmajwx_*F(;QL)?xue-uZ_z&YQDbyW<_tAnJvofINlf<u;;Fju+vT9 zQYu#@xzM41>k;@dT_{gM3U~3Z-V+@9-J$A+^_L1m55M<LjQ9A&C9$oY?lvXdzUZok zb`>eOTI+7J3}+wG;0{K|aOD4$Xt>Uwdz{^#f&>mV;>@~6^}h;u>QaGFrD{VD*N&#S z_r$?U)y^=@#%j~faVfPZ>UmH7@-&=QcokQwo`-$arhiB)G<pz5Hb6~-#H)SRSqA=t zU&FO~XC)d5ZsiE3^TP=hmKTC|Hh1ZN8>_a5+o)-@GvXcR%#vC3li^>!5_~xPH8RhY z_B}k)Lb<5U8fE;=#+_=(e29%usPTQdQ9GVV$QWBHHN)fBPo}(uw<Sp%h)Ek}QekWC zY}&6&BC<u>Z_j@FxK3D0ec8S34V%y<q`Db4I8{%Mj1Ho`xSjXbO}T4EyIpvg4?CWg zU5YK}K1+MsfrwRmYm1xl3E`S-^}@nEbm!dXI_)(@Zclv~GG~7dtwC$A?Gev;LK0n1 zqp9Wm)pXN-Z9eG-#Si2R(W&<YRRg3irT>i)TQ_^8XQ?;sc^#R>iqRy<)FaOt>2>)T z?CP1{vvD>08zvWLz8pN{F+3UwsOXkDtN(pT6m1mkJl|i(yn+-?Rj|ZrJBG#c>R5`W z*wG`hzY>*~(}uBrCzxmmd1X*wIfHT=s|fQ|#BeSf$dsPLrC*R!<`$}iBOUνeHj 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ⅇf_;0AHH<-@xezNx8u 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 D9);gD literal 0 HcmV?d00001 diff --git a/src/main/resources/leveldb_palette.nbt b/src/main/resources/leveldb_palette.nbt deleted file mode 100644 index 638c5b2d654a71761a314e491b104c852a65e60f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 105946 zcmZsDcOX^oA3rj(Lq-Z^Mn>6Nva%~=k5H7o74FU6yT}ejvO?KABgu%eGp;?az1{me z=jqe;_s8$oAMeh2UhmiQp3igL=Nwof&*Ol9*trZ3-^-mtA;lxA8ZHCQ1H!6lcgFI5 zyb%<t;4zLUHX8bvaHW%5Na)><AFl+3p#S3pg-k|cN{W|$tVOM7xFMdSS<KPH;y#mp zp8HHP<)5p{-@VIuXYSBiX!k=m=~dAutEZ}u1(O`M2BK%YKeHcXl_x}4zvHHJ46|lb zqmbaM87g?pG0*YX0b62ipd)>$P+cX@TEA3NxU<zG-ddRI2H&!rNK$WGgSD;g{NeWm zM2bE8<?bLgHJUAZ)eUVa1h38jCaZ=h1doY<hk1mn-`_8SD2Y(=(`RoBLjg*%Bh#;q z{uJybkD6{1Q4fDYzC9OiXE5nuVLKAER-%vid<QX&XG+*H=s`X+k)8O{yIeLse3*+i zP+dLbcYecy_2;wD)}P*z50iV&F8Hb5FzMwgaem)@)J9P1Dv?!<`OUwWOxwpY?TVN} z9f|NS9^A;3>z_ZxwC@nrq??;bBW06!ZbU3ndP+2vdt)vn?-W8e9uiHp-k6)oJ46@z zST<Y{np>w-{EL*lnR4Y8n6RBfF~bFn%Fd38N3ZttaPPcpbmYUzg7JzEZ`IYm<Du1N zXI*x-Hj>t%e#XZv&eQKd7(o<6;Xb{K^53B+yCQ+f;cEQjA!BK=At2H=F!MryirnYv z@6~G5Y%I4p=BxSXi*_A-^zC|O`|x)<HG|<V%#4m0%O=#@6K2LHBG2x1H81G$B<Gus zEu0pnRyJL6{8n6TaMHxhSFrh9rt0Mz=eEd(GRsz~QvA+vVwHN0D=doD_XLV-2-<_# zh9>Snh@E2qq?Kws@{^our8m`BQA6CahmVKzO4$M(4NZ)J8x7N+3xAB<A|r`aifTC8 zU|s^?QIB`9Y@55OLlYO(yay8s0>H2`ddi}Qj|-k7i5{NgMi{I^J1U9@6sbx8_6%V= z#jLQ-!^h)?<U<qB0NLZ#OqfVj2+*mxI?-KqdNjYQay<W)9KD_7_^x1RM>50%d-m%~ zK$>!WO0B2!V3^kfF0YIsn^mEFB)#zp>hyD&>11uRO}Uxi6;-j|)p|wNNm2Eqjl;Lx z@wJsz{eF(U9wt4U8x0<&J)Dlkd+>Go9(0YMYVa={Z4<~A<9*{d{5ge7dt&bFbS*xk zVzu6#ohp-BH6*v}uC9c~-Q=v*+HY=NCY@Px0Y1wK<&~827ehzGxHNb&?RhCGZmGXP z6fC&&$bT9Q;?R03Nypb)&P|y*WWYM}<znaP57qRzQO3!1F0zJ@DhuLVlds0*ZP~+{ z52bB&?Yvh@T-1bqrmFP4@ICQO&(3hkYfJDbbM|xmu&wu4Px_K$I6JkQb)Js>=@*O3 z2i#%9SI29$<?9(od;LgI9$ot_`8WI9nI`hD4z<T@=v-K}<bsqu%G_>p-|_S-i#)m5 zRy*?BkHpe+Z9$fxjNg+|Hdx+#Z3e#Hch3qip!ccKmM@BQmX8n<YqTBUsybDwcX+*R zlvCy|p15liB}ZH7HEfm4=l&-?zy6mm{#HSSE&G93{avf-N{KYRk9HrdojbEab(Re{ z2lorA_g_05E~p0ak(J@ujkaDOEsKFJnRD$Qg^5{MLek4R51)n8+E?s%MewzaJi^MG zZGSG>kSa`Qo_vA_l;MF_*Ut+r$M(qiuJ??GA3V%0GvG=5M*buG#fgBWy)4}y-J5f* z+YQfR8df+^iMwvHo~CIr!y=V_Z)G!kulK#MaO0tg?P$GU%7Gnox~I;Y8S?^hV4omB z?lSno_i0$2XnS%<wZnl&9H(`?;{Y_`kvOBSP?W}N&S?2ZnEu`K!yqvQN<F4zZJ|6N z&Cayw84U%uoHD-GVm;LZd2~^Ss~_F?P0=Hlj;Lnq5SPUt43F&F6;$rnmT0!M*3cE} zWx9C`sm~)?yDO)zw_6!siQ=Fe|6F2qLeYfkNDZYZ?T%%dkPyunM;J*9Jc=s>m&K zmBzS<Y6)GSl+M{{J`v_xb>pIX$DnBwac6ommYc-I3f*@xxVn(iBH~VckW?Hu$rj$m z+qb!Iy8Bx+?uSV&UC6uM#^ioo=OT(iG78csV(NwT17og-mG6a4@k?#B8i!v`(XFlF zr%O-1(iI<A?G=OiCO;vr;x>16Bh?D8=g|gBr|aC+p-z`85@CBT(kc{|e(C)pD%k$n zrgg-^C%bJ4Wv_(~+A2ni(hS!RVwJPFq-pykZiPv-?a35a^b(#^W^&!D@Y{Cl@Hm=N z>75%WX37{(j|tWx-a|i8Qzu-Qc&l)CEYt6TYQNp7wSGnTj%@pHdrQ8om1m-52yHi! zH1A8!wa|)eM20}JBDmKb8Y<~0gH&xuGUlI&Zb@fXKnpjM!#xI4a0}}7fD}UdpO-wL z>7$gK`fZOIm8Y*x)sz-nY7H1QJ7dJkG_=~H8b%~4F0*#Mm|CQZN8>Cpb20D~#8_9v z*M>--ZGzN}`z|T29n(vwU0M0kMlYo{kv~jrj=Jvhw3MUq*L_sEnYwREZkwME-_|@{ zEvxnGLlRumN6Ck3K=J_j3`j;Gjc`qWcJgd_;MKPBZ|wm|4rDZtnn3p8)#m)-+2R8? z#Xvd$sR8Ej1d;;R^!}6Ahg#r<A4o?anSnF|QWi2d%(Ha{*yn%@1X2OWuRyXv=Cbrq zDg^Gqrl?MQAi*^kxaI=a`S|Wi$ig;l(j}6*w!ZT~k^o5zBp4SszZBtG*d_n~0RRX9 zpcD{F!FVb0QUpn1TPXmX0N?}wCqQrl<7j9mwZb+u0MGzH1Aqk(Sim?7G!su@8w&uk z08jy3)&?&3f$Kg3ccm|SC;@OY3Ia|BQXj}+AjR-%Enn)PoB$6ERMFs?1zfX$>ntb* z(R!#XaAOVtb8tNcuBX7Y9KL(|?ZP%Ba1aR`M*{z_z&|V)$3o*hRZEdsS7>>t`e{AG zK{;Gl;F?wI-qs)C;HQCyPathbFTCvxSir!(y41WZ`J`@g?d^><YJD)99$xL89ljT= zE?;0o`Zmlj0n-uzJP?qNAh}??tB7g=5nq69d4B*54Vb~q56>8Tp`SL@=_gIN3ZUi! z>QBHp1(E^qaB)qukLYCLy^FvET3`YJcmjdzK(fP9``={rbZ|8Ez$bcIa1G+)0!AXC z{D3IcL5kGDU?Q;m{;vd?ef%$g$KZAzK=a_57F^SU>(~F(UV~db0O^72-{AT;xEA@R zCIW810O$+2?gD{I0$KZCZns{5+hZUJfeZms8OV-*YMkIU56CA#E&xdfWYRyihv0Sy zNKqgwf%F7&|DPHKxQzi)3&>s|`GG9?r{)N5H-KaYG80HMSh^V8*8g$FcO177emT1V z1Q-T7=muP(VbG!X;i8IjzP+)4p=*%cx(NEv3@)l8R6@`2SumX}m@fBa$(s}7-QzZ5 zprs0HX@Qbu!qcWx!-NB<$Zp6;vn+8w*oqCvF6fmBUeFn!35=n&GBK4i%#{h5UZbu8 zvkU?k|2F&?xIXt!P4*saO!h92P*~8lB9LIo$U<!l1^U7d^%c<AfKfJZ{kM;E!L`-@ zrh5p;pm}9Mw6gMG<UWv4WPhhi1T-BmoesDj1lNB(75d+FIxsC0EX*tpIG`*Ld=?wH zhSL2vyU#%~kANfqG8jlDAlspxXv68}DvdcSOf%;J66D7mtWa}cz?}QvbkORDK1zNA zDFWmdAU%NG`#0UxRd}K)usWx}>YM_Vm;z>|ATxix`~QB>Kp$xT`<%ITP_?udjs`0l z4OTQ76cY_rG@2eRKg)j$UwgtElnen%h5#i)fRZ6V$q-P<Y#Wud*%2mi?hto?d;p{x zkVZf%|5F1MLx74QK*bQCVhB(%1XQuVvl#-G5|Da8Y658rq{2TnP%wl#7&Hb_1xS4$ zwg0J^!?zf)tztkp7!VEygoA;?0Y)=H$&sL5NKh{%s238{s}IT-*jJk_!U<{utE34e zu?Zxx2_&%zO5$HNkVhm~eMmq-0tymPkPro^Apr{sQjY`_ETCWk1q)Gt8Wymyzz`Nt zun^^8_{l4<<6ir3H?NKb8v_U-8Eg!GpxqI0E4d*5X@Odm06RAT4t~t32Bl(#+Dv#V zhl_d`%zO^u@}L!cfwTahAJ7ie?;DwUR~obdGe{RD@LvV+{;t~l|28;VKtcc+1^Zb4 z5|+}S9hmjwypKD68as_xMf##1WrQ1Bg$E5&J8snnu|C}@>!iPO@~WaLwNZ1S?X+wW zeTR+7>AQEAvpReHk}@eRrCEQMMZm%xEy4qCX6cr$=>b#=snjKthtl3uT!ANalxCkT zHh+}g5JgO%iG)jWCe=x-x~VvZe`cJz6-j&Lq*%v{*=0_X<H(9~McUJW_rYR8@4LfK zG<D3C^=p0aH?FuIO&xuW*^suE9A>C#dUGS+*+J55*jCzND*#t0GhL?8Wg_+nm(-(% z!OX#dRg9f6^1WdAL|JPMTgTv~_P5|$JtuQ?aITqpdGQa|M4~TksO1lf!?@}<RanO! zsV>}AbkeYlQ563EpoBFzYItDaM|{|q&tz`wmVA8OSHca{yFH?HZ|!c4_twn3%Giky z@l^iK?pEqR9Y17XbSdX!Z}*ver~1c<bOd>sx1U0inLxJ`gL+XoE%eQ#<RpTkgL>GL zgUBj;nCS*IUcX|{-ZFw{x6=2xS&7^l`5kcS@^+1rzSqs9YNugmAl-p<1=16iTo7@h zYE>LaPbOoDso%!MjV+q9+g71oTgi&=N^Y6Zt18pnnx7s}^tr!_J}e{i>n@>RQ$#tM z%8i7#Mc2xIq6znL|2mn;uw55(nLsMOI_5GFEYt9T^vp2$f*Hw6las;vp&BB_6y12m zZe~P0j?O?<1XytmrD6kS?Ce!_HT=d;LUaadkah=^W*@9=r7zp992U;#1}5)~nM($# z5qcdRXvXWFH1YLTJ^yrcN2aGr>C@5iuJBY+%fgNIr;Wt{J^Dj~zQ#mprB2#A4s6R9 zj^pRXss{q`PEtVn07(R-3XoixCt}uP0#Z8@O&v#`Gd0NQo%Qc-SjNTi=tYc3Z<SQv znO(XQC8YaPz(=hX8Sd+&3d=`;?F35!R>;owZ@M`j*Pf$E@4f7!oudwq1-oZ4o~NQR zSN2lvXv&;9sS5O-`Th6plE)~Q|FEE~M!8O_IL2vr*^p>`&l9l{)?08yDg0hWpzzqw zZ0n~Lfr{LERKMe}mUqfw`y(EvK4GPS8f??-FTobS7ZxOL>?p=xLMp72{m(376zgYc zy=c2m2hW{Ay*KoL4el|HH*8dP1qD|JghJamGklMJ_`V~~h&N&#wEX!=jE^~5yYDQy zcEP^7SN;8~CLQj~S8I|_Z!dgUYB<oSmymnf$j_1y#=hd3mbm58P|{gk@TPvTKWQC# z)iY8<l&Oy(CL}k0!hDZ4dL~G}^vwga2d^WzsN>zm1^F_$iNDarYDzFj6??BWQgmnu zFsJb|)b*{szLCak^u{~8T-&W&wu<T?%%1auA%3s@y^8Z8Jp>goN2e8BO-Jqz5kuej zlS@h@elqk5znd*n?m_e3SY_>U&Z{Zl_Oc6AJ63WPJNm_Nzek5DiesgU<5StSKciZ1 zH<QH2hXTq3<c53?3avJ}Vwnc!gMB3jo*m%sESttk+jgdK6{r5WrhNLM@a$Vs)8ts` z^oWkr@xf&Aqzo%^Z7ZWwtIbVLCoAl;2vRgjOJ$|rUUXnk`x6@NDCyEp;VhQn&EfpE zZn-TF83)OHT?$XJjKfdguMrHIkCwa41jb^pq?s)lgB8nnc~+jUJiC7IplmMPt0H`O zn^v|EdoSino1Zg-3I!@gJe`fm#p%intI9W|AN<AMfJMax{u=)ie2v#AT|DnmC>JyT zO};30R^0W%>Z~_JJ{E6Lq6ox@$FmWY$!b9d0Y$Ni5-|9xM1s%>Ijr9#jUD1e?fFVe zw3CYE=TJpf-O=I`-`_wpxt@f~(qHYpMH4yael7QfTtWRi&I6j6eHLOHW1X@D%+HZm zcimWxrMEx7Resp}Tw8ASeMEjH^X@OBSn**YlDyti+x;S)(&u7w9%VICHzGCFpQi*= zeSfiU953`olgn}T62V|oo%<B+*Cu<lQ%`oL$S2Uz9-WqA>Vpo_gQ5}@mIo4cKeG=^ zZYCV&*d-=+&GA;9b?dELMRBE@-h8x8leJ&B7TZlMkHykg)?zWgC2*rzCmSWj-rjGk z(_f^GN-dv%RDtZKV4E~}8u6<$VKi*rm}-&!3g(@kmO;sBo_yP4h|ZS%cpyQm$5ZVu z^<#E54lTo+^|c+R`YtPjX6lvxlQ#s;yLx}KISlUYO|LE%aP?eh+l<V}kl1=LO>JYN zMb`9dG=--0HoqGTbi+XJAT6ZJfOuaa#5K8B`yIl+mU=>PRa=P*tYrslDScT@&ycuv zdK@q#aCWrQpz@<O?6BR)p;j}a2*Kv6Fswl7=U1glOYbBea!6~dK7J$4R{V9$W5J$f z1M&CIadnLZH?gHA<IHcVRr}NSqM?(K@p(;1ULNX!q;UryB;WfL7LF=9OfEbVy>B7} z9jJVq_0+HEj<lJYdFH9KwI*fSQ^K3+ms~ii*It446sfEA-j^wPLU}#Yue5Mf1R8S_ z@v+!iJE`ks_B^9I;6j_C?xKp{*-8)0apW?UN<^76N#Aqc9p88AT{}78MFp#jtK{IF z*scfmDqJX~XLe-{@SfTHv~!{DMwm(f=_3r)s7glY)uA>k2)9GIEM5x}*;14bt8z5m z#?ab`U0_jJ-y%}VBD21*^<-1WpNw^q1W~*CcBbx6BZ^YWse;($Y%G&vbnEEx>^>rI zy5DE5X*018?W&3CH)ZBeLnccM_NUw0?>uly-HNgyeIl&oXR{!~q7;5uaM#qz5gj?4 zf=!yK2~f};pU+5%EgduWTgqGbu$7%9AildyStMdU8$6YmmHN@?TgamHTF!der5dF0 zu{6(n?=jLAO*W(D_JF5V^@4L{1IZZdS}%VSjl;=a?79G*8ISmWpp?D$yn`ICeEry) z9W@&@xAMkO--bpL=iO0KZCOWubm6v&`KNAz1L`deg_(1>KHJ^R`X)a(N`4iZF2;nJ zyLBZA#MHd1e()te&q!dng^s0-+t=xa?t{Vo<gesg)_y-W*>06W=kjqrrqD6F3PJ&r z`3Rvf=WH`0Hz#mlUj+{ArQkF74O0~{#Sc<;8#3_O`#~do_I`33KA-nnS_)YjN<Fy? zZg}CtdRZ#?K)+%HI?9jzgaFS8WWjTSEbxFp00u<Fi~)fgCNxsR1oH}qQ$o9q%fLc0 zu%HEW`YN|B7uB$U=L)gF8!n*ZCSN^HnR$5pgvF;V|Fav`sWMw6@yqG_iU2_<cKHiU z16Q(aq&Ko|Qhfw7xj$SI%3O1Iw@sZ$+u)`6?yI2Tgs-o*b)9fS)p(>DMRUDZcdKp_ zQWYK^1jr{k@=cXav6apfMK52`9lp@lHiqf6bnSkBYw&`)#(hjcL+zPLYf4dajFOb2 zUDM?=$^-86bV%rseWDf#osCcAB8jqEEzTWw!l&q`tMJJ=#w{zy+w@fuepXH_y7oGG zjrRR|ezlWo@Spl0=rCT%aFbi@Y*@)0K8^p`2UB;w;N$lBmi+5G=Jd4iS$)m`Jgy54 z;8&(~cK6Kb&%r18O40DKJ;M@gijW{HCktEq224Tb2~VK_PW$^I!ht#cS8(GDo4M5r zPZvcdt5k!nWtfNA%60JJ{t@g^`<rPU-NVvI0zfbT1n3z8M4*&adXKF&=!b83=U^IV zHB1W*)V@F8#82k}8;yh#QWV(^$ZgO}C#8|?U|MSM)Ik-@$9;J5-05)p12FUy9x981 zLopCNyeNWMwUhuu^<bz4Hdn;p8{rQD8~`W>Ksx{!eIw){Km-7_0Qd@kOTH0DkPRbX z!vThGjRJtiH=+e%m;y#7U`)Y){RQ8MYY?CSfOr@f836#bZ$vf(=l~!E022V9@{M=| z0qOvF4S+EKKsII|z!(4-0GNb<HOK}P1gHQY5dgreHONLh1n2`G4FG=t1F~@+0<-~; z3<Kvuu^IxkZ;c^M72!-P6~p5n+~5NIoYwh)ZsNBDaYul0&`{(p`3$p7{3tNa0?+mn zG!Au3zCkn6R2X#EJ6L}cOv!V2(dulty%E-DU5BeJ2re)CMz~;BbAZ<x9<W6|Fs0EZ zcL!ALhw#j|{<^*hv-T-v+XFDGC>$7Yc?%m5{{}~P<%(}aFjS#yFdcOdz;6I7eRz=u z0;m9R7XYsSAbEI^6tZCjx~mMp;sEyD<fhv)r{@CwNCWy!6~J@TI=m}Q{92$l^FeP8 z0`@W#_79|KI(Wj>3!ZQZ0l=^&|IJbpe=k@<i(o=GFyV8EF#$2qa8&%xaOCu0!pH#a zvLI}2HE0$S6o5fuk_oabRk0o}HL&y5VAYu{!*B_SY&$yyd;vfQoDCDOVU&={wm*Q9 z^#Vi{2;iSEJaN%C;yqN39uO5BfC~Z4cX+WITCOPo@B~080E8gmxHPg3MD+*2%m8-U z<W7RNmIF}n6A;@u0JlKNf?9{!6^@M(beRfZ=R%>kAWfygqd{iSPTC+dT!GqUIoMk7 z@34>3pi44As{07DEIF_qTj{VX-k|sEr{U1e$Yk5ULlJ_NyaiUWH(1H_&`Q>Ws%{5d z@qj(u3IS{=kU}VsD&WdEfSCa7xXJwx3Iq)TIRJqW0r=~*PSHjazbpuZ1q7lE*hLU9 z0|B5sS%B9Ec=S+&gGkdKpz7wJtfhc43<2FpQ{P$G`9{E-0z5PndL_2@5bRH<U@0Mh zJvpK*%W?<+QJt>9JOo&oP4w^X+WW1=bn2(aY2V5gQu?mm=Jqvr#+EnfJG<+Ss}<{( zyD}B0uHYw$*WoHwGarT%$c8&AYJLV9S#G)iB0VWekgFD=vRlyJ&}bt$j+CM)%fxsl zaa-4CVrF_3i}H~*LR72EavK`$s|7{xt`07*#)dhK?!J&64C~b>E>mo^Bi8OZ%Q7&E z&plkqZq3CWcOKrS+=!DqUh2)|Le*q-2@7*ES}mJUY6~&<=UX&a*-akIbD{0kUis#6 zC)^Ek!wQb~r*?(drR9E4U=ikcK(9S?!h2l)KrAsb+0!s3t*f21x~Md*t4(ly@rkS9 z)*Qx>^MEhYWYS~%Lqu%p^Rp}kr|;U#qY@GcQXRkUZiuLgGlg@#(y<%Ee0J6gRU>pi z?Y=8I&`3@j`r*8!;<u}ckNdA3GX#AgkyI>@4_kSw@aj?!!v*)SM#5{)(x;ddX(`<i zUKg$rN;FA@UDyFKFasJ7yVi@QU~TIUxIil7-aEjO%&%Tw6q(;(EuI$hE+;epg&{o; zH}qwRLi^h9_C?e1@<;ZM&I^8p9z_d$P==l`n^{G>({au<Jy9+TzBcs37JA&Anvw-Q zqkhtXPs91EIMeVYxG9HGaTvXX=8cl?<NZb#Vjx5cNcixuVfED?t}JVtE&g0$^Iyp} zj)x2#GwF32bRHTFl*+DuvA(?2{^>4S`&!9%PJ>)<wQuAe<|3+AW9^K)&F5z10d#Yg z#PI^D)`a3#-^aYdwCixvYCJD~^ulv15ZR@DX<Xucfdr!ixtLNyO`v9U@L*=Lo+htA zfm%CF_+7~>DRh0Wf{Y^b`DssoJ#E)Xra-Lhq6)g*GbCSpby;mZ{2G{Y`s-STPNJFU z!?ev6#^|X*v&z-@MM|csMYS_A$6l72yq#~O3)~;g_2^jKYXSmeVj4d!(EDiiu(*5E z-+eAkT-ta-`sipnkBZbH7?Sh|=!e|ICfUWY!Y97*S(VO<!cIogj9JW)A*-nsGm-+Y zBi^4odF?o=ww4_h?f7{AURz@|iO+S*%cj3gug{vVCKY!uM;Pc7R)n$+{bGcaM7}(c z|8SP<Gc#}4WTG0ha?0T*nrVCamr2@*%Er_$9tAy{2Hg#=`XW@r(U&XHoI{;srYf<9 z9uo~-lkOAts6WH*?>J1*9668b8lJnh%v3#pRON=PnZY`3^iDlLObYR?^lAEOmT%FD zzHIiwYo(`nhbF{R>w~7^3gcHks99F45Bpa=Wp?rA{W>>3&v437D=)rxyZo5<zq6{> z*EK(jnP6jBy8D^R&6?T5X+I$#d4ddlsY2~_5YiwkIOoMDM&ixx!Y4)nBr%YD45K$^ zzLzUeK7V-_CjW80Ld>Bqbof=oyK#iayL72GyWTw4G>yC((X^Ofj~DB|8VfC&FFv@` zm|vUn%D!!7#6(Th@{+?fj>~0}mF|grzcThJUqir)aU%$5a9{(#d@ViMBz8&*3<<zP zo^c7$22N70e@F>e9k{qh!}cmG08I(jSvQI+F4J~(T{+MWKVUg{sx`ED>Z!(SnE#^P zz<2XcEwk_1xA!YkX?th9D7IgG@$NiclY9<YcY4}Z6J^ePcQ*JJs3V;z>D|H!D)S?2 z+EAn2?ItL%XOEzfsq%Hxn+LnYPkcQcv$Dd|I1<&whDRrP<4cHG^lJ}u1k7&<r^_9F z;%_1j<EVc(Z!GtPpmWlF+h|p${`dVOD}fQ(5n&al<KZn%R>#TfPOPZP82Iv#JBB)? z>8E*LT9|<fd3|`8$HhfW#iCq466%a&gKE@RKacM4^Di?RJOc@z4<{d54O_(`4-RkX zaA{;Ip1MlSig2~+xJtaPyYuv&ta53sqEp}1q5C?Je5hPrWpiP8Y<2xo8)<+pG$M-i z^YJ0v!cB9BZe-08m)9@Nw+odw$mHgDE4w-Mec;W!^`**2m$uIqf_}FP<?Zc9;kFAI z`S>VnZbdCO$XHoyMV;058hk?Qg|7P@EbP!U1v`9ax|R~X`@NL$fbs=rS<{sZyBd~F z5;vDp+AwbS(0#jS9<wd-ee5NR*MQ6h@)3d1<s@<MFYVYAEkA2PY<SJ&q3*XCe@c#$ zB`N|1ZN}Z^J@q2LD_btcrJ@Id(#-c88t(f}vyoExF6(=h94xs7hF2_~MyWeUPVR?C z-@UUJ9=)A#f8D9#*Yhsx&Kp?gN)cMpPSzCrjb`X{h38T~hK5^UP`;+#TKGucbw0yk z=}_upZe`oHiXIdD^7|bkZdq(Ddpa@<eHxqOjNvaqPH8A?WVPeO#N#x#<K4p*e~5>} zMIP64X1I04!ZmZ6A&ldP>6DVi#&}$}B^8&)$LWxnR;0oa7xPb4yf8gMphYMD(&e(a z5(M~7>kgk-7?a75H$SEB&2pg^H*Gy~bHuZ`i_=^~l+KNdGfQ_<q4h{;1II@7=C!QV zsWQ)VADb&Ua5fEb{PDz9Ak#N)|Mp*0yE<)6{Na2$m;cK&y0><lzQi~apK-{i@u+y( zYN$-E2jDDfIme&JD>80rXT9)ggCIHVc5}@o6CMa~bWXv)ftn!WPsVxaCQy9`4`--& zT8$Xrz$>7T9dh`5NfHFGR4WJ29W_Xe(8;`|tAE&fbkZQjFnweWMQmz<)FUtxz<n7> z@D|TG5Fba2@ug$)WsdZwM8daySWYVbwrYY2UOb#xW@qRv&-AqBV%GD{8+cXWSDUNJ ze_X_U>B3TN8bET(@toIPab$<-v=;GVUk&jD2QJPcvvU<bPLy%844faLYW)D>y~9My zqpI%O`~C;2IQ_G+5qmhzXVf_2hPWRod~f67a;e`|nwBTVhZ0~KNU4f5okw7cLm7GR zT5t3;6&1d?SAdGr1&co<%ryQlJp{Z>@bP+krio!7kc-2BYVpbDD}0=EpO!&toKKDf zeRr=m*V0Je@W)wH-b{yplja@j&mR`EXjkPa8?XA`?vvvkI)^6!Rp1HvhlouM;`30b zv;oN+SDk<1yVEza`oGNOd`n!<(;8_|6*#PT)#4AO8NL5YF9mM}{Bx5`Gn!On230{- zFhDbz^!9F%ajO*d`A-|?(P~$l>#o&v;o_nZn~nJRMdK|*FhIzn=KPKsnOa1ocNmY4 zBZCn)q{Q*%#eJDb`*x*8oBe`wIleCb6V<Cw<V%dFx_CHZe7DaXY6LO3IO1nTUTv<S z@x2y+v-<F+F+Sc<!?Zfpd2zA8Qn)$E8<VD$($iYRsBxS(2&U9<nrmr&x%_cfwKf~@ z@zO81P{P1DIko<rOu5(f=kALcZE_JWPVM9;c%7Ug!2)Ueh==@dZ5-;sYvZ=qv=epj zl<K+uMQq)9ddmqs%mt|Cl!rR_IK7(F0&wd6o1mIZ5Mrp{W;oOb&45)EM>d+ibqWvK zv6;X>4q?Z!m^gR5ACL1AdXvVOXXf<C^JU{>YK;%{nRh1FN!D)Q-WHG!t{=xahKg=Y z@ImrZ6F#0+(#we3HTZarMlVZRNd7L1w-16ypj7C=U5Nji^*mJOb8z}epDA9Qo**NP zfKqgsEWrZKgfGZMAO{ZAF(IHA$X?%m>Bh#u2{oteO_Kn+z0-oZKaMR_)V)oM+8pTp zqu5vQJ^`gc1@glZ_XAE;5>7J|J-K*O(2myt&hAkx)yy5Jr}0>EZf@XODd9{{;CDca zp@uYy9^M-a?!kM5#WiA{wrX;XIA=^$U2`kc|86t}ig-A^G8XrVaTuyzwzH?p%~ISf zn?B8Innj;H99e);A~5+JGx^zDSXPFPT%}=QOp(`QIK78l?Y-r)GlhCFp3;YRlB@4s z<z_rg7Ewo$b?GcTnN5l<r(RAYyBnPp23>>&4TN&lBz6qtR~)81#J)IY=q6KmRP& zBUVBW<|yULb~d>MsQQFIXG~&;N7HH6ss5-^bkaB{^Dtl+CDdhHPDd7ZW<$MV`mEjR zbl|roPx4(yreowN^9RZ_#$Ve?%?>2U>#%x2CwStF+FnIR3H#U|8P-oT+3^z}dnfF2 zYnAhACk&w2ySLc6Z)lZEYbP|et3Et0uKMk8(lDNj*acnGmKap+^>9J;&V*FqCi}IG z487+G7Y=%#nI`gnZY5-+_gQVI+fgO-P)*oQ*t<2~UHvge&Iw7Y_kdBeicZ@|CQOy@ z7Ap9dksz_sVEK#`MOmU^KYv8e({L*3pcy(FE`=-(*g{U6cAQ7Q3J&|CclqhZz52~p z2IX2xb%)b-UNVP4h>F}gN7+McDK7nzwD(4^yAC_mhCx$JVEf>ud17*Vt;eXJmC5({ zx_q~)z=KWws>Gv#3)92UOP9YkYJH!Et(f-7r^ap78fFQQmpO_~!!Ki!Ln^CJa!a1l znYj+OA4f@G3w=|mGM+NBF5B{sa%P0zEp$n9fX+}=qy6$^Yj=Jh<;E^Qc)jIrs<087 zu;(E4jMJ=iEQ(g(;{SbJ98HHYEa_((wSQOgsJC$Po+?%%Ff6z(Jgqs~jI_QWt=aDd z-{uoXyyShO>7GCM{HtG@jjM?#s)q5Lx~8bF6*e1hRRx(&7atk7@^6Zs@;##WHpV1< zBCTgas*ey%W;Ssg8xTzTH*u`tp}$R0d-{8!oQUh1rFLMJ=^@N2Ipw?mjMiHW(4+t@ z5a>e&JpB)V{v%A%^$L=DF&K^6gt<nrx=AOjk_(#^@0g<YE5_5Ffv4YtN2-C<PT-j= z7>I|wF(l9ed`_63J_l<v!?S9EIQ))`bDH#~h2fE2pvMbeIxfI^I`CA^q)7Ew#sry= zIZuCMFu6PMoCQ{#%z`~<X`Z5{edtdKW+ws@2LjD&up5W>V0~W(q`IyVfowV;w7|2) z0(}*rp9}PFL8*C6duRX%S6~COP)67AvLFMV{@OsFYpY7}i{5l1AaDZ%x<H>1=w|?Z zX#R6Wcv&E$HXyDFko!@fe+B6CBh`Ny5Xi;?LLY2EU>XjaejCnrB+zd^<-66-v&jKy z4*=~FY^D)}VGhD5h1}}V{}ZkfZ_*CWQVNnc46ND#FFgSL1tzKA_jks5-*aFFs5onQ zQ*AKA2{5Vv14{9o9X&7BO$V6d%?WftP*cDf8YICBO4>r<hT?P`FdzdAm;wJlT^d1M z<e;>rme$hvvj7q$$aE-RumQ$>m~jeK#<p=q3m*Z3k^^S407Dxv@&QBWlrQ_}qZHMl zAs~V3AV77RKy}OkL*Sod$$)eNn9&0aP#**cr3tbD9Ge0pCt#)uFr)y(6fmGh{p%Qr z1_{i_0R{_T!~zD_e~x+6!MVb`g7b<5?JBnj*V7#26{`GS_tnAFv_Ll%=zfK3&@>P0 zPW^Kq4W_09&Zqze4Pd0e3~cLv?jr%I2{<za7%YIH4H)$QxsOQ$Bwz*$7+8p*H;n*o zB@VR!$i)fp6Ev9#aQ_4}A_AVVS`MlH<4o@$Zo{A9>^p49nE>{u1Y|=GsDNN7r=7xU znpR3EO@KKfFoisfd&|O3Nxy-8p`7_oOB!U0au$yLD+u5`&`N-{Vp0FJz|?(UjdTJp z3Bc+A>r^_XiDLotu}*f=8>ZG@h5a%MgD21i>xmTXeM*PMmK&wDtLFs1!|A36CTW04 z6mX9iu-QSJUVhW6V2~ROg6+vH1fEi$8)hfZ{^wb}G_VGCzEZH?>Vy6B2C%93&t@sG zsSj)x0!Rb|kP3|OVUpgQ6mA?h+X1Kx3^D=B&>jH=1Gb1#D10~MZ&McUa{#&qLz5s* zrysCWVqhyy+pcPxF!vZHnv8<|b?SwoDv$_cP>f<=c?jBA4)p)*`BwqfIDpmxv=8hP z0V8bx(vbn6UI5Vp=mxMK4I4p2M*g}+2_P;2MZ(bE)jkRH#G$SQTGdG~G7P%*I;<K4 zF2P<}4XtQla}O@&u%BSL%>s)o@U#;kzclbbb@*Q`JTAC^pTMV*4$w4*HPgX<cIm(U zE*-Rd4}f`KbB-Y7K{z63C<?H|GeMpXK!r{~8P0)-{bAbS)qh*(O_(nm1pChdYWlZz zLji;B@V~7y9+0lXuK0r9kp;cO0(vJFvH@?`&7FWm1I&a220LH~0vo~qMVJN5XamRc z0YeBdQb0U}|G6&<lAYB97rPIHw+Je_19Xr6?LBzF0O*`X&^hLya|8e*8DfAvy&I6` zflDNS!2!F}7y*ZE4%q<PMJXT+12e0D0e%WNBm@koaX?-hp-Mp?Gr)`!U{nFdIACb} z%PaUXq!IiWG6g;~(O?;Qfq;Gf<+TC01ojbMXhRwyK)i%!mjjK#0x>{dDFI0Z7yv)2 z9NvOmIu!?uoU5DPjZ0)PDMZ6vL}qV=oH^5C@*mW&Z49OtX}7f!mzu>_|3AM+cWgba zkWWkL$FrF|7#S-2$76}rsUx<Idr<tAocmU9HvB{QXY)_+D`ts%31!Z|>`Hoi`q){& zMBvoi$k=;OleJmypkMpW8zaQONYlMKTCzj`$L4FWes#%nWd!Cee}7tihryWREO+L< z3H1AAZa%WrPyHhD8!k+I9zZ=6AQUSu@*Y!f(Ofl_UY{=+q`O?H&_0vc;qW?3^t0R8 ztBFUg9%hmW_cVBAcW)4cn(?|#&J2I1=wf+cF&xwN$Kw0Lg0*UFd2xY(>eN2gOnUjZ zS=O&6StiwvingNR7k&?W(hX-OX!pv=+u6*~XM>R)4Z_b~IPGGVds5y@H;DQ8SwCmL z6#e+yj!)rmDESXcoy_TR0wL|j=Tkq1Zjg2NT@gH3eaqbN#+wUz#YH{L>l~v}y|Z_z z#jmfCQ_sIKTN!WXDtp8{3wxJ4bF9doNSx35y<6Y+^nf%5tJU>RHCdqiKIZUiZMvEy zwYE{^G?8hnj7#F?_u<g2OIlZ?y!x&EcqdTL7dkPYrbncl#?imH_@xAqYmavYY@ZEo z)pnRYH{63CtCGgWl0HbUwVPXiI-?h^|Jh^NTc<;P|9c9OdRGG_ZA1H`{>hg-n*BrR z)GUc|w<nTGs!B!)Gk42NT6CH;7#<$F&D2iF=(6OR-$$(23cCztNib^G51mf@v_%Ck z(#I&%l-{wSk3wvJ4K;2`t-&q(OtW<yVlRF`k|#18*;VV#YiCwxPWR(*D|ST8j@d;8 z(XrWkX>ZwpSHG^tPc&Q7`@=io_&Oyw<k*(gr56N9+BHSX83Yp~8xS;*OhJr~(<mT@ zx9TjQ*ITzvfD8q)vxJ7eRC+J?_je-Zv@G#TH%oPEBS-pJ`0b?$xz&1IwdL$~1q2Vn z_PxV}7Ab9+H{PQoadxl9g7k+!6jgk;<$dioK7pliFlHkxjX_e_dVaIm7r^b=u)29N z{PIaM;}1;F?>)|o^IrPHX>ZO#3s*INU<RR)C)oLw7c`lNRbZqOjI2T<71&itA7b>* zWAxchCGpYd?Bb-bk!8HS0<S}Z!>#}~gWG)JR9@BbT-WKFaJSg4y!PC!We1(q3OCNZ zZ@b5#S7HPZdq11Bhqu)8ye5kJJg0swbjEJ5rWVJas{2pKG5VPe)An(bTT-~lJ|=LB zkvhytVOzyw)7~7_)|1k!a-dZAq*Q*pA>PhrR6h_HVBlG1DTw7Rv7EZyvETh}pvU9Z z`W(VvV`CgMnd<3`wa}`W9rhpVns3pF`K7diqFSGexXqcY!d8QeP%j#2`OfM1fk*5{ z+8dK~>&G;KMAe3)ZF7w9fOP-w@0?F4dEWRPl}@o#4KT*e+r%zyb3TWEJG8atKJ;*{ z<`&uCubESYRqW$V@1gJ2d}ZE}D@Uhhby(jk66cu%C0s8yY4;<~tHhwZD32-0g;l<J z`;pDUZ@$gIZ_3>YfM11ErknG|WH&e+7Fx2P#7YeXhEKmQFiv7RXL2aTcAd0DIo(ns z7Gp@<9Szla6Zx1WB}cN$&WpHXZint`zIckN8b9mAmlBy3(b1tl@BZv*Z)b!}%(|r8 zKxIXDBX)6*D9pT5SVU#<)Utvj;og~ORlT+Dcvj`v5c}w@FMdK4Rpw*m?~;Es5dL=_ zoBe*CBe$z*!2iP;`a{fEquSyrzF%BS;W&n^-9|R%;D>^*jVxlaX~9p3yaq{^bsVsR zQPR&izO#eT6X;&R6dnHH`f);U>asv8CAAZy$Ms>7{*kOl2g3Z~^v6OY$6GJEOC%4% zafVIryGNcg@oX;;m~r&7Bfa_ol{_co))A5=j5c@E>*5nZo4fxm5RgHednk4C2|;)5 zDFQdf#v~s;yUkIRZtFn0`zx8KdgWu1G46DEVXvRk%hi(WwXbpb>rlV+lhz&lMgQ=W zxAXP8nbXeH7JhUT2_h|(Jo-&lQlGOvbh~;p!p&Uga|?lyr(O8N>f73W4lfH!p4E2Y zB%69{tm2q>Dg7)absnY7Z_Km}wWDOZ*H<iHG!r1bL%wikSHe?6zIYZ{BDoj6a8_8d zR2>c9iSKAyry0j_R2A5}kZ%7HI|q9=eg$|(3cTaPg}tkyUU!h+pBT#d!uL&R>Isz0 zj|t<RlTAw>b*3CAn^q!X#-R)^MF>sFSAID{BwfcY=!vO0s*H}&%@v^Woqy}`RS7je z*3UTM2YIZ+gY$^yoEQ(Vul*AoX&krMNYv_~ulFbOJiVF6w$C+_=St(jRQ&a*iGw5u zXZa*k#mZKK=PO@u^i6-edw`Jd0|yzPY15A{{-m(ke3Zj1Y}#NOPP#vF`9ewE#}yQq z?BpsunFy<icY6(&wXw-3k70J>*#Ma%_O{phdl^J7%X&6h0oD5v-^b}u12?o}`NkFl z9O|QNv!RJ9&JUaFdG&yabU-DHF2G*phl@&L`!4UjT{BW5HGP{e%LFw`s-qlemNVRv zx|}5noa(g*9l3jzodjd<L|sGGxBJd4f>ujD)+#_#tjtj=Rc}XBN|DX9-snAZ{z*q~ zJIZv;2!$uJ$A(){`*Ar2uX^pHn;heG?Tv^H254gOJ*&r3=~$s;scHAz#hAomXWY!A ztsRVr{_6?izfBzuHMPF`gerWOe$>_4!`GZ3rBq!_;JuQMXX2sPok8e4s<()cZ1&K< z_~#OoN0$|w@B5WFK8_*w4y2nu`ce-{Yd+5PW*!LCuKemtIVi2oneEMh?odDaXdD~} zvp81}T^RyK<B*@EIFO(7{P?h+P3M81mcUPSYaipcnqF{M?M?_NRBwFzSa)TnwNMK~ z5_F@~w(bU~Y$>&avwzZ`-#N7W{W?vAZ~`}sKmu2B-~^Ts!3k`V|NQlmv|G)%j90F0 zFzNOWr@t;b{&msmuZxa<U35xxaFkD)&=^HQjW<OBH=Y*}XuJ&2cwV@0<4y66jR&mN z_$b;L$Jrpz^~fGQ4zH<t<WIeP1hyXeo0jRI8M>pj!C17U^o`gJYEGG8s!mfVSEYAC zOte3T*wL7o=Au4F#-814DRaVZ6!r98cCp2b<VwaeX?E(Q*gL+Q-(ukx$LwTnNIc(u zBDWZ8KKT5!paz>KZu_(=u2PO%Wp-KOn`<(g*vEA_w^$$cW|wS~ODv&yiS|}?C*Bo( zBk7GToX*#@u|BNLE`E`nGMA>S5$&^?B+e#Sl#4czcuBqYgb;2_;dSN%`<JskkOuv9 z^+%LT9|YBUPmtoqT)rp0(T>yk`(msQceBg6$j*idkBJ9^<uh`|=Hg)^+N+F~%kFl6 z-wH^6s6p}il}~}eN&UpW5O@p2O`<+22B~NCU<<3Ui#Q~)x?<Vn`}!6|Syp2QL+FT; zxfcW1T$AUdMU+BOhS;Uq3wDcq9pY_c&!s+yXFKloN=3PZec9oMXzTTz450>6XsIZx zekqol9oL3>LjgzygT5vg7JCN0#9gP^9<gdUutL8pzD0NkevQAQp!Y?=gtj*mT9mf$ zg#Fv|w%=L5JthYdpA<=1tP2%x?+|+R79LRl{FMB#A&6IYGoOIS@mSE;{VOqgk+S~Y zqW|CDf+^8f%<IgVvERs1hF>$!@hz6LReQ&uSwwj<@i?m{UHj$P#Jn#`_P7<29VzRs zvRjVb$<R;2PEEdgH?c+QNCSuXf%oBJBID>8=rw-F@~wtFkCrktbJ=)q8JB(+i;v=B znKA3K?#8<Z;dje#S^4fdk;EXTFg<mziprHSOM#NHnGscw(aV0v4$#lfb=!5&+X4sE z2hfYL8`Uh(A8wd1mm?I5u1-U&NIImjoS29`YKt1)84U3nr5kiG_Awo=?PC-;%&KGd z9i833;<9Zf!24S6Fs?VS>TEwpmZ|H5<NNIde+M~gM{LUel+UZ(*p58KRGKz{S_HA< z2h=cc6QQ_^8gB+~=ArRR8X?uv_ueQp0ga8xi|_oaW?1YHi;(_m?~#a+qq1}{qb5n` z+av>NroGdT>y(u@6?e|M=t8b{G>x=gXgKoPd~~P7Zh-}U#p5U$eiZ~`MXFQ(#MP=U z$gqUhW8ew-qP<pqZMVonJ4Y!sWf~qglGm>|$Mo%>w*+#LTgJ>J?iO2L1kf}~&8Ssl zGsdOcD8{6{?dG^Ck+sY4%|5C7RTuITBFVVwka>c0D)whBl;dViH@0e>d+U^REW0FU zt8RY3K|;ZdD4WYp>4W|r1O4Dd)uH@`f?Mi8c}jW35r3){J8i^DGvzR+8J)3Q`5i)A zV~QoeEq{J)&)4~0SgU1#Is1Y+b65+H#?Q>V>&KfC9b_!6y%(N?3e=~+a+)YbC$YA! ziNX56CvGP~uUC10ClV*WJz*}Qb?(B!`0ae$uoI`aL7a9*BiStIzb8hr)pjpw>3mPa zg^270ud`TK(o^okZ!0^xCwn<6$^NRE<yw>bWGt&l7oI7{S~Sic@||_YQuDfOpRue4 z9SMEponXsq-iunP0lr)xeS>(Zp)1Z?3DJu8D9JWFpT<y!1h`-s_{azr2?d*O(xpp% zV|c{NP!&fIlx;Dg(6POq*CPF@db~kGi;lR1$-17GI{osufbx6J8!V(<cUr3bG~$#3 zTn-Ejk|Ot7*#>G&QzO6K`AHh9u__v&^f=qOoJY5~<_#~MR#I?8yP_fQ#AgE2^vLcz zGkJWr7uC-v-s;t~R(-@P?iH`vYVgUCO`SNfr8btAkBo3pxjYJXsH?oZKFW>FP>YVF z7y3eGc-^2Xp4Pg|;*Zk9SM33AG={1WAY=X5&~j);<-13nn0~xc&SLDFRnc(0$MMc( zoVxF8Qh6`((*={Xuaw^te@ap-G#sbYs;bk^K6LSkRn&N=QR}^%8!UFD3N1DM8gv~2 zZbXJE45S@=)~1iHGaSV2S$8qa(v>&zQb$RBBP_q`?0EI^_d6{$NPBitg4zADG6<;a z<SaKS>dYGGZW<kq9+=Rji(p1-loDMRO$=`B`grfl!a})$xFH=6o$)tIDEKAR!~;4K zF$ekF5w$l_E{?3mq*g68k@jp+1goOu#Wxv>PQH1zS~xqhkHbLX+STrEaj^uZ$Jx^p zDxL|!0Unn}4e3aSOs(k+dBw%zO<_R6Gm+zo3^v`2GS$t?koN^fzJdAEhyop{*y<g< z44>}-Gfy6Uuvp_D4ax0zJ1)c<84UezxW@P6he1_B7-Z8&6|!8PrkZh+vA#}Kmw|j} zot=}6a1|k_l=QC5k!!Eq;L`>tdAY%+Bd3euP49yQKkF8YKWdDRp?auNm-7}wf2n0v z{<&z^ou9L@+j}@lP>@`OcYdd5{%rvzHOLtUdHTi1@_W@!Ng0)l-lRgoUjiLabd#=V z{B8w^UH8#VC|o*tJmG2i52yj2z77s>V@csBg%gy;OIK9)4O4F5?8r*=4XQ=xmFwz1 zUSObK`@}80-0(b(ms$pEPP!x-9{67mJP-S?2eSX`0m{U`JwQPZdVmV_fC>rR16Mer z2o@<^Ki{N_GX8c82Cj8}Pq@{=Hz#h$&fpa{`#64j;<i>C<PcW;J{?KN`YbyP#3nwj zF%UQ{J?CC0tQ{YmLysB8t&K+M8eUH?BGI$U<cthXe4K6RQp-!{I2OR)D`cJRJV+-T zAr#!ju*fN>z(Cq-I>-Bn7wYheU?{IDy;OvOZfp|z5Rfq^ZD`g}6Q3?XO0X#F>d$+z zX#86+Tn|?IF3Xmha1&k#V0I1Xy|sFTHM(8pv-7}^9c09TI=Ktp6h5f!xEfwJ;U%Gs zxB*!nxae+U#vK`ynD<uIgyTmCLse{HFtqCBN~l5t+|JcJgsd{nNrLTQ6$TU><2bCK zj6|2;y}5B!tcw*&)dXx6;}Xex5n4yZFu+_z8#mH6UTAsvJ$IX3**)P~hS5fo%GbT{ zyPUbwTa6xOt?J3U4o|~u!i`cdwS7WBM>s`Xd4HJOK4UcFj|~Jbb2EFXT$ts7&ln~c zRfTD|ylt+EgU{Rt((5Hx^ImhHG}}`CFnuf1l^!*aUd_Dy<aa(>aeKuE`CP<fRQLqt zMeRC)IH5wjX!b~(KP=x;-lq;0;FrlVpP&1&RJYc0o3UJU{mTUg77}X%KS?^a5Sc4& zp9b7{jiguNEv{Cse{cWF%*Wu_>O^XOs@tB{_9<_oF@8U9p>kC5-NReFx)1&yZb_O| zT-5gAt$vz~dKycwQMcGZzhQ)VW3w|zw~{_o#$D->p7}K;`u$+Mq(EBqb;9H%vQ+AV zNUqhdKfP;b+Z9w#kPDgEAm|Tg9my@bBC=3N+R)#}GLptVP2TB_Sd^Oue?Y6xGouML z;B@wC)Vnypfc5Eiy}d%UXhJx9c|>-*3bJH+X;9R3`NK!b;T{&hyNf@KU6C>wVO&Ph zv8TQ*@p5wLk2eaRI4RG?h_nyHE2cSndqj7rAhV~Juo|^nDSI0=a>(8LrXPfD9&FWC z52q8`uy%bow)~ZsHr~MXdfzoog;Qq$A-3=I&GgrV66=c@r7GgEC*EE10dYw!-HtRD z$jn^g-o_@i6oPBFxVO69(;ijC=LwzMH9rl=mKM1@Tu!rDpy3@#d3%j6WsK1={EO<7 zlPxzQC#}xb0!7Di!M^4R_4=5DJAzxs*6i|454;CG2m`(vmk3=bWtjBL@b~_`8M8{( zb|ciw)aI(0O#PyyLKBJ%UDw;+B>t7CMCcX0&0)VsNCb+ErKb0uVfu{K@THFtLcS<T z1rL`H!xAB~>%E%=t7IbWmt@^C9$C1A^pOfmZ;j9PWXqJY+P^>hvuvGK`Cg`HmA;~i zuJOF@^(~S|p3@59$@u1kvah_!H}#+!P3&rOD0SVbm}zK9{V%O4$ISzox`oh~jVnfr zzlS(2nY;oA`yL+4J<6Wt@<r~Ln+bk8QyH7$v&9Z3eB`&$i&`+Y7Tytgo}yuAK)NJl zefw;6FrNJ=Z5*9lrZ{Vs+O~a8Flqa)AY*r&2|{t<!O#6CF*#<4JA{K(XSqV{_Yle< z4}Q<<D7D)n<dubNnuhR>X)oRLpiolaEw<dbESMHyAovk)M@sO`cU{TMrxEXFe_?is z1XFOA4TOtE>#>R<LLbOu8l5nxg~1)J%Y^fPkZifPwl<J)xCIR-=C-_QXbk10$g#Q` zYH>BT^>wU4WXdytX#L`{Z5!%xE?$cE_tJlL?c9Kmmy)@)C?mRLDU?ik@el1;eAl73 zmy(3w6(qXk!}P)|$}jv>XJW0bE{8fb+W)z}t~||yk6ub5qV%tQ^vPZ><iw*HZEs4R zFrU{{em@oXwqWVZN7h!96Hk^QH7?el(my0uXEVFs`ZI+r8Dn%-5M^kdmBkEOHExY} za!wkZEHrC)aBKkQyUNX<BPOrtWaWhEql07p33Dqp3Ep;n;PvGdrfmq04SZr{IWwD0 zd}MGK(C_R{*LXqrQgN{MOXXRc8B&G8Tc0+Ss&OTbq7a?4nS30+_pOCFDH<6BuYL*8 z4(iAe-fWLAv&Hu%w&s__no(bhQfuP6rtow8;K4UrjX9sE*0*Ih8ps4MXS@)IBvZDV z?7o(`MT4G?pJDJmAerz@_f>8(o@`(hWTZix5nqZMtJ4qZ$l2Y{iz~CO_tAqUMVpad ziuzusuM(1Tuptun^NsgG>bIA^^*(&oq7ZobQq)i#7hB@i&W1-EJZUY(k^8Bc*?_|G zE~<f9_%h9o8B1!MQG;0XLXJC{JiEqr&W8=o>m)-e<gA!~Ud{npGkf&S`NJpuLB3?Z zZnol*1XFmaQ9~_NLCUjSGsg085{gH0!~J%jw~O}MYw-FS+unIbIz^EdInY`ax{y=0 z$@I2miPu-fR*;8uia0f5vehdD&THLmn(eYp)9e`t6tfjHBAsG_Y88ye)$D9;J+LNX zKa^kcdGz(x*5*x7)-Pdbath_AC@TA#jI^cgoVjjWlJ>$pmtrlgevW&SU5*Kl(QZ+m zo1NVoad*En`7&DBZqCJC6rt8gDaw3~%gLIH{jh9_*j}7dYceSM?)sdn^G!9bgHJ8C z>C(ZP%8PR+mbv#Oo-<yKoa_=Ban?wej*R}+*_Bqdyp-<CBg$BuSY=VM<+lBF#G5R= z{&hy&MCd#1P$-PK6R0;yk1$Xh$s%x|Hi}_6Pp)h;OC+Cozw5B2Uw4-uaUM!Ft=vVZ zHN#m5$yK;ep$dsVUmQ~P+nx%a-nEq^q(fYkx>^&fA;$<eCd)a^yACs*mTvcr4qf_9 zg#Qm=Umg!-_r^^`S(5DQh-@X2eaR@H4N1~!DvGp76ftH(_N`JW+k~f7DrrNQ$({;L zn?%NzrNvkqGI-B*^80;0@B4W^@9&@KntRTD?)zNldws9%JRWI^rJ-zQB#p<x?og-Z zIv3`iiH)FXuBZ1pD`|?5p(6TG$iePtXWrV};m1Sovj=2%PRQxf^jOj^?^1q^Eum;2 z5DC)4L^j;-@LKEPEI-lma;M=u%A(aWnN%7A-%VB@M&=|Sb7~$SGju5+lY~t8XuAvo zkpz1)2GPwna*x%j>D379?V)A}>wzTRMB9O{ZmJ&4Adc(Hy#ei^MyqA71d<{|Y}sF% zRe>RXT;QDy;*7sMhJNH*EqgzZ6|<$bsnmB}lt;%`6aP_hZp_%88!gwaaqho8|KWS6 zg0alakf!rs=&jim{XeG`A@oesB@lWT?C`@LsvUtg5+nLUAvZevCAsbhJ%S7|uymFf zpoh>Quxui`Uu@lD{lcN>8w>BO<Q_PtyGfYJpB7(RNr(hHuLC>h`gt!87N*VvwG$UQ zysWFFg-4+u^Rnx`S90aN4JpzyI)+Mo!qg;%3vG2bh{*}H)fFUbfq!-k$z&I5iE`CF zb>w-ea@XS<8KkuWx$oF+7qk0+O&KBej7*!aRv~aGeAg2l%USCMa*fzU7wRWsIjt{G zij++$ov=DZAkXN6eN)81zFE;=-(q0jQ#N4VGYi1JiFRP$PT0P6(AKn1Yoo0Rq(+I@ z{rF15_N|Av=2$BEOJns2B2V@RUvNzPrxlFz2%_yI1krp>GlJ;gv?zionJSDR%3>ji z4sbXKqBGNc2%@~R;=rD>{G#@G9Yh3C^9el!(PdmWfar{Zi3%-H+jJ#25HYRvjG*Dt zr;)R<4Yo%*scZe2J11Cg_XkMNh#AUViJTR7u=VYnUHcf3!>c9U>w<*~MAlX@cLp6I zc&|1WF3^dz>-Olpa_DUUcV*clJrfm8UzY&3_^~U~4-roV9ikpvze&76M^G?#_3$g9 zW2=0m3hq^F<oXT2T7Jw>NUq>sZEeoe;a9xKPy@;Y6fPzwi&fvsl^9w3+53r|6~S16 zs7^>otb0)(eNy7o$SqrwOtO}-qJ$<P3QWJMqCzW^%x^54n39ZU^&fHm?7iL53fq2P zU6e;xkH<stNz=)Bh)Nu-q!kiB)z=*W;~xg&C$BxR?u1n%D>M43t1f>-ZS_k{YhZv% zU;z38U;sTKV1Rhc0P7LJWJrn#U~zi92w>Hz=c95ZhBqSTq)eAIlqZUk<kOPEBK4|7 zUpCZDmLBSSjZymzP)j}ws3q|NYTeEQYOi4LbO=yOnh&Tg5CYV?MF46)0&2;kfLgi+ zpqBJ{>5ZfmsqteL<xvHRqIK0(njUJ~;wQ{I_FvYZ>mm)r6+|QU>-yo5i`Yuw^Ug72 z`ckARCDsDas&9gWy=G7P{#>T~<gL%gxR=QW$1DXL>1Gy7OOjm*+U6nNP4Pu%)PMEt z35#$xpZxTBzo8>t)L1Sv#Wz0D?psgm0S~6d<Zob9vf#1xR*rNvW2MX#_W4A+A3YWx zpw)d3hh<GCAN)MOjJP+{<fOUA4!VV@+S9A-Fh{$AZz16+%$P~{4)1`&$^u@w+eXVP zO8UQA&U36=-*~h_Zu0Y4B~#_hEc=QU?`cbcMRiVZ^UjUxuNn(Jv;Gp{X*-W&UG>}U zj)V_UbTM<eo7pC4I@#cPiiwSH(_ggQ7Fn~f(bl(L*;Glw#wpBTM1L%_Vz^&qjcB9Y z(SEs;rj)3x`F$Sy`x}17@DqbVmm}2>eJkw#`80Y2sqjpDoT!~z-}}P){x&iZxg#SM z0XdL2VlSdRRC@&vs*MbfL_8emf5P2=%y9P#B7b@!?J^+_fGOsI9FqReXpbs=zY(>E z;&(E+8^9wGg%lEM>yv8%mL3n8w==jqkLd(!5*LA{3CZA-oL<~QKS~rby)(JXkc)Gq ztOgfHtodg1qxE21uyvMuy;CH(rabJL2C!=q0ASJ<0x%5_hCI1C08F9+0F%ZCz-)>H zV2<9X_g=##BMiw?cmYF<3knxHq92j~>Xmv?5HoFlSfaI(yUklyUYH>wpU_yT7p;-| zXlMnB!&Y)P0XtBn43`pw8S?T;jg<vq8o5u0j%G6_c1@gq8z7>?4L)IgiBL-6L)sK$ zh$~#&UF|zhFxPN6y|BW2J6HPHYI7NeltSXBTHmVLoTB0DVCLL22t8I5L4=+QtXOdG zfygFZMoJ3-J!{$!dc3FI!M{@oC|pQki7m1l=n>l=!8DzW`0N?rIJ4MT{z?ilA<>rI zle{0)ZeIvi?g3UlV+t;w7}ikRR8Om}_2}qv@{?G?^&Zbd(U0t;0dh*~W^|BKN+t?6 z*yeUO*+ei~CWPO5ZeKm4ibBPB22#)Z8d8)-k4IO5brmyqA|EV#MjkAjsst9!s1V&S zRP})cl-%iywr0;nE7}_AnMG)8A{ju*oovJzyC$X(B}>onB1#sXU5qF>l8PwVtCN5z zdG~}SqGTPiG;Gc62lX4z3<}BV2OICqALEz3e~=Od{)uQ0{z>l!@=s*r)zXRQsIhNs z`&gu<5s2@bUv3@yJuQm7&g6`=+=b*2YV;esymppa1T&2z_R^DEJ!S7{D$qVHVM3sp zXvmgNd4a2+76w;8D+I2du@qcA8(jUgAh`P3Sa9{!rQqrr7s1sN!PQUefvca5-cnmv zn!o8n=R`=LYfO4_JV*UG$Mo4<tGCBQ-jv=&anZK&{8tZ4Znu@*)iacNN_HEM5XdcL z-<^5uDfwrg>mmo+A1qfXG@8Hc?vk_%?Jx50-stp}EW0b8me5#{FJPTJoH8U2PRa^_ zH9k=L`H{{D6iw`$Aft_wwvvTdG};=R)K(PU3KIk%lB&u^a3%ADmI>JP+anjXYr-2s zQ5pe4(c~~l8-!2tof?sw9<)4n-$w`HC_*&EQQhGF$!o#=lNNyc*Hi)ZR)TuV7l3-* zB|*J<QJ`Llixu7tA%32#DP86Ks}=d!?w8V&Q=0k%UzciPwcUgqD&z>{Q0@Y9@wKI? zw!Jx{gWoPx+%5|_>!}sYcITB#j14S^5qVix>bCB1?&#nZG--K=6q>ZXd^w)9VJR(% z^?Gx#R;GKITQvCoqsTesbJD=~mrH~1FAxRapORgFdn`l=alov*4B~)u1%ikJUNlET z<|K+ZK#6RFIA94~9Dv5UrgY)6J<aX-JRkv+NFad*AOVySAQE8B0}?1Y4<v9ONPv74 zNPsR1B*2me5-5rQ68MElU=ff2+5U`mIN>~90!V-*2P6>43nZ`)NPuhtB*0Pv65zZ5 z5?E8%d98oK{c{YNY>B;6hzIgYXMs{LH6QHOKRIH4W?eWTUbzMwxc;&AKAN<7Kmp_6 zlG@KfjPHjiT9YLRF}?zt&h+d&<m6IJ4!mcYom~QMehtuRRwdZ~)FX*YQy0bz5j@nI zz`>uDMkuOFM@douc{z&56N{`N!Z!pO^+j}>)Ab%Diq|G6C{a|U6H%fVP2fqab*d6O z4=55W<hwNWax4Pz$<B$T085G%;*&^%K!dGEXEfrIT__>&+%8R72B{}uK4>}__@uKA z@yV`<Lc}N16h*`*!bD}@6I#c}<j8S-^))SydYzgm%=Q~fK_HSsX@SlrKH_}2c)J%) z03!B#1fsF200g2bD?S7wGA$N?sN75CG4l>**USA+dP9v@%MpjI1m%*#0%@-s><9VY zH`&;Ebbakp<J%U(ng4d@<#v=1NGAr8&Lb-MDx((Re3qk*u-+c34FN1&1Ytdpl=KGW z^KL2;%wWzHL?vZY%YjNljZ6p?$r~5~2xw<X!VVkmw>KZ~cYe+pN7>CtC?C>hC<~|Z zCTm@oQ=A5r-SBgzQFbFhS%I>fa27Jy9~}k=JsT%}VHdLK1VT?7MH8%-A)I#MOC`Iu zH|GgT2ui$lP(pyLoFSH$@T!s>eJiJEND#9@1WICcxJywID@;Wh?x#vZ7-aIv>mZ4( zE9R{a75USRG9X3nLQiX-`P3!y@$X*H*45^S59M7f>E}+#d9GSMO_7$1Yp2tYdQ@3W zYJ4MpY?%u!{U4@mJy-1>r7XOjKnr3dh}69(C3W_yaVq)>a0b+jYb2+^zA1cQ->mgu z-$h{GQ^&!+DS}|%jCinbmJQf92kd)_5A1t-DcJYSOLZC|{7_O(_fI482;&#~lg-{I z?vwgR4Wl#skjr~v#pZ!xr{zGg)J0GuAz6cBk3o%OMje?Y->i^)l4AuUN+b95knasp zY!Ozh#R+Q)d)unr6C9jk3PLhJD-Hxo4Tof&hSD(|t_&pevr>@EQ*n`mfzq+UAtFl0 zbhu8yptHijptGWoj?H!o^z<B$J7E?6i-bJ1@(l9OH)GsV&qX+24?4X-Az^n-{?z^y zW^vGIZDaY!1h-TaHGT;?jRp&tyMGl^xY%BoKjrbBx!~++Gh_M8i*9Zx8FwD}zUi3d zjHCO$8i$wl>C#{QN51=@I~?8D@F-mTQh)RXg!w8E=8IKAJT(dN6nT}gba{$c1+at- z4ggjiGdI^HBtbx`_ONBbY;ps{xK?rL7t)hQ1N#mf|Coz|04@XpRsz5RGN=T_94<r& zkFnJvR4S>hvvFaTq8ib$E!)XU5pFfCc#(RmWsrKuhis90k4)rX*3&;>4avhwVcG&P zaG7YJQguwF8-Yq`{166g#$mua2m|OM0CAEugaLYj5C*))VSo@018l+R(J$FpE9I$v zZ#fezN|Hl?Vst@x;)a!7`QZ`HX%jw(|CZCuQAi$N5SD1C-hC8GCKJ0Ms3ISSpvp=v zJ<0K9bA?^5(a7n-mI>|2V4UcLo>_N_kmp{`izr%?wHS12g5tD*kwiq%8Dt4W(V8qt zg~V6&CMZq|9BBYfos>otok89VN@WQFsTS29?(XYp-47gnyk+4+N^$V`nT~)h_sAh9 z?R-|Ucrz2;y$<Bp%oQKae8KF0L-smpj3Vjz8S(86f%Ee`RQpO%WL`M>6-DM_WU-UR zyNN6b#U$Dd_WZ`W7jLV6#w;WHg!-byAxfFo>mTE>-2tmN*%a_;=IW1WnaXEo*Fm5k zDr72Cp4}YrUSEGq;G~^Zl(Jx5uEAL7ixzLua&a_Ww&sgTS=nF-3)KoScc+7>?&Obv zx4X#&0)4t3f>A~u1p1Z{iFP-8ccG}<z_%9Sd>4eGptHg#Fum$jWtaPOWC!vh+pLrn zlEYHib&eZ`JU(-9Rw=k&ZJWhp7@E(KvKU}VNC2-Cy&qMcCbLoQ<w%hQRTCA#EfK;T z?E8A`59Tt>C!JC5mBIA}Tv8HVM}I1+ksLo~S$>Hij6n1<IYQ$f>5=Zj60J}!KLU{w zQ5^Z>TBmT0+)KkLD9YK#l|+WWoU$Alz64Pekm-aX_VFPDjLhp8nF1y%6=Da7!jM$b zM4<xJw;w7{CAb2mj4M!L5I4ynD%mp;iRnQVSD^Hv0!5k!6{ys8P=R`V11eD8+RiN_ zo|zT`lc%Ud4UaS*YItrQ5zN~YMkpxPrL06jc>*aq(bl)S8RbIEVFi?WPfR326^|l; zg7S;3goawDdfy6g%0;InE^%*+w>|`*Z2=$8P*O~ISI>SSkn1;Mfwm`us|!w^p#V;v zjjf!6@-V-VDU^q0a49Gc)1>mCpdp1A19=#sOJmst^ShYMV}1P*dz5oWa-C7mO`wXT z#oN^pqEXI0tcmlbjo|31LXa;JMIm3Jg+adLg>vpSTr<d*W-K9AqJ}}fbcccMn}lo3 zsc36fa;3n&sZtOskx(@BXs8(iP_8J1N;4V|Dp7?YRAM30csdk_wr1BvDdyxgspq~p zei}I*#~lzH7m>a1Nl%7cp7_G<AL(H+RLyYUszF_JRyIAMv4)5Y`O@$%$mM6W5uTK0 zMIe`_CP6Mw!nyo0sEp32L)titc&;g3`kv#A5nyi?Ra3syGv<Stv!Oc54FNNs<pndR zR)d+-u=bn4%(*gP=CdeQ`&3V@0yAfLfSD7E7nYoD&Pf+LH6@Xe6nkZMe&dGw->A7M z&cTz!4o|~_GkVA{ug?lL+GY3hZF*7S{grIJAR{R(L#&^#af5Fk-=>OT?=iBqoT)-4 zO{`ydQJwu;!7;wRZr^^sKmKZ`MwRV2BB9o1rb>J^gsr@d8;<nzEoasvGq;v9m3x{k zc3Z$6nLjTdn0~ncqLR0Q;b8h}I}*qup;jj?&20!GkmTFE=$w%Iv-PBvK$KO2!o^&t znZUk_zI;&#jKibipJP4*TP;BlYqUQQ!F)OCjg(<RBcQOfp>DqiGac24PHw3t>lmvN z^a#-o_IcmBQC@zZycQ^cz8Dltd*SessX1Hvtw$Iq`?iqmlbs>iCy79^uX!+qX*Ssf zH7GhiI48YWw7d112LQG=0k-5L09%p(z*a9FV4Lc{;db|S&o!z}df#3nPAeRddLeh` zDcKfLpB`ypMuOcNH>3fBQCFmZ4`kPqy1^zSQM45q1&bPA)-^Ws)aAY(HG-lEc_k`y zgwu^p34AuRH%S|Y)V>Jb<AbtE^L${_@wfF_qM@NDSIgV5c%W`#!ta&4dMxopNxx{Q z2e2-Ep{W|dh9#+(jDF<v$~78mr~su(caTLyLN}jT-_{iT@q6En{-7<2p+^HY_-tee zC?~#qS7d9S%N+alwx!=PG$g=uxigDbDWUyC;QBg`obMLdHY0xHnas(4&rq2F)7^Vm zQkhA#2kfXtFJCkVdU;h(_qg4U8w^XTt8V73kN)TPczFj?D|Agll3n+Ykle@2$zXn^ zq*u*HnHBw^<%^V(V+-t=OD89SMKY5coAYi|G5-XwF;^mze60mNJAU|`vAaa*rHi3L z&>mlYuYazN^P_T6KKg?P%-g|B(6T?QL0-w(Da@Z4@`sMRmm#VVy{vm@&=C}HDQmu0 z?&-0^NRM$=QW?o%*+mc4E^)m%N6<Kta;c1qU*6^+?92b5AnZrciQtq7JdJjT`US)N znF@c_!no=E;FTx~P&F%?AOz5i4yzhA7^|v4Zt0J>E~;%yyL|HhJr+(`hAfqcDn-aU zB?}1VJ{k2umfC|VMF^azQiQ;nTvwZ8GAe;AHD)pvS!x-o6q%c^B+4O6edSbfxLfmE z9!{Fx7cbmMc{nuy1v;(>Fe*_37}X0GPIf`zgklVZ6IuimPWq5v^cxw)<<UtfoKSe7 za6*cK!igSEoMikZx+vWIPo{djc^(J9rG^`9J34u&-}ACN#mrPL@fwLY$<C|SE&Q?b zok`b@y<;aRMo8&q41tV`$(bbKB>TL+=CH@iKa=AfC%OF70$`aGH51iALAuYcQ4Ihb z#Q@a@vsvMec1QY)prH5XII0guSxIChzIy8`*q386X7OU+n(UuUu=Z(Puy%$*M&hTp zY~JW@uYOKAR0yYv#Wzy+PEDhwbGf`|>1(Kq!O{tp0=XAQqrum6#i6K2m4u=mp$eRR zvIi9PG?7(4;ckSY9#sg6dc;JqN?NDJvPs@wO;|~NxTtp)?3~IAc22^z=2TqNI|M~N z>U=2bG2)=8N2-FNUNbK0i9!}SqXFbWeXXATte|=i{iuH5hw$KY37<?@QBc}r<C=3R zl=fznp|r;kLxE#A!5VVXXy`i4h@-#}xdiAsQRhL|=`k+txnNf%hwDcf3KyEDBr~LE z=0lMA*XxsT-^mTU{)`cLeJUS#eHQLJ1!Aus0$zWH54`>?O52Wh5^>*&hWk!>ZRaw| z9GDH!&!^+SOHr%9OR=z*62o3f0=(30C3q=DVPXGqE&)Xd<}-STL<zHE=}C4kk8XO% z>`y4uINwlfUw`z^{_f$xk+f|m);YF6>CZ}OzI>nxIhi#2=a|cm?(fAE6JDV~9udwK zXC2Ip<q}hxx1*5C^sIxrvGjfP<?m>pW9x%83*^p0RAq$v1ddjVfltLSXh;1dPL2A_ z3|iLSKfDU*LX9xbVZ%z4AfT9vEDQP-!=V6V=&MotIIg|M9(6aIhnXm)+(M?Hlu{hE z?d*PZ3Zr7w`=LS{QYo6Km=OBdS0j_7q2?#N4r+eupyp?VOZqZVP|~->C4FZUUI>#! zAo_AT4_SY_4P^aGAnT_ugRGy%54lw8sRfAn*RN@HEa=<}PHN~70-7y(7Xq3DX^}#L zT}^5o%65jO5YRlywg_knq=g7*Nd*ZAXmxHC&?s?5KuaVWK^TBi0|Ycu1Zrv4-D$pu zeNS_x-9KkWp5i{qYKT+|A|O)z0X<Tb9H2;*ZUxZ-3nf~~DZVkFQ-uQ%sn$ZIic$mU z5@=g2tq^l7Xc2)Bz;_XZ0DEpg2yhib0J;=}0KTZwezUjroIkU4@)@GR9<l|Z!87zV zP}FB5j{MPQ5neScF_sC@`JcwW!M+T3J1c+%*ipa&r4hga%D@72O;d#{*(f(ab^QZ) zoC&z6<a6Cjx!{$i3iq>{F|TTY!%HqyTf*%a*9HzKQwBm+Kw0B2%Ot4lLrPk5Rt>RV zn&!j8{)RG8<26kP_e-#^*9HP$$p8W5K^gjNROW?_x`$r4wqyWRXeS&<Nh8>6(xO0x z_i?L938GUDB9yM4DMToKBYZeIwFWm&TmZ2A+rAR>n|%A{*|i+y3wdrMHaH7C09h!- z6iU$qUMNM|uY*#wGXRrf2&HHOAC#h#BcT*sw-!p#!%&K*%!5)iQ5=_!s!=K0=Ccz} zNoO9Sl06f50G1SaC`Hp2K@RGLsASi~b;v=j_;6A%A4R>DO;ONk@<&v%lDijCi8MtS zsDwyUxX@GgfOHN_UauNVJ|1b(fqM{4p1}_$uU7*m{~D?CIQJBoJY_zZJYxx@p9P2y zj7IWt`e_8|CwKRWb(aWFC`te{2HKl1^}ak1mMTE@$<=_cl!5%&E65E{Sh@#ipT}|b zsR2DE7Eb8{aogSkcM0~jo#!X+o<4yhZaaz?>R(=9#W&avbaU4FGy5l|A)TM$LH)}F z;sOU-c9$5o@Mv7sQ-iABj22Y&hyqa6Bj6(?PAB=6a953M;;NntRP|;Ip{hsZgQ{K= z+L~Px{!rCJxdE!`F?b;9Ct>^6LtC?J;x*X!j1bs2RSI_rdY5c%b!$D&y462nUMBE1 zBZd7*V*iu4NpmPYQ~BgB?5<#)hr=aBs87t#Jr0LUQ0WMr*I?O3<rJ|ws89ToGlkB9 zw4u_GvHAGSVss9qjJQstTeF>@R;52Q=u|v8QLbOn_w_OEDd)XB_n{pXi==M`Qj-#F zyT6iD{F!Gt$5640J+lNAyGj}BpcY6%#jfE&UsUWWo7#hlUF?}fsMr-qO@?9@_3yEr z6IMyT?2&$ZrzMbngJ%<ve%l$S^!}1lfb{!&S^?>oGK)(3Y!=n}vA*~a5$RWUf`s%d zG-HDF8!^j=^jkR_302csKfY7Ly0#o0^8ST}3aH~jINPU0uq}z~)LFXNXPxlkIMJUn zh4E6QF`~}mlFbc+m+rOv)Y-FuS7G^Y_lF+rxX(51A@+rwb_?SVrRfhn1N+_-`Cpi% zb2t_pLb+_O<!Iw|uiiI<CU-_+9bfth6?|8*kjc|7ySq0)x6viY)t*_Qb!+IZ5VPKN zOQ&n*`n;Ts%6#+q#@ZT5cUNumrQ?$BA8i9n<Io-X{hr&=9VSfX<!hDs&*L5Q#?X&d z0oJ?exeQCa{rTUu&E!oCg0A$O%dj8vVHTP?c9bux%aMH%vboXLh|0OBnDBn$tJ^89 z?*pZ@9`800meD1?<@zC<)Qm)*F=Z7q*>62-ww_vjQ-Hm@zO(pRso?cS``3YEsYeHu zb<ouQoW=FLW=&shZ0ovA0}Ga!N!PTMKkC`L-Ji4kQiNYxsoNFBxZpu|hf{_pdIt}{ z+<{^+_n`Z}Q>$K;goxJHUrsA+zO0xOUlOu1aq>y1Zt~bR^YM=IXZ^?8wzU6<aeR4p z%-!vbZqj4U?E1bOS-O49B0Hnd0!?$dyy1|4`nP`=<^0Kc>}PP!SH(PgiNr)yPL*?F zheE8Qomqshj(OTPn+c7a_kQ}3zCM;JpWRSLLBm|Z#AQN@xzNn1GyQdAjh_%}U4U+! zHhD9%;`Xhf`|CBk4I_P5noEsK(i7n|I&*C`w;vQ@?FcZvt4+3G)@$Vr{j#-p@eyLJ z4KUZeHKdd%cz#NC>~5xKbob#!tX*f;H%3h8Rz~<`RM)*+WTzj;UTz`xQO9b>8AHda zi4%`JoSnK$UoWa_Fr`QTjM?<2JLHw4z0n1>x`q5V)UZBdeA8~E{7_x@K@Ebh<<gGk z9JZQ;qKUyI{gK+Kta^@wazghOtF>oV#a$;;Tk3Bg9*KRF>z7uqS)XLPCWWAa$A&#p zTdtqtbTu={M}xTGjNz;6ZF&~z+YBZv9(DWarwAr!o}aojX3lIWzWy>IN8*(7OPfdj zei?ODIsVQG-7f0|bJJ>n*dkRVn8Y~Ru8Fr_@6~-+lDIx#ow~-b9kcVc!0>GpV*3ZB z^m}hg?9^ut6kn@c+0ENYU%f^bYj``>@WPf7Q^(GY%o2p^%l-QiU{SHm(PhGrdFOWC z&|!FfWnR4F?P|$(RddCUl2$eWt6yo8Z=qQoI-}zp>uMw^*BUvmz4R}(AInp&6iZc1 zOqb&>K)_4xu#Zcs@ta3eN5JEuTt>hf<uDQO<Qd5q5%B(GtwbQL;uK;~6Ew{fKK6%} z_3Xc#!dZAJMcXKdre&rm<ImZ6YqTQtz@Pd{xqb$LG*vT|zLKf!$XXh|=50)})eofU zn#p`@3ytd8pV2#LpdOu*RoZk3y+z5?v3lMs*Gii%0;IFlkh!#lKIrk>)bO<r{c)$7 zwov*2`5fsYB@9s1E)xe}rf<Mtu0d)RVfou4HPiLPGm#yJ-oao#guzVG#bB1gV0McG zFq70Vm_K4LlRV5-I#P2;E3l?dqy5XOEJ)3a@0O%Q^zW5)nMlluaCYc4iFK?!B1F>! zEK?q1ERSL=GZ2N`)#mQNSZ-YpSZ3_QShm=Luq;1jp>E%OSb(wlly#%p#Mc}Tzl;|$ z8;~;2vDA<<xS<6|8MW-GWk?xxHf%}f6fY>FoNyVqgR%kHcm31>WZ&{-go|L`Q_k-v zJLnT!{d#8c^-@Br;)T(+>GS<N75fG?)$4MumC`O{#QO|VcAr}1=+7~@WaHfQwP{mQ zZA061M$frTZ@xBdZnQHBWB@C*P3K@W>tQudKWZdDntDany*Q=A%1tfS>U-q|XdQ9a zGGE%qoN`4FY-}%xZM4-V>n%}A@cBd8hcMX9QOI#|ZesbyHP$xp%v|k1ziCT{Z~P*A zqi}|rx!gCAnFk1jqMhtm0O1UM;M^w5o2$$ssVZ3M(OBukOQ7_bLacO6taMr&D1CNI z!1C|fTz?F3#^TJavD)0t$ZT#66|C3H&8TEZD>>ZJ<|<&jVHhc0xLPOqdQ#{a8*|94 zJLV8E%pugvN(l!gXE&Z%eRmhvtA%MQ+Rb>0Og(XSIWqO`iGwZTSFY7jQ;?~v%<e;` z9?KPLX}K(4KYLjzIsQ6V@BP9fXT1wE3?`SfbQWK)pS`MdadgW}^3Z-ouMz4Y!EVbG zx72z^JN*>5DNp<ERVi+P7nI^pX%y(DDka5VFMFQ*Gv<TA+0&6f10>Nunk`I3Io}%} zJ=R8dxV=is8{6XkwI*O)Fuf7Eoum1K%gq&JB+i~TK?Ja<=i}EyeO-phZk)xL@dp*z z+UBw!4c*ldeIF>>ja0O&EEVB0p{pElxrD8TI6$S{4RL^384WXw5e7WRMCn3S!Khxd zq?L7m@rheQVOae~(cGqyVoT7}LIfQ&wWQU0OfOq8y<7np2$6On(z-R|x;{ELt<p^@ zGvSmF$sxe-<}LK!$%1DPYnI*G+PJQ~)p1Q!fi6<1|AahZTIK3yNyN0v=vRO4m&~2` z@+if*q3g%Xq}n60B)2m<-;Z#qkE;9(l9DedImezQuW1>$EiiKNQLeLN&yUxQw?1s3 zhy4uN`=&=h-f02n^!JYn^@#De&<lUYWO$7@oEPjiP4QiBAzQPBe*LHCc7sVRw6hI8 z>92vreV1YqzlurRSKUJC;}-hUpWc@ZCXJEWjZ%`Y!p@TSbT8y(>@5$r=o~1%{;EhO z>*DB6dc>HgV&8~Tq-L({o4_T?i9V}YyMl}zb0>8x{hgb7J<jKKA2wyV2HC0K8ZE5s zc24XoUD#x2Mq}$*W^Cj8qg*-ar=QI}VyW8UcHfPyZmDD<@yE1N&djMlWV2JP-#UVx zrCiN<dh(F>c8Ndq%Ex|bZ#fcK@xkk8DtPQnrNMIjY$r+OWS@1kWtIxx&Qs0}o!sQm zFDTShS99K~C20K%J$K}jOi%RsGmtjwMk6ATHZlc01#@K^e|T!<rZq$$k0#$>1Fo|7 z079+Yo4l7vx2yNM>mgn2Y*#^^a=Y@yOouz!+{G=!#n<W;5;gg`^1pBYJlXc9#{oM^ z53rv4l`R!ohewLBinn7GFKj8i?KScb9;pqsQ~&<ad*_Cp_n!8-8TILhyj*-VXu1~4 zG6rYk8&f}d>1VrN$WyNOia|WF-pVClT_fLQHg=X`5`byq-hfqik8l^DkWIAPJ`TKP z(~``@bZ0IKGOk>!PL@_m@>xu@15lHnVNloob!-^}=gyHTDP@?MH6hp$B03}g;4-lh z`G+l?n(-*uiKM9_YtiOz$B=%HAx+$eA#H#m-6RQ+CT_-%er%i9eNc#47hwIeU>^<Q z2LFi?jOHeY8<tV%7|ruBnq^)Ani(4~nh%1S>-t_HHCs|*e+F#lo4kq9oLUcPrj%eb zyIpf!Flqb4?G=ETas`7qAGR-rqy}K7+{c<ejU$qRRHgV}4aQou)rUtE4%u%%sKMBH z#{BMeaux=20|s+bDyAvv_Xy0pr+46x#8)Eo!l*T+^ygm1zF`Z9{OZ_}h`Qw&n~{B2 zA(w=xyGt09A<3}EK}m>hbgq0gTLLL#5rc%3Q9C5K9+W|S8({rH&xN`H6GybIW{+hg zQPo1GPMhnD<B~dvb}9)n$PG)*oJVfhWg-RKa3oP3xnY-y_s9)9bT-8xHylY^f!wgm zgg<5uVu8VppYP7}dc@r<?PV(LzpU0P3{gY`LkmR_;#_qcbL(ve$H`Cy$63A=xwmdk zK5}nPtb&cmy$zLbIAyJVnBA=Il`CJ)x}cbFYJ}o~gYKeb(cK3JDXjrk>dISWGfG(( zan!L72i@#g6#EQPNT+n-N~ZYgOK$fLim3~B8wOIBnMsbfO&6ko{>@j~hDEol+otdI z97935*QE&O#;>%^j<-IvO-J^e-23J$DXr1I&Oi1WS*g_xmVB1F68lUA>@!($;4`V3 zh*-y_%lkc%%O{|WB#S|nMEK_AY68ArPyyfkv#1mBN{;J>vJv@8nRx6o(+AE4Sl-;( zsLc(;esFd%+EvM!JhZEMLkzU5*D6yXo{yYei6KtF5U1LDp<QFUYCyKr{#w&qu|~2z zKLTev1*dSf6LMNI({Vt%%q?=@+!L}r>%j4{Qw!K1H=Z)oJv`)|sV>-K8qT>ZsTBX7 zzdb*`XJ>}sko!J$^gKq$=@i8TgrEG<o~@VP2No#9evvts6}QH=lx{j@e3w~@B9K@0 zrKu>w5N$74hb<WNFej{JF-Z?fN(-=Br<V!#SOzx7z!nVZLEf{*`jqb7l8_rXNZehY zeY<aPEleFKb`?!s5)y-VU~7GaR^Q;2WtuK12ikSYRJV7~)v)*d?-I**n+HBbaPHzg zC_pRkV}@KqYVu-t;z%(cm>{%ZDNcJFked3~PXYWqq&)!sq0@*7<SUxNxe3gI;}q>w z!{8@11zA5Vuwol+!{{H!==ViAXPq|L2BSX_qu)dt&`*!W=)Z;0@2i2)uZ5$Q23v?) ziWWoEf-)tD_KQ|4FCL748*+RlW*KA5GWNn^wSG%jJ5YAwOtygRWcb%1dz417RV<{( z4e7avb`!cS>VakGk1@;WVwUmnM7+C<<$A_a_wYzztwd$$%HV>p5DjUt_MvFVU{V?L zIR~O44c2B94P7T^{PfN^47pNHR(&00R9ooDkWuwaK}MCr)<k{+QYGV?hexJyMn&_s zT-uiUj}s7dESCp`C}KT=uNrIolxaHVGBu`@3n)o>UrfR*Q-N8A7hzYf=Er&nP{iU; zd>9iAAzW2ePOi35l#_U7QoMFqdysW;!4}`eXj*McRkVNF$T30clZctz8RwQ7$VE7y zTnVK<CpY8$D2AxB(*;piTBSrziZi52%*$z&E|_2PPSZ(N@v&ovEyfpHK^A>@=rB%v z-b2P6R)iz+rVA);p2^ZSlg%5R+KP~u$F5pt-+g$(YA+@hwPipo$(MmxveeD4Z;RkK zU#jxc4<sBnC$!1jBB<e19_2D9m9K-0YS79Nvq}(V6^UiQDkxV%tP*+&!(VeVuu5ng z#`GP`DtQ}$RYIR*R`I&lC^#WC;02L&PbiA4Gwj)qAyf;YsUal~t+7WQb6{#OQb!`I zFBW-BDoqbX)qE54b0n7Qhmo{U7`T9PvETEu!9)$H`Fs;<#>H!Mm%QiYWk^nM!*rqt zPC^nnCZLlQm`)17NeD4E;UMrM+CHbwX7JmRl>1oI&9P|zB1w8EzK2u^l1!7ozBpyz z+(}Y|l@r!-I${vZ@Fp3h_-=j*;txr_iTjuQof}w18yD3c;nMd(&17m3N@e8eY|I@~ zy7eu$?bxO2g1H%GG8>RGIzp4NGB|0Cf}EN@4v>9YC<7H^`UVBpL&b!s1<Wyh0ar|_ zaK!}WN~mH&*^VnFb;|^|BmUouSgB-cA*K#Zb)XK)CQKc9!XqV`hC@$xU13N}{$s#N zjP2dRzmWQB6TgDu=$#|eO*7hI`<Lxti`gzYUVJF?ePQjjkBX1gEOUjkJ&tBQ`_tr; z&-$W{{vEhWVt!g_e6H^)@00N|(T@51c)n?`IH5OtaP1@^q;85u{&=Niq^k9GbY*AI z>a;s=&#a!b*<s!-LhViduDF4G=&OO00r9}oH4%@SDI496)5?Fpo_uQZ>+5(-nv(mT z%lT`H%g^k)ai{SS_qs)?^|A@p8IDx)<|QE|4{!85NO76wlzX0e;OXFA8TN+ta9-lf zZCCc^onN~ydK+FC61-)9=@4By<f&VEzVTQ0Uq<Y^4G-P+Y~R}OUH@#ITa?Hv*B>qJ z_gfyVOLBegIeWDA+Z{`r%Vw1N?W#+Xy&h~o?sR9f9ruq=ZU5y2&36?;r!JHghHqT{ zT~+S*O2L=uE6gw1yes_EKeB%0Q=G;^euE`qI)e4BE3W;sI&jHK!Pge;G&3t1S+<Xv zf7^w!yzV!Cd?WO$26dwKg1Z|PO_mXS%>COQl07@pZ1#E+jr|*zZm`ln)v(ms$_UE_ z_|xQc!_uAfRVN#kuA&>l75ZuzL&xLk`uK_w9%F#U)ZS+|?Db!8iI9LkFA%UydjnT! z4*UEUTqTJhIe~<)*VsKU<A3RU7=6~W@h9OHl3&y2>xhXwr_{&XIr?p9)RV_gejV%0 zJ2>8A-&(&R^bPO*vU^{Cb4$l#k1_t7zmgQWd-g;p_4P@?ZFj669vF~gulP~+Wbv>T zN6(>}E@fA?UcxV0%8py4(KUkpN!I8}T9Gh8a;TPC5my{2cWwn)=OE$SilAyGp1l=n zx`HR?Cl{;1cPX9+9pZuWPTC4e`+4!W4aKbBMFR%sbqFc@xxx2R-r{ikHl-I*-*mc^ zVK9duZWekWW!dEe148fsg%?svyQ)KB_CaB|nFzlf#oxuy_hB9$x9tyO1m~gI_QHT+ z6&N7MhrV0#!(`b!5BjTB-4}v<1zy5^K8UVZ6b11B8u3O|c@f<G9bTn?7sNCy&Ij=e z&x6~Z;stylKZnb+VsN849w7)<H}JRx91t(SuOtAMeHXxGJ1mNTsDYmHO0``QZft<} zSH@HK;t}|PSJ8-C^nWdc5%=Ns+66#l;t_alXV8d7)pljLj5Xda4q_%u*DeR*KAyeI z2wO{h<35-}8Q*vU=3wI$B4zj2tF}wP=yLoXcmeG&A{jO?h=5dD7{i$lGFG=H57K5C z5%yIA=FH;n5UN=oJQWfaLg0b5g~82E0_cVbto~{EPC&~>qry#Lh@TSNy%zSy27Zjj z^B_3Dh+@1T0r*i3--%&@apcV7rnQ49QSAWN>)>^t!ABC_Uny7v|G5>C`TYyKmcawG zVUG7O)`<_`gmp5&^I+?}9L<<LaAy`)^%fW>1}mIcg+;-Wkq4#LtR<t_f~xHSO_sti zdoJ8#B@dzp&))2vwoZ$i@H%(GZOQPEi69W2ctBh~_qv(@Uh-;~EDw}NUXDe4)qL)S z4e-=>A0A^7VcoLJAHO6*8YpIBFHGHppF##v4?krc7U#{qvOY}h5Bj8Iz;DK)2u${x z_wOr{@S?APUPu^qAF+t>{B%w`1^6k!Ftsm!iX9f`p=AXr&%JUw-Zc#Jl`sQK2zLKC z77@7cD=&iE&|f@5+R()!s4IvUUXsI8oD(Ef?lf7O7=m9CThSDV6drhAK`btKAyt## z|FK#OHkrEs?o5Uq;mTqWUq$;A=f=#BrQrwRZKvUVk%MQ_@Xng?xJee|xJeN43!^{P z)gg}lMi+YF%~-@%xq|0`mb-0%hCBWtAO9c#V^m=t64=9p=-fPvh48~3JVq2m3oK#- z3z?gTLV$IM!6wcu1~D0nvLHI~>~$`Sb9dls!#ZdH2Cf;1B!CE49gDnJMQIa9;rR?+ zc>W$N;_V3mk;-$@c6fQP(Qq<6e-;^|@^BLt!ES~NK-3eM8{><Az?M(IB3=XukKvsg z!@@scG%$D`>=)-~!^1uUFZc=0Ej|_0-iGN$2wuVh(=)cOO>;v6;cbgoz%stVst4hz zeqt~>WZvIp{Tshqc3Hvm5#Qid{Tst0LjMoJBaBTcnEVmJxLgI#vVmtQV<c{Zi3;X; zCrApO?1pK~2*hZ3WlKfaijQ-K@o!9zX7~@$W7?R-{I~#SXa>CmVW#;3`Y1!N#^fUk zz->pswd_j7BDO>aEFw#ca~<O!)`A&KFvALiSOm}wT7gA@zqVW^3Jc1`w@JffzhG_u z<$Fv&Y>ZS#SScGrSQo@((0vf{te7o5@C?|weZ?XlOtuD#h}Q7yn8JfO*f=fWsZ*Hb z^{~iWysj`VOC<}F`wYzQgkN?E{3?hEO=WK7_3$eJFj$D89{~?RfA|l%W284=1XFln zKdc+PUoNnw2RKyV{rfI{SX+1>Pr!3bVKe<C;9>f6E0TsUe(=h9fP@TSC{uv4A99zl z9zV>{N8oMHA8|zcj6w^`E@`;KOL8&QYP4pbbaJq&z2(Eq&qBt_DrdA8=3B|%@!eqd zcE{Dz@%A}P>q^2k<&9S-cmJI46}<c8^YV`DEa{8`dO<rW6%A2au0PRQ^-$OCVW4}( zb5GNYcRF|8c{c5Je9c>ZPop}roNT^XhPTDrLuW2moxTyUp>A`yVm9ADs)yCLiK_2P z`cQf4$vfShhmQ<g={>pHb;&oam0F$WgfHicX0AO?NY+0kAg=P*A#*MN>%v=&D&gNu zZM+tTohr*Vh_N=y<~j6j(BDz1XlA3?W!_)uO~;mAe{}G`-*7+7XZP%5mcV;6rK1Ul z9Mvpd#0K;17E37qvoY<iS8`<epZegtMVa@0kK}nTX9ZKfyr$jFBcFX0*BVdTv6bB8 z*PNi6yrU<s;ETJZT<qajlRt99<gWW`NjWZ?w|0g9`#0&-U*k^AyJ)8DrnkEo5#=-2 z&3~k}{-E7_OtBA{eth~<!NX=^-i?_vnpr`4PbXEndh(t44_@5<nbtyHb}``g_Z9h@ z>QXLl{Y)!&^(l9Iea<jiE%t)z_x0C2_|KcFH5$!Zmm%LL$Gf!g;%NE&;gZ3N!U^XV zuQ`=1zBtETclEq=V=rsws}zu@UL_Tp=r)Co`3lC`A55INAtkI)dFLTtbm$NN(ITtw zQC_6tpa~ka=NWz5)F+$2=|sC_jxOb_$Mq50f>dvh-ZE3y2J#)_!;SeaAK#=NK^H91 zg%Wh(2fE+}BdpN{_46iiq@LS{=|YtXmc98)%PKtR0w5m6;vx`#EG={9ZnHNU6<yrk zq0d^-DXKi`0Af?bkU;X};RU`!*ZORNawp~|`}FQnfhnZmzE&(MfoOq6iAseUy^!q& ztobm-SC~TY9894MQ?MgJv|8XR$(V3%AQR7<1icZn4vMJbs$g*~h^s(Mwnh(Azu?nr z4=+g(fhW{qaUO`8SVRk|(L2B0fG7$NGk}K?g+cVkq8Nz6i`%s*X8)v3+QJKv!r=uD zVKEGeXP3gm3f7^A*<SSNZH6_t^TT~VU<w8=i0N3I-<jBzYIpZKD+;Dif+<*dIcu;O z4OjVKW!BpoxOiFuUXDIIfrb~Ujm5R-VTUgH^ftj7X7F<Ku!#4k0E=jUYV@vdH=yF> z1i}kY@p8mKoW=X&1S_jYEAxUEpu%z{-9VJF0W~VX!x*q<ktsgCL|EAj+8xVY243Vm zSQ!h?#@8uY^M%@_v~qS`P()2z5h(jG0d7}<p}MfN61DRtl1FD3_6orhEMR4AaxmK_ z_+ARjYeR^LdpgVSZ-OtuOSJL9kSutL8{D!NZ4`P`c!><mUJgqgL{C@tC&4XKc*x(k z2`|Cm;DZ-XhUNONgj*u;cagudoLvByY+x4mh47#&Sey@{>(a99ol{P$3It%Ct6=(Q zc%w3NxaTOmFFGXocS%pNLKNV((^!-NaTeaj5^WExX*3wtkbE9#;%q2PW&)r-YlFq` zp{Rquh!tgcr_=G&c&F3x40u-?=3d7a1L`VF=8G-l6Ba=SquvXAwLZ;AylF+dP*<h| z@7ao9nGZw@d{tuZl`~<b?Rcg7Faui%X4na@6Bse~Iu>wS2!0(+EaKO}=8iOvwUZ26 zdzJvVaRAF_i6Cm?dyxL|hR4GEPS|dE;8!uMgw-&kAcAtxFNV{2;{`ee{|2^U#sk}h z-+wJ!v_Uv^pDH-K2o__B-#HkrD}b((;8|p}k#Ws4`PUU-SxK<cHZVeej6`AB4b!<1 zdGKQfDBo-$jKJD8gqL}*Qs!Lto7B*T(4jmU08hX~m4#Kl1;1Y8klpYXs?q2HSQB9p z3>3#A%r&Y5W0J$?#xU^@cxR5`AF#2TVljGd%t9DL!`8SFi}=~vSQJ8h@pRUw7rj@7 zhZUws11w@Ae2hhextgQ1AK&zW7Ayr}q5hx(D=`p9L8SA{jgf(slf_}F*uX6@N;kti zbji6fsj$UlGZ^EB6^pnZiTJ9~oCXUpQS63w6ku}4o8gN|+79to!L*wJ3sayajPZpn zJ!=K?6yYgX%+2GAZ$1NK*!X5gEMlOXn49O{s2>f-T*><v?rf2>i(#?Run>wYi1t`S zYeJN-@)xK?yjK#SSR&pl7cBmV_qX1g*1Sf;8)OD=LBmg&4<ZTRI;lQqTY7*&t`Mw7 z7_>{lUhKcL58O0)8g66ZB{^UbFX=cI!~WV9R~KergI>8<H3U%2e_7wA@HcJ$3|@;9 ztYrqTr3s6Quq?f~Wl`~3++ZzKycQEI{)hDOvKV+Rg7A1MUW+Ccu@{n=TNVS81AZV2 zKLx|>znqU5j098b;m!Aj8D_DFi1*>OsVi%P@H_v86}9ogE1rTem~{4n0r{h+Vs^sJ z^8j9?L>QE?57amaEbAXQH)IXG!XP$JDH!q*p3DI)mYB?uiWR;D8&-lHjS>Ei*@$Ou zNElpdhJ{%!fETKO?_%)MXT5nxf5;@Sc{*4ndg={d>-8VGJNyp|@7T9`NgRKM_perx zHgzhU&FCqmw`P$2PE7HADI%plO!Jtz74T^3fn5#{Oc<vsdJ8C1pBpqEJUe<~G+)w} zZY6*1;zISmfntid6Z!W@zPFB(VwtPaRrY>8Mq2I2x8EaGhF96%dc?gRlR_brA4gyN z>U(Yd{^}zmG{)5L{=jt!<2~lyHQk-(e;6E14FAP?c5?8#Z*<d};|$-a->t?!ik2K` zPIot;zTcBII@n(m`n>IO>)6|}9@W_W5yY$onkfV8KNFG<8~4fXaaC2^bnD*E;*T+5 zTA`H>Zr(BUd-UKw`pkKdi#{(s$VZ>yGfpl?x_T6|?*wRu42&CI89b)9W}C~vCiKPV z%HTo0@~~U)_WinP@?rb)^zt#gv`v9UsqH&m29$rlOZQ&>^}Tzri^I+QpEpfbXszkX z2~n1PenZ#oIVohobJeOtaZ;j(2Hh1`oT=w--nUGC<I~Ge-8J&BPE)Ce_b)VF()`oe z{R*dKIitkwn?><*Mx@)f*ZZ!p-sO)dp?_ZGk4Qau<@?KZ(h>dB?K;``;8pUcpmqt9 zH?2OF?AVdkBlz<ud|G(pPkh}1U$?;5OM6mTcl&<6Ca=l*qUJ_7E+DI1Ee+W)Up4k0 zKAT&I&9f5DSI%txy>Y|7WmoR({HmC=Y~7AKg2y~WvKQa^>9Zj6T3?;~x~08SU(4)J zD1POuX!hdrN6||qbKHI#o?kci%6-08o$`~JVPX<3-A3**XP1qtV$x)U+zoHj(PX>4 z$r`sES;Ml-h256g>lORTg@rPWe7g2DRqLr(4R5@)<YJKJ)Y&b#9dO!t#pnL5_a5Eb zW4R<zV9Csju?>&jG#}KnaJOk0`2A?gTg#%#{R}Jok#O?VnMN1krp7BuZSKYfuXU$* zHtu>{b}!K8xm5<I>f-iGXI@dB`$#kxt#vzIDL?h&u7mf%oCja34Z1g?%VLRQZ(Y-a ze8@M@C6DfUbg3y(>>avv@@F&pJVkzpe#q@Uhdv*7o9}xX8(p^+edg6IMW1zbo!l>b zlFd#~em;Lhj4e2~EMfqq3C227G0&E{Bo!T$lGDkLE#BcYoU_Pp-vhnd1{C(ovmE88 zM*5k8_n$xaE*{VNGH%~3I=_{2X6;0`y9~{V{Jnj@`f8I0J5(2+E+5!Xaf@~T^Jre8 z(>~(`J)s9~s4FJ!Zn&OU7aset+e`5GFTy3p^zj{>@;|+_28ZoBE5m-S2~<@+^QxWx zt2X>NduAxBe9@7T$Qbojy36XZB$ofnwiC~$1F3h_m38xD_6(c)MJSEmcGp}Xe(dEA zE#1#Ot`*jX@*7QBgljFAf4*6<$!m|*l84Pt%I;p@WUuB&?Ja0ITD#_MOZol7y&?%8 zqkkRB3*P>0&z_dvs10$q(}r&QG(WpLyx|_x-EiPz$q@&!qQw^1M|?C5%oCqZhn0JO z$;~Oxz33g!O6zhmIzRn>wdT=l51CKGyEwmt8L!L4%q3Wcn~itvxLZ-sS$OR6F4e9a z%da}(`s<3WsjCY7kV}hcBR<MJay6zu*QQ~Kcxb@2U8*_Bi}X~2hZ1t#oh+aK9QJAV ze*vu<@1Pn=*ysYZPwl4L+>Yl6A0D{tpvcY*_^6@$Qt5ua@v!xvEc#XO)Z^3mQa-+9 ziZ9h6pjck8K%nUgYl{V>^f&@6R01C%DDaiI;8`+vZ=EV%cslxl+nHSte!n{V<HNmx zeZM1^0X_p=;?&jhxBbhq`?DT(U8g<Vc0cgu??-t@v^MX3>eQrX@#iu<laTe~Px0Os zt7^fHq?A3bzwNolC!aa`X4-UWhXoiO(hH6Xv@JFAJn`<{AMed&VL{u@+zO0*ZD3#R zKBU7II^$D%^J%kX>5bm9x|yHS)B>^LNlNRx{MjMfaDQZBh65Sgb*&Fd8`)LQc8@OU z+9MyfUqyD|L^>a{|J1#<XU%Ke?Drj{HobRFEl#u|+3)L;{xeVMbZl#%@aYfgCo=Vx z-wx^f9O#G|cq>nxZuTxvNc>>(5BGYZ!D#YH@0z4InIDH${*WJ>5XkA%V>Vci&E9-2 z7vtZ#`m0FwS-+AK0tcUztux`%Kj?lZJxtc9;9Y}tLWUc<$FAvUfWcz5{-<cT^O!jr zz9G0A4L2-bi-za4?|d;dc1imvDQH%(n&bMeaO>t<XC5t8I{ro^?)0aCW|nsGog39T z3XP#XbtIYIO(IA1#EWt+dj2Tgo)flTjr~J$bmZu<)MD;Ml8ZvtrF>>-xt+`ART<M6 z?c-6tx}S&o9{a|BdSIkd)cL)v;-7Ja;v_Y<@pr!cc2!~8*Sq!AGFp6o%%)$>bTt?J zUUmO5`OvP0cZ4_G8t+`<$HfbcYgb2?-t*`sd7LSmR0)#0KkL(1TC;*aSbyv1$}y_b z&<@3Qd5e8VMfBx2c;21~i7Dz{Y#tdD-Dpr#=Q!lJ*fMWo@EtjANTN(3`mJ{L{4^&C zN(`q1#`yAfb-5-p^nZVoH>*xNQ+;Q^TGc4`L)x|dHp}Faq^%B}DO}ZW)ugh#b;ilG zC%lW&q_sTfI^*Zz$Pav3OLaoEeua0g{iU?_UQ6%>s{LX~v7>_v=5>mHRa(2s@@~0W z%(sB#0rrCMF24q)wJMg`rD_YJG)Lv0u}w}^xHr!ysmof<SoOIGtcuC|=A`gKMfn=h zgC`>B@XWnb*+E9`WCpKDj!BluxwT`p!e~%?kUqhquDOr3rn8ZHwVZ6oKE6Nuq-oY0 ztK`vU_0>i9XMYJR>Ya7JY&xhXBRL&9Q_smdRr~1B=IPdWV=p(!$TF9n6&j{7meOe* zDvsyf98NCwsyv!?s<^pFRYGw+wd_QAM1Qi$mXnL8hYwrTISLMnE0tEgCOGG>OIL02 z5*we{ena>B%);3{lj#HlQ@^2N{l2fZIwC$HA2&QcR6=?<d8Xo)Ua0|>{Zhtlsjg8; z(Y1#=w$oi64yT&Cv6MsZM((_Sed(e@&y*kO<ViWuA8lHpe8S@B9<lAe6+-Ttyxu%} z+v~U9)Z><@)T}T2Hf3yPSH2L}{2a3CWc4nNm58F>a!SYi4Bz?VZPBYvu5SwE=d&Mc ztHWZ>!1<w-PEDagaLEf_S_Gm@nq$nj?<IN%I%2Y>ganfMOA4@9`#F;3_PmcC8KxFI zXd!Ew(?^d6QP4w%BD030CZfpnCsw==U&Ao~ks(_;RN7xcf_wLfz{I}rjOi0UE7>`H zGw0w|Czxd>9Em43@}mE{ls&^N;PazHSI#Cobzj{6aH-2mWzp&k7CO~fP~O4Hl3o_v zP2+}_&W!ySJIi@PP1%Y58U|{MOL3(yQcr(;#twA2{@}~+s|CXYcaO9C(-uAVX-%bk zy!_+tangwEduO-fQ`-vCg!VoCwYkj49E~A;aecoFUA^=mZ4+EA@p;~lu2#CfcSTp< z-A_9KGaUOE(Cpeu-=2Rf8qKc0+h~ojkKVKI53EjH$vgFQv|TfOa`W}6$PK?+RpNZ# zwiJ=BFs2J1AO0D%VbY8tp?CEq{mP~B3Ll5l6;}&(KKc>-_0O;M<Yi?#0?I)J;SH?c z)jn0Treg*N8$w%szo#!rI+V1fd~|r|%IWy(FFSgR8VrApI@Ax`xK*Sr-Bp{Hj6Usd z7l3@5rhE0>dryZ%ZRs4+pY(%E{~S68qCjwcSNG)2Pm;n+y@)M^i>FtA2GJ&<uz2Y8 zRu`T1jC-DOk^QMx9MEr%Hu1jx;W}(z@H50@c$s(0FK7S5A+x{2wlv&zZ(f!(>jR&o zs|t=Tsq-&xHi{M0%@5g^67=>_6nxP+8jQX?d~_AQEI$grTzHfOU$lO}FX*oupgYC3 z*ae|4TOVb?FU!=*A8fRg-9=>do_Oq5-k0ax%Dt9f;xl8y%8Ry{85%3T@`n3(`gGm{ zg5j~PTl;(L#mN0b(W&J&&%8piz4z~a;YWLJujcap;sv5)k{9Xe#(fzkV<>uA+x{}% zyxKO%L+|@ynRbV=+ctZTnT(?!!oI$NA5LGEIUOTB(ChwTsczPuAm`}f{U-)&mAX_* zuD}o7pWuh6R``J@={LHM+4>3H7kQ#I+`P|mqUUjez?S-t^&t^o-UVJW;YSA&=I`Kg zq*GJJ^PP4M{A|rOpcV}=CSN_rClSU)kKb>4XRfz${oeX#&Fg>eIZ$oktvdLyGA8iD z`n`1Y&%5<)ev#trd$Gj_(Lbp1MTid)fhIvIZr<YV3GPC*<3_+5)E5u4Ez^V%_+)|Q zBIpm4L$^X1_XklEO3O=}jk|di-12}PLl6}@2%30Kbc6t%Mfh*~Bd9tB?zV!?#zEX1 zN2d;Q!fh4dzG6H*YLWz1FNGPZ@QkS8rYY^W2Cj<21$$_PSmEvm?zZNl-uxTY6VTf- zg)U?vG;95E$LbD@*F+t}dR0f<0l|ZC8`2OS@V7O)s3T5SXRhl3Js3;eUbeySi95no zynC5fZ2w1_o0({9iJQ^5k7R|H(v9ap4I9fYbOwN#X-me<AWi7CkcD7HxG_VP$0AaM zVElv#yjT--bS8ALh&!~3AoihFux9-K=x^If#ZMf>qhZD5g)nt07SYKQB=X@lRgld) zeA<MBJDaYcClc0+5f;&g;=Z&wOjLjyy7;7lFMcl#JpEk1i;mlwlW-dgw^Hw85%+_C z&9%wdxHs+yx3OUhCQg7TgL`6Pb3L*D(cH%EHw*|OehS+44sjxWN;ei!w;sRpMwpt0 zpK=6?xE;I;i+q1OhFsjc(}TxzRY56wxUGy&9u)BWZR7oq-Zt(S`hr@yxXb1XTgqJo zld<vD|8)O30D&3Ys29T`?&&3C5n&iFD-RYx#cS!qB0hPLibVuwysZC!Ya5@#;Nrs{ zM7$OQcoPO*A?_{5&-HF+@l)6^^(=l04U71E!;-nSAr-F$?MqPgELw|g8w0H1zeko( zJH9<HxZ7^_6zJ3fR=XFb^3%lM;pV*yJL0-r$9%eQn>7%+$;ApVA8xD;;?YiX4RAMj z-@Sl6KRuWr1SV(`1e>AHoevnqO?BMnod(4z;})wmj6TZy_jrIMR%-<;Mhtg#wcv3p zVD2)sulU%58~(u+ZgayuX?!#S88<rKg!m30pEwFCE7O3RkK&t+LHvQQ>dwvcKYH8v zStej8L2B?U6WlJAz#{Ie8_cad89*B34?h%P+T4sqd=+gsK8E3oyXxNf2Yj;y7I8!T zKc@`-M{^s`lZ<aRftv&I&G>`?K0X$N)c$u2R*El-se?@@!w{H)4K73H)$qDBu_pfS zQ%%alyr6f&g1JrqpWW@QM1_Aj)TI1hj>jhm)bN~O>wf<^)Px8dxIbx*dH&^4Q&2TF zf0sGl`IkdY9pXi>1WVN1$F}z`hnnUmPvC<K$^3um;@=K6p)*mKE(_<L^lyin(D^1z zL!NVXl?M<0e-1STReRy92yysK#J?SCQXVb=UC`!^22A|Rp(gao|KWUm2;tujHKC(U z^T4WB{O5erzZ_~pUH~857@u3#za44{s{VUc0G(XG8e9o4_x~JfQXW>qSCMzY?Da2) znvg2+xe3mH{0Ke?AqX2x!H0mNv4{@=*$c(6L(u=6<H0YAZ&I<Bh2MuqX^P^dk~=Hh zdze!0=Rc>^?FhQK=(lQe!I0{=->ObMGA-qUsSfu4hpsmdq^gVlhYcZ-u_9v`QVGeJ z=@J<;B_tW5GQ}nHEM-bag^(daWz0MerDUGx;hN_m<HhBA&)z-H?|q-|`}_X0t-H=T zYp=ET+UwqP&gU%rY>szxl#_NiCYC2O4Zh{cbK|0ut_*D0q7TZCq6$_UZOhWVo9h%l zU9x(#-;_^B#f**8y|-?EW%xmCs&-U(!w+&_qn;C@{D-NDgEh<O--Sk{+nTB+jXL5^ z3ck`|@=C(9LI|mZS|U$CE_y~Ckq;Q@2zjNgQ3^m-eO}5!E_yIkF953<vit#g!^q<2 z)9ukr$O!}-4i<@&HDfVXhH>6eSM+!XtvB`p6>Fy}dIFHeWfcSr-dHNYS(^kLHb}-- zP}q06vP1FnfWr>Ozx@>_2b?4*eio7p9aTQ7ALMaQ9K6EqqFl<;WwT@+s8iskLhP@_ zYCObq$H_hQ;xLFh9&|5>t5`;ZN@1aT|AvWk3c9*SKVd#JcT1Ni9M`}9)wNLnNYP~b zgy%MTTX*eYE<T}pF=3^*reD9R%lMGYySHK>^XPM~hrZ`_(w6Sp<6L|}_2TGCuWzS* zRZ!J_?*3P7p}t90u18eWK6<YgD>c#a5zSV*<5SP$yRDrWi*3ZLkr-Bg(X1IY3weYU zksmG@`Bi!Y&5ZB36IW`ntApH=4xA8BPYbCC;Eq$JFFE0Bb6I_a=K$Qee)}gky=zDE z1>vJO)nPO>>8@~8F@~9PKKV=G;V)C!GUQ-|KOw6K!lk49+;L~f2X!_1Ey9g|MwkG& z<dmPUT=Z_Pr|)iGmE_R($lmE{eYP<18YLxd$1QY3nXZFR?%hv5ufzXFSwbf6^Iy0I zdw!g`8tVR6J1Ql*^Ub+F<-*Anq_KzAsAOK`&tp?rG0JPWs_%ZQagAO%xGXZ#vz->w zg1>`hGQXeW+|MfaTdw2fz3j5T6v#5@S6OzMb)%n~qfQatGCQ{L?;W#IJAwY@A5RsO zw%P<r`e)jAq#pL<@i@+Sl*Gi-DGT2u{`Pw6=SSDKr&ZFsLVmw$aUd5qSh)LAMsnZH zFmj-wdQ6Y2yez5YbO?X;dU1-kDN_e#_~p)Zt6Bmy?@&W3A#qi<@~xXid#4+0eS4<8 znT!X5cH>+)XhJ0PmSPWc({gS<ul%C$!S{I;GP=qG8Z8fK3to4f5b+wlopd?}<LpUw z?n$qEz45B`@&Ubwkd!>5o|Y9>5O?3c<BgJI6lt;k*HBXbFk50n$8(E!3iF?s?$kV9 zO*Q*g{1tykO=_8%?9G=JQ6b-9{l3&~o@?0qwyu0pkBX*ERPHU@ksro53|Tvhm*{4M z)n_T#aqbwg+qw07&$@U!6Dn1448GpW@W%Ah*bgM^c&igORGDAi#4J}-NBjL)&0Dfr z8s0AcMf~PKA{{%LJku{ywRS<|2-Pi#?%L$MwN@$DnQP&JIT`2k=~w9eXaeP}CFU{b z=ZP5ET5Z>Bqk%+J72VZnuJKaIU^{cdpwS2YVs(iVLvg*F-L@~K>)p%O;#I^~HNG-) zPt;1T9L&;%S3X}Uer-R_y5p_Z{q~hgFZz+koO-#B4Km}Mo#FRmo~gy`wjOKhi#~?H z2<-5KcE>~}wCD1(wr+2X+;<1puF-Hhs><%RHLi)@k^^=Y<YZ$HFs^lp^Y^|;zZqpj z1e=X8y3oG6h)Gr$S9<#qR)vxZ=&<K|wnvnzKAPP)yN(Ud*fH0)aqJd|2_8OZWInB} zDfP7qx$d>+(m&lVRd|035*KjwMiDBFHagKCV{t(uX;SOpN;9eeo#lDfZqHp~|IRb9 zh$UT;z$<w3DTex;{Yb(cF@x{VT!;3abFsLS4exQYxCMEm2DZ4&Z*LO<x3-<rUB^Bz zKTpSweO{4A#!st15YDHy48L|qW2Rg-uVh3OGZ%|1&*)7q7PpK9l*0E&*F2-t7sP9M zYud{pZ9m1r@y}P|@{}adt}+)Ue@pJQ(K*Z~?8xv>4oF@^e+`yj%iARHHV@OD;89<z z(w@q=wN_;?lcBy=Z7`=PRE0yD?LP5X3%lnlvEwwCy>l<GK@#oi6Bx|7HeDIZK~>c~ z#(o*7TDwXcH1=SQx*>5*)%Vf@igE4u@;b5@sM~Nct|0NZnu~a2Kh65YwGf)u@IKMz z)>oh6g#@vMeRK7-O6@@;#%p0ic(-m(^ldE9`!V;o)hJre^vxbOo(B<5iL1Cqy6}p( zs~VB^;|VVKJLF1ZbuJky;(w@cJ`%eM`uompwhLE+mfHx}=cPurg-BSOdAbw0$MYmt zaDznQ70>6}Xzd4BUA)5=w0|W}w<Bpx%IBN7;4SuLZxb;<rQO8TWA5Zj{0*?~TMW-M z9oU*y&HkKk`(c#CJN{WRc9jn1ioj`*E3UCHS7OZD?fI*p79ONm5P1e~I$?6*+PX#| zUKVcZtBG$=6;~)C3q63fQP;e7LDx3{y1WEk#+X8v6`{*wvB2e|AnA5@>&p0VWD=TK z@9Gy*tDh<i;qun#g5@2xMKb$GH6~oTv&=1F^J()c6-?(E9hlBWaJymn;C6ckO~k@< z#v8+Q#=>;2!ECj~ZygXL`IJ1*E{sG%{fK7g)Lhy!+b_1s%8_lQV<39cmtpkGVf5G- zK=ckn{Ebjg9G;G`d7=`Bw{?eIs~S}=7+#Cd$qWq~ru)781i|F}uGQCz3g_Ycn1@1; zi}$F}?gq||;%|-{;I+6qf9dz_*O-0cImx-VtgLxfanV%CW9aJbGS-E&M#?h%Ra;pP zxKd&gg<dIab!`Sl1{-^PBv=?p7YOpz*<t%Qog8&s;ZTR@TMPBCMKOD>h3=i_gY4D? zW`-8}MTZV@40}zN<tJo6Z}Tjh3Rn|t@=*_{(S*>RJi4Y}c5Hz67v1I;>X{qnAyEXL zr#S>CwW>)Y83~Jj-!)_H3EFUmd}7EaNBBF@-|CxsgEnj-pA7OT0H2L;$44daWK|8j zrwQ@E)6ocTf=EV(i>MdMuLy96GHyFh8PVvw<awIs=s2lnG7t+)lg_GT)!Vku{i5R~ z+#yjgtN(aF$010x%!?>5UD+E#&Z@0`ru<6$9p(m?qMx@ufy<OwKfW9{cKFovaJcTq z#GS`)^}Z(^SNI^4cToU*yZnq<WV7{UR3>2wAHuQ;?cTq7FfJ%h#z2YkXwh2rpWul& zD^YH`J7^wwf`PJTUPd6;aZ`VEj-)Q{F4kS{>OnfL@*Ja2zqc_Br7v>ycp!>`k<G+a zZqTlMyH#+j`6!qt`c?ov-rl=vs$p2f9W{-{v0U`z<IS`@boE@R5$;)y#(sI1U>ZW@ zU+kN!rr;mu;$I^|vt^C*(r8?dAPtv^RX@i3<Ge^?4U_UdDKkf-ec`dD+I+8fRqa`L zhn8XAs<-k6X{_9KH#r*vTlyC1pWWq}8Ff8Jy5B-KNOCRjxd8_*qmQ_`m2MDK$Tpx& zAGx%KqRhIgrm&^WQ=p*0?koz(M>3tep%xj%2b??3;h#k#F4Ea-kr98>68^BKWIjNf zCZ#%hg?f1sxtaLhwKcb(J&=dpem{-*NRjJ9h20DI=GovU)aj7G1iIq>1wE~~H|Y;b zuyOYM4ynEG&eqWP73S4|x4<O3rDIhUCfDsP7fR?WesXqNo|~Y)6~Wc)#kaFDTE)BO zs9hcOt18J(0HZTCMn97&yn0CKxn~{bsrha#%ESj_co~(sRAF*-e=lmNnJ0h$CF$db z_{cJ&rVn;{mrvWXh<wdB<{x?2`%CC8wCzvwlC4g?52a39y~wD#)vZ^VTICj2Jb8L& z@{r<XjpMI8*OCEihn(HlmCqzz?=kBoccQpiC;|_+V>!Yz?w2TMZL5uD^Hk<$b-fI7 zboDh--8v$8E?$3Du;Uz+LwFBU{rR>yIVK0MG*62>IhGXHK*sv>9u}Vgskgz{(6HIo zMCuScX7KYIRi|VoBJN=!5+=@HDbxZOKTb~4R27s1>Av3$gXkb%lQ>uBj3$;gYxkvY zB6a8J8Fc=eXUZMy`CZPj&!mNv`>#_oUE)pLBj>&8`$5!RT{<cx{BCCQ)2+6_9}zyL zoA+l3_u45r)Kz$ABZ?F_7cTTioao~pIA~^%@W3Bl*EZXXmAk@vD~<lakudM1aGq}T z_N9Ze;*56If+K5zs_?+BnP<x1jon8Nn&>5R^_*)bBpX7|LiODtZadl$0MHxGXN$of zohbHwy3xq2_`%)W4rfEB*|l-zPO(hf5xQ04hPiu+d4ID&jc-y6kEWaHe1629$1H3A z``%@Vu1FH*QB(XhIl84RXvp&1><EWZ{!vl3pv2EC1)bVz8mIAn-cl|1#~TIugUQKF zdO~xU=pmOIIZer4-yUns?FYU?VPii}{k|LZrND{r-Y1#pQK7}BB_G4f6*QlGO67MT z9KMS@L{%*c2`{go3RXuRq9!UlNl&%o@f_8bSad1wAHA<Ms_Hq)=hU-emRpe)UT(DF zT+*do5?{62hX|DPk18sSvh9?oI`^zt<yK^em+S7751;Ol&Yt*+kGUmrUElT5r1{I1 zS@Vy}oP*1HtfAQE1A7U_nRdL^7fp)d{#Ua1qg*?XG1;{T88cnGkg?dc4;kxRJCU)a z!BX*D&QgW7;yM0xa}iD&ecY%pCk+FB6rr&Mp2$%<vK{2yk+aKbXP=d-FlHd5$v_3c z1m?L!b&*bg?6L3pYrLg<KF>qf@?Z69$LyYIl#y7Z!(ax)YF6uqYELei><{Rdl~--_ zpZ$4bLAhyrD)sP0amD@wgD$({*!YM`=Ee>WlhxCs59k|sJ)W@Q>JRR2yQ?F5xLN7b zcHa4Ciu}XETL(mt=MNHBH+^qB%I*(%ll`qm|L3SzgLR7jPs!4YGL4y&GyE^vlULr; zXUses3cs%Hm0xVGaSEk8EXeZ`SH$XgBZSOGa#ms853qB)U0OmW>-;ArsSkXTD7>da zcz?Z9s9k?K@uyYJ!~mmwd1M-Wn2C3L9+e;ML!Rn7mLx_pNtLwrLP~z~P`@zG!jcuw zJ9MhOqv?nj`QHYXtjM3|7M7=vPV*5x4Wy&)qawU6PhP0_TIii@sQ-tL$L`VpKC7Di zQ?^ZusI|){aDSwUGv|=fIZRX+cXpA9?<1h2dwc@R71gXz@<yp?a&1~S*t&sy_StDl z?vC{5Knn%X(n<hy$C6mK7AW4oYCfT0c3k5Z{etNLd(j6BmDa{!-$&zKj>Io4v$}j{ z(WuJgzAX1=zKLV$ukK<hws>C;@{oVcmDP*Z<vR?No3$9Kp6Qn!d}#0`u(+T19X@cD zHj%i<hq#lwH8Qc=sm15A7SqG%xpRBDN|#z4-_1##&c>ARsXr~mHcopVl?b;BCT=># z#ZhtH7E(=Z*}+e@_1GNe8F}oOr9Dg&^LnafFfjT0Sg`H!*YvlB0>`g+zOp_3<}D-# zU)vskr}JtkkU*k{V1DmsC>!R9T?C8P*ivfBGdHd;2k+{uSGrFWAMR!I9O63|a%YQ2 zC6BsyWqZ)vBfj=N9{51&f%(k9CTX!M-ja!;O;MJx7v`6IIiy4qu7@71Tb^UI%S-wA zL@G=AdTR2xc4M5{Y_%xzRz|vMtk3ZJ_;PD32{USL*M=#mCjV;5dR_6)2fSv|Lk4Uy zTudv=2XDw+&<Cb1xDqUv>TC&m*h98oE>TBo^2_|Iv$@h$JVhgU<5l8Ee2v$UCwD1Q zVl}gJN+K_qMXM(%%?=HgJkdG}<;!%13bhNm!8<ES`gw<ri_h6VT;JB8H0)KQ)^$|$ zZrNNoI5>gNT)cpn{5GNYU81gKhn_DM?|AUb1;04a=X&#}BM-I<wHSNYj1O!vP4M(~ z_##rpP+LpefWN^gJ~=$VfIJDtbXN~hh+|GHmX(1=zeYsF$W!1nNT&aN_-q89IG=_r za&T1l@5ARz@YJ}16g+&kfn*!<@cF0Yy&U+&nG`%WwtE4dAYXtFpT($wWIT|Jrv_^H ze(@jhd#vg~!RJ8G1(2&(T_#*R9bJ)U<kGGT;3>814}>f^$G$pl+<){GA41G9Jsw;V zC{kF@Or$|=ZRF)R%_gu!i(xD4Z}zAcQ(oo$Tv8WZurHeZ)Fo#uZ^xsDx9E-W;nP>R zFnQX!W#V5SgB`c!rnKs>@4uG0h^@Ie^<YX%K~vg{<>mYrB}7Jg`z~P*tL9eH*5KKr z+2p_yd66LE!3dvgcj}y)*bDCoTu%&HblAM3pfCL(@ug)ZPpb~C_x3x*%h-I6A*s`y z`t9#CIQAC}5D61$ec7V%wdt%*Z_Q@yldU)jb)B8Cx{nH6Y$wUmFQT$8B%XT5_B^q_ zZo-Vkvrd6cfx5<$gW7+Jo8FzqqP_XT#yN3g!ALTPF@E7lvbuF^870$DgGhM>)<5>) zoNN6j`HXIA#b`WQtzjYbC_(U8QnVTuV(@L8{WXVkBJvmO4GMnKoaoHUWxjFVW9Fp% zMb?#zXasvVhQmhpUzF*$=ok*v3Kp7YMMTeT<(xk?N$Jr}bHahN3Vl34{JG3U**!B^ zqv`dC_VarAB>u6jB>3SIRn%2qZ%UaT{?5Etzu6nHY14Z$v-IJ_9GSF=SGEjRN-XMl z{q>~DQxR2Zt3ObJjq<zmI&Y<3yps`Xlz+eAm>|VJTPkpCgXVBEO@%GRxB6P6{8oE^ z;`(lMGYa#5`uzT!;ae$%BQJ|LLGwv#&KsqF+Nm$N2jllTxu`o)s8}E|N0c~q0eMP4 zUl8$o{mxwOoGNLGMsAg}!_jUi@}StW!KCW5{D>HTTP{752Zr9zr@S#g;DtMHLQ3TF z<?m@WxRht<rAtv7HK*?k?1qH)m*Lh<K0UoLrp$t@OTb%ZaZ%-Vx1ET$UzR<76*u4M zcanN%q={fGqaI0{O+5BplFu(D`C{wZ>yn6R{c~s5=VwyFJT<F=43_-RDo`o1!(@zR zFS;Zs`u&79*H#G?ha&Z%IpEijD2Qn+Z|#r|3m6K;VVw)KxH=yGyzzmgrRWjKuyNjC zcPNi-`Etzrx$JN|Dz5xB56!!JO$$F{algDtn0>KJ+y$$L>0}OHY)|x9+&+=#J8Whf zbU-xG^R*)Udg@yK>B_wb|J#wK=AkGy6$#Xdr?{;A;*rgior=#x;fV}In9IF-ZpJ-6 zs5`@r2-o6eQG&F9wXerrLj$d?nddxDqDK}OCo8EM^3l@AMPIztC{fGtED3Gk4ryCi zbWlk_SGeDQGe5M76grRMAcP&hVxYA#*Ewpp=)7}rGK^e(LW^s3y8XcF>qqX`GJINs zk6`dpTXxr-xcPBw<ldvkDuS8q?5q-rx*$;zk=~^cgl!d&o~a$rG#b0_bkd=jE~1|4 zurbi7P!Lx{VpG0k80|wsk{<56+N_^gDx`0kw-RVWQYn~T{A=n>{iW`j<mpx=)OB>@ z%@R66wm0Fu7S=`h+io-HCr_<H2$3HYI7uxfqwmy32>Fx|`FZsqunr;bF}^V=srHH} zb3JL~&1ZH?l~hRa56_QppC+;IcG`|E`AQWuUL9?fugX>3+E5+S=az7v@&Br$xV$;_ z9jD0=z5RkikK@gTot65^WkuQGsl2bWG?N`!kB_~`YrBe;yYGvubSVWdm+6Utm&=$C z88(9gg6Y=K4DYS6J;&hOoLee)xA709I+mSm^UxmI{_4IZou8?FJ&)YY#_#QCGiIY@ zGL4u?!!5g=O~-@#j2IAd?#P|6#DC07Pm&0f({1EHA~USY`=BbjQ|PXcf=X?B9F$l^ zNM<d(xSNwWd<>kR4(jhPB_Oqt`kPksOSj@WD6)pg_kwf@HzdzOR8Te)h4sA?>al^S zpkh#}?L0xLb^!n(J0)1&OX$I=lCV@$0MdM8f=RDtwK%Gu39n4WzvjyO5M%#MihXqT zUfv=3NnaMvT^=kgyBd#9h_Vz;Ww8rea~Si;x5ykx$0I7&6+0wZeLq&XVEt40qV%cV z&G#qjIj2V$Hl^=(np6Z`I&<T=XPnvjwxcQpJ(GbG*SS(Z*X4--Y-BRo)~<&xIUg3` z-Xq6Ty%7Y>T&GX^-QZ12e*}^vHH0}u;lQST#k6d$TIYff#&$6G_F{J%oyx<@^M`q8 zy5md^(X0giiXWWF{F7<t#-~q56sO2cOCCQKHZOuY&eUl`_B@%k>P*K|rYrqqe9q@A z6>>@BI)aTUo&k0r!q(4KCzRv#$Yw)MDI%p2PU+_e<X_`w6K5mxSKPYC36y99b{wVA zn9<ffN}!qo2#bS5Iwv8Q2r#$+;}VeV0xCRgnE;~|OsY->=13!08Lic3M|*oIO-_}Q z@}99Hzo+h8X>!L{lG7I#*;?}<4?Al<H(OleGIm>Ivc#$I@Puc3A7kq=<m^2yL~{%^ zF(B<nEm!+5vl1c}4n+d}twacW<ug%X6;7sjl;^EL^T(O0-K<MA2$ZJ#yK#+R$&O{J ztbz%v`KjRf^g+dE!E6$GystwmDyCi$(Mmg2YM)C9(ztp;VXGr#jF7KC-qA+$8u-)* zMSotT>6Fi~CV4vXu^Y;l1Nk;6A4?)Tg=X7ZZq~F_$l6VWKN9bvm83FFyW11LxwhL| z4^mKW`gEFK?4W~a(vS9+pU0hj!TIf0z@33TZw1}vF_n04l*&2PlBvOaguy$KO1uP} zc8-NaG%L<IdIXb_+H+PU=c<o8SL^v{xcE^N5;+wU-~W6%xr@p^QqPay#V@po$f=09 zrT0WfE0ukSp5LmApIQ;oRvz*F^wY^1tm99!33OfjcsB^C&briPqgMZ@NRes%vBN2F zusB~)^l_K)Wh>CH?y6{#ah}Pn%p8$3+OuA}9FxgwGG#OqzSz|7p<i~xXiq}AN`1uK zI5(ptq)BXk!8T<h%)=x8v)9|iu?v^mySs-KX5u$_#eONaoXy|S<(a!YS2ZR%V9MR4 za`f(VFN=#$#sl}{v5A1NP2?MIxt$LINCoB^B6Q2$5_jz&#xy%@3YDPXsJ2#y`*+s% zv30NDd%w)MiL{U2$zIufE|`(_wmM(N78P?C+h=m-+t$QA-C=fyc+pc-FIZT7GqCro zXC}%i&I?RRRWP3y@V9yPrP6D0$}QqcrK<gC=xYWCmNog`q2uM4JI1U%4eNwD?os0& zG>E*o%!}6yb=!GgDw}#6)@dRC6yyg2zMG7Gci&qyZoYX%Vy;xL$wZOmxM-j}zQx4u zw@;EF?k6j@W^6IU|8m2t($tjZfnWEd4lt)9t3q4w8(H>J9|qrSo_y3kqPZ(SVVRs} z`~Bi}$jsaQOTqO?U7B(&yqZfH=h9;>Dl_cYRdxyxezlthilmdF9pUd!k|2#j->rr+ zZ0pc?ufOp*ks@H+rzyAflXpz#UCe4|0F>7S^0^C$yqPxP&oK7o;<k}5Iw9zxLhn9Z z2KAuBI@v3yocDb~bF-v|JW~71RvO+oaaQJhyy7s8>ClQ6??GE%RAU(~k-D_R>-+@M zhp>v*ECI_^<Q;eTis#-V^U%I9uaT*sy0U!t8aeSqyO&~$AI6OD#2#f1`ae^+NXN_Q zfBB7`M6YQszB+o5HU3BAyQ*C>4;Sgj+@Fn(wAyomh<kE;-<gbTu<jnb-Y)N@Zyvlc zVJ~&I_0&cNM`s?+YIDke1<mNbUzeos!C!KCVA9-+T(gn-F2$U^k3M*N5Z0hMzp?ET zA)G7cb(Jpcp-<qUO&p4EVBaR*TX6Qp3IqMzgC(Z8v<%Z(_AqW18-cA@o`B;Yy%S9= z7h6p*xWUCnRLJwls_T}jJj`^_z0(po0w+ZAw=<^Bj0$_wn+Ys(c52l%arU3CZpnQu z{zQ9ap*2Ey`f^sE@p<Io;3G`@nOhmxl&YLtGc0U4YOb{^u*~MF3(+zZp>GM%>Q|zR z1j|gYy9rhoQj)60S{1gssncVFefGckEyNOTYAk(gH>@vm8e}4F&)Rx>mM+EBM^yfX zQ;Wc3<zSh;UUNEZvB8tT7xQm1Ew5?3H!2VbP5f^jm7PUtj`jXH%#8B>G$x1pM~Kx# z$Az4=Pk7{EcQOf&20za6xI=FG6xE^t0>@<F7yNl+YXXCOjc6P+IG(|_?f0Jwtui5v ziZ%O&Z9bQD>5OL$<)T*YQ~Pc90-yI{A#R@{!$p2w!PYdpXt~$9u$S3STtNQ+9<Ad2 zoyMxE)D@W(_Em;0!$$@s#Yc37DS9bc<~!?g38|?r;ci@gmUEvwT01W#YSgDHa3nar zeaG|tyriPV%Co{3soI%?8n+ijLfVZoT{_$3O04yfS8TF})Wyh0zm67>P4;}1KA~*9 zB78VypcXCddu7+heJQtj{g2oH<)YQ`fYM_9vp3H3uk<3n{CPbE<S*_#qJ8eceB)d0 z2gaTTn^XNY6_2${zy(1MycFnVym7uQ84s=#Ed9V00@M45H$!euujZ{d_D^eV$P7NI z2s{kZ9477c&<UA8k|T*KHgI0Ctr0)0yPsBxQ7Tzh>3U~>g>k2!j0N>+3xH-1TAx`u zEl}hQ#r%ps_}Y_PhumRw0yiEh1$)j>JqU>so_vSDx0)OU?=HTC_aa4t<+s|b*`{{g z#?C&b37SusfVU+_!Cgoj%X){21Q9454ev#Y0%-{nar)Vs@`c}F)xRgE594N*i8*|w zKAccUNR*C=ac&|Iv#8;lGZXAgc9y=hoFaWRr!vvie4aGy&nNqZwPEUe?Qg%csxh!F zs7$=<3h2jvvd`!W5JL{mA%A+FveA2tDmS~|IyH6|jtX$R1IJrnWJ{Zox^M!W1ILfZ zxT;nRP75gMf@2>XJHe=Ab3G#D`>-_}t>6ea%=@f`9~u-USc|}z0O<rs8$nv|UYmK! zpBy+29DH9`oXcf@TK{IfzSNk(@@v5r-<<%CK~0VPTo>nK=TN7w2rR`+s5acl_AS{x zZ8S>!<<7Z*ye%OO6N9<5fM69<vA^xODlSY?@xF4SR<EBy{%ES#v+6Uh8^;Cn>M^b( z4Lx@6)<evh6l|#E4Nxaz3;-x|2uP%z$4ja!lMr{72)l@I0e}=WL^MJO8iM9b`Eb*V z1^~r;;4uLB`fmd#r-!r<&O!xnRZ_5*7Hk^C45kQ2V4eOqy<pFtFaVoT0+6)-09s#a zT$vOH6X<|L8UQl%0U*r8A=VY?;*nrb-wkxxL9iLpEy=AO;&R#{una*LT{fr>0!S_| z2zm<wcKi=OtI^$hB>;Kz5(Z~CK$O&f0j)*`^$-~5MFb=uzW09sTFbu$YXYR%E`--= zLg46s0j*9Jv`G*-bsc!x4#AoKZP>JBN^1fEegQzj1a|8A35B6%1f{t3kk$mYu6YC{ zV4t4<qFFzi{ZBY6+qyFphOp(6f8kAIe$EhDCI;M=f_;_#B7*;I)&zlQXgIAXSXnf5 zhzgKv5DAOmnjjL?72@R}%F`1pLp4kw93=n6NfCqjQ80z%AqgANK$sf}@do0gkj^cC zg4y9L<beEdr>4IsPYBp<2Wxr20@k4&%zXftM1bfoc-03sZi@lA;scWnhO!36%;q0x z7Xxtm0A>mrtn~o|J)$8O0X;%Ank#U?2l|4AB*bX_pGHj({-p$b@rAzdKoZVk0m*;c zTTuXCYGA~8Aqlc{ml={I|6p=quQLqvg&4Snf*!(tHmJXt<#TxGiwOj5!U_M|r|ECw zAvg;HY5Wr$1Kkn>%8nq3;LQ-XR_dQO17~)Ct0NGc{a++2Scy_NaRQhs2WTv%0BVfD z=T%aObCl_279)jN%2G0LOd84pz+Wlss$={wcojlhk@j$Zf+3W01m+nN{ESo%q_tO^ z8v#&23>83}CafjT!Dlw2e?XNVK|piiW@v&WL_vRr<X?lVK~Fve{tB|k%^B$Lhnrs= zl1NuU*!~LX%J={2)$~7`^8H2aMnW052}l9?-?n^zWz<0k|JTNRe`WZ92mfnpzP~d6 zZPtW{|F=0G1m#x1TJ*oQ=liP%c~=2B`G0HB_t$siZ3U$N)BpCe!r!I%Z?~rZ`MLtc zz*7JJ>-+!Ppbs`H`@d9pq^|y3;6a{_mC}NhE`=KvmShMc|F>Ba1abaf%DmL%zs;Kd z_L_pt!eD9&0sj*rnjfYLI|B5ECB+^re+2xxQU;_2NPDkSAmfz&*;g_!w1Xfz>@YeG zkc0_9{!h8kg!>A9_x<fzcnT={KgAwa!3_AVLJCw{!0#I9lLh1=(0Eu<VA)TCv$%s< zlHe>PfHXmBBQzPdkwcoXA$g2ou90^Z!1{Z^25>R|qDA5R9lXHlYC7;`oe4>}^N>cD zu<4fxoaMh|A3>_Z3atK*TXQfP5G}t6vb7qP7(+-Rt-xS~TLH#lAX+{Fk`OpM3P=Hh zztzhNLZz|LX#^^bbnSzM4DM^{e~U7Ns0RVnKJe`o_%-hX-!N(T2a}6}v%CeWQE(QB zU`9a@cmYX$)tl#7Psg3e>yvdW^^pVb?5{-)nq6cG=Se*)Og7$>Z?~>2%%r?a+|Ai| z%_OvH*3r2CgI(@kWxt5UfK9cQmg7hyMf;-T?JN2PXwTM9QQ`YKdK?4ecQezsQ8?S| z4$u(GpjFUwouNu#?AvwvsHzP$rprOZcV=R$a_`d?(0w8GT+kPO*LGm^b?e5OT1-Y( zY1N-hl7I-KOQk<%Ois3)&ao3<DY+q(^;upU_t=DYV7Zq#)#SdE^VG;=n;|jgLIi2> z-Gl*on3*G1ebINS`vT>>@PkzS$Friw!`17STr-+4<Qw0erod&tcuTTOVk~fx#>-~* zH-3t!jEJ|0;qEMh?`67^IHCAZt~XS=UXOGa+^*4`z{$rtO7m{t8eE*U=c3I&vG#je z3XR-+=NCuEO)r?vxi@oZmL<&D@=3|`A8v+&8}(x%;6}YCxE1eS<Cjq_vprm^o<S!j z0nxxe4|KkNGaWEl^GcoKT{;u8hrAzbX~T(Yx}5DM-rr-S>eNm)YZ*MY*THhK^E!KM z>iPXyb8q45F7M5d>r5$V84)G<tyT*}q<U|}kcqur%;Y_gQF9blz<50h51fzB-R>;? z(<%l<`VcK*7^nqD6sNG3`j}1Hr<p~4?Esf8`<Xw%Db9I?8UNAr$B|6YD!Xrvr;pta z&S9|;miKH_d+|S>-H{iM^wxTqwscL*33;qj<HXTAGTVE7)dSmdF#*9Q^m-%?2>J;7 z$rX-k?lHF7a7Rc!9bMp^yBdh#M?01Cw^huAX`Vb`j#;9MY^f=y!q55NrWd$wmXT8( z5i*B>sVPa#K7MvIiBd8g?FM%Sw#x9KG26ixeYf-39IE+ukWmp|Y__zg%36b|%2Hyf zK~=&L(lr}P>gZ~R@y#_hiMY@GIcNGacHa1_qxSIr!`LKL^lN9X$TB<o#QZ~}yb0tV zxK%EOq~<o;mYR$SwYzb!wuE;^(>vjf)Rso7mSZRXeGI6aC9}M7VT&EPdmDcg@T{N& z|N0uEIL`fA{T%WJ>d&o_*Hl;ScM_RzgJ*vCmB3Bj%fbmX)iVoa8sHwVbKo;hj~qI1 z`<ValQ@|X^2KR(>B*AT8=~qU(YA6<_E_xtz8EE(lZX_=~f_INSzzyPlGH?v(R;<vz zEd-zTi9i}WIJ6@GT8P7pc8#{~^8xxfyvGgSPJQiFdWLsnY2h&G=$&8uo4k89w#?)b z(l}lLUh1Bph1s*^lbJL|d0j7aZ?LpQ>3f}?EX}&DHPeWSkg*sFl9?>1v^*Diy)PDZ zOQeuX2Vd?KTym}6*j*PvpiGs-B{tOyvZiNTbNCRn^kt;e*xgw=ig!7x|97rb>hKvf zmAL8dpZha%?l*4nOhua3tMVLfhtwA4b&I_f3Rt3B=%W^!LVEqtY83mNOWSu&mhbC9 z-et$#yT|C&#M0N-Dm$GQekPC#p?eYf4~ma+rin#y{_(k|cXRz7%#q>0v6WXTg+DOG zi}BK}x47tp$bN84H9qiOyX{)diCoz(WT9z-N}cRXkulNkGi}4qp2A3Qtt$$zbuWOc z;lUnW7R*z33!&%Y3Tv;ffotGd$iKlEPO<jH3S2g4Qv$?J5jeB>QtjZ(zIh2|JJjT< z_~B;aMTP9yWw+iBUlxz)823pt#m0p{?7!Ji5UTZMm2t&NZgLmBgp9}0bI8bOw`L`0 z=7p*2_OI`$PR$^Z9HdyCwyJB}@t5?xm)B)%&!9=+@{NII%Bn7B$G^hkUS53F_+5lo z!Z6a>?JvDd;)*Ra)%2{Bmg2knNTAe0uI^i^%UXeo#Ui=q(gw+x4N2g#Zr)1yN3qq7 zwL+tiH7&7Ybmx7&g<m^WNtyjF^;gGq+pkbC$XpHhZDlR-TDkSl@=YT79nm_u`&$k1 z<L6dMb_DC>MmUz2E(I+ykWdb`A0z8zrw8wXf2$#<N<m#XV_%uKJFpjOqyI75^;miU z(n{i}pFE{9OW!-C^(TS^h~AL&;g@Vg9yV#CYLSsprUMzjeOPu-F*_&z8=;lQv87ys zr&s{z%5k^*Zx|^TrTA4|k;)-G$bZNI5jUD0MC9D9b!6n|@kB)En1Bd5ZG@WMuo~N> zL!<G|n=rPRWm|!>+occLJsU4XsO=QRymG&vpH%f}#-WkPv$3>+8#k<`v0XY%;Mw@1 zftzed?E;0W&zvN;)4)j0Q*PX;?Dsho;u@4Yd>I=hQUyL61I-x`?(w@P6oqeNUT^f@ z?0(>T6(@JI$vMDDE+8<Qz0?a=hMKuNU1ivLLU>==eZk3~GZWp5>5WS#*5CeR#b#GP zb-#Bh-MzHidHm~`Nb3*V&S#k(ox;?vfye<v<baTRd|ktL>7K%~Zlyu7qQul#`|ii% z6KoHLD5XX?)#IN9Nj4XrmB_P?xMoYrw%VEATKhx#7mzLh2R*Z_g#Oq^2-yZcJ+rzF z57vQ$JaIr37f_Y>1gO%v75|Zw0(%E?9uH9-@vKSJ#upEXKB1@kKIVBLi&Ww+Ye&_> zSbU%E6K+9=E1bhGI=ZS1Ef@T6mowO2;v4nssKO?D=H{k#5RM+W_%jcCr0tR{RkqI@ z)!>(SIX}$FNaWfrC`-^$V~K!KbE?3;kH8uRmH3bE13bS0Iz}0=zW{Rc6XHyQ0PyQk z*W-&1Ciq_itDl0i8Q|FgKujN!QH5uR^6Yb?fdmtf$c+M|1SDesNopnZF5e#g4$v2% z4izv}0nkwbXU#-(<l8f-0d_oKs{sqDj6i@S0cmvEHqhnDh-4aIs{wWeB$WW^2}#W% z$`ylBEYN}T13IXIdAQe*gxp}bUhL33L0|_vG>;sRF~B6;CqyFOKIsjx)C#bZA^~Xt z$+v)Xw-RzPo2WhqViVehtYS46Ae$k{0m#g*$MY@Uz14wb{lGGBc|eLm5}IC;);gYN z`N1u1SQ4Jb3r_O{BtJCB8%&3KE&BXNiUSc=9)5x*nL`qqqyx#f|HwFkPc?9n)F27P zZ6F!>kBlMw1fB2%<Pi^;mKscpzi3MubaU$1>Irbb7o6;x2I7_pVt;4=>^J!Yjjlb^ z1)nf*QTTucxaPjlaB3?d@heO`_D6831>h_&FW4ao0`Hn#c$Vc%n4triJ5aF=)@WE2 zsN?`LhG3x#ulzN~E(F+M3*`8~xh2TCZlx>Wvn`UGz+yWxaF!(0q5`zE0e@UTh)R&8 zLS~{b9R(+>@FTX`AqM`j_k_}LCC0iQ+us$|7~g>><O21FP#Wew0gxtBjjwyqDgI1S z8K|!V>ZzdgE$|s`iJ*T)AZr4MgoCwkMbgQwlo@1};rV|ed=cnW0$x_YT^A4XzXI;M zc$I%<QUsd6K{a5xTq}VZdSKV+{UOT1&|g&=xo;T@;T|*u&Qu_K5Rm{GmH^YSuj}z| zW8u|T6=C4OFqBDvB%Jv<Ad#S*_v|bcTGaqEts*gJTjhu3WkA|NlHWFP`Q}tx>06+C z6ehAXBw_ffAc^cj#HIuXEL>j{a1aaA0mhFS=E6BEp@#xYuD_)30^j6;FVc4a`2&)O zTS!s;)cU&0a^x&l8K{VWS@;rI3d8033Xn4Y>d`~axDWT3Dv&+|){+8Uy=i~7l)$92 z1_x~b|0D>4Buw6W|0p>LR|aeYJ7Cs*=pIre+)BIuF{K|EF9zaa2iLX~9t2)z$B4@B zq&QfNoCPzBf>o5%0bWc&hl~Mf@XsoE`h(MWfB~LwAPH8%6Z)3%?<!aUJ&7PoSE0(p zOMpaFAY8tGR$&en%N(F$4%jYf2&TCXWd#0_VSuLSg98-6&?=a>p^#J<qNED_71FPA zA1<*J2+ka|B^KzU0H#+VYk>%nj|2d<#z@|>twuqT8<4$_<oqX_i{K|%NVWkv2J2!N zB#}gfH3jZ#G%N#Wz=UX+CNTL+{>k_X#4xFmVQ3><gfyrDM!@KwEjk8^jWj#}3#K<D z;YKC?M@9yzV{EJP;D8LwXHG~$XU+eWfgz>`2l!zzQ~^f)pOO#j*Z-~R|FJiL_4VdI zHO>T<*hk=G6IhXTAqiDF|09EeXQ+b%7$|NFNhogguMFJcWgz}*U@36V0Lc!L1sC~G z1!IQ?U^PsF2jFrgK`v4SV3jk02VfyEfd}B$$3QN!^`Q&~9)LU47n-I7?9m4qgM0gr z3@=y(Be>S>AV_%lrIriH=x|T3em$}z+?4Heol@yIho!C8v^zJ=Jy`q6xyd{B?}jyW zZw<HX_MVsu8OzmX#56G-Reay&usL8qF=c2tb##Z(<Vo0(+5@i;5j;9z-8O6esFZol z_Cd{&{sXCRm|R^(^ynIy=c!+YSX`ZPz<S@Ik3H|-Kat<vmV8j9MSuScjPB~S;SR^? z{vKowSg-dn`Fb^RU~TyFE1n&k3u3`xb=7w_vzoPJz?Lj$HtG59gHZwVxau)#E@O>O z{i4Tt>w{A<>X5Au*wT=#={jXtl83?G2CS;nK$0CwLPN1SfPDzqhXjC)>KNUfJhOw7 z1M~XifqB_ez`V^+TNz|P`+_b3`zFq=b5d2<^-SvrW=!IQ8*r_rM#6+N<W>_ypHKQ1 z?a`iX4Vuz-itGC(+<TBWJG)eL)}vNG*XA43=&ge$_K3X$MXr?i>q=p_BI2C4+Dh!C z*CIT1w29+V>5gR9m;7=iKHW^M4nEUo%b_a$(}(ln`^3rpIMZ7t7wo3*nT0&Cp0MV~ z|6cT4)cIZ!TewwUi+0YVVhWN>8?)*rtq=2yme^vu=WHj=Zuk8TGYWmedZLfkd!)Xq zR{83Eo0B6wRkhYSHhH9Wvtt((pMR{UzRTHA+OOpM{7f-vxN~kqWa(LQ+5n-pDw!_N zDL3r$y;yN6T0>`l&ye2q9Qu&>is-xV#M2Eni|rla1BFrFaOl`Xx9@5?4Co5`KD96P zs|!I>T9(pm^WRAAW~|RSTtId9%1$ExMl17K{osCI4k_K-+;AzA@`p%tJZ-7hO1Eah zV%fFR14Y;Ett<6%g&$9Q1(q6lRPYpEf3zYfkhROxyPCXp*h+apLz>>=W5`?Wf;xRP zBjK=gjyD(M39EF?c-mJUmG!jto>(1=yE?A+DCcw$=Z053JC3Uf9=|fi?pVhZMpEc0 z6?v=@pGZrO+$A*ZU!FEDE&2H;VUz;hYqrb38o*G*Se@xPk!?tcX|4J*dFF?!zTQgx zGrRnJYo6~|Exi?m-NdG^3CA&N21WD}p1heZ9A4~?dNn>gTFJ~B3i)&RPFIq)x&}y7 zL+NKg8l{X#XY7&z@dFw}`~~}0ApHYK`;-Fdh~LLejQUsJ$L#Z|24JnJa)n#`?Dj9q z2iEktM0b=paLkK+pSVC|l65`>=fuaPCT#h_IXA<6N>kyL<(0DWTAjrOCH&jZv=P0+ zhs+gvOWSucUBX_R6zUBw!YHZk4D<G{rJnGv3{4dYIl3n)lh#2tk~5{mupe9!&W&#y zS~EZ4U2D6)qSLdM?m>Ke|62+xgHyTQA5Ocu;wFFgrxl4f3ZE9+tuq&Q&zCHe<}A8J zzas1Mh<aE<l5K1>KV_Ry9Cw4=z9O8A!n@wKMYIUfpN=W26)H$)a#pcDKmH{)lQoxj zm$t;wuyj0VBxg>sE}|@KjDrL<Ey|h{C?)3Di@vZi-O`(=MB&@Aa*ToZK(1uTqw<f^ z=r4zE3f~S}4`XU6HAh|KAD(Ae<+xOS99J4?Lkm&({9f_!xet|v3jpJGuZ+9j<IGoc zktB4Bn|)lPd1Y(8SZ7!zKxW*96;;-uH1g|7#;JY3;Vs;q>A~1|Ysr)DPiG%DXbJ%9 zhR-4vxk#f5#v4Wy(H5r<UJpARJQ**GjdzhGbF)VDwevxJ?oeNn(LAS}dGU4RgXp0Q zyZht5NqX5Y&9|367E`>=cx>n5WmgwbnlQ|N1u<=D?<NUqR@9>cL?C@CMz+0mWNSFB z<E@aGr%djjh7k?>O~licavy_<i1M(hGpF#$!&?VWrw!ubUm6BWsXG=->7P0X9^PB| zyCx>Of6VPtOEQrl*h=`E{3FE|q~s@Bi|!}V>={|va}Gewy7*UA{~;UASrR*i|2!<d z`s+KpQ+y)kg_Mq?{?pLc8P=;VhqFgysdg4+hIn?$3L^~q(D&-9Xo}&%k?DYKxexQ7 z53NS+e1wUGWy71pU~RZul_sdmjrwEGK#gsGHIhi#*z8^Ucv5R*wDzs2zNC*Pa2&JB z2OanR>v#=xocdgQ@BQuJ=f8DC^gZ!f6A@(z<81t3z4ti-P>Z7Bo`s0X>2nc;?LGH( zN2Y4&Mf4@{3Q%7(Sds06Mt^ok<|=93Kk|0n>lu-AR(v&8rB>ZpL8FOsiRonnYZesV zl`B0v_dimX8Z{&dY|)ko4pYEY@}c!`nN1lo_D}p!Hn1K;;ZwgdvZGo@U1rp7!nZ3} z(m8AclhT*f-EKBzbWAoe4$&v}SDzl#hvA3%B>(DTaC;p6Lr8Whw;1b}@i@oD%N;KH z7|6z@Ju>JyE)ROne!jgG@x0;pTOq`A73jGPAN1S;dXB4vo|_P~cOjm~)OHIap6f!- ztNEcmvA_C?p*{>@d-qc}&vlRC70+?2xcEc7$;XJ2gdv}cTZAP`yAc60lcJ9ewdaZL zy4R9kTUgkAwdF=OS!;OiWti;kFxijNV6xYofypiglil#7l!#*i#s;YwNEkh#+ea6n z+ey%EJZ}|-=j46nh!1DUKkbhelwmrVy00$Ys9TJCuU?XEdz8@W@}y;Yc;~&d_tzD? z)$pCnOkqlsD7KxY>O}=ZQF3EVoasRSK*PgtZqjLr3e;lw^55N}G{W&7D^>?JPu~01 zn^x+*_x)*l&)`Ad%eqqrrh#tn>f8*oV$T~3GTO9KT5*$_Bkc`unq5_BEp$2kt9hg4 zwV5De%45}f)8IHOPEz@vTVJldXOZgGqYV5U>jLE%o}af)mnUr*G%q*u^=x0i^q#i$ zDn8Ei4)wyb`aPqIjM*v&Dc3&j*`$7}XW{LCn{l31=zDaK1V++zrr=9nb?XB{zI6f0 z?7kSwi<XMExY6ruA*96R0;W9~JCXd`9vzg7)n~pIyrm@9j!Sr?99+K4A%El4BmEea z%Ip0qVhF@bqrCYwjdPcL;ET_16BoF6u=ppldr1d>)LuR`WeUkrO8KY|Cvb*xA?)k1 zdvB|&F+}WL6v;T|>hVWa6&U7Z++{_~Y<}Ns8G=K7!2_4WHHr>jZcmhZl#MdVZ<Tit zx3G%UANDTM%gHS{Bt5rf(t#(Y-52<BBzK%yw(ICQt*GBPq3Q7(M<fvhk6u#yt!6oz zpb4XiolH88<O$0W!3P9~F@;C=et3!|-(#{^l2H_$>Bie2XOSh_nlOU>q@Wp*rO~4k zet6!qzLJMwL<0n>Cu;48Uy%+FSX0U}+nf90GX)7okJM=%quvDhsUtH^3z`bOIC;*4 zhVgkGi9!UCIc>4u^aE3t@1(D#s9ur1Kd!<?dVyfeWaDDqb=jkv)I^2^PY62AYoiI6 zWjAQ@PRkx~M-b2xKEA<R-DFWkB2EqhGGI|vQ6nOY)qPc+a9B(@U-NRJWa+3FiAz4V zdt;a?8K=7AQ;r+vyybgrriK{tc%_E(#u4{+4LvgN)=?W0kUs=@6fhJOL@hqYs0k_v zK^!WOXxu(}OSms!btFx&FL3cl8i~w2zKY-iiur(YVBhzS_wO-+L(F(kt!VNQ4zav( zcX$D9;&(Fn6Tz}*tq)>%7@;NcDs_ji2x&6Tm*D%d(guzFIp8oU%Zofh1!^KP&L@PO zskP^i8_I56wZ8~s!9seK_Kg%J3sr}2V`?oe!6Ae6aTan!Jc3Y(@CiYqeC;Veyc!!} zIO%(G+g%+~=Hyy)0@2z)6lpObb1hjOVb2}E1mXyi0U~SFT006$0z=tI63df%L{A8A zo=Fz-V}Ed&%j4YfbI8n`&%*Oa9#WI&9*>vZxvLeHN3K9kDuNiZWl3}F{pk=k%5>zc zPIEcFTGH3jr(O^g`z=Q%Q<+i(vmO7A__rl)N^mPwboctVlX<4cl8=}Vk_Sk)e3@jA zqF&V`5UKwY3)xeq#GeHTY-<^1k9ryXR()9>@N>Bze;=Ml$Q41jjjX}exvRtv5UnIk zG%zbJ{EY<J)zcsnNCc5cT=Bz)vK`YP7$8V#@S`V0GV1&|7mS1kj70no$6X0gzFHlE z@|p=WmC4#_(uof5)2V2Snr?#qT(jMkeP0R0vgO0VgEoTs>NizMtN7UC-RyFENdX<c za=4_h4quhSr00lLhrayZNok}AJ_L36)^XN8@f(alb_^A=6Apc4WU<`azJy3VuAmr_ zkNh`kXp(XMt2M{L4iO7Lwj4j{yf53>sp_%A8YC#Hzc3oUF-RP_NlZ%!XOSgD(&3~y zd-4&oRj^0bUJOP|pEo6hX>lJ336d5R$ljRO9zXGd@0>ehDcu8NWj3-LBDh6zNat&& zwa1xYFmgy>FqQ~lFg#!|><B<w#Q*3w^b;d;^8JBfsU91xL3RsLG}?SZ|BZyuKasE_ z{3jCGFcJca^EJtbX!-e?PxzQ~PY6_wFo;RWu5d-l$e-hRr%ZP^-3UtQ5s&dTNKJaP zU&EYy^k%1q3`T+hMnWJJXDBW5;8B00k<RE^=g8sW>oZ~d9QW>)EEE+j;DqABvqx5% zeM`nfdbj*$(+@@JKC^WZS09Nww9!&ORQTZB&lfS$;jnZ^0aF<4cIBG^2kqz<H_>I= zA*_c){VU~3o>%E9g|pK0j}!ajuv#8tm+0p-FV{Rg=>AffXyqRGCBQ4{{0|cw!*?t3 zKEKf~D7(AMubtAobwiD~{7l}$(vO@+O8yh~=9ZO4n)pNNTa`ZtB%&7eJR-l4eMn_@ z%$_>waq(e~WCkkd>qziz-y6DRw1dq(QOuF}Pbp71Q<84$+bQgKpIw-{KUHy1oGGf3 zMs26je-MZIyvLEw7WPYhl)*t<@szs+j|XcQw}!KKr>XN@HI_{&g)gYo%klHrg0K08 z1_envQAQ72eT;QoY;1NYzUGH!4+?y3;{W!z=F{bhFcI7%-NCfJW&JykN{SxWe7#(e zAc7Oo9bCyC%*||CHEs2g*L6{{*@^#}FKJMKxg9k+7<cHRxWn9BZX&brg{1z;=iBh> zwxx%I@W)kK3wihrn;7RIoiG(?c{LeFr0Ctve%gJA)c<jJWwgwv8qQ#OmKn_H`twu= zYvhwOb41OjC*2Hl60jr=!9#npG(n#es3G~Ak7donea;+tO6MX&TYuhqP6v`Y!Ec|t z$}j*11z=R40%RuT2X<=I!<)UdY?J;j31@Od!xj)YFjIT-6V1Z%JKP%0-+QaiG)>!x zG%DQmO1%)vc!2KZqMtfQh<iAB`6t%;O^D5@BQ;H#qUn{Ywd&by<;!Izj`?4DGghC7 z8&MUK9=xWtpS@({LcVNr>%MiC*eh8xo<}3;m$nPl@u|P!jS@NJGm8R`K3}NhViKJ4 znPkJA5ZU_~CD(0Y9(JUnjm`M=qZhfR;ypbhrv5a0FSBPlhO|(D!@v(!adW)&<G!yp zy`AA<&DX|~Og!e4GbU$WpSKC2MXWbizaC*J{na$@?oRAEV=|66#go4}TPJIu&h}?! z$>)$=)jy;=9o79@GhVC6#<EURAkDubEun1Dqeb}D`<00bE{6}f5xpH50lqV+fca+3 zp5}QCEWXkAgo8B}jn@h8E-l*A{Fay><-U<*R`u-8Y^(3fZ9Q#+%vbM1-ZIz~9*hX- zTuO-7Nhxv|p38ZfpdNL)?9Ag)&!&47$e)m=mqru?t7?z9a{>%pzvPSN1kifwSt$<4 zP|RK#$-?{H8F)RLr<GTJVK!5=h&0KBg6xLJB&x{8Rkt!Rg~s{yWt$~qChdroYh909 zFb9+P3a@vnOHnSOTJ*0Lmi#|>zS|v6XAkYFF!Xe#50P=P7mFNNmne@{UiMDdNSL>^ zVv($fSAJ#AfE;yLUbN#73?IEl$L_r`O^gZ6I+x>cVW=auu|r`=PRN>B^*;UD?Od`} zzs{})-zq4{1y`jXDhGSaUy{GkqayJ4#}gX-c-sFBemv=eA5T6DTp1X*l_GOyjU<JZ zoy;(;r^RS%Ib3&|Mq^Fu6*A=VxSQQtFf7q2X4^HYHlDbSK%e~HjO^78UH=r8<D_M( z9TH^zbvbmW=47ubtW?V~e!Yc^H19G;8IJpZw8oP7o4z0Nes+#A$HnB_am9x;l{Jg; zx;)Dx(l<M#SG12v`Ia-qC@Z{dnmM}-Wm0!i_goKmRNcSM;H`M)g$w#RtT$csE1z(3 z*VPwiY0=&K@c7kjWYm47M|bN!cJQvsRfm(7Z(og<{2G%S@5SP1#p;=J8x(9NKMqav zIhZT9RkWs`C1kH;tg1e29!?`7O&fLSC$6@ftaKRNEVXOJi_+JhcljphFJ2!mrbiPr zz}oPow$WQr+!*Xua({7!l_&hc@;z;57Q`)99qbpr*U>A|#l$mf9{VbPZ==}qmY8}g zy54LkkgcHPPK?H#UwVsOAvfM57!^h=^4?p>)NUU#KHn2PyP)ISeMUXukKj6s?4<8u z(E^&|k8YftZ}zC2<;fvYPD`KGrZQZTvC@YeVQJpU%&s3kqx!Zrry7>5*eS&ldW5C< zF$eQrq0|g9wmhWr%O1m_bu^xnRAyJ}!^={yy^m&B78q6mKhqO_B0t^-ZvxWDC}4d< zsvw?`Qd_73krxp%0Bq@Zb<^Xq(ojwfFsdNQ!#bOFUek_w)OE#evbs-#F(zg=dW9G9 zztsl`#muq*2nFE@ho@KDo`Kk~Z~;agBqz_bdGM|CN!|gG`*92@dDrg)lJnH$^mqa% zD16sd0pk)BPk1IYo0X)oB95aX;_1BAqR;-SclS+u<(!ZA{QFlsSlsSZTeiM*yftdY zIo>BV0Gn+Pzmq2B?b6vOf;uU2;GD9MuEb#A>K4&zS$^bbuBwXliWKM0?r#ZZv)}s~ zeE|uPdK40z>S^SHW#^M0av8&t1wx?14K8bnKuAP#zF*C?CIQaJUIyH3z&-!y>UFnt z4G@<+@Hr3+k(BRqjdS7$>k8g@oN4>4R-Iy0A~||xV{3O!$xFBHn3{4@PB;1kU;M1c zMQ!5rrH}PUpATy8U&6>|2Yty^WEAo&N7&cKJ^JNCbnArfQx{cVFU~s8xiwdnI4^43 zBle|(b|F13((86@u>J*ov)Q=-l1D;O0rdIe{G12<uIXM5le6fw?~)=x{fi%iSEBjV z@!6W0eO44~No$lT>;)SMa^kBmyew!`B?U2OU({(dxssmGdb^n*x4ZBTVM|DeAZ%}q zX4k;&!=6VObCN&y*%tGKHWD6qW1I$K_uBHR^~;yDye#fw*o|CVs)e%FLii<lF#iu< zUmeh8(=19UVE}?6At@mUh)78)sUS!zsi1&#cOwGQN=OSzmvn<jDH0;x(jX<$eRo&i z@7#0ld(S@)^UTi9?#}M)!*6G2hP-e4R&+w#Y7j?X(E9I$uHINK!Ey4@9C91*G8${; zUd!$LfScp9gOq|v^N6eAMxlY$o7JGPzMw!r&=PB9Rm;7n!Od}}lfPwgzv2*6c``Go zIQnvqpm6Z>;t!)HUKAGJ54Nv=X?{Jaq|m?NF|c7>P}=2K&ibThG41@E(k8!I${B-e zT4rv88iPWc*P43ME-<bPwW)`Gue{6Gp!Vu3j*J|Gu^XB0AeNU#fKeqelTMqueMQ_g zoh|p``@$iI?zX<p7po5E?{2d2?sutTUEXlFEfLEdSds33@=?yka5zhKHmy;qeN(5` z%1la3==ae(GWi1wV&kF7$Z7SJ-*r!Z-n3h2Y>HUyO(ltbXbv3nwLB{nwQpKIn`Ht8 zl5vvc3+c9tA55Jr`K=fW7jkV^RF2xS$o==~Bzdq4$VReay#~lGWE;?K`46jb6YV#> zvVCFWXLdXtE3Kw`hsrdzqwv)kWA2hL^JtSZ<-!b)HXQttsI~0eS!tJjdKv`-Z92*s z7^Vi?LMVPs7HyO8sZsFJEh-kIlC$fMK3*xx<vrBRni+e>ckNuu<aBdP;toj_wpDn# zNptAnxN3R#p~~$u4i!mvKmRb_Rt^|FrUuVTM>^oCeINp!f)}@|lJ1UlJAOP=ndE5k zSxG;)Elh1K8d*;A`@?=_V@<g9hyvqEl+aIiR;%M_E?9*%6jsr9`UI-veZ3A)`_~jI z0V>9+6`*w5y8#ymi$p8HC22heaJiH?2f6GDtpYA&^Cw`H+vMW{qWMoY%#RpxJD52L z_DoK5Q`=Ru7?}x8iO<ey3%Nw~nKe>pHg}6)+|L&f#_gjcjlV$1r2Fe!!EVOg!LNBZ z#NB@CTKMz^r}Hb%Nywekv0?OoLsq?Vmx$}UV?!kE<kImyb#H%?qcT9$u{Lhc=+B?) z(E<wOj<i9+>+Nk=&>vlOP9i>~ZuKsamD-UP+pWd`$7Vj@gZ67n(}m;6k!14bBW<B? zwpCwh)E(R}Kfg`YEpp*DU5IlMUNTWjlftOfmqXQrB>ZHelHV0X1d43B0AKQIt$lGl zk5h2@t#a9skngK4{2z||ud3_c;<~0$cU>~;yRu1wpX6KoBCXKq*_C1Sb5s(MuI%-< zB0lp0(9Jd5o53$NWc-dL(dJqW4z422@#`P7Kc{fdQ{`&ph0-wc`|mxVtH0ILq5#Qr zRO%Ibtle^6K^jAF4NAm49Doc{`lEBXH4~v*+pb-rO<dJ63>t<Pzx0o1?B1-m_^l$b zoD-E!&-yG_=)9tO9ozd>x0@S&5cX(QCbrv*h9J2vbJD2QCYKk->X|!=scsMN5H8p` z^UqQW8pw5QQytL_aoCu;n0A;Q%NA>|3~ZIh59`Pl3-=T(uT_+4uh0!q&kk%ok00JT zXGZ8?>ard`Y%W<W{Hb7>-P*j$-^k^?{;=lkh`mY5axm5K80%bVU_dDw)o|h)n_<Qn zv-~^7HLj`3!}>P)^}G_p*;K<FPUBZN8HbJr3zp-zcYl{k4A=3OCKP424ilRl{~WMi z;TcFY+UIL2wHbG~No1C9Zz>1^?lE+2Q2>;KDs#$m9bn>C*S7P0Yl%2CNyu$5qDa{+ zI(4+s$Td>GmZpkxHRalv^Oo|M>f`Fo8|l??!0@&!=i~qkZi0(5z(}UKS{7~SYT>fg zTSx6OyZp|av9DS}-{9ISSH*$KZQtG-62>>G9DF04horre!wmL{$?G#(h99*rr_wvT z;3+G6ITF)162xiPS(|*6@MFAASY9Yg*-%a)gktet@eDalLt^)ykDy_D&C^qp3-6}C zy1q_W>z`Mw!u7I_QWNbL$5-8S5RM?~!2lrp8C&6qur68vo_Ok7>f9l}?lCkSQ<o&` zo959vnq(wzv;UfGMjU*bUvh%~d$xS7Z&B%Sl4lX6&$!smpl>!g`fmph{`!!r^exg! zs%9CNfnRL`gaSWnO1L%X<j;-`K7iPJ@H;<<KKVg}9-WzWi;JOK8xiN29|n|L(mEYY z@7Qf7_ND<Z3<{5oROKx$@MGlV#ccQ+#R`B{)cH&?{J}|Y!83YX>t5le)6sACp39mv zzc|O0z-z$q?_BU2jS-=s^4e6J<i5zAdxdu4V!M6DuX3|bww5UZtknJ5+}J0JmEOOX z0WBFtgrO_HOUD&)_n?rvLc(yZ%wAOrsi~~W5nVIY#5Kkxf&Me1`fHlvXC`^2>^^5~ z`+6TyjMl2VElwss_@<t^yE!Y**m*l;W6eWab+D2AqkL9jfQ@H@>QfR!%&Lr5rVea- z)yRAC_)BT5ar|Th+N!)(yw@Irr^pAtEYr!LM+%^tK)nvAR^(j+s?D9)ht*DR!D^IC z>7bh6fUaukW-jdvZboZqE#><IG1b97zUL7%?^%cU62pG`s6N#kzo6vqpJlozDBen_ z8foTcBeVNBt1x$!@QSDwsLW13BMmFdzCVyr9X!k#l+J8jv1m_BVRiB3**8)h#LgXT z$!Mj1(!Q11N^ROMqNch<WKx>OTGbY-oyl4?61%J(Dclo#>3*beTWoH2E46uho>HXn zr&#S=R+slYRZ^;3QhiHrvz7=<wp3IH-{lOJWEDzl7KvvS@|7C&XSR+t#$Hm3tjXjV zz8_f=!y^b<4&rf%%~~qxGgVgI;y1BZQQdMhv6oZb!ZESeQr(*ETUy8}Ec;;4pWQn4 zJyuv;bxW;p>7?o)N$w!G>QjY61DbSJ7w~g_4Nw&rbSg#G<nrv}s16Q4EK*R3tf}D< zoZ4@(spa3k>#_fim$`7U<o;*%Oy7pHtHYnIQd_@pNS|}4Qm_(uRsO{->uY#p&2qO^ z$Yt|qNh{Wut(XO;zP6GrSz4~ipGmiqlia$aNvcc})U4rNVN=$YRCL&UcQ=OBI$%gx zR+2;Uqha=E#;hHx^A!?TlCtTeGv?M+rq3xnZBq-E6$<B)mgHEw(Yy=DnYlRb1C%e9 zIzZtx`vO!tCx;S1Iaw9F)SAo;l!X<ww6f@<9f$N10qRa5teAe4Lm5y#E)WA1ZJTBw zFSZL=pBWRg%5MXdL{lWBx{?QZ-3edIoUK?+8rUrv%Qux35~coj?)cL;W4Cz8yqIxO zaI(XAT#(P?l{#16u|YvAq_<_oPDIUhr{XML=$o%vt7jAW6iOVhiKrD__&~a+5(g3p z21YDXjBRGt#CiSlAF4Wm7n)I{2|>*q)BXqfLse%WhEdE>VtMrJbLpP3OWx@?0@ajy zS72oa#D@%lrxMRi-*?%@fSfrYW*ZZNbw94$I$N@h0WpM-e47Y?f5<~2`8Es0ghKM@ zwM%;y4JOpbJ4?hG1o<x9I<Io>67DaLQJ9zsOlhv$l<ATT%r?&%E3v27T;?@y6tA6b ziMQ)ssi(?MzZZNVPu@0=rSqxzG?87@n(%H#Xlcyv_LR7@vNN`KDL=n`wtb=Nd-H_< zuHG5qQRQ>IZ8zDUoN;CyzvV8@{jQRSWh#5krtV7Y@Ag1G+jrg@mf{@^iED~Gr>aU9 z24}x}eN_&&H!&|(>UVpbPL^k2ocX!Cwi$2nOl_EmHepwab6=#91xHtB#ko<|==nMA z0FGA1y%Uo9_m+Y=St(5D_ZR^TGXEIm1k9_G2J=|qxv6$NAnD4#0t@X>VKOLO59d1{ z%tL$_l~TZzGOl)`PhvyxWpk{f!7pt@lMWfo0F1x8E~wx6%>6CI5TZ)qu##wpzX;1g z^J@!v9q~3nwyDTSy3skl+iN%XofL;XGgbL&$=>jvIcQ8}nY=pP9hAY%nzuXm>fIbM zWlw&uWY)R3Hr`Yp5r*9zS)q%$UDyI68+&qhu60dyM&gh#W9qWtGlj_LR-TJ*@XgDQ zz#-8;D0%frFQ`uOoLrDqv%@^6#)sof(cBe&j`Jg;%PIG!=0xK-tR%%XQb(hA?h0%7 zyIr}lCLSCu>^dcMiCcs|)-#Aaypix?L33c4TE?o1Sfs?H!Q`=K+OE!td(r;nL4D5* zQh1V{HD;S=%<d%-<aIpL`oi{(UH|Z{vkm+OeO5~}>&H(GLy3210vJqwW5nr~<lir> z?{n*jE+9L4|C}xJg@oz7-&oG(*cD1z4;#bvBjXG4Vv~p?BrOlI-d@F8RL6NrtW|f5 zB@jb1`0Z71lQZ6hczNUad20fe!JP>v)#*ivuIJs9PgTTh5T1V^oI&`dtZgfupfrz| zp~o?8N!D|r>(N!*4|OV@k+U7^Wdw=5UT+`IK3@+di03#C*6h$aW*w9RKUq5P4ReRu zBiQ=y6MAOmU|sqt$#uTIkKmIE&V%sxnRtnP-f8rfFNx(OEhF(_Uw(Y%^N7_uJK{%d z7<NC-zKJ2Z`DK3Q_wrHQBTrA5uwnLDB~+y~@o*a)D(!r;KX<<=*yD-sGtNTagS7`m zeYy89N&LKmA8A!x;B9A1)>bv<J9Y;vC-wH~Rm`XA^V$-n_fPqbslHFNX4x<&e?%k1 z|D#q{e&o3$PU#xv_YC6GGkaI3zpaq7dmMf9+&?P+Nj@A!@?4;7grR08F!X`Vwu>ld z1#|E3kHt|81|vxana$}M38HM$wVoXdaXA6R-}JcJlZ4!Ph0j_XzghW~_|m_|iD4zx zzg%_9--V5yqm|N%MzS$?U1~&~*ve|me;>2_(^vWx<o|{No#}9)``CX^WFpCS!3w4A znSm~0gvdGczp|E4WBzzae_;)|_3<IkQ<UIA5z>Yt_c?tG^<o2J3*;-B2GJHM|I0<# zTnlOr<SA;pI=KeR6p*{<zvLs4#1C{td2(JOCrjjHXNEwOF$%bFcD<0SMj4s}p{rgs z@~Reuu65POwGKFNrbI|qqf9ha2rUSm<WMT8F_4KvXcBnOc11~6lR-4{#{VD5M;sgX zfwSzTz#SwhLgb7{3rB&RF?Wa%c*0WB<-!{Bf~7%D;lyxQG{`9u2}J?qlES^P)`if} zsjdJy<LX17ygoP};Oi?XeW3ukIV0yi<R7gH>D8e}pBDBH=!%b0R3TUVJUAu%&@I;r zIWRxQP`5Q8ri2xwA;Ux{?*iBZD^fU4FnLsJT_me+FCjZ2lx7P>0zhd%Mv&h-NJ+vE zTtxl<ubd)(oHqZv+dbsd$c&sT|0R_aaF_&+n26@zd@&sx=a91^NK=Gpil7J*k)Po% z?ED`OeB_wV3DTf60GH^U|8mRR0Z#b8{5jEb1RNY2f$!wRhnI=8waA<Ck2fcpf!mNU zA9<hN{X49DloZ7n6(IM24FtmEGdn;OXy_KGh7>HYfaH$EuoXTuSm5r8x?BbaP>u3~ z3`0@tpjG}KXH7rV4V*P4$l*V(`T&Glu|tip!0{XnO9Z_kpL<LQ(xAmfhrpA6X^7Dq z9PiHmbkPSMsh=L77ru3H8Fo<tuI>XAL7@}*oL`05&VOkB_rz0w_^`qn`H0WIc<0l* zoUiF$3#R<Hgj0@<Uy*kySiXbV^9cc>NQ&aH@1jWPU^@UH61p;m`jh+=2c{!5W1X<x z5!yXC5l8?RYd)q{c+JbPnE!a{gW;q6Lv=`69&n)?gZLOrsv8}D{Pn95izGB1NbOi* ziykK}CAV0e4JUPB!O?lhdmPcAND#;u-UIh9jVPi)j@)$!L@_8l5C|d>pn>&+sh1RL zLmG^t6R5$ipoj(|V0S_^Amdd%0|y*yIm#TB3Q5Y~6qW-`EmZFkBq@W#C`Zu-E<x%Q zq{oJ^%V5WXlIFHjM{jU8Mv$^nLpCUHQaRYH5SwkdsLGKCup457(p8}xc({Mrtf5FD zxR8wyTCsGH4Olv0O9AQUkO0MDU23#HBcE+)<n@l6n}ad_Ohoy=?C&7K6da`EKNC8J zCX@^E4@C-!f<#O~<{*{{0Z@xRKJ(MV$bXt0N_7l1kqg291UNxTScX0!C`tp8o8y1a zJ%wl-pt3lk%wG<OoD9kfW&Cmk*{1%`poj!0yP6C7Jp5Ne!D<kl0V(r7DgXf*z(x<H zQvD}4AjG<Y-4@Xx@wr}uG=8W6<@oZ0<gWaGX^_xdkt#|fX(NB`cX0I3%l&NjJpP|V zX6WmegC+nj)c<VxsHOjR*9ZGIN=gJW3R*spR3L&YP?`#Tb5W*0)GOrq4z>z3>U7B4 z6p_JcK&c7;WF=hqhX?uupwHh)D78QPG@7m!>_3~zpVU(%aBf6UU<C997C|ZZlrjG7 zxgtoPx)BXpqTC2X8dUTT4N@W-RDcv_9fFBS{iyzSxDzQg7VOENZv!2aG*T*8tCMRz z+9EfP7yCKx5EB|R@oqjUZ@qRHz>8f;9q{6uCGY3HFxl(3FK~|7T9#X@vc9*lkx`Wi z-8FfY5d17Md1ScmM$MsYbxp^>La*Cb<>hU2bJ2o3az*uI-%A$0u*;UNY}&rOe9TYL z-B2*SweBdqJQ*%Fxk}+-crPg(n3g{7ny0yF{@i~GY)iGApJm4Pd?wRNHSD!mYjOl? z;N2^?UOF>_6jy!xbL|$Fw}{5-J19KB{p*zNQ|Ft@3Y&G4uho90@JRi%)7Q6?P1;PR zIhEhxFZyJtj7-O>y+`FBV|K^cOp3n$4e@E5*BD2RZF79|0i$ivc&B5xs`Ta@FH@YJ zuk}=<F(26`F+z8?*q6`ud%h)hX)B>MY8<n@ghx@3uANWO9%qv~3IZ<n3<ZO*gSn$P z5KK8;TqGYvE&OQa>g{ZIcG8?1B+mj!9(f3<EC&9Z$ap+tT%x(BuuC7%x0&W_Jbc@C zG>7!_@bz!(cfB-9PTOYBhY`Ds>2=XDc$$-3x{hTO01#4@+P0tZ-U>?i1z~8g-hA>r zbCnd7&2vDq6?xoB+n!?0t?`BdpUR7*+&%&Cj6bIDlFS{&az0TR{uuE;4(QI*v}}GO z@B8MWO_=cPObsW)#xLBYSgKu1EmqqnLynY@k;xlh)dIR-IZi6L28^o=C4RE?2R~yJ zE^@`4qS?-tWyU!YldAOSNy`@9eWb%$qrOKTA2Twd>O7FN-&>5!O+k39(*5(5^l{wK z)}2L--yXYUi`1<~M?dQuqE<y7%V+IUKWj=csg?PrYcgybp8ighG?a;{H=z5wBfr4i z03YpVV*OaWK3g9GyL9H}rL}_0XQ&P{0=pzTx<bJ(N<A_sE%@tFfd%}!bdG^P3zORX zYYjey2cEpTBsp^Dfx{JkVv@7Pj_VCa?Nw@?=@D2&nm?YUVl>{F<i3mPubgNYiA5Cs zG)b92OzVxC&K0qM^FL0cVwmM%aI&6YDZ#3KPx?0Z{Ew#~L}d;}KFbLsixZBjSHy7H zKAPOcys3eyqfYSc3&F1!N}e5Ro_yVyvuAV)Opn$DbMIlEHahu$k0y&R$dh_juJx|4 z+3VxaWG4Re=%T^(>ePtj_X`DQ*_wy%a=m2pNvmtVyB0DO%*8{Ucb1rPO1tFZy=aP@ zi>p_fSk4#)JSvomt~x#0{#evW_lnQ5`)@C$(^ZGl;w6*~=AyeY>@P;$e-|j7_B)(@ zeLCz^WqjAL?xqRB6H9KHgbVUiscBUkS|(d$x<VnketRn9+;b+f9c@Wh^k-cyzQpX> zJZ+2T<PhNYNDhiwZ)Y@|<&5OAsjZxDJ2?NGudwcDvh9H9IiF+OxR|3)%K<wAZ$VHP zg2z6;q_(0T#m9Jf{yG`DD~)II>y0|1B!R_jbk+6xIwvNAm-0XHgDhGTXQ<aXiN=0# z9TPy%0)qK3KuxDJurZ>kwgW*}&jYbwMj&d84>cy{$gpq1)zNl*8+4%h!Y9hBvlG;? z$r{M0%o{MO!23iws2gHsV15R)V8aFLF``m0SW5bdQfRi6XzcUR%OGwWL0ETK955Tf zgasjx&;haGhW%lgY8yEL3-VBb59EOQCrk^*wSo`F^_8D{8`VHvbVCKFVAG<I`Mx;l zvMhH;8#XMU#)G4#g$1~<RUsV6{5qb+6A!!Qm<lTRmJFLBgiSf|!_G6Him)lpZ~WXA zIFR)TSnv)Nz)`L@!#WmNusdCV2Fca{;zHq|ovuRiDMU^P`E>JhE5PY;vVlD*L!c}I z;Xs_|;n0<)+Jx|7e{$gzj-7;{83NBjaG9=p;=|VXo>cRMAe^ge2H1umoU3XeI>)vu zgk^iqN7Z*k(pH5Ekmz0`CfJaPFal3~qC9JIj6Yk0-f&@o8v+R-cnCoejK4J4=nYJ< z(5w#vkpRUJc=|6*1A4=PGz<tNfZ!D<4jLdWzmj8|buRd}4<#Z3R*{gw8Ax&$ir|oF z%<{p$!Zlo2*az$9q1mN?6cmuc=P!kVFA!D*tUgNwM{Eaq`oS8!v`{!nk3ygfgdlGN z5=h1Z$+#eosegF*K_1kwkQ`R$Lp2aFm@mXc1eHF7rAZiYvI7tZ*TMRizig7Q;9Djt zI0?Z81Y$xk<{z39@NEzkU_-D1fw&Mf#k1hgIb?n_2b*^lf!!oSJt2cVI|av73xtlQ zDG<FOxywQ@5pi&ZqvG`W^`c?O!`lfHa)^d2yqXAdV1vS}JqN*T+UoF{t?|X3j<9UR z39T#>1frF-h(Mr@gC@rbF&58(fOeT6cQUw!T+!kxL;VDctFZq_ked^3AiKPfCK?r> zb>;xUYM}3k&nn`>0clndIodW>;S}zQ0R789bTFu;fgMB>?~c0d2fOWl5n`qPbUS8O z5wf6x>bYwL!6c+T<Z!?Ww;dTkLLu&TWhhxuq?z0Zg!{p|4FaJ8mK=Aqal-B$09A8r z<3u~$bqMAoEbHHa)uFjug!SssT*e^~P2@1x!*upn+YZjcdaH0lJfMc)S13PsG!(*r zIxP-+Dhk)}u08_cwy>*=K)8Vqc^J-i!g@zYZ8#B#)FvN+V2+R!>yW(Au2~0H?yd*y z&@oz;p?^D6jaVc>(dppBakIi^Jdn`I{!04jJe27;qCsLpisgY=68=Mjw4e^s98p4Y z4+NqWS%TKIg`44LJVH2T9oU2>6r&c}A5GwUAljmm9&w<JAADaN+fre7%aGn)g71HJ zv7|>Ua0E8!<At`c5pvid2c&lJ{j;VkC}D+BD2^347d8s84Hwwv2<RSC4G~yj8?mQ> z)M)RbM$!@ct6{jXf)H9#bdWk4b^0un)BV3BeuxBp3y2{}C|tTu46r$%M`%x=MoYj5 zqWI9)lLUdVWhd^xY+B$_5<=UP6=DOW>x2dS6({c4@OW9Yz8ZZ;G||TteC!?DNZ{}i z(bm%UciXT)dj<Gnf)5PRbnu_{53W@-DDb&Nd*v#s(SZukPJ}*}t7!GE{H0Mq{RETy z>&-SA5GMi=mgaAlk?5SzcXJ!91q?_-h*TGRG|?7Ch!zqNDnP688UhhH-d`Fdv0WBe zkPfBbdIJs)31msuF!8vzDlpVZzh3RitWJAdWSSz))^3R<?fc=k&cc$(kFJJ)OIv73 zp;ma!ah)X8jxT)v-K@DDn~7d(Re)rlghwq&4STTu$59DADm!><F~^!O{9?<`pvF24 znj?$e0Or(lU%RRFz)^jzQ5)W3?OpEq{Cfu)r(zX{H#$JLu8ZH?ye>a`sxN7m+3#cU z@v~5+X1MY5aR0k_1PsKf*HteDWTa}S%8bRET)7-1(W8`b?VO_QV~|fSD#-Tz*Vdc% zF@@9Ps#o6UwaYk9Ml{~QswVg4;h5B;QpE1o3SwyPx8(}CSd|fIm78nDMK<*^*J>@O z+zR(#Z~bc%CrOJ@9eGY5#Y7`VO8?-KsQ>Ys;9lGc@Mt&Dr~#29a-VTu|89S^>RI$N z*}1gudpFWm<0=#Rhq*qvD~ApytUidd_-u5fa@en1nP~Y=+3>bTYVMc6m#-g8(RESq zc9&ecP1Ex9`RKvC*S_wSjU8)_gUy2)1F3Vb3u9l%1RIo%T4&TxX$LX9w2TV0k~p6j zMqTzOE>Lu2vB%~pPI9}fk@4vD#i+1}{PqeWg*KUi!&RDEUB{6xna}Ck@sl6f-D^`f zGY-%<y(grS9ano7+=k?0e%h$D;4)FT<FbxVxX06dL(d*}nO@<JUYagVb~0fE^PKSW zj8)~|)VHZf71~|!tXPEDSLqrte{EFz5&6|{|E5;bJ0rloN@tcsXPD}zRr_Nt)x><> zl6E5cq%i6ApCm;~jqWsGoj8RAYc_o^9ao#pW_z7d%H1|I7k+Dk??7(w`=fzza{RC; z^Si@n&ZVmPtOaeny*K1RJ<J_X&XK=J$g0K{q}Z$+w4LDC_k9yEU&<cOo5Xff{K1qE z*Tqj9MNN<JPPHqZoHDC0=zyL`vslnyY4#<!*k_Y$a&3_FTU$LhSpa-3fcN&I{UL~) zn79?-mA+l*#H0nT2lljipMK+T*6nBGm+_T2Z}M>WRzB_RdkP1<v2RFEP`GZ^KM2lO z;%k3v?m<1k<3#<YI*kOlCcgnU?iMsEv;=Sz8BRrt6O~x`TH~cK<*A&#)s~f~1fG?7 zYT((LrwE=yd8%g>jeX1I1tc`t8aG?%h91(H-#^<mMqu^M+W)MYe_VvPZSn1^N@u&i z5Lgvi`**1MYXVfk?U5|1l<f0*1=jxNYW~9jRd)NT;@PeS0;?)(|3o!^!3gn<!rNC> z&vp$HSfyM0i>Ucm0aVHDkp#@OBkbX;!Y+PZSNsc$+4?jlbuVh<a_ar;G7{qAl)GW- zB3k;nTY&3|sk7SU-L{MiDd{@f8M4f?TH9H&%uJfwnX=3V58;p4G<8CVS0#ja6+(zt zB82$+p%5=03i0<sAzn5V;_oOxy!6+eR1%VR%;2gVcS`#A?)-syuUpK^C58`K-NbC1 zZ#dQPeRrJb+vwl9Ew#BPdVQmKIHu7?yt6#vj*!rXsnl!<*(m>{KSOn+zHY%I{wSH8 zv(FT(NO(=AI-lNfKinSg;CGAG&+NshH`b-?N)O9Z9kfq10Vf7}_4luK-A}Qte(0h6 z&cusq<+JeWb<Jl&@jJt<-}Zjj{J5^`=Wo#Lt&y?ju=r&s<!g{x_GI!IEY~|(7&hfK z+l5asXFqPgv<1(dEF;WWj2()Xm;sqni!vuK?~H|Ue5ATK{`@*E=FWwenE0h-;iqMO z)i7v#3S6r_^;8V&moO>zx#;{T2J)~UF1nQ%HjfT4T0L=o__3V4JRoxWI&J-S0b-3G zfr<Z7LGiSV*6ridUp#Tf=f0nKgFQud|Kw$iIteb?`mOV?F!5{iQ%_yPZMJxOS_Z>C z4zs+_w0HA$?eS_ZEq3lH`ccR3sHeSm)-~Pzo%S7G`X`JPH8C9N5Epc7mVyYc$`VT( zA?(y^gs|iTC{Y9@`z4NzAV}onhXRu_2PmW~$bJq?1t_*+Cp;rPMplD-j69<z`XdnW zQE0LkqVv%qxdS{5J4UDI3K;671^&nA7~KM)5rr-U{~Sku2ezTUt9s}R6P>1VpyNRv zc#779j(dR>?2Y6I3bu(16T|Rya0HznZb1SE3`<~YD@WF|1b9HV0;Akop(E8QL<>4- z_MmYTj$s9cinIdV`sMEej~YkN5g!O~%JP(W8y$0_K$M4&mKxcW{yBqAVGndf=au5H z4Gw5{DM!b_C~zntwxy)tg|&Y<f(GU>C<;dqJ9MbXUmBiXR&&A8#i7Gn6rU5!8aUzx zL%j5$5t-GfQ9gM`*iB|cCID+_0#PsoI?6(H85GmM*%!M4B&*pW^)O`mZ}!C$?m$O$ zT<?I6z||o;2RI%_G;_c>223h7o4F<o+o<PATHB~SG>6-$8kj?LV!MrMqH}vfH0MZQ zglNhi{iPv9vwj~o1O#;=bO*^P6p;fvEaDT28bCHVBqUc9u^E|}z==FEIz2!_hX?O^ z=%5=Jo>0tQ5XTf*((;f|0j-sPu{J{E)_)9+zx>a@{-8+G3Xt)@UyB_-48?4P>;d3> z+_AA6u7x~w8vl=N@C;mcoyg(^LZO2|v8WWqZ3WYX3{tMh^vewAz!e?hr=!DnWTpa# z+<$1$iMb&vV1i8<B61K%8W<Y?;e%qi&Y=Z@*6N>7u>Zt=MaSx$aN+JCqai1fyB_Qm zClWeP86@669FU3jD_p=jXmKM0`F~861h85@8WyrZ<wI%iAVX9BKX$*Hhz1>cqvLlA zM9u`U;L!UYJKKM3j*gB0VsQLpxhX?-NfZTI7K*qQoxNAV&eejEB6HL#8iNkxzKRx( zI|OOa!nyU=7U%xIM~MAni$ih2k>%*$ERM)%wN?aYRTOF7It1&GhyoFa^Vi(O2|Im& zR%t%mTMj7TWaT3)SRKf+RflYNny4PK6$l~_#npAe$os4#xhhwUwvKH$?jsZcoe+U& zD53ut5J})*MbYGPBM^!GpRnM6jDz`5UPtJ1fdvB5<$_fNg3$a(ipOY7gs>OKD2^|h zXb+@tjDI_wj}{Kv7Cey9(YD|Lh3*J8V>GaT+up%Wk7|OQ;g4k$7k&*q;AnKfh%c;3 zQNym+z{bIz^{3PH5Tyx)riJ!ud?-XS*k_JE>uiydkJlIq%nDi0h9T*npyMHskptGB z4WS&}lhB40SCH27LB>^x8d!*+-D)xsRzSN~ISOj-hNuy5(Z9L@E=hpx7fpi~q(+<9 ze>QuxvHZ(^4+Mku2Zq0U4>g*0Dab$_s<aTY2BTXO7GRXfvd#ocEl{j?0|<sfQ9B}Q z_tZaZkZR+=f$*Vi1<iLEvKWFe@JN}N;nEXAQLuBNq{q;XXNExVVMOCzMsFxkJ2KOb zA@T%-1?D|OqmACs_j?qAiEuX7kg;|Zj0SaC2ljIntz+<+{BM!KS3y@14JIt0K}+iv z0udiN1cJdLn#btv|98RU&!qw*IBF!50kmWbU>y*8A4nAI&z89aMS`+K6hKy1=i$mh z|9kYmC(#{rI4GIQ%W@Z#9I1)nCzU&pt9ks~ls}gpms=ymL&$2am&E*D$w+?1OzVBE zZTN#V;y@wSoaDzQsjoLzD34(lr9fj;R{Q7OrxPC}*VXcwtW!2Csn=D1rVakc4w5!8 z#Vx(e<-J)z5!xcY!8)x&(N%VH`@6=&?oTVJ=O1>T2C!x~J%Ba4(<-9X^zLzw%9`lk z3rtJbE?F!aUfTTlnO*7GO#3rp2GStjN)8zQfQun#KZP#$yZ!MB5jTB6(lxz1g6FDq zz+JimvSx1YZQ<qbuUie&)4c`xaN{be{fEqZ=yNsF6Rry<Et4E<?wTh3Y7TNORd|y^ z2N33OVmi0*DXF46w*n52cL08At?2ilduGWY3HYOFvd=tQbZFnVrO;})znr_A6yLce z_+VI+F=^SMxYWZ~WO((7*>SOJ>G<H#(E|B0XV(R}kwWt-mHor+&L<=-l)A-C)LFgv zbW^z)=4^g*e<#T!FMT>UzTbYt8u^Wf{T4U)B{Vy-5<|Yc!h6v45Ldg;kDuG0JgqJb zc<C^gAEpsMth1Ra8j)oPF*dA<mZF;Ku;L8npG*KgkX8qRoi#TmjrfPbbAZ1EJY}5R zrH1_YR*LMWa_eMMH(PmqMV(6MqSiZHZ#hr(uk_mlZx>5*%JGO~RIFvLS=#AwBp5MK z8LhFtIAq(%!LR}5sj36tyO?;VwBD0?5;ZgdP+cp9)bm?QDgfWL0n3$)L9G(Tr0d~z zVAsMw!~#$9;~8Mxx>A1|!0{6V;Q4;?E_milv4Usu^v~0tKHFM*pXYbg(=krozy@hx z^LI`JIX*SQ=B(GA0@sM!>UbG`e-LL%+<dqjlKDCLS(&*ldz4Hswupz&>^fz*%%q&T z&rKKLeVfwRpU$kiUBf#wS1`O9c6X&GUMrF%Y_ojKQJgxCdETN;f4xy);GUCdB7cKM zM9XZzdV1XYfRL5Ftt=?-a5HZQ1TpQCLc9v`Wg}65py(ANq@F7m4s9nfBz2fBaGG>| z{0vf`T!HKgj`9FC)t6Oyi=#cuC!tlH2j)d8>%w0GNd<Iy%9-CS7uAlj^~@x9WR_OF zALTjqY(k?d`(0{c>E;XKUriMBmFb_q8RrUY&kCm_ci&M8JKeE&zz!F{ZgQ_u^f<xz z=A~jO;jm+t_WWI+Se~Et)zXo`-*@VXwonAMeM5Y~4B4?;JIf?id?Np1@I?c;Q@dGh zjpKufLUywIcWQ1~2syF+E;gGO>TSTf*jUlU0G<Lp?;zZ#PjIc8y}x-1>!O625S{0( zp62VAr&D+n+dh^z3GU{FXN$-@J}qCzsHXnxVy%Cs^gAo%ZN~TKi$qU4$jf#22@}71 z^Vo#0k~UE(@}5e4H@JbH>>;Jf%Qe=S)yl;5v@^4n$-uNTyOk-;h7(-LpLGTv<4jE6 zoDd(t3Gre15Fecn@k#j*p9cKJnbLACAimhb;0dSu@(8!Ey6c<Pm2loK*L=Eew#yxi z*{5_B?HVG-`QaN2MkL>|iyc`E+TFiyF7<uE59Qf0>-UjmZGT%A^=7W_HF3?2+EuHs zb#ZsDT4i)+ZCc6=hcWMu+g*9dPwC7l{4-5BMB_kjcb8f(ivnB;s2o-4U0fi&b7XZR z>f^Kc*#Ei^p#F2g=s8xc1f{CHI)7B)hXI`vPP#ZPD|f%(UZ-qXF>duEu&fs7YnP_K zT57jmfES`Y>PlY(oQY?Z)h(+RXW}{~9+)o9+7MG!Rqoz4m>TG9k0!9R-fUM#6-rPA z22^4AbbJFJYxbTMAI+MWq*y;g*Yv}R{F{S?k6q%vP^@W7HBJdu-?Z<oIcAUh*-e$~ zdszR(0n2%kv^#IEZ?>+}=xAm%>e#-hf@E{PGOr&eeYlhLI_LKbPDA~d4KAJuFyq%Q ztGK%@%J-_K;_~qiDeG*lS01(u6)e%y=Z%|V-e4cRH;50Ijs&obPAUYGdV{#FHh0N* zrsOUkXF<$(PY`!_wMIKXiTYlD2Du^qt^+DehJ~V#Tz!TUVR9kH1Ck%)$67J1Jjzw& z?&zB)2n8>%YG51Yk%`sw-xMu6a$Od5Cm$Brg(l#hZdiH9m7m_@KR;pDFMG3vGD*|C zvS(yNI^osvfI7)(^|_shdy>Sb<2T#VQh2~0OD)CI*oML{R3geT=h~IqOT4@ojPpoO zhGbNfg1G4ICCbI(j{WJgLm+lKPd_Hahd{iYQn7fBdOM8CZkz{lZYtVnKmOq#yU1k! z!(#W#z{?d#v<Y%_8s{;?(wfGz2Le4>79b}3>>fz>$<q%qie^-}>){?z8Rv;Y5^NA_ zJ(d#W4(%DCEx`6T%fojOlF&g;ksw<%k9s1e!=!4RaGn0l)Rpq$=q^>iVxta+LADnL z#d|9fHe=s^({|15J^HPRBhu|Zd*CV7^kJjaVx@R%YieKNz(U~B5I5dlkW9+pMj6#K z6_*f0%GY2MKAn=KGL^>p8?N9!iKlz=ZMjpiTZB(8H*-*&zKmNXQoYhvC0j#%16%5p zMM>60uTN4-FBlXD{A(;3NO|>`bMAM{wpWZvC4V5^<fZTp&L|58h0ep5eR3<GeA;?V zoz0k7kk$REH;qeM0dL5E#{BpmcWmg==1wfxUY&T(l*|_A$j%`a_giL<3BqS*O4&}o zJe2x;O}J^3r~b`mSMkgCk<;v4ZH;NPN-gCvLk6QSaZHvT@aYfxdiU~h>HZeS?VGmB zGoMB0o4J`&qjF{m2Q0okHs0AW;g1IPu9`N@<{7BPY54HioKly*L2;VV{bOupYPRqu zpV4EJS+%^a8|U~K8o7(klV~qrsfm`i?)?~PFzK&<W^}0co3V0#z&A6{g}YQ?x#7c= zRaUQ8he}?UM-x|?CZ$dg>eLCIAF2{4{_=LQ?}I>ogzSM?cc|n7cipmf$YuF?drN^= z{0hTeGcuDKCh67E&YBco6F^C`HCZ%6K*31wjEvq@hp+yw=Js=0K8d-^^l>`o(_>G) zMuFR)V$xOMW%!92x(zNZuu89V@6=od-h>01-aZ+jZV2Q{n2Iv%Jop{nxf&ZbDdWPv zTIoRDL$1g^@1Vf25B$M;3+rglD&D}w1Fm5<ZwcZ@&fA0mgdpxIi13G?pTUp#0hf<1 z4y#+^vRvOeFnK#RUELX$`0R$Ga~{NrUVXQ#d|1C-OOVfK{{8ZtRBeyMmj!C&z<!V` zmc46ha-29%a!7Yq@Oz0|%U5^liZZ+9yxn_N?B8d-$GcWed{^HOvEmG??U}Q&AYIN| zzqF`Bvk>SS>*7{i^+|mJywr59CTg4IEuR>XEw1>GUKJ&y+na4|756=Q<9h9Hx3AtM z(pr5J@7x0XF88@5=!uJ*RaBf(yI?gLxz@Czd9=Hp{lm<IL+)v+hw4?Gn1hiQqE?RW zyEZ@0D|b`Y_3n4SFW;`dsrM!}WN>lauHj`6r|tMn`y@duGU1K!?SSsuqygJ%xmaG# zXiaefy@0MPWg^-5vwr6r&!yMAU@KtPaSvwkXKc2Q^?smddhbL*<A8zd!hRpO)Y0J& zDcvK!)WLFrsJ$QdBI7Mu#3Yz>&7vGww^bT#K?-82pORuZi{oqt{+>jqiYX<;ax5J3 z-dw@bbL+KfvZAN$+ZIu$;smewwoO-wi54l7+2n~Z3u>t=CndmZRSKK@Iq=F~Ir#*< zYQATaCkC%dmGo1Bzsp2^?`2<7Gjk`iYAV~kpzJQ{sbv)l{+~LSvIYMuIkk|3|D@J~ zxxqhTbL56sPSAF6cBm1E#jD4^BlYA@;#a?dVMT3~2rE1;1<&foA@HBE30GM8(+ODF zrvg5`PeB&gWU1I!-G8-RaVXQ#PQa4cS5o~Qd+e-TUd_+&Bh#Ji!sh6Dii+5HabWf0 z%IqG!l-o!9hgyLSTAWtQ9b5K`sVmci<0c7n@^7bt`*u0ZRH~|SqAOk-+63Bp$v)dS z?ER{J_nk!^hwZcJrIj3=38mHf;Vkva9>FS?EcJ>WLFfFcAx4{_k$W}Gsr+%8nf;X{ z(&N%K9l7`Jj7zldY#o^S9huY~9cl`aSYc|P+IclDXfS5cwzG5a%D$eCGogf5Oo(}g z&&N)YVQK7j4BtynqSt$I5@P;e4(W-B^j>)sRnb<6dN~E+;VCkdfl{8r2RP3&!iTSm zHEWmm+l@xG$$Pzbdw!LI+b7&V@r~jQ_oPO$>se;K10v^6``+kIZ^x4o)|rfW60n%e zos(QvMSZiI!dcpRetB4Df3RVVO_QWEUSiTNeCUyN{N~N!JKE7%ji;}1t$%ecvAv_0 zrw&X!4mIF^V}I=xc`yfg_R&fhdSq3aV4iX}sR5>>&jrjxzs4%O^kq^rZiZoTirP`^ z3dV#NE-0XOl!2fh0wo|QjX*gFwv3)M>h;D=yiXwJ_$WpR;}f<5>j_$+Ul9mogJ2K@ zI}tX17(<fX@rP3?#&cX9SmlQ#0+k>L?vw$nA_P?tXhZqd4s=8tw!;T5s!=;CL$C~i zp!*YE4hRG*Zo+GO<RpuEJ@!}BHC>1ygoQizAsC9l2M|<1;0^3YtD@WA%XXcnNI!-Z z`_&s5(`m2sxao|lpTGX|HRnP}jcCMXf}KLMLmxh+E0gCf{s|k#i(^rx&1B$T*1nzV z*i$8|x5?s5+s|AHnpoib;C(c7Qup#3ErK82Z8wv~@qE4SV2$yZv3*j1N$j8dt3(fH zQf4g}JRhv(f~V110(d&Ext;tJE1GUrrFOhJD*234HoL!V`pl@MILpPd^^%g!<J2<u zmNbQGf$f{)zf~5Wtty|HcfK5}6+P9xXsdkcgzJl#eaZm$&oTl>qy2`tc-6Ny9&2cE z>vOwB8t9qK9<{F1aBFzX=H1F)akP5lSk6x!a$}H{i{Fm8y+`jX&HlW3qkcHJ*%GYY zEZ3f7?UZikK}FDPeQ;r%fa>O_8=@26h|g&Rh)eH0i)6jIy4N$>$!{w$dgv`K-7}hx zK&CSF>0h~7z3?TsKAGX?iUZZnl$E2fVK(cGDp9L>|H}PSejyoEbqHkkSy9!WcwHtU z$|o7Ub(Aw*7+qa`=BKVYcEHaEi^gy5K1w@johc5PV6jNV)^uL%&QQxcsnKGo5)R_u zUM#ie3A@-Z8T64*N_TBA(XEy~$_4mckI=~0eo0<i_b(;9u>E=O77X_4qJeuuPxwd1 zIi3kcvr4;w?-{(tpTu7nX!meusoZf47{Augs8ptC7TH*a|03NanYQcD-ncU9KrfZN zsJTobRCnmzvHWmJP5H|Q{5gKu_xjnjG<|Pm`4AD%RfXJUd45t5`1^Y=E%Dy%$PSmX zBfd>%oSF@t|3{zS_t#iR^s+XZQEaG-yTR%EJ(Zj>EvDD`YQIVHJLe}iC{5}V%EmqT zJu5X#FM3V41WgsZS!OiOPuJ`o=c|dmvRj&#RzsJ*&+^WhAeo49Ko-xJrGD%-iv<1_ z@$G^!L-JmAye8+mrz)aliq!(Ew+?;p`wJU+5)l;Oo*}(WxApecZI(vr8xQb&2Yg;U zdD|xNbYNm;c<k2OgKgg_&k0NN%s>ChUM2Axo-T`t>lp6WZk9XJ*S&Xb_tRRuc~B55 z_9up|Vr$?}x=Km#TzV<IVWfV2HgoF*Ht^Tv!-DRi;m|$Q?7eCKuRMiiRp`>mM*#6% z2>-{CbQlL>?m^em;nNW00*<8Y;}@xUF2FY{NE(d$iNV;y8+nYD2|%Y%O~|Z_5rO!~ z!IT7Yh{J)dsC|e%gX7&W@ny495Mz%pgb+Nt;OLMjwA_Iyj5{KiL+(ExOaA@c(ezLf zYj=jZ>t^Ay+isLj2A{F?iGn`$pTR?SZslyII={2;@E3OYcy4My*<Pk8DxJOU+T<5P z0bV!a)Mr|+X~L)s<3{X8Ta?d<_Uz32Sj4C6{|>0lxM04QcQ=rxn5rc3c4>1oX-j)l zvb`!^FQeYccXqKcMm>fUYd>S7*6fx2`vuN$zKtni?_TC8UZb!mEDPd$NT<_(lYzqQ z*KY7Mu9D3N-PZkz-4weQ#Mu)|`7T+FvXz&^bzTd#P4iZ|Bxi*1D-URD+2Qnvh-ESS z+G<j~bgPUe>?dCJVYrI)uS*#3Z(^;A(e@HM;u1Rw&W7-bI8?7Cc6>=6=qm|}^Cwi3 z>DM0}xUL{fsOUeiS(i`Bw6kG!Eed-)nPB&>P0LL=I<+{lZ`W*6Zfen~MjxN#Hmqh) zF)ub&J~by{_x`igSMpUBss`et1S|u7ERdb7f!MVL<6_9i-(M(0-rl_M*%Bv_$%5ZI z>l}oVJ`S@xRO6SD8FTmE{UI-tP>?Rr*-Az<y;$DibdS@zpm4$46#6Va&3ZMWb2N>$ z&{JREs>c4%<W6ajPjqCy6n>IJ&9?7bT3y-wPb#-~tK5>+tX)bza6Z3m!tUpm%;B>N zV#BN)=52FM=4q$GGl4vg8iG^FMAEY2AQf^h1<*zFWO<U?bQeP208RK5o-xc)!CCVX zD6GqUEvOY~fZFAL4b+%+BB(JhY$K}pJc{>mjvb;??|&DmC|}94yP-$1_MpQ;gNldy z-7dFrv(UBGxD=K3!?-ww;tSt?ulKZA0nc*<SE^NTKE%P#Cj2|`ok6b1N`IdR$*T3k z?N^=CnRi0cxB7BOMq24d?DVAiHlAgNS~!fWo_)BLAKQ_vB5%ze#PaI3_9=$esfeHt zi|TLo&#>mvvlgyV_S71ID;#mv%um}sJ9UciFido3ChoHJZ^$u5Jgt73y3mlE8Cu$h zLQRmJnz?3_eg&WD<S$zgH0x1fIPt+$x0|2E@pr?_A06%W?hO`3yj+rr;H1$4#I9L* z*v38D#;~<pG}6W(f49S)JCuK&pE=1%KuFdlFV3lPCt=Qjn#;Wi5H(xKi>zyu2)WNy z#YhK@pD>>#p5p6o7t%Z6kn5~+kPH3FCt~oBhMx01^DApd`nsO(iZ$u(j$A>W+D_^I z6fWHwYQ`}F`|$nKxv$syrUTDk?sltfJe;01(z~goA6%<)%XZ*e=DZU@7O-3Mr0ttl zijKRzIE#PM&$+J6&6^{5KYq&oL6|PV>fX|BbXDi0qB%~LFuCnrg6-GKTL*0CC5D%F z)vsifMr|Erj~W_1?yPHe^QPK5FyUq~u&pjtcWkw5G<6weEIm^xVx6G*u(4ujll&o> zcJaEh$H<lvE8DWB7DwH~jgy$C^-M-sJa5H+8@mjia~#$r4BLDGV^`sc6gxbe8exUt z<msT^beeCwP0|-quQ|WZCE`g@Y}uyMjH_j@S=cWXT&yjlsmr+DYdhrkgTCG}7X+kl z@4>EpV;&nj?%%XD=O8fVpqavvz*0C2u1b{H`BmDcypIBRDprz5GdU8<&Fs~s_7=kX zooOBli$v;#yK{L+(U<A;zjEk&75#jQD{0(FOR45oI+a6q8}rYj#Gr$?c5xwn_nN40 zccxx*1{)XE9E>t9JLTBr+Y`>KY6wZhYmO}%m|9IuJ?{IqueEG4IlgDI>B+O@&ZjSn z0!gM=(^ztsy<Z^aGx_PWkh#5jv)|=7XF~M7Qn&694G8}?O2%D+H+M)rXsE)j?d=AF zgA9yPUudKY5ex-C0dZ5L&reQU?$2<+cp=m%mLWa_qbhu-v^iIWuE2<4%HAM&m=HSz zxj{6sUmZmV!3+`W5UdXKQr<ke;!$jNc%Q)V(U9;r3igZg?n3$UvhI3~kM$Bi{-T@^ zyB3q_>lo@$$09AR(jjYoLt>)(sx$~z#%6t^@$!MH0EM2?Q7l&M2o0WxcF2o?^s%sa zbX%ExGe-VyKiNTPlCvg4e|a0Txw>DLRJ}>k`0gjUY=S8#o~``R=Sp8ScCDXI{i8p& zc$}~8lG=;Ee^LJaFtKVv@`%4{YG&odkdCvHsPMLz_Hgz0+2sM1$%hkmhEl4|{A+c+ z0i?s$CP^%9&g`TbeC#HPDJ8Dw_Ps8irfVkzmzWIQFP{`=S=R@##^hW@h~Z5pWq6ZG zgam>%>W{01I$i1DEv0C9VaXK+Eo<v<ePsQsjSnHfov5BR2LvWz>f1gm*=(dfu797~ zeaHQyKx~Uk;V}p<q4J54ykgFSUvHsoS%JyANLOTjtoyNO>p9|KBj2mU1z!&?%StTq zv7INLIl4T^`j9>)#660U{^vXy0`D<9e=zLY%)X$#b@^6L@z#x1!smx+*Ag?rU~&uV z2k)*WUfdrC2_X7Aj_zMn(7L+M&2ZGIANpYT_nl~>&i5y5g<tVcw`@#PddG5fT}&IT z2;-T}-+1F^KH?PGyBZ}VzVUswM(S*I&~;t?>4h-o1Z(sBp+n8{>YB~_9><C&dT;I~ zPe(Z&sWx>dZr<`P$@=ip#-(J#D70|7(P?VaD4#XwRa)7{61kqcF=sx-hu#R{xcwnw z6GOPNq&yWbL+_3(u@ECh6#a)Ge~p(ItS3H@diARHDd8vz;>Z$fEpwN<Yp%_{^h!ML zxY#}Bu{j>yO_8rX<DA7?Dek;ZShn0X`+P6EjP`=K@x(%Xm>IJymWK9kGhDN&i8S34 z8r2U3D&NIE(R*#L^x8DxAZR6rY4GMM`6y%l7wwc_3*1vYJJ!@IdEf_^;%XYH=f#5+ z08-3{t9zaes51!iP<T4gIIyjBK|aXE0)TI}znD1QNqyGyb0SK*DjysM#P476XOt?h zgXaRcPZ~pnwgZ{BoQ<wq8Q&+ramZtyFGC@GKjcNm^IKr?d!3GYNB7gf|7L8Ig+?gH zYw(h$1q$PmLE-3Wn*f6vHTqy`4yYk`AStK0RBIQJT~YYWSSnX#I*4UK*sl<4b+SDq zee`Hc)R3J?P2$jDf#JPLx81&Aa{~Pr(U9g><J=an9#uJB4y*nusY^R1@l-^SGOjXi z)hzQkF0+k!Pn3zl<LHvd{_&s-$z*bF8e1{uxQ7F~P3;~Jukb|p;|7t0jfLm!%J`$* zKS!?_zvFSvV4aI<d0JdOXdv6?UT$xfH52k|n1lLrMf)1LnR(IsykWB@HYKyPnr1fA zpk4a4N*S|nD+vdwiI~sg6u6J`E7mbWQ<jE)aomxauVJ92_l~%1OLU&H2@B8veE*k~ z4^>YJi`rIR;YcWAF8b+VHibO+@VH^D3JXi^D(NjnVpC6VdiNrHS^GEKubp1E3CvX; zElShI&y5*&?#C~X;4!aOWo!)E3GXEnf9zad?4s$!+97uqKKbJzhRBVA7BTFP1%@I= z;|kq<SPADZ@Dm7qzP}#d9pxl+D22t7`ayLd&g3j_kC3QCOnP4!uUI?Qpz(?M0OD7v zo}3g>hrhiVbMp5N@ec{H&X%3?`=N|+$Nsd)iEE>Nlgb#%hYz-|P??^f_EW&zXj<DL z71KT4kdCo8`8+oL*UP!40Emfy4hz2^a8ktAr}|<DMBup+yycVvOx(Cy#fX<~Rvl%@ z);`}-J^w{_em$GGj_K4AMZ7VKXwlc}3N0@hya>rdAJkm_vY5#nEpeVF>~7=itCWnU zT8YcFCQW+^Mi=(A(|bP@_g8Ez$RttBFLQ^9@X!hm(F$+JtukrzB(pFkl!jB^Gg6e? z+D(;tyA*wUaoFcN>9=vN6&B2-XF0bPvh2SKjhgV`EM1Mt&C}-U7wW#hUd32O8mjS~ zs$6RJTfcpkJ%d81Rk4KU>eRCU&+<euSsaP&hcXf&7Sjt{;Zb<=?}`9k<fb14ZMp+= zmbFb37gl2gK4RWgeNBJsp)2JNA@M`2di`em(Gr}D)&9G$`E)Z*?9JiHs<pVZZFchY z)H&~85}&;)vJ-o+q2PVWG|{(|!rpoA{#x3aEt9G7iYnQy08OQmZt6bbu)|7WtHZ^Y zS!%s(<3fqki4-4rIcycq%)2DH7>g<BKDn>C`~CSGp`f1f-CVPwdzHi<GA|onyOBi` zkj~v>Pd!zwGcIcTosw@w0pIpyl-1x#+g;V1FO-Swp$4T_sxE2hJ_-NbseY8UR_OC4 zCcH2^oigH3LM1pd>G<<n@rmO*yY+L09{Q>!OY^_Hk~{+=XlmK_iHmSgXbJuL?(}1B z5>txiNz?7Ud;=0JExC_IIBX<OIGEmEzO8jEScLn!!qM{;-Ur-UScI)Eueyxz3b?#1 zd7lry5fh+F`(Wi|eUr4%%F9xS#Z-<qo=}pM`+C49BO!@$M=}0ODjd}J&-$^Dk+2vx zsBkb5g`5ly#C`snuKDR|<|`4FRsCo^u8yYXcPZ*sNzQ4>i7nt{YsEJ=orqBTV4eIX z@DzV0hO|CD8HuG5CU0^?`wGd7=bMh^6Rr4MKEbEnGfe97@`iQz$C#SFr8*Z4`*K&S z^{K*3Ynir5J*=6a@}NL`KxNIr5hm}?R$eY9Kx<&b<z+3>Hc`l?I@r~GxmNZIe{>*j z<RvrPE*~~9(k@)J0>|@YB-beFTc2lFya7YEWYuYC!j6#e6fTcyKO&hhDh~<9e_k}< zVJi2Q>g@UeV0$Uga0+X}vD^z3tUnfs9%cTtX30QCh?m}QPfG|;@xf+!M?N*-eCqn( z31#q@F1jY&Shqgv=7yT@gX+jY{NCE<xfDzz%}qEF@*i58B(=1}o}FMLGoPEpl4t$c z<%s)74ioacRSFT<lN`Jp_vQVo2Ft~6(yu($$6g33j}AT)A+E<oNyd`i=%%&xp2e^6 z90$|=4*&g%H-L)z$0XLmbH}j2Q=mE>EbvR!vQN$OIHSo^xb~QXL*<yh)VTZ?9vPXX zb3H2+&5RzlJokgaUl++J!(L%_w6s)|dq$l8aO&&8l5BWL0Q10r*iGidC;HgSZyjF- z6BfUoe8wvh7V^>%7O+Lx4Cc~bI^St+<z=fR)Y5cf=+>GL8Ku!&<0&mu+Djyyu;6ZW z<I}pVA@vGX25Q||z1t<6HrX%m^DVhBQ%Q0P*Hk$;-WGdDp31kf!el=G!^kxu5Fadx zxL+sZ60Wpjt}%hJGB;|PYSCUIwkRs_vX!cCXg=9Wx5j~l;G(sa3WTt)CHFR-n8uRJ z%T~hmbJL0M_yyij2zrcQWzIFQa#ShxdJwo)+*6fl4lQ@Qf0+JJebA>@OX%Y~cJ>3U zh9;aKsSnp5vy+kBKZhyb6#7>ql0FQ2&{LgBDufE*+T4uOI`K;Yw%b4kIz&adIPgY{ zpQd)Ya9@I-x^{9BbHa8fHV`+WVS*A0|HY=394*xuN~kI1yw-y*SzhV2>H-(|thka- zChqAxC8?d!!@A4-U}n<buNXAT%ROHcSl)1NX~KzD@w~T_h;(A073#zqHYJ&ibYrNg zDQyC=XhDIOwT!9$6xK|{PR!Rt(wVS<jwYN|@&X^E6J$sybSt1vxK9CPaotJyny0dv zgrz(4<iIBo7YZa&c;HJDc96)2mrdG#HwJqfE=)$^A1Vj0194dbFkF6-#T_z(HSGsg z;xQ*B35#FDxT)6L__N1AA1#HJ^!2eOTz8_su8>SP4s`yXw!S+M%dmU+@jUj<Oxf8x zBYP`^DA`*gWbaL~lD)S`WJI>iQ1*zBy|c4NgzviV_kDkVeg7QiKG#{-xz082aUS1L z?HhY^U}A*Hs}oU@Vq3|p^HP$oKad~N2lFHYUs3>{_U*r|cWYMV-lD6rySPMe3P@%V z4GN0m0R%-6NU$l8IvH`_N)j?rjl<>ox97dtHCA**YV()UKD1jQY%iUm5}&wnYqn~q zY(14sp%FJ6sm2|BX8!K=lOh$0B4&y=mLXFcxDH%XLQbRY8Li6ff}b>EKd8Ln66MLT zV{0^9Yd3ITHICz$ZpwT4SZ>#pyVRp%c|<gHM4UI{^hd9Q*oTL8%7;v)f90Rswo~F^ z-Jv)ZkceVcxpHJ$$9r9KKzN*ZxvMcLhFvLx_49sg+4JPGF!~I$<gyt04C`dOK>Cd5 z$#$tc7nV5!Vwcsz?gkHQ?b1u1KFuu_n&^$<a}6@KfBOzk(1jG=C^sX+d>y$ngD+3H zzV>_Li$h(`@yz*gWHicBx#%VT8@JCBn!=wjgU?@vbh+W3RyQYE*SV@UYCoSUn;9J} zQf;j9nMwmRCw1`>{+(J~KSGNa;-ykp)zi<8MRWabYUiM)Xe!RBhyR3+tp&QX2D=Yw zC-Bb&oSi4ScT|fqPH%gRCxL%GOve#_=iI*LDl9H=(9F<`DqAe+<!9<g@<`8-v{3Y4 z#iFkO9Nc&^$~4KqPEsnDL9L8Lx+?3C5a%jKGoqn+zc)||w)ozTrdKWdWmyw3>m(?J zF?>sMk3FzuU2mCPJ5gh4v50ZzcJm9Rz^ipNdE}824*||FtCz^=pn+G;%4jUYZzL=f zeLbN?kVz)zNS?}*aO%A@4)!oJN=YV=g-QvDJnE)DWz7l3q<X84=GsLJ4lP2_U%?T_ zoNch$W%zd2NG#&gi5h6V(*>#H@0Z~}nYV2;zA=-*X#GSsZ~eZ-z-Gml2~FN&iaz}V z>Xv+hpQOYVL4+bVWGT+RsLRD-v`i-$#ky52sLMk5B6ek)hd2^6+AH3&*yZ?h&u_Zo zEBj<LdFAJNis%Q&mHym@WSVzcbPahb-zIwhsK`@FSLeX#l9+o^watb;Nm8}_5xa79 zpt6w4Vxr})%iy|yexS?7M*?4yAhL*G)D8AGe*RQo(r|xOSxGuw_{<umvVqTKXDS)> zeFT$Y>?W7pg2JgTfEK4|x>Gd+Q_B_ARRDgjcIlrYt*G8gtr=<``D8nu9bRBGRO)vx z+1;<|L{xW*pnq!3+*(-BHYcoLw!75N|I3NE8tq@t#a}r4iSC8fc2$miWT)h8I1LXc zvW!h*8&)?2svKgjQ{%0*7zCJaDE~>_c;22qCX{Eap06O-(6Nih#2d^#KkQ)06;>0d zKgrU;giC08xF`ppX1n57w3ph3D;!}HP60|})G`5N8jgs|#li)|`5n^*T|TSRZnMbj zhfadU3)nVK`v2CfR=(S$JKWuIoHshu)S50ds^z(Vq?E;Sk(Yie=@riX+Wxr*SOHAd z4>WjMyE)y~Z>`t&u;CjUgwo^6a~p&b<I0C}%ij!P7)>_Abyr7jXok-wAc9{3Ab$f~ z<KNiMM!)&fHPmCv&U&x6SBDVm)59Z`E6;Wge*p<9Ebk#M;<CqR3#6(5nrhexaUfLN zbfa()iaJXGr<POxS%h%Rw9p46EcrLKPZ1(ZkDuft+svXyZjmFRja18lG~(M^=t9oW z5VX7tO@k!W>)$`X2n@zdxrHuf?5WUo&N~tGN8}-TZ?w*O05wt4_oZmzek&^UXi4Ye z;^Bs?M48CyJB)FAD?OFJ6M|*?G;b0d(H&FvqM1z6t-EQNOy(deQyN$^{1)mgfAr0g zmgz7hopFVf^I=QZ`ur{FtqUl4Z9waN`@L<Lg(aye!T{R^n=f!DyaD}Y+xYh+dvg(K zY8P@e%;DQQptaeF0754|JZnlZ;&=PbI$%}EH<F_lH=K9Q^FSl+!&U@YijZXZPtTdn zkI3j<s}@2{y7ka&vrUv8ACV^A*;RShPb~MR_H940PM-a~eH5AOaDLJh$%;&=BI`y| zJEqkC)E2tfz2eAZ7(2{J;gLPJ9!)YlDomB=&h4jVcQy7kAAdug_M_*&x3;&@g*87T zT4v<FAxkeXo^r`xa=B)G`}?514B)LZ556If`WQJVa!u^jt-M+rSGYY7IwjeYKWuQ} zOj4d0&@>8CwsdHCIx25OuUXy0aKC?9@MJ7I)wNJ|VKdv{6l2;KO)cXm>6iCPGSMN+ zJeVs+ok}wE^J;I=tFI+HmD)J!De-!|Jb73iAiX!F%$agil8Nu2HM<6gjbN48*FWo5 z;r)af4Db5q{af@=BNo1vPWjDG+`1?F#WqvHGe-er9Mie(#^<u~8)eT1a~$_?n4qPQ zQ2%;)x<#R%-ErW}pI8}^Vs!QFZhZrtvmVKdxdI*)<iOvGzId5K$tTP4bG<Enibl!k zEqz^vFof~vm_2rdYeP1*TMP26;_oq(pQ@boA<O<TDoiVeM;Ly;M(056NlHiHB7C>h z-yso_dx9JYN7Xf|r-+5zL1zG0j;o3gZ9O|{8fbwD^F#Sa1Z$Ux3<ly+c565;BGWJc zha4gD_lh5gIbV@O0+%I_z~ux!V%o~cr=far;ZEM@p9dnIU-e`eX?4nWr(Wt`owgLQ zbeUs0^t*X}T65#SX)3-*0AR+V8h{sz+yELZ*#X$NM00A^6s<GbXu;QJ;~j&KsjIe| z@D)+AfdZ{UeoC0A(huYtd~~$;YC8^Dqi*AD)0U(^N|=!x&#OEv8PvX29Kb{K!+g%5 z5LIYE&Tf8@iOeXu&>5>hulOYOFh%$7_34S`ho)mf$0<b|*4?!PbEo;0*A#JFLk%L2 zQpVWY+gOc*8S&qL*C#WhXOMbu19N17l+4U@H6Wd*$>!g5z}vk+J;F|nDZk=x#Wu$l z^e&e}_{TX7Y!}71cjvLGAEl7p=XN(dc99>eIc^-7sws3UwgLBcXv)`=#ZT8WxD?SJ z`TNHigCBpN3n*VLk{&`tkEfUrQ)p<F_=GlRQwwpV<P30#v&Z=+sRiYo;}QLP+_55k z64KuD)a~Rf&&}{xVB1+d-tAa+6DKp2`I;@|uO^|N4)cy?sB=3>%6qx<_h9tSp#GkA z!TdeIbiQN2^HQeO?sq%p(KJP7jYfh0MKs>+VTXAaNQe^>^3P2_CZv6Qb_c##f;jI% zLYJySTQo=0?hwAS0Filt2mTjHHsj*!b@TNVXqPAAkjxd__^4QKH+SA0GHNFblUE=a zlZ}@p0z5B9>vnapOnfrkhllCnxgGpHd_DbQY|Uj9x82n6&)!_S12}l)q=qkI*fJz^ zcY*kCE9U3^f;Fi*&0{1H$sTL@438QuEs-uy(m56#Y1UgSEcNEO-Q@a-ptFD%IWzwj zc8qDO)d|CWen>U^ot$sBdFSUWBhf)Ww^D!4FXq~xzP|c>#hBO6UV{>y4|x1-r>U#2 z@J;pYf>lqo6E>xsrvlj1s#9#v$Upwv&?_>Y49yFs*zWaPolI&|{)@8H5b)4R!?Gx+ z#w9%5-qs3x)ghtnGgh#`r#@qRY`E&^TVhat4sBtAxnTFoXTZQ3ri;R-&R0)+dfKt7 zBm0htT;Jq@Mk~EnU6zR&Q+Y;E>-Bg0b;P^_sjzF{Zu!UCVr{3R{RA|J-<`{U+pnkA zCHT}h%6IuHDwZc>W_=blF!^$bLr@<te$1GRN^zYu=^^;TH8tx{;Gmz>ioVt6o;AB_ z-K=lM;A4y5AVlF@c1zwqI3)A&vu?Z3u>ycbL*c9)#Zb2vd()U<r9*@%^rkZwVFW|y zOq{%VID^Mh>`n9TFc&5KyDNj#B>Bn22REom<nbHu!a9lNrQ%_pRftE?3F5J*Cb=Vz z-5?IPrbDf#3s9@1D5RJdR-7SkZh_0N<_z&mK$OfOhP$1y7mg621=da%*4a%W_Xnn9 z!So=A?nMmgrR|5<!yJ{~PRVt5!L9G0*1x)tGGA)gb~a?2XgT)gq+B;E%tH#faG(rx zTELvRu(m;lyUwp;6=9w@*a{h}Aqmou^BuCK4x;1sI(7)|N<OUTCZs_k8RqGMc)FaF z-hPqmUWQxIVf_@4p6qU@b?7$~g*D)_w_Nvsx_$`7`rt3@4-(cE0ckS<{F`_T7jWx+ zs8tUh0}KeAgd>6i0}(`A35S6d9vXVc5+OVSe6Z1WhP$_L87QF`TZBMrDd9$Js8Jgp z3vk~rNFtZr3wb95nWqN9GVgYR&`h|aqrim$g7da~qc^Yw4kXM25B4bBOMJMOcf&d> z8f58Yvf(iBE<##T;K9{{`^FD*iT;<X<rU=3f5PDc_t6k;XNHuwypcEmHIqw3Z!!S6 zRSxT#fOT!byw@-UNn_>`EEVW02@3_m8o>~mcMHS388FuXHA%;BOh3e53dlfHAjHMH z0&&&BT+=WY<NtKQDcceZam~X$*@eUI0ms(|@Pbev4w|7Zd4VniXJcTEu#cxO7uNr{ zg5i!cz;;2L!x^OEv7&={V}SLH+ec_CU9bboutq9Kqdy!QY1n!WuzpW?9o4N4tG0r9 zogt0?xM1FBSTD$jm-fS0u_o|{@xmQTfqDC2AIM<|j{gSVKME3R1sUHNI}b}Zz!G(^ z#Pt6HMgt4+z-Hi1w!&#A4NK7d&wX;(ad_N}zd;(M;eeIEsd4MSVQqqENC4b@X}J42 zu*D(R;u<x{^6>4W=GvH}Gq~^Na1NTlEafoE1l*0=gb_~b1m`E+Td;OaII{2p<p3L< zhb6#>L+W<o>fp(|3oAH<-Q<Kia|LJ0HJD(8&9d{fe_3HQa7?aY59{Dq)4(Pv|7Wrl z);$j^+l8BmV7UY+mQa#}&E-18G=WtuoZ%WCyKC4dC}(aS7{jp?4(k*-!u0c534n=G zFp(GXX$}tUHR!ioceCXGIsm6x1H66=!ck9y42woWHYUJQ0-MZ)CkZ@>oZ%reOo7|s z7#@o<eSassK7B_RmM4H`@EyqROe(}B3=f9d|8c=W127jJ%mpv)b#PD0{>N1Z3)#V3 z;xHGy%+kS&q&QgI<Q<0V6#u|0o;bYVbilk;5U(#f)Zaf~W%^I_m0$(%DoY3FPa`bx z2M+Oz|3zOC3YfDx%nNt_6kbUi;CO)Kr1lPOk#v@VRd+yHcmWTWKh)kJ4UuWv;5i~J zJ(7Pa1L=_nhXOzZ>yd}Y<1WPQ1)lrC3z-z0vvggMwitL>fw#p29N063|1B6KkaDRs zNLLQ%6xfmA?18sOFQ)(MqJ?{=4lkO8aR2_ZbW*_<1^>qdd)@_mE&xUBKm>Laj;=Zw zKzJpvg5@J%FJ40JzJ#y~@Tlhicj1*_2=<r~o&o>a?BpQcZFqp8T>x5pEa0sMX0m{T zL<sR)z@1SCfr0JGLUPn_2AIL_{AVM9yoB}?c%lCPZ7bmn|LO0o|Mo5#_FDw<^%3lr zHPnR$I1N((0bUHBz!tKgX#QuLrG%pi=lx%j|HYggQj!hFlo#$$3e3d`k0OW(ye&wC z!CK&$>%cKBf!oL6$o+3+^5uqfwZO~hHN46V!5SxEjkkdWyx{vne)-;kdEubV!z+aY zJQMKnyO~`ma%%qci?Of#Chxu*Ajf{Bme4VtYxjoap~Uv6(<*K*lWhModklX5ksOot zg68}iCZj$szuVv+G3j~Vh<E*<cka%#!Burcqcl=8Fyg_eY?V>4D;Ga_S7knm$*7CV zk0{>NsOqR$Y2>(nBr@*(7HF1_w>M2Te#JC9%;hH??|P@|2&FU<H84WNs4RR}Mfx?< z>`$&{GUYF6dgo?L8^0|#6l32Tq!!TKkGHo@_QzNLlB9R;#I!MPx$#_S#IAotonE<4 zNJTn=X?Bq7k}TfUr|M`_X(TMIAV)U-53m%?H2Z_=k}KYowd%+PG-MRyD8~PJne30I ze2!gp6sa^~)IY+@piBp}Qz?Hb(K{z++Q{!8DQ8sn5Kw6iWtyGkx)h5)`eC^t9Q)oU zwP0Q${tw{AQ~r{ocW%eDF=x5q5&Pa{U}Tq3c`dDAUOs+MSfw?FX|{{&5-r}<r0OV4 zY2>nhgfsU2MMi-RqcRbnO1WG-?Th3qY-R6QJ#V4-Bh#v*TTB}amK!xnBVDNl(huTk zZIZ9hmCtpnjtT)Otw35nUTTi(vO(5+aywH@#rgEZ`)O?@EDQDp&f)itoohNm%AXN@ zc)n)Dp5C5~3sD*64{@s1DP!~R-Y<VQkwTVJ+!M__PQ|31b2>d$#Sb1+5RmmfkzlfQ zZd+6DbW3~jbeuZUH;t$AyU)Z4l~vSB{0Jrer#i!Z3dkhm4AaYQKY=zKaBr-94W9P| zYF^LhA`z5`Qbh~|59(JSF2WGM;s!aQp2?IH+)SIb)dAL3v<$!+iCzm>Q*eF502AMy zBxs&&(+2n2OF?wtwp<0<UjgV~I_p93x$adgii?#gqaCTXYCk@p+(dt@h11o`!~XP0 zI{e#Mc&USW$nmvn6Hes-J=M*9zpDjFUPMlV3<rmX7Uexz{|6X|d$P6QE<b}_9H>@{ zHTe$&UZ};+!}vUmOT%~;@R6mP=PAOJ)Iyj8QGs*73PVZ=R$`GOexXlB;v!zY`j<sm zQLE@Q;W$^EM<&yU8`$$kKa))Ry>)znqaU+`&<(;fq_ZIHtFCKl`%Sb_|C|i(HplA; zHA&OtT@Ov7o3qyqAFZ-~HqZ(m_XmqkY<<Y~B3v7H)*O;>cM+KRDWE<%qY9ejg@pUm zBoFZi%60&U#c#_-AQjsnYQLF=Wo}OcI1YL1?7z&h(n(e|>+OwEus3Uyd&ip`gfW}Q zGJe{1Gr;ziRO`E*qa0h&E&Vqx9rJCV(K69nf$jIlG=m)MZxR|Vw~61STG`plGm;(N z9JEt-jZ|I#ZlCW-Q=ZGBz}ALq^YAsYG}jVFA3-PujHX2&Y-9@?e>v@nQ!miaf11m- zSnuF07pdf5ayS*O^N=%zIf(qn)N|$B!|8f199K^RKzL-oR>?H{d1sVay5wSz>Y13| zJwE11_MOiAY}5(lev<3gVobKLSUH`E1iB?Wht4JweqUpj{y?$5n3)mTl6j5fdLIp} zBe#H;TumJj8i5#-Y1kOLMT+%NOTNpccsFn>UPUv>p=oRAFp`;K(A<X{wuc<v%#J>2 zIB09Zp=@fE4spKey7P|le#hOoNFtY*doL1QP5#c?`6(6%PSJRyJ<rRPiTm~`w|^(@ z)875v+1=xdjg4CNkns(a!Fcr(6V*2yUxbsREmdS2xWfA%O3pq;YCG`;kKja7@Mizw z$2HvdmQj9O_S9M8IM3{UoM6sVl4!4{0?V>Fd+Vgs8t2)Obo=pMffwXQzo`F-TpQ;e z=IM)QGg+VSCamRx=gRjoCq~(BM1`O#d2=ZgpHvm=6568GRHDVHgQtGlbQ~0>3P%Oz zXqF1n6M9Y27X{Mfc*DG?BHciLDkbQ-AU!HF!17uu%n1Byi%6>o*(2kc9O^ZvE_u6( zcV~X1!p6<MXY6g-uUelzoO2@Hw{@QRt3hjC)Ur5H74T>NRF*yzl}`3km}ezd3qM1J z?ipKi!O@`+_$(B$hh%1m3?>qKdTsqLWH!{Kf@Dv^FK5PS{~5tI4-;Ddva9dWwRPsN zg=T5u%HA!HFpmqRKap5%{#NybWPpRo;yzFO9Ve5P<=!q-5^vSV%(eOhzTAV=dr22! zpL~%Te{nN<{it=MZWKvOe{cr>TuvRyG4%Q4%#hP+LQt=Z9S4!C;a{@cgQR;&SR$V! zsEfcOq}8v{oG}Ov6FW_>*yY|k1W9YO6APCi%)y#Tw+ru=T=@)aqL(&ov&m12-|KI3 zH!~8|s(pMe63bj78oT(B=I2lINW8bs>Sbm)roVEuJ4o8_rJF;U1Aq8FAW%Ux?&sU; zB7aHJX@Ul3*=OLQ9(P?exU<q0N#MhCj!_?6smt+kBrB|8c?6@Sbeh*zsYle{3DOS# zBJuvBuHk&yTNR8qWBr5sg3>Eke)}UuwD|rtZ2ImS?^?bwe$&b-f5d!ye_1aOEz%c> z$47r_&>RgDy;p&{g7MO!rsDmXplIw~AEEd5OA1q&n5+&Y{`zMZz%A)ci>q7p6EmS@ zx8T~SeV46waIGEFbqWJKyRyva3?W#r!+ZQ|lkJJ@CY|>9b18ks+&Z2-W~A6d0M3ir zWU8IzVj^6=tjJQT7WE{E_i$=!hN&N2o0xi;e)2br+LV6xo%_-Evl|vc(b(_%{Ks}0 z?gc~}2S_{ma#CmsP3q(peNNS1@ifrB_f$Gy<4fOfzhOSD=4a<dRjUqXMZeVq0?QJd za1dd^_2_N4@D@oBwj6I&5Zm*QzA_`+1Mr-10-K*<q#%BI@w77|?2987NDy_CMSwX< z_^}~!K|Av!zz0Ncf#2J2e1xBZs1IU|Rzb{+B=8Y;Q#QBmkEz^4p`$5tJ}r==tR?yY z3`}Ev10pA~I6P<-ej0C75HIOeyO<HRM3zAJG#2GD1O<TMonRG57{a(7B=q$;3|C=D z4#Ss*NYe|`=pa+gx;2x3OWPAq&=m`_UW%U;4F#sNHLjd2c-3vvB&9*y0nN=h_iMMp z$QF}i@fD?oXa-R)XA>N3qvmIx+?Nxa*_nm^hIhiB?i{hEzvzkVc|-C!_CxXgo?w|| z9fs$g>7gXDqB~(_b5`j-LfK*qTSA_jZ4?tV0FIBl0hl~~z4tSytkeslj~_$yF#?D_ z<}2Z5TC({7z{pJ&0Btud0aV$f0FcuLEnBPtqlQ?W=9f2rQ|(1YN?6HP&hbmU%xN5( zCm+vh`a*?W0;*~UYOWF}2~oAGd5cQ#hRPkN^s{RYp$GKhw=4Ss`ptvQ;!0k%-D}_Z zgp16;@Tlj&eu*=q*YOHGqVH%lBu59(2!cKkF5i(+Jt6FG)vt`lBk5^-f9m<wkJl7q z9;(nA=+veZ8E&Vhp9Bv5#`8P}zti`{ql4|q605Cytms(Z<b@>jmUSzKja-Mcbg{8p zi@(ylD-_pscAyb{!N<TKxvD$UUx7Xr9$qZ_U?#@qHJ9vzn{y@2SKR{1{-3X3(P`qV zjqKKcZaf=X3Uw_nlr_Y|3Vwbuy668lm}k?6W?gzE&z~FXT*c_;kYnz3MdW!J;SrtF zW9h!2tNheUqPI0&d>3@X0n}C3XqV3cVO=_C@;dM;0YY!j^??8U*dO1_D<zGp$77@z zBk`Zq;bMPt`PeK>oyF^xJmp!_km)+oAffNBYfx0L6@bCPLuTv9Fbo2Rg*g$M<MbMJ zAsfu?qOPNm8){NrD+`eiEJ{ikXe5f4Fiy3C!nB*#TR8U4a635bgfU47w72!>P!5`< zS8=aWW=D|mi{y3Hfr6f6+}!kN#wDU<4`Xnf{v=cE`86-Xa07x=#(vHF2SOu?YV+Ii zBM$=-U*C%EOju(-{$_Nd&)CO2yS)%|w)C!|m5;H@Jo~Mu$mX#{*n1_77_Egy-`in# zd@7T}zmeXr<=S1S_{oEdb;SFfV%3N`u^-89NpwI<sH7cNim{_SSGcZAe?uyFQ#Wu` zJ}(+WLp#=$3sD*W;Tal92VR5lpn>zo^DGq;<+%5fRv)&rs?3SH?>0I5EWc4q?y{e2 zAQaIdW%nU@%c8O>{7tmf-m~!JyWbs4UT@o8rG?*lZ!+HW(h4olp*qK7k!U76`cLRh zrx%|Ah*~D4re7l>zeps*UVk^F6ck8%o(WKfj_I$$Zjq{!Q+lW%rb`o3iEc+4cH&`q zlXxVW<PkZHE+a&T-Z?-r`s%poN5hy;=ySvHcZKE!FIGZ~+&!x_Wvtb4wcEdH%;?lu zT~HYtwKVL%{eE#uu#WNaN78J?A8=|O5Qyj>BiLo0pE+Hmdr*3IH?=tXMB?+xT?rGX zUkAme<%PyixurZm^le!at90f$Gdb=NZDO_GRpu-{YbJ|Pxr$x=_<7({te0)zOT1_+ zW2eepM~M>T<f+6tzEVROfb<tZiaBT&(g*7Tf!nO<Qis|t$l4!AX{gE^>+^oJN%wRq z7n#~%z{9yfN$ZChd)b>mgs7bNJff1Z^tAnFS@7bZa8~Csj?p&Vf0Okcx?rEM`TBF# zF!FDO-J=%YG~J)w&;KdCpVZ1fnxIvB{oeAySqBYzIimfM2eAzf{kAg}gFM2lP8>T8 zi5>7IIF>nZFGU=?27~xyfxEYVW!ioK-DiLNJ;v90PnD5Z3v^B7*0L2u4zl{h|03&X zxv@`{xfStVUGHf>bN+%AUuUlP)0|W(8_VJGRIum%_y<WP?j~j%qWGY?Cp)tI`xK>; zN)&TnYOp@qAs)A0+IsG%2NRWUrxWt4ov4il+*YAo#9z-?+>b4n8LzH*>GndnW0dJH z_Q{G{#^d{MI{lt#V8%4BJ<pd<tvfz+9%kt8`gOROE1w!uvNm=<jL$?qQ-_GA-B;Gh z*v}RFa2fO97HG^7i=7~*Y5%tu)bwDT__r1DiqL~FKIu2~<v3!m|3;vDsr_K+Ct~Tg z4J9=5my$Jh0iw@v5A-2X?brztn!L|cdE{aWqyh@IuHhdn!|$yV5A+k;{a{GXV3>L* zU(F|9tr+&gI#dLoCNGI94<)8xE1;n08h+0*{Lu!nfgH--Uh=fUAP6tNkEcXgDwp?@ z@{d3pui1);g!~7q-9ZTgZRGk%O?!;Hjv(!aN(I=7D^lFi0qh#bAn!KtBBD_SIjo@r z<#0432GYAI!$)S0V^zZgrm#R1EGX?I9|uvdXz)T*?*uRz5>+6%Y_#>?rC2=F`wCVI zi)j8@j}ub7JGBPN9ir}Ydu=9OTIgY|dFm%$*Kc?sCnJ>16v6}#R}%KxVm|e5$)iwG zQXJ*HIB&n9Y7WkS47Ux5_HC)lCEU=-w@gO+^VzecG7mF;Pq(y4R*-M+%`H{rvzoln z;bA~{ru1TgT<B-_h@6$)zFz^C{%{$U8y8N@w>M(Zn3%?oyAs-T%Zkhd1t~Z;(MfK3 z-p^X6RY|h^smvq+C>FPF0*V~0ZWK~?gGmtRw>I_iQtxo@SZmHOSN7Re`TR65p@?}= z#?$*Cg3>+amSD+S{vzM=om>^#boaY^5kyj<Tn%_1i?nm<54y@$vo=5F{e5ifRoGDS z!oOdCZe!!k{PAVowN=CQkt+KKNq;dGm6nxB485u^a{Y!E`0_iS<(DaU1`m-Q)f6wB zCpRK>5By}338tp@o~`b-?bC^1<6LH09C~2v%}ROYZ$vbkcRpK>*pa}DR*5QLtv$R+ zFGU*m>s2v_pG0)FZbeMjC@J2_KDxL0S1GAJ8DIO7AL5N3R7s4Fe29I6pl_pEQpvQA zdXuXPu7c?HzNzF_vdYulH&(ADOXO}@i~wf=E9q&q<Ehn{l$y)4HE?%Ln7PFwqb;jD zZ(1$1Aff8FaG0~ZNy*221Bc^;#u1GJvCzh(gT~a_Qb#{2=Ewl91BcWCGk->K8W8cw zJwO=7j@)PoFujU0Fn&>ipKkc3#o$jy<}hRY{N|$<h3e)*oQ_6hXYc(d?gt%aq5<fV zR=JhemPMLiM2Yby5&M=xO4ewJ(~IBsGJ?kx-fCLQ+7p9mC1E2a4g++l?|kFk_S{r= zMo!zbE~QELJrBl|YRgSr>ZRJj3+h{GuNd)56Xd{K=`uEO?aeNC19M!J$>=Ps%YL@* z_+$+k=;*{ookG;~T=lx3BHpS!Bjl;h>%dP@AMh^M8*DFVGVt&Mm$k>vxHQ3${fsPK zb}3w4yD9PSJ^_0~H5Pi``6r{NV@vT%BHiX21p&RfV=oTfxIdvDZcT3UR{Q+>+~7K+ z;r)}6rmH+vis~%BPq^DRVKv<31cSNHYSBybxION`!zFPyRL9Un_ph4{4*$)Ci_8a@ zs1@70RQRzC%rE!|KaFuR2cvmgic#SY3kY<Z24*+>(z;o$JJ`<z$iGSWZ{}L@z#J5} z-Ueqjyv4^{PwVS}_+CNiBxWkn2QHE3>+#knd`29mM|L76-|~lqL^ZA+Vvil%b+Y^@ ziuqApe)<F9pELC8uZ7+Botggh4Y?S4^JttaH;(*`Rx!az^zZ%TO4n6Tj(jL>r}L$J zc|-?Qkn@>ycF1YNHgKeG=i#Y`;kkb9QI&fEmI#j@jmEiX;|Pi-<%&+@+^caUm^vjJ zE7AY!rz2CKL^wG+FXd0u_YbZnYsT}4L}Oc>sfLNepb@(1B=@-qVPt{yXU?6batGwY z02`etuh1F%#H;>K)ra^E7;KjfM%HUy%?P&35Evn5YyJwF#ymMSyz>F2WdGi?4Y_95 z6rYz5Po0dtZ)ltwH;xPvG|;eJ#)5W}Wg3}{0{4O};rT3?jj)}E;wJnaXa#h@qVKzT zf<*tHK%3_mEVj#3!2QmfSL56X$SZy|&pqM?2-YvZ?;~%Fzu3-va1B^fvBc33Gt-&+ zE7wZLW@HYKi8F-87f8o%A7VDiS%8%9GvprO1JMizc8zm$fGlsA<sPX5ME>|j?h!L^ zrfW1`W<v!yLp0X)wjlCoM6uac^GnVl^Pz~wxg_AEJm=3nx`5p#mDwQLhTK*MfvSew zmc~dl22OZMND?F(TLa`!5|wED9C#D2N0@lk4V(zM;?nT00X^D3%KYrAz&q=gthNy} zNZ>`w=9dPC1y5!JjH9^l%Kh9Ua$qPRiWjZG3XDk0unFeFArK#lY-6;CLp6}aR@}sv z5hzgJ1Z;j$Kt6xLE%&Gfh!Ir?zb(*ec8&Rb52S+didybb8m!F$t)OcXjIXZ)s-O!} zYcYrRP`v`yhL<SS2Wul@ll%b?4L>?Izgk!=D)$Hj$n&NV1&t&?-CpN>TafR)VVw2y zA(6YWH?zjMBZv=y9!BCHOQ`F9=!t*q;jR-TN=*S}zYS1{f1ojn*TYNv1BIu^A3yOA z6dtxSbhg=P$lxUzTNX$|K<U@@E(TAe=z#m8Cj2?50*@&u2CayPyHHmC9Y-rTg(wy+ z=nt>$fQZ~RF56`c99kg_?;L;(oDyi9n*gs;`n_|H{Ndrl%smPK{pd<q6l$NQIQDwJ zZ$h;dG}41dV1D1`-RkUqbUDZT$|pw0w4S;@9LS0Im+m?@>P;;o8{f1gfl7!HY(su~ z&$!)M9UVX4&1VKgv=}NrT6xgpr+2od_`FNAwKaClTbL4#dzjJ5<ViJ^Rtn+G_r-_y zcxLT<k_`{X-nxh*DD4zG;^cBnM)E3`JzlI)&4pgLEUc%+3>XeeYc<MGtzO(OzD4x+ z0}B(kRCB2DOQO=99Bik+kk%+-ETxbf9H+qUU(rF&_T_wXJ=}?x5Y2~p41~kttt0?_ z=4rb#bol42rYkf=O;f~<5U0O*e)pged_dx1=Q*%_;xuP+de6Rrk2%5pI$O}_Njy<j zT!D4+P|K<Jtp^DK)RC-poxSCJ<r(3a@e@|<b@P8ZHL;{Fc!bBAll)F*q5Vm?>52OV zhTSyNTxkmG?d1!((d(g;mO5&6$pG;1FX^B$y9#w+!Tzq3Mq*@xcY@xzH{cEywZ^tn z6r;+I>(2>R20Ly9@7%pO#7xg_H{S_Vw7(3Vzk&EBwGxdWzWVkDJJ3wpTM-eXa6#01 zr=RREEktknMT5q7bB?P~a`^dpW=26U>yyzOmz(iIW!3)(V|7JdT;9^O!g_Qdz~?%k z$uCVRKhe~akrVRHcCL1yJnEgTRBn6@^rtl5!Si(MWQ*tQ<@#{e-G#H)txw-j|LU!s zkDj=${nusgcxS>%i+FwE3%ExvGZ@m6&9&N^sK~9W+^q-#H_slwhP7lD#9yuy)9=yT znk)O_x0qCQXMp!FO{(4MP${|3Q@fVMtdG+Z<+_s6GX+M)XNwy?BaijouxfUcQ;;*~ z%v%Y|H)lWa7b3xC?YRHsNmx#1RMm%S1(`^pd}gF~g(&xp;rHAxA_6~8#wTOnEGml) zZ;GDKx7BAfEvtFQj2zcDfKk9auI~e(RC8-z@?WTa<s14=+m<+f9oI5h>L6&V%{sv^ zLA6@Wjxnwtmac;~uFwVGd6VWq38LB7GiEcoARvd%{^y49LYuv_V)J%UbGP-7Z->fb zkEYwjty`0aJP);oBwaN(7M3qjHhJ{y$>DUrd=I{neQc}b-0B)z59*?n$0|RJX)8p0 z`M!5PN@J3BwNtOJGnbjC<l}9%xfq;48fV(RKZ<ooz$<Y1d`a)3U^Z(A(bA3bDd;)9 zq!25&_Y~ORO&GMk(Aqe7QS5zDeeZFSBUhpz?{NiQ7fGl`)YXCZH5DbH-=%%zBh?q5 zvThIUKKP1uq~d`~NFNscVF3|e99BXdJruBZ`X2g_i4yvdshM3Q@QSbL&d~(V)`{@H zioFGKxzSH9Z&-1`DO+fbNTFZ2Y3H!ctm~}2;i@N?=>Ch$Z2x;hdaAUac`hyv?u;ip zxb+Ts$P$P=i=Xczpji-US|rG9m2~+P6;d@mi=^gd_aSU7r@s+XqTThdIs3zEz;2|C zGuMY9s(12Km*Cs)Z__!U=(;T^fMQ6*f{)7mZYjtkx9<_Sr*-~)7r`LN)HWp=3RuMD zt(5|6T(}5uD%_*<8PLX~7t7LH{q(Pw)E@7!zKH2wMXh(Ri3Ga2ATh8iY#xD5j`u5* zQMbJjO?!pX<t?o5UGyEn2cIC1_hEyNl$+l|9DQsMn)&c6S}A46ZQ%gvoiyeS^WG*h z#gwDVLR42LM|R5Yrt#ngb$#HmD<&3?sbAFZHjL7`yAVM*8AVw^JEXAcEH(7qX#qLD z>`7?Y=AGFVgr^&01rrn?f?$)di`CYU$yv*vh2R)ryC+|@1RNszq`U5mnjU9v<xy9# z4Z1ugTt7ZEN<`_OAt_e_2L+o3oUOvf0wnzRohXrAZ0&J9#pVA7nYxCYyM>!RI$KQ` z1o((n94^cbS*KuawMK<KeCCv6qTIPO)?y+(ANCH9>vbfkEg)wA&tp{b5%QF}cl_~< zOh4b8Q*IDw9b89~epHq)vh(80Oik#g$=F#P4DZQ3?cS`>jxP0!_il;{U)T?y6FyJ* zYmj}%R@0p{u%(cS-Q&x)HboBCw<T{oKSCc#f?j{d;_tl#J#fvLs#>S~>h<?xo6niW zHd;)1HXj)+@m{HN?TzRWo13FOp!`q88Bx2cMB57#C(#GKeFQVX()oNEH=Bhy+QVCI z6l`Y{iNZ|z?=(6=*~mxY#U->=T@6slBubeFRnFjRHb9j#g0B`+nal)lnyYfrXW}`r zP-sMlppz1@6}Ahq&=M1dn6pq2556X05m!xemGvTRObjnjl_+{oV0dgQMoOe{-o`;< z#RtlbZj`Flr$*q2L?8;6R9R>jn2_6FnGH`;*%Uhpp!y+#s(MgFMa{Deil|)cGeP~( z@**x=L^Y}Ep3T<PLCr#|@hGf8Rf3*|P=_DX8r?Je)`{Nx!%+x$!`dOFdXmi{UYYvd zgPpAWxw-6fMGB+p1u+&1Q`aCZ#;ceJtebYGt6wEpXmj7AQHM_md<O9_#j{_i>{AdS z`|YwH=*ThC{SDRo*^v|~TWTMGgHY!P8u+<PE4vg#Zc^}n4s_(1>Fq=UuG?@B7~vpH zr|HUwkp3~P?0C9kJNl$+vN$%}gMUAFin36vBe*fuGjIe;JI+&>lvD#MeDg_74NL%q zZ*zT}s7z4!rrZBKCc;^829zE3PFB1I6+|72{!j%`7*;)~AflyRfhveR!+QovS3Pxm zJ5jwGdwirsN?{#$R8{G-2x34uoCPJmgNB|xk%Z>LO_MC`C|$$vov$}dvb70FvrWyB zkDyAY1XaT}sOl}-d<ppkRK1-El9I-P>c2%Qo<{ZeD05LtN+s6LDnr9unj1q-pM%^V zNDO_$9AVf;f`-D*NY#_~N@1NUkkuQ!0(XPl6`I;RF{W_#IY=p$usUs_2BLC-$)w_w zW7{#M@4~5`8asHuiWk3Ts7lCefO0S2$Os&^DAVWd)2ga%%IG5G^JYtEj~Lr%oBEz# zgCI1)LD-X`Bn92WGBl*mBBTSAY|av;3s4Yrdn`^Pz=b);R@bxe2(0%VW<sQ-N>&}W zSXe-%b&aY-ZYDmR04VpmN|@?1DwbpOZ|{7yDgCjzm%<l}+*_WChn+)UM1Eju)A|l} zq7nrIQTuo-6d)~Leih?sRDCVKsKP@_{3xUxl#Yo~wEPBk4bMU&ab5?TNrKuc)(%V- zA60O1hZGNct(^s)5)NsAm;9ap$QHJjX69&%s%OzXgqj2rY0bS6XOg5uHVZx5S|_HQ z|D6*7U_>6?(M{XCLrUrhRlVf^ml8l7=yd(|-N(_PInYGBT?^_OT}8H`!a~=x5O_}L zz;j|gbz9<bbl5y(mFCKH32npiS5GJU$YccwZ(_cgFgz!6z?>+)et*Idwq&BpRRjW3 z^71R~f2YKs=b$k4wyxJ6;MEvG5OxNn>ITMRR$yGzZ}Z<E3<-dyKlurm7|vY(z@W4@ z40nN2y)R#fyZXMNzVr+iQY1*SfJ@fk2n<)3cj_!499?obT+loXnF8~a8g~*}G;l;Q z@mi#hX9=abvU4hP?LsinxJv`B<n$uWammI^UhGc}Al>kht@D2K6|eH|y+_x7ip{oY z%4TmM-ZA=qyWDS8x)?lHyhJfydlXq3)d@9Q4tU$!j`Q1&dl(3GluowU4X9RG%GU{1 z^`}S8jF;Mtzud01tktWEm`)e99QXiu@7msTF{t{aSJg0`e%znV4Y(Y}UvAV|qN*%8 z>x6En3mG()O?H<~vKYBc_-RTTjl0g&u@7jIr$bdnql#A+qDv_>>T9(dW7nlF%enuw z^Rtz-9E=}JW)CmDX~7}I*S9|s91a*UDRN*+ybLndK2ExfxYCK@ylQB1ix2OVoIL-e zd{AeZ=l)bd45j)rJG|(T#QPo!9-0T8i2{0RFZiBdO~h6b{nIXu6C2snt$oZ&Lfgn9 zmifB-Lx@--=F@T|j(x3<QPH{2Dqj}xSg5-1xH3^Csj0XL$z)BtIGu7E(JIRd6$f&} zzq%j4{Mjq<-6r|{atAu6->n~u2S!fPJ3bdtNA#qxTFNZ<#7k4-oFmQ3d~;?sYkel^ zKZOL#>Wy+M(K6m8b#+N<+X~EWEJ3fKCZ^uukPSt>qWwf-Ax6Kx!6BQBdSzU1izae3 za@&I&f!+Xc`tA-RJcO*&AAJP&`NAiRz})g6Q?HAH{KmHdG$Ogw@zs@+AFgHFD4)L9 z?Was))NZsB&E{$?E=p%D5h0646}p}`a2gnly;mY0-r4<RUn`Zks$|HIN93qeZ*PMo zh32~+#FJsf!TCo2<v7jCC;Ja1JV>b>(-f_I@J30`ExfN-R*gfbgm(Er_%<HrIZ-P3 znU;UnpdzKS)Vs7V=U)179`5ktu7-csi&{QSeLaS`AHN0mbQmTRZ2CM4+{XFSYq?H3 z%;lXoO~H9in5%d4-gUB7?9y+yXWl9&K7J?l-Z4>jBf;weMj?{DkLib~0@mA4qq#57 zG86;qNq;pE*eoj1Q|Q=9sT;A=ru=)yDB7@Ad~L>*>tJKyxq0)R&j5NMcu8m=+yPz* zl4joluLnnS?#wcD6K)vjMo*cNaUg~<3iFT%HOE^3MHM!pVyW=3R(fwQ;7hnIOw&ab z68auL125@z{Yq6fG(kO|nV-SOgWW4t@GkR$E=&b+)aGf3%q3#EiT%6fWV<tg#ZKi$ zwdj$cqq9kePHC}rN@Utr&hw({)sE|jLo-hJj=jPkSjcirINm%WG_ddSlssOIucx(* zsO0=5k#zb?LBcJ!b@00;miHDlK+6Z+)MpL;p8C^fi|sY%!~l18DHlGX<}cp69!nDB zl)Fuj)^xPdzNKYE6J3H1oyo%%CV~yZ&%E%f;#v~4DFRw_MQ+A%@gy}7tK2>t^wXVc z6ZXH6d~eTB1)v3q7yvRS`Gab^kzPddJ;#WGw*>@-HlkE2x9eB<!vXG1)jopB^A#V| z>7|%I`U>zKu1)|4o>o)`sR%~mPhEx)>_6K2cgIohg{<yj7&`Cp+nYA(B6eRry5?Ik z>!t5_ojjQ5k3dgc;SCwT%}RyqNnzq9kCpoOhig_d-e%Ud%4t)MrdZi%OW{h|;7S>n zzgq3TSN*GnA5yNx1KK~@om>$=OmfhBY<!?{Rl#&-^dfZ0Z}ZZB9CeY6b}G>qW8Ebm zt<r^G;9xU$N3eWw`{~9bYn{Kv7#O%~r#*?+3gq@GZx0=F&xyi6Ribcc2~Cdz$0Q?0 zJ#_<rplef*9abQiaTW|nRKPzsh)1tL6|581A5)}pge|&4XRKf<RjyoWY)Y`jI5YNJ zfO2Q0`qvQ{uLTyL{QI~*C~i@l8CK;fA|rBxXL%8^DpQ{shLLy(eyN{=I68Pzf|@=P z)5UZ9&RE-FP6>6Wi<hnjDkk7lC5FyH1)>12bYzc%mNvIKC<#mFa1X&suprYE8ipZa zjWBTJPRL^FedkC6A9$`)J!Qlwe%}Qf6OZ8#IDmb1M8TVlFYSd&V({VvS_+N9kPJ^< zz-9k?5pCmXeMlHqqP!Uz5gwaWyBw{!>R7<JOVZ(<HZ9@P>%}20_l0|KEVOR&ESaG_ zAl21#6eq&dh(`>zi~lzi9{iIkjMP{jcft9Kc4h#azk(czAgk@o&_06``JeOG^5+Bv z5t8IR$3REMnV}A}-k?1Mu)6j&Sb~e58jENjG~D7^{T%ajr)2O6)>tudDA8d>?o>*a zR>vzvFb*Ra+SX@mL_``>ZN0%=o?u7DbflLW>_BD;26F3uw_uHMW|Q~|j#(e~n85j~ zt6dX1@ZhO~laMo?L<e;Ks>tlRB3U){Oa{?+SrnzEh>Z?e^8zs=HwG_4Ry921K!d;{ zGZcr`Kc%-K&iILliUSt)gd{vcphWyk$s@`S_|OW$f!Q-E3`Yq%FmFjn$UH><6)c>( zeK#&TR5%SBm2c0Pn<IA|Ju@Qksz(=TSa@Qi!#tn{y1C`ghYDh}b88^_u4jQ^Sk)bi z^0X|&gQ4USs`NAjoRB+I+WWtJicgZP$FRS}c<O5GsEihyeYK&b$c4_!pB5L<9)iO` zMKW;78iBRcCBfCEjQaITJ$0M^F=2>$8g$%|dIk<2x@VaYIPYB+@xb|uFw_k`f0-Qo zdX`6xZ?azz=*;6g(19HJyhlJpq*U1MO$&aqB7{JKD>oD8w%0bF*0uHKDvK5ak?=m4 z8S0wlm8kgxM?^kWX}hBU5guQ9Tkk7w2`(9BqzGOs$m@qBZG^r<7Q7LT)GwcdW0u5O zPy|LIYEcn7b-V_LC>arwS}09e{`Eg&jc{aoxweQ{RQUB3&IUX<8_MBqXoa)k-{3R4 zkC@rn#;bh)g2OO`_KuD?3@Q@BFxZStlR_Io3XFhr;x<U+N4MI7&RB`?97hnf3%o4i z${?qq94u@%W}yXx+zQ2>^2!XH7EOP3!AEi4m`mo~uN66PDoBHg5gpP(3j**cq{WyP zx_N4;0rNzJ^xSgqms#=E=+Bb$Fh2n=5%3EMA9W^!xc)mW#()23TEv1tCNc$9@r8w< z*pw0C8eu)gB#R<NeVxd5FO3)|4)3I(Ss{l4cO}uUL*dg`l;<|njp*0kp%nmI4J;6% z#OViMTxbQC&CnJ_UJQ1iwObFjFSJEJ)ApGcYYONMIw2=4N+-kVm1-r!Ve2u$eIBX4 zdu+jW?qQ>Cvk@IXQTnqt_h@TIr**JcfTQ4Nt=vc5<Ru2Z(N>+-TT#8~A<=rdZeQA@ zpI!HD@Y`=nvIh~8BO}5I*GDnTog_p3oU{(ZxH3LGpES8(`M<*c-Qk3+aIyzKm%^#? z30NLj>gA{1oXX=*<Ypk17gr4}Jy^AP^&$rMF)cnzcnA1gW69>{+u^T)6@|N0Zo=fQ z>I~H?kx%_rtqWRuoI80hUVVvQ<tV3&PW)y!S-R)#{IiIf{bCniJqtIz@|p&BG!7p# zyx@@qUrj$>Si)C}R5P*sub!N{X?LJEDI0Q_sH6Tk3qF}4S2yzSoXG-;abFRVqfyX{ zlRprdItp`{-pV@`q9#C8V~Be6C~VTBcP_<pZf;A*l=5zoazRTjquPWA>ylW?iq5iA z&nvc$(O=b~826NUvc-nU!6!E|w^x?IPU=G^ajaV9Z6a~ZRpl)meE!zJ{>`lSP!{{K zCHdx?Td{p7^whher@~K-y-keQ;_l=;(z4fj{GTsTP2z4PwEK6BVcjflowjjrE##fc zDxUk2_l1T0qCLG>F%XnN(xlL(8eh$`hDtuR?QS`d;b8|S>_40rHvi%&l9r>%F$*}i zTaS)qW5_P0cngN}<QTxI>CHB$wpK~7#_y?Mf*NU`jn^fu+toZ;jb8V6IBp7bYIt9~ zE-{egqM6xwnQ8f!3|p7h(kU~dtG*D^VnO5dXJEhhxTFyMa<q#&L3j1?tR{;8WvZ;* zsrRn(r9h!fn0t-mW7XX=sI(PF?c_fB7Iy@`dVVmLn)#xs!jlCGNi;R;^<ShvI)gB! zZ~(fN0MHXD41k_2zF4XmSh_RZSq0#aPM7|r{Nc{6)NktdNy}|1qzTlTvi1JC3htj< z?h6ce-cJ3dm>(f57#Sl=>R>@Zm%(#ol0QL|qjOPAskubM+4;<gn>uQ<QU3lDrlOCd z7CCvC+(ULhBSXpWkN7@O7WuuOoc?@BA(s1YU$lH|mVGjfLu}TQe_zE*3jOh3=51Wu z8?{(#q$S0FzWdATpH}2<D}h+<9cw48avFl9^Q9+2=FKJ>f2Gs*s~2y|G9&&Uz(^Qh diff --git a/src/main/resources/runtime_block_states_594.dat b/src/main/resources/runtime_block_states_594.dat deleted file mode 100644 index eec1d33e5e486bf4769857e2c0de4b6ef7e974c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52862 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 BUI73A diff --git a/src/main/resources/runtime_block_states_662.dat b/src/main/resources/runtime_block_states_662.dat new file mode 100644 index 0000000000000000000000000000000000000000..1b3bdf5aeb415f42dc750e2b0a1736304bb08d56 GIT binary patch literal 52833 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 OXD&E-T=KgtHv<5I7a2JK literal 0 HcmV?d00001 From 8773482c030c341b94c28bced9fb201d1388d96d Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:12:32 +0300 Subject: [PATCH 14/21] Remove custom block API from stable branch --- src/main/java/cn/nukkit/Player.java | 8 - src/main/java/cn/nukkit/Server.java | 9 - src/main/java/cn/nukkit/block/Block.java | 35 +- .../block/custom/CustomBlockDefinition.java | 13 - .../block/custom/CustomBlockManager.java | 354 ------------------ .../nukkit/block/custom/CustomBlockState.java | 22 -- .../cn/nukkit/block/custom/GsonNBTMapper.java | 186 --------- .../comparator/HashedPaletteComparator.java | 37 -- .../container/BlockContainerFactory.java | 16 - .../block/custom/container/CustomBlock.java | 29 -- .../custom/container/CustomBlockMeta.java | 57 --- src/main/java/cn/nukkit/item/Item.java | 13 +- src/main/java/cn/nukkit/level/Level.java | 19 +- .../network/protocol/StartGamePacket.java | 14 +- 14 files changed, 9 insertions(+), 803 deletions(-) delete mode 100644 src/main/java/cn/nukkit/block/custom/CustomBlockDefinition.java delete mode 100644 src/main/java/cn/nukkit/block/custom/CustomBlockManager.java delete mode 100644 src/main/java/cn/nukkit/block/custom/CustomBlockState.java delete mode 100644 src/main/java/cn/nukkit/block/custom/GsonNBTMapper.java delete mode 100644 src/main/java/cn/nukkit/block/custom/comparator/HashedPaletteComparator.java delete mode 100644 src/main/java/cn/nukkit/block/custom/container/BlockContainerFactory.java delete mode 100644 src/main/java/cn/nukkit/block/custom/container/CustomBlock.java delete mode 100644 src/main/java/cn/nukkit/block/custom/container/CustomBlockMeta.java diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 42360627e87..1c9706e558b 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -9,7 +9,6 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.command.data.CommandDataVersions; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.entity.*; import cn.nukkit.entity.custom.EntityManager; import cn.nukkit.entity.data.*; @@ -2740,10 +2739,6 @@ protected void completeLoginSequence() { } } - if (!CustomBlockManager.get().getBlockDefinitions().isEmpty()) { - startGamePacket.experiments.add(new ExperimentData("data_driven_items", true)); - } - this.forceDataPacket(startGamePacket, null); this.loggedIn = true; @@ -3041,9 +3036,6 @@ public void onCompletion(Server server) { ResourcePackStackPacket stackPacket = new ResourcePackStackPacket(); stackPacket.mustAccept = this.server.getForceResources() && !this.server.forceResourcesAllowOwnPacks; // Option not to disable client's own packs stackPacket.resourcePackStack = this.server.getResourcePackManager().getResourceStack(); - if (!CustomBlockManager.get().getBlockDefinitions().isEmpty()) { - stackPacket.experiments.add(new ExperimentData("data_driven_items", true)); - } this.dataPacket(stackPacket); return; case ResourcePackClientResponsePacket.STATUS_COMPLETED: diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 414af37f455..839168172fc 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -4,7 +4,6 @@ import cn.nukkit.blockentity.*; import cn.nukkit.command.*; import cn.nukkit.console.NukkitConsole; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.dispenser.DispenseBehaviorRegister; import cn.nukkit.entity.Attribute; import cn.nukkit.entity.Entity; @@ -482,7 +481,6 @@ public Level remove(Object key) { Potion.init(); Attribute.init(); DispenseBehaviorRegister.init(); - CustomBlockManager.init(this); //noinspection ResultOfMethodCallIgnored EntityManager.get(); //noinspection ResultOfMethodCallIgnored @@ -534,13 +532,6 @@ public Level remove(Object key) { this.enablePlugins(PluginLoadOrder.STARTUP); boolean regenerateItemPalette = false; - try { - if (CustomBlockManager.get().closeRegistry()) { - regenerateItemPalette = true; - } - } catch (Exception e) { - throw new RuntimeException("Failed to init custom blocks", e); - } if (CustomItemManager.get().closeRegistry()) { regenerateItemPalette = true; diff --git a/src/main/java/cn/nukkit/block/Block.java b/src/main/java/cn/nukkit/block/Block.java index c219f570a8c..955f72a859e 100644 --- a/src/main/java/cn/nukkit/block/Block.java +++ b/src/main/java/cn/nukkit/block/Block.java @@ -2,7 +2,6 @@ import cn.nukkit.Player; import cn.nukkit.Server; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.entity.Entity; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; @@ -162,9 +161,6 @@ public static Block get(int id) { id = 255 - id; } - if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return CustomBlockManager.get().getBlock(id, 0); - } return fullList[id << DATA_BITS].clone(); } @@ -174,9 +170,6 @@ public static Block get(int id, Integer meta) { } int fullId = meta == null ? (id << DATA_BITS ) : ((id << DATA_BITS) | meta); - if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return CustomBlockManager.get().getBlock(fullId); - } return fullList[fullId].clone(); } @@ -191,10 +184,7 @@ public static Block get(int id, Integer meta, Position pos, BlockLayer layer) { } Block block; - if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - int fullId = (meta != null && meta > DATA_SIZE) ? (id << DATA_BITS) : ((id << DATA_BITS) | (meta == null ? 0 : meta)); - block = CustomBlockManager.get().getBlock(fullId); - } else if (meta != null && meta > DATA_SIZE) { + if (meta != null && meta > DATA_SIZE) { block = fullList[id << DATA_BITS].clone(); block.setDamage(meta); } else { @@ -217,9 +207,7 @@ public static Block get(int id, int data) { } int fullId = (id << DATA_BITS ) | data; - if (id >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return CustomBlockManager.get().getBlock(fullId); - } + return fullList[fullId].clone(); } @@ -228,12 +216,7 @@ public static Block get(int fullId, Level level, int x, int y, int z) { } public static Block get(int fullId, Level level, int x, int y, int z, BlockLayer layer) { - Block block; - if ((fullId >> DATA_BITS) >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - block = CustomBlockManager.get().getBlock(fullId); - } else { - block = fullList[fullId].clone(); - } + Block block = fullList[fullId].clone(); block.x = x; block.y = y; @@ -244,30 +227,18 @@ public static Block get(int fullId, Level level, int x, int y, int z, BlockLayer } public static int getBlockLight(int blockId) { - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return light[0]; // TODO: just temporary - } return light[blockId]; } public static int getBlockLightFilter(int blockId) { - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return lightFilter[0]; // TODO: just temporary - } return lightFilter[blockId]; } public static boolean isBlockSolidById(int blockId) { - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return solid[1]; // TODO: just temporary - } return solid[blockId]; } public static boolean isBlockTransparentById(int blockId) { - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - return transparent[1]; // TODO: just temporary - } return transparent[blockId]; } diff --git a/src/main/java/cn/nukkit/block/custom/CustomBlockDefinition.java b/src/main/java/cn/nukkit/block/custom/CustomBlockDefinition.java deleted file mode 100644 index 6602dad2fc1..00000000000 --- a/src/main/java/cn/nukkit/block/custom/CustomBlockDefinition.java +++ /dev/null @@ -1,13 +0,0 @@ -package cn.nukkit.block.custom; - -import cn.nukkit.block.custom.container.BlockContainer; -import org.cloudburstmc.nbt.NbtMap; -import lombok.Data; - -@Data -public class CustomBlockDefinition { - private final String identifier; - private final NbtMap networkData; - private final int legacyId; - private final Class<? extends BlockContainer> typeOf; -} diff --git a/src/main/java/cn/nukkit/block/custom/CustomBlockManager.java b/src/main/java/cn/nukkit/block/custom/CustomBlockManager.java deleted file mode 100644 index b89890abd73..00000000000 --- a/src/main/java/cn/nukkit/block/custom/CustomBlockManager.java +++ /dev/null @@ -1,354 +0,0 @@ -package cn.nukkit.block.custom; - -import cn.nukkit.Server; -import cn.nukkit.block.Block; -import cn.nukkit.block.BlockID; -import cn.nukkit.block.custom.comparator.HashedPaletteComparator; -import cn.nukkit.block.custom.container.BlockContainer; -import cn.nukkit.block.custom.container.BlockContainerFactory; -import cn.nukkit.block.custom.container.BlockStorageContainer; -import cn.nukkit.block.custom.properties.BlockProperties; -import cn.nukkit.block.custom.properties.BlockProperty; -import cn.nukkit.block.custom.properties.EnumBlockProperty; -import cn.nukkit.block.custom.properties.exception.InvalidBlockPropertyMetaException; -import cn.nukkit.item.RuntimeItems; -import cn.nukkit.level.BlockPalette; -import cn.nukkit.level.GlobalBlockPalette; -import cn.nukkit.level.format.leveldb.BlockStateMapping; -import cn.nukkit.level.format.leveldb.LevelDBConstants; -import cn.nukkit.level.format.leveldb.NukkitLegacyMapper; -import cn.nukkit.nbt.NBTIO; -import cn.nukkit.nbt.tag.CompoundTag; -import it.unimi.dsi.fastutil.ints.*; -import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import lombok.extern.log4j.Log4j2; -import org.cloudburstmc.nbt.*; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteOrder; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -/** - * Handles custom block registry. - * <p> - * See <a href="https://github.com/PetteriM1/CustomBlockExample">CustomBlockExample</a> for example usage - */ -@Log4j2 -public class CustomBlockManager { - - /** - * Lowest allowed Nukkit save id for custom blocks - */ - public static final int LOWEST_CUSTOM_BLOCK_ID = 5000; - - private static CustomBlockManager INSTANCE; - - /** - * Internal: initialize CustomBlockManager - */ - public static CustomBlockManager init(Server server) { - if (INSTANCE == null) { - return INSTANCE = new CustomBlockManager(server); - } - throw new IllegalStateException("CustomBlockManager was already initialized!"); - } - - /** - * Get CustomBlockManager instance - */ - public static CustomBlockManager get() { - return INSTANCE; - } - - private final Server server; - - private final Int2ObjectMap<CustomBlockDefinition> blockDefinitions = new Int2ObjectOpenHashMap<>(); - private final Int2ObjectMap<CustomBlockState> legacy2CustomState = new Int2ObjectOpenHashMap<>(); - - private volatile boolean closed; - - private CustomBlockManager(Server server) { - this.server = server; - } - - public void registerCustomBlock(String identifier, int nukkitId, Supplier<BlockContainer> factory) { - this.registerCustomBlock(identifier, nukkitId, NbtMap.EMPTY, factory); - } - - public void registerCustomBlock(String identifier, int nukkitId, NbtMap networkData, Supplier<BlockContainer> factory) { - this.registerCustomBlock(identifier, nukkitId, null, networkData, meta -> factory.get()); - } - - public void registerCustomBlock(String identifier, int nukkitId, BlockProperties properties, NbtMap networkData, BlockContainerFactory factory) { - if (this.closed) { - throw new IllegalStateException("Block registry was already closed"); - } - - if (nukkitId < LOWEST_CUSTOM_BLOCK_ID) { - throw new IllegalArgumentException("Custom block ID can not be lower than " + LOWEST_CUSTOM_BLOCK_ID); - } - - BlockContainer blockSample = factory.create(0); - if (blockSample instanceof BlockStorageContainer && properties == null) { - properties = ((BlockStorageContainer) blockSample).getBlockProperties(); - log.warn("Custom block {} was registered using wrong method! Trying to use sample properties!", identifier); - } - - if (properties != null && networkData.isEmpty()) { - throw new IllegalArgumentException("Block network data can not be empty for block with more permutations: " + identifier); - } - - CustomBlockState defaultState = this.createBlockState(identifier, nukkitId << Block.DATA_BITS, properties, factory); - this.legacy2CustomState.put(defaultState.getLegacyId(), defaultState); - - // TODO: unsure if this is per state or not - CustomBlockDefinition definition = new CustomBlockDefinition(identifier, networkData, defaultState.getLegacyId(), blockSample.getClass()); - this.blockDefinitions.put(defaultState.getLegacyId(), definition); - - int itemId = 255 - nukkitId; - RuntimeItems.getMapping().registerItem(identifier, nukkitId, itemId, 0); - - if (properties != null) { - for (int meta = 1; meta < properties.getBitSize(); meta++) { - CustomBlockState state; - try { - state = this.createBlockState(identifier, (nukkitId << Block.DATA_BITS) | meta, properties, factory); - } catch (InvalidBlockPropertyMetaException e) { - break; // Nukkit has more states than our block - } - this.legacy2CustomState.put(state.getLegacyId(), state); - } - } - } - - private CustomBlockState createBlockState(String identifier, int legacyId, BlockProperties properties, BlockContainerFactory factory) { - int meta = legacyId & Block.DATA_MASK; - - NbtMapBuilder statesBuilder = NbtMap.builder(); - if (properties != null) { - for (String propertyName : properties.getNames()) { - BlockProperty<?> property = properties.getBlockProperty(propertyName); - if (property instanceof EnumBlockProperty) { - statesBuilder.put(property.getPersistenceName(), properties.getPersistenceValue(meta, propertyName)); - } else { - statesBuilder.put(property.getPersistenceName(), properties.getValue(meta, propertyName)); - } - } - } - - NbtMap state = NbtMap.builder() - .putString("name", identifier) - .putCompound("states", statesBuilder.build()) - .putInt("version", LevelDBConstants.STATE_VERSION) - .build(); - return new CustomBlockState(identifier, legacyId, state, factory); - } - - /** - * Internal: close registry to prepare for data generation - */ - public boolean closeRegistry() throws IOException { - if (this.closed) { - throw new IllegalStateException("Block registry was already closed"); - } - - this.closed = true; - - if (this.legacy2CustomState.isEmpty()) { - return false; - } - - this.createBin(); - - long startTime = System.currentTimeMillis(); - - BlockPalette storagePalette = GlobalBlockPalette.getLeveldbBlockPalette(); - BlockPalette palette = GlobalBlockPalette.getCurrentBlockPalette(); - - if (palette.getProtocol() == storagePalette.getProtocol()) { - this.recreateBlockPalette(palette, new ObjectArrayList<>(NukkitLegacyMapper.loadBlockPalette())); - } else { - Path path = this.getVanillaPalettePath(palette.getProtocol()); - if (!Files.exists(path)) { - log.warn("bin/block_palette_" + palette.getProtocol() + ".nbt not found! Version won't support custom blocks"); - return false; - } - this.recreateBlockPalette(palette); - } - - log.debug("Custom block registry closed in {} ms", (System.currentTimeMillis() - startTime)); - return true; - } - - private void recreateBlockPalette(BlockPalette palette) throws IOException { - List<NbtMap> vanillaPalette = new ObjectArrayList<>(this.loadVanillaPalette(palette.getProtocol())); - this.recreateBlockPalette(palette, vanillaPalette); - } - - private void recreateBlockPalette(BlockPalette palette, List<NbtMap> vanillaPalette) { - List<NbtMap> vanillaPaletteList = new ObjectArrayList<>(); - for (NbtMap state : vanillaPalette) { - if (state.containsKey("network_id") || state.containsKey("name_hash") || state.containsKey("block_id")) { - NbtMapBuilder builder = NbtMapBuilder.from(state); - builder.remove("network_id"); - builder.remove("name_hash"); - builder.remove("block_id"); - state = builder.build(); - } - vanillaPaletteList.add(state); - } - - Object2ObjectMap<NbtMap, IntSet> state2Legacy = new Object2ObjectLinkedOpenHashMap<>(); - - int paletteVersion = vanillaPaletteList.get(0).getInt("version"); - - for (Int2IntMap.Entry entry : palette.getLegacyToRuntimeIdMap().int2IntEntrySet()) { - int runtimeId = entry.getIntValue(); - NbtMap state = vanillaPaletteList.get(runtimeId); - if (state == null) { - log.info("Unknown runtime ID {}! protocol={}", runtimeId, palette.getProtocol()); - continue; - } - IntSet legacyIds = state2Legacy.computeIfAbsent(state, s -> new IntOpenHashSet()); - legacyIds.add(entry.getIntKey()); - } - - for (CustomBlockState definition : this.legacy2CustomState.values()) { - NbtMap state = definition.getBlockState(); - if (state.getInt("version") != paletteVersion) { - state = state.toBuilder().putInt("version", paletteVersion).build(); - } - state2Legacy.computeIfAbsent(state, s -> new IntOpenHashSet()).add(legacyToFullId(definition.getLegacyId())); - vanillaPaletteList.add(state); - } - - vanillaPaletteList.sort(HashedPaletteComparator.INSTANCE); - - palette.clearStates(); - boolean levelDb = palette.getProtocol() == GlobalBlockPalette.getLeveldbBlockPalette().getProtocol(); - if (levelDb) { - BlockStateMapping.get().clearMapping(); - } - - for (int runtimeId = 0; runtimeId < vanillaPaletteList.size(); runtimeId++) { - NbtMap state = vanillaPaletteList.get(runtimeId); - if (levelDb) { - BlockStateMapping.get().registerState(runtimeId, state); - } - - IntSet legacyIds = state2Legacy.get(state); - if (legacyIds == null) { - continue; - } - - CompoundTag nukkitState = convertNbtMap(state); - for (Integer fullId : legacyIds) { - palette.registerState(fullId >> Block.DATA_BITS, fullId & Block.DATA_MASK, runtimeId, nukkitState); - } - } - } - - private List<NbtMap> loadVanillaPalette(int version) throws FileNotFoundException { - Path path = this.getVanillaPalettePath(version); - if (!Files.exists(path)) { - throw new FileNotFoundException("Missing vanilla palette for version " + version); - } - - try (InputStream stream = Files.newInputStream(path)) { - return ((NbtMap) NbtUtils.createGZIPReader(stream).readTag()).getList("blocks", NbtType.COMPOUND); - } catch (Exception e) { - throw new AssertionError("Error while loading vanilla palette " + version, e); - } - } - - private Path getVanillaPalettePath(int version) { - return this.getBinPath().resolve("block_palette_" + version + ".nbt"); - } - - public Block getBlock(int legacyId) { - CustomBlockState state = this.legacy2CustomState.get(legacyId); - if (state == null) { - return Block.get(BlockID.INFO_UPDATE); - } - - BlockContainer block = state.getFactory().create(legacyId & Block.DATA_MASK); - if (block instanceof Block) { - return (Block) block; - } - return null; - } - - public Block getBlock(int id, int meta) { - int legacyId = (id << Block.DATA_BITS) | meta; - CustomBlockState state = this.legacy2CustomState.get(legacyId); - if (state == null) { - state = this.legacy2CustomState.get(id << Block.DATA_BITS); - if (state == null) { - return Block.get(BlockID.INFO_UPDATE); - } - } - - BlockContainer block = state.getFactory().create(meta); - if (block instanceof Block) { - return (Block) block; - } - return null; - } - - public Class<?> getClassType(int blockId) { - CustomBlockDefinition definition = this.blockDefinitions.get(blockId << Block.DATA_BITS); - if (definition == null) { - return null; - } - return definition.getTypeOf(); - } - - private void createBin() { - Path filesPath = this.getBinPath(); - if (!Files.isDirectory(filesPath)) { - try { - Files.createDirectories(filesPath); - } catch (IOException e) { - throw new IllegalStateException("Failed to create bin/", e); - } - } - } - - private Path getBinPath() { - return Paths.get(this.server.getDataPath()).resolve(Paths.get("bin/")); - } - - public Collection<CustomBlockDefinition> getBlockDefinitions() { - return this.blockDefinitions.values(); - } - - private static int legacyToFullId(int legacyId) { - int blockId = legacyId >> Block.DATA_BITS; - int meta = legacyId & Block.DATA_MASK; - return (blockId << Block.DATA_BITS) | meta; - } - - public static CompoundTag convertNbtMap(NbtMap nbt) { - try { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try (NBTOutputStream nbtOutputStream = NbtUtils.createWriter(stream)) { - nbtOutputStream.writeTag(nbt); - } finally { - stream.close(); - } - return NBTIO.read(stream.toByteArray(), ByteOrder.BIG_ENDIAN, false); - } catch (IOException e) { - throw new IllegalStateException("Failed to convert NbtMap: " + nbt, e); - } - } -} diff --git a/src/main/java/cn/nukkit/block/custom/CustomBlockState.java b/src/main/java/cn/nukkit/block/custom/CustomBlockState.java deleted file mode 100644 index bc401acaa56..00000000000 --- a/src/main/java/cn/nukkit/block/custom/CustomBlockState.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.nukkit.block.custom; - -import cn.nukkit.block.custom.container.BlockContainerFactory; -import cn.nukkit.nbt.tag.CompoundTag; -import org.cloudburstmc.nbt.NbtMap; -import lombok.Data; - -@Data -public class CustomBlockState { - private final String identifier; - private final int legacyId; - private final NbtMap blockState; - private final BlockContainerFactory factory; - private CompoundTag nukkitBlockState; - - public CompoundTag getNukkitBlockState() { - if (this.nukkitBlockState == null) { - this.nukkitBlockState = CustomBlockManager.convertNbtMap(this.blockState); - } - return this.nukkitBlockState; - } -} \ No newline at end of file diff --git a/src/main/java/cn/nukkit/block/custom/GsonNBTMapper.java b/src/main/java/cn/nukkit/block/custom/GsonNBTMapper.java deleted file mode 100644 index be340fca3c3..00000000000 --- a/src/main/java/cn/nukkit/block/custom/GsonNBTMapper.java +++ /dev/null @@ -1,186 +0,0 @@ -package cn.nukkit.block.custom; - -import com.google.gson.*; -import com.google.gson.internal.LazilyParsedNumber; -import org.cloudburstmc.nbt.NbtList; -import org.cloudburstmc.nbt.NbtMap; -import org.cloudburstmc.nbt.NbtMapBuilder; -import org.cloudburstmc.nbt.NbtType; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import java.lang.reflect.Type; -import java.util.List; - -public class GsonNBTMapper { - - public static final Gson GSON; - - static { - GsonBuilder builder = new GsonBuilder(); - builder.registerTypeAdapter(NbtMap.class, new NbtMapDeserializer()); - builder.registerTypeAdapter(NbtList.class, new NbtArrayDeserializer()); - GSON = builder.create(); - } - - public static NbtMap objectToNbtMap(Object object) { - JsonObject json = GSON.toJsonTree(object).getAsJsonObject(); - return GSON.fromJson(json, NbtMap.class); - } - - public static class NbtMapDeserializer implements JsonDeserializer<NbtMap> { - @Override - public NbtMap deserialize(JsonElement json, Type typeOf, JsonDeserializationContext context) throws JsonParseException { - if (!json.isJsonObject()) { - throw new IllegalStateException("Expected JsonObject but got: " + json.getClass().getSimpleName()); - } - - JsonObject jsonObject = json.getAsJsonObject(); - NbtMapBuilder builder = NbtMap.builder(); - - for (String key : jsonObject.keySet()) { - JsonElement element = jsonObject.get(key); - if (element.isJsonObject()) { - builder.putCompound(key, context.deserialize(element, NbtMap.class)); - } else if (element.isJsonArray()) { - NbtList list = context.deserialize(element, NbtList.class); - builder.putList(key, list.getType(), list); - } else if (element.isJsonPrimitive()) { - builder.put(key, getPrimitiveObject(element)); - } else if (element.isJsonNull()) { - builder.putCompound(key, NbtMap.builder().build()); - } - } - return builder.build(); - } - } - - public static class NbtArrayDeserializer implements JsonDeserializer<NbtList<?>> { - @Override - public NbtList<?> deserialize(JsonElement json, Type typeOf, JsonDeserializationContext context) throws JsonParseException { - if (!json.isJsonArray()) { - throw new IllegalStateException("Expected JsonObject but got: " + json.getClass().getSimpleName()); - } - - JsonArray jsonArray = json.getAsJsonArray(); - NbtType type = getListType(jsonArray); - return new NbtList<>(type, getList(jsonArray, type, context)); - } - } - - public static NbtType<?> getListType(JsonArray array) { - NbtType<?> type = null; - for (JsonElement element : array) { - NbtType<?> elementType; - if (element.isJsonObject()) { - elementType = NbtType.COMPOUND; - } else if (element.isJsonArray()) { - elementType = NbtType.LIST; - } else { - elementType = getPrimitiveType(element); - } - - if (type != null && elementType != type) { - throw new IllegalArgumentException("Can not create array of mixed types"); - } else { - type = elementType; - } - } - return type; - } - - public static <T> List<T> getList(JsonArray array, NbtType<T> type, JsonDeserializationContext context) { - List<T> list = new ObjectArrayList<>(); - for (JsonElement element : array) { - if (type == NbtType.COMPOUND) { - list.add(context.deserialize(element, NbtMap.class)); - } else if (type == NbtType.LIST) { - list.add(context.deserialize(element, NbtList.class)); - } else { - list.add((T) getPrimitiveObject(element)); - } - } - return list; - } - - public static Object getPrimitiveObject(JsonElement element) { - if (element.getAsJsonPrimitive().isBoolean()) { - return element.getAsBoolean(); - } else if (element.getAsJsonPrimitive().isNumber()) { - Number number = element.getAsNumber(); - if (number instanceof Byte) { - return number.byteValue(); - } else if (number instanceof Short) { - return number.shortValue(); - } else if (number instanceof Integer) { - return number.intValue(); - } else if (number instanceof Long) { - return number.longValue(); - } else if (number instanceof Float) { - return number.floatValue(); - } else if (number instanceof Double) { - return number.doubleValue(); - } else { - String str = number.toString(); - try { - return Integer.parseInt(str); - } catch(NumberFormatException e) { - try { - return Long.parseLong(str); - } catch(NumberFormatException e1) { - try { - return Float.parseFloat(str); - } catch(NumberFormatException e2) { - return Double.parseDouble(str); - } - } - } - } - } else if (element.getAsJsonPrimitive().isString()) { - return element.getAsString(); - } - throw new IllegalArgumentException("Unknown type of primitive: " + element); - } - - public static NbtType<?> getPrimitiveType(JsonElement element) { - if (element.getAsJsonPrimitive().isBoolean()) { - return NbtType.BYTE; - } else if (element.getAsJsonPrimitive().isNumber()) { - Number number = element.getAsNumber(); - if (number instanceof Byte) { - return NbtType.BYTE; - } else if (number instanceof Short) { - return NbtType.SHORT; - } else if (number instanceof Integer) { - return NbtType.INT; - } else if (number instanceof Long) { - return NbtType.LONG; - } else if (number instanceof Float) { - return NbtType.FLOAT; - } else if (number instanceof Double) { - return NbtType.DOUBLE; - } else if (number instanceof LazilyParsedNumber) { - String str = number.toString(); - try { - Integer.parseInt(str); - return NbtType.INT; - } catch(NumberFormatException e) { - try { - Long.parseLong(str); - return NbtType.LONG; - } catch(NumberFormatException e1) { - try { - Float.parseFloat(str); - return NbtType.FLOAT; - } catch(NumberFormatException e2) { - Double.parseDouble(str); - return NbtType.DOUBLE; - } - } - } - } - } else if (element.getAsJsonPrimitive().isString()) { - return NbtType.STRING; - } - throw new IllegalArgumentException("Unknown type of primitive: " + element); - } -} diff --git a/src/main/java/cn/nukkit/block/custom/comparator/HashedPaletteComparator.java b/src/main/java/cn/nukkit/block/custom/comparator/HashedPaletteComparator.java deleted file mode 100644 index 53222f97923..00000000000 --- a/src/main/java/cn/nukkit/block/custom/comparator/HashedPaletteComparator.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.nukkit.block.custom.comparator; - - -import org.cloudburstmc.nbt.NbtMap; - -import java.nio.charset.StandardCharsets; -import java.util.Comparator; - -public class HashedPaletteComparator implements Comparator<NbtMap> { - public static final HashedPaletteComparator INSTANCE = new HashedPaletteComparator(); - - private static final long FNV1_64_INIT = 0xcbf29ce484222325L; - private static final long FNV1_PRIME_64 = 1099511628211L; - - @Override - public int compare(NbtMap o1, NbtMap o2) { - byte[] b1 = getIdentifier(o1).getBytes(StandardCharsets.UTF_8); - byte[] b2 = getIdentifier(o2).getBytes(StandardCharsets.UTF_8); - long hash1 = fnv164(b1); - long hash2 = fnv164(b2); - return Long.compareUnsigned(hash1, hash2); - } - - private String getIdentifier(NbtMap state) { - return state.getString("name"); - } - - public static long fnv164(byte[] data) { - long hash = FNV1_64_INIT; - for (byte datum : data) { - hash *= FNV1_PRIME_64; - hash ^= (datum & 0xff); - } - - return hash; - } -} diff --git a/src/main/java/cn/nukkit/block/custom/container/BlockContainerFactory.java b/src/main/java/cn/nukkit/block/custom/container/BlockContainerFactory.java deleted file mode 100644 index 8995c3ec718..00000000000 --- a/src/main/java/cn/nukkit/block/custom/container/BlockContainerFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package cn.nukkit.block.custom.container; - -import cn.nukkit.block.custom.properties.BlockProperties; - -public interface BlockContainerFactory { - - BlockContainer create(int meta); - - static BlockContainer createSimple(String blockName, int blockId) { - return new CustomBlock(blockName, blockId); - } - - static BlockContainer createMeta(String blockName, int blockId, int meta, BlockProperties properties) { - return new CustomBlockMeta(blockName, blockId, properties, meta); - } -} diff --git a/src/main/java/cn/nukkit/block/custom/container/CustomBlock.java b/src/main/java/cn/nukkit/block/custom/container/CustomBlock.java deleted file mode 100644 index 6fac870ce08..00000000000 --- a/src/main/java/cn/nukkit/block/custom/container/CustomBlock.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.nukkit.block.custom.container; - -import cn.nukkit.block.Block; - -public class CustomBlock extends Block implements BlockContainer { - - private final String blockName; - private final int blockId; - - public CustomBlock(String blockName, int blockId) { - this.blockName = blockName; - this.blockId = blockId; - } - - @Override - public int getId() { - return this.blockId; - } - - @Override - public int getNukkitId() { - return this.blockId; - } - - @Override - public String getName() { - return this.blockName; - } -} diff --git a/src/main/java/cn/nukkit/block/custom/container/CustomBlockMeta.java b/src/main/java/cn/nukkit/block/custom/container/CustomBlockMeta.java deleted file mode 100644 index b8a49f1e4ec..00000000000 --- a/src/main/java/cn/nukkit/block/custom/container/CustomBlockMeta.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.nukkit.block.custom.container; - -import cn.nukkit.block.BlockMeta; -import cn.nukkit.block.custom.properties.BlockProperties; - -public class CustomBlockMeta extends BlockMeta implements BlockStorageContainer { - - private final String blockName; - private final int blockId; - private final BlockProperties properties; - - public CustomBlockMeta(String blockName, int blockId, BlockProperties properties) { - this(blockName, blockId, properties, 0); - } - - public CustomBlockMeta(String blockName, int blockId, BlockProperties properties, int meta) { - super(meta); - this.blockName = blockName; - this.blockId = blockId; - this.properties = properties; - } - - @Override - public int getId() { - return this.blockId; - } - - @Override - public int getNukkitId() { - return this.blockId; - } - - @Override - public String getName() { - return this.blockName; - } - - @Override - public int getStorage() { - return this.getDamage(); - } - - @Override - public void setStorage(int damage) { - this.setDamage(damage); - } - - @Override - public void setDamage(int meta) { - super.setDamage(meta); - } - - @Override - public BlockProperties getBlockProperties() { - return this.properties; - } -} diff --git a/src/main/java/cn/nukkit/item/Item.java b/src/main/java/cn/nukkit/item/Item.java index 66cbdddb71a..66fef103a53 100644 --- a/src/main/java/cn/nukkit/item/Item.java +++ b/src/main/java/cn/nukkit/item/Item.java @@ -5,7 +5,6 @@ import cn.nukkit.Server; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.entity.Entity; import cn.nukkit.inventory.Fuel; import cn.nukkit.item.RuntimeItemMapping.RuntimeEntry; @@ -461,11 +460,7 @@ public static Item get(int id, Integer meta, int count, byte[] tags) { Class<?> c; if (id < 0) { int blockId = 255 - id; - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - c = CustomBlockManager.get().getClassType(blockId); - } else { - c = Block.list[blockId]; - } + c = Block.list[blockId]; } else { c = list[id]; } @@ -498,11 +493,7 @@ public static Item get(int id, Integer meta, int count, Tag tags) { Class<?> c; if (id < 0) { int blockId = 255 - id; - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - c = CustomBlockManager.get().getClassType(blockId); - } else { - c = Block.list[blockId]; - } + c = Block.list[blockId]; } else { c = list[id]; } diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 2b23424d951..3cff8535ff8 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -4,7 +4,6 @@ import cn.nukkit.Server; import cn.nukkit.block.*; import cn.nukkit.blockentity.BlockEntity; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.entity.Entity; import cn.nukkit.entity.custom.EntityDefinition; import cn.nukkit.entity.custom.EntityManager; @@ -1677,11 +1676,7 @@ public synchronized Block getBlock(FullChunk chunk, int x, int y, int z, BlockLa Block block; int blockId = fullState >> Block.DATA_BITS; - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - block = CustomBlockManager.get().getBlock(fullState); - } else { - block = Block.fullList[fullState].clone(); - } + block = Block.fullList[fullState].clone(); block.x = x; block.y = y; @@ -1711,11 +1706,7 @@ private Block getBlockAsyncIfLoaded(FullChunk chunk, int x, int y, int z, BlockL Block block; int blockId = fullState >> Block.DATA_BITS; - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - block = CustomBlockManager.get().getBlock(fullState); - } else { - block = Block.fullList[fullState].clone(); - } + block = Block.fullList[fullState].clone(); block.x = x; block.y = y; @@ -1869,11 +1860,7 @@ public synchronized void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, Block block; int blockId = fullId >> Block.DATA_BITS; - if (blockId >= CustomBlockManager.LOWEST_CUSTOM_BLOCK_ID) { - block = CustomBlockManager.get().getBlock(fullId); - } else { - block = Block.fullList[fullId]; - } + block = Block.fullList[fullId]; this.setBlock(x, y, z, layer, block, false, false); } diff --git a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java index eb7604e7b40..57f75246f28 100644 --- a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java @@ -1,7 +1,5 @@ package cn.nukkit.network.protocol; -import cn.nukkit.block.custom.CustomBlockDefinition; -import cn.nukkit.block.custom.CustomBlockManager; import cn.nukkit.item.RuntimeItems; import cn.nukkit.level.GameRules; import cn.nukkit.network.protocol.types.ExperimentData; @@ -13,7 +11,6 @@ import java.io.IOException; import java.util.UUID; -import java.util.Collection; import java.util.List; @ToString @@ -91,7 +88,6 @@ public byte pid() { public boolean disablePlayerInteractions; public boolean emoteChatMuted; public boolean hardcore; - public Collection<CustomBlockDefinition> blockDefinitions = CustomBlockManager.get().getBlockDefinitions(); public final List<ExperimentData> experiments = new ObjectArrayList<>(); @Override @@ -174,15 +170,7 @@ public void encode() { this.putBoolean(true); // isServerAuthoritativeBlockBreaking this.putLLong(this.currentTick); this.putVarInt(this.enchantmentSeed); - if (this.blockDefinitions != null && !this.blockDefinitions.isEmpty()) { - this.putUnsignedVarInt(this.blockDefinitions.size()); - for (CustomBlockDefinition definition : this.blockDefinitions) { - this.putString(definition.getIdentifier()); - this.putNbtTag(definition.getNetworkData()); - } - } else { - this.putUnsignedVarInt(0); // No custom blocks - } + this.putUnsignedVarInt(0); // No custom blocks this.put(RuntimeItems.getMapping().getItemPalette()); this.putString(this.multiplayerCorrelationId); this.putBoolean(false); // isInventoryServerAuthoritative From da0f1690347a0e17a8ecf0eb6d571ede2408a713 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:17:14 +0300 Subject: [PATCH 15/21] Merge --- .../java/cn/nukkit/AdventureSettings.java | 56 +- src/main/java/cn/nukkit/Nukkit.java | 2 +- src/main/java/cn/nukkit/Player.java | 6 +- src/main/java/cn/nukkit/Server.java | 4 +- .../java/cn/nukkit/block/BlockItemFrame.java | 17 +- .../comparator/AlphabetPaletteComparator.java | 18 - .../blockentity/BlockEntityShulkerBox.java | 5 - .../defaults/DefaultGamemodeCommand.java | 3 +- .../command/defaults/GamemodeCommand.java | 2 +- .../command/defaults/SummonCommand.java | 26 +- .../FlintAndSteelDispenseBehavior.java | 3 +- src/main/java/cn/nukkit/entity/Entity.java | 56 +- .../cn/nukkit/entity/EntityHumanType.java | 3 +- .../entity/item/EntityFallingBlock.java | 4 +- .../entity/weather/EntityLightning.java | 2 +- .../nukkit/event/player/PlayerKickEvent.java | 8 +- .../event/server/BatchPacketsEvent.java | 5 +- .../nukkit/form/element/ElementDropdown.java | 2 +- .../cn/nukkit/form/element/ElementInput.java | 2 +- .../cn/nukkit/form/element/ElementLabel.java | 2 +- .../cn/nukkit/form/element/ElementSlider.java | 6 +- .../form/element/ElementStepSlider.java | 2 +- .../cn/nukkit/form/element/ElementToggle.java | 2 +- .../form/handler/FormResponseHandler.java | 2 - .../nukkit/form/window/FormWindowCustom.java | 2 +- .../nukkit/form/window/FormWindowModal.java | 2 +- .../nukkit/form/window/FormWindowSimple.java | 2 +- .../CraftingTransferMaterialAction.java | 4 - src/main/java/cn/nukkit/item/Item.java | 8 +- src/main/java/cn/nukkit/item/ItemBucket.java | 2 +- src/main/java/cn/nukkit/item/ItemCoal.java | 5 +- .../java/cn/nukkit/item/ItemFireCharge.java | 3 +- .../java/cn/nukkit/item/ItemFlintSteel.java | 3 +- .../enchantment/EnchantmentSoulSpeed.java | 5 + .../java/cn/nukkit/level/BlockPalette.java | 19 - src/main/java/cn/nukkit/level/Level.java | 63 +- .../cn/nukkit/level/format/FullChunk.java | 10 - .../level/format/generic/BaseFullChunk.java | 18 - .../object/tree/ObjectSpruceTree.java | 5 +- src/main/java/cn/nukkit/math/MathHelper.java | 2 +- .../java/cn/nukkit/nbt/tag/ByteArrayTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/ByteTag.java | 2 +- .../java/cn/nukkit/nbt/tag/CompoundTag.java | 2 +- .../java/cn/nukkit/nbt/tag/DoubleTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/EndTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/FloatTag.java | 2 +- .../java/cn/nukkit/nbt/tag/IntArrayTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/IntTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/ListTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/LongTag.java | 2 +- src/main/java/cn/nukkit/nbt/tag/ShortTag.java | 2 +- .../java/cn/nukkit/nbt/tag/StringTag.java | 2 +- .../nukkit/network/CompressionProvider.java | 6 +- src/main/java/cn/nukkit/network/Network.java | 4 + .../nukkit/network/protocol/DataPacket.java | 6 +- .../cn/nukkit/scheduler/ServerScheduler.java | 100 ++- src/main/java/cn/nukkit/utils/LogLevel.java | 1 + .../java/cn/nukkit/utils/ServerKiller.java | 1 + .../java/cn/nukkit/utils/SimpleConfig.java | 1 + .../java/cn/nukkit/utils/ThreadCache.java | 11 + src/main/resources/command_default.json | 19 - src/main/resources/item_tags.json | 749 +----------------- src/main/resources/lang/ara/nukkit.yml | 2 +- src/main/resources/lang/bra/nukkit.yml | 2 +- src/main/resources/lang/chs/nukkit.yml | 2 +- src/main/resources/lang/cht/nukkit.yml | 2 +- src/main/resources/lang/cze/nukkit.yml | 2 +- src/main/resources/lang/deu/nukkit.yml | 2 +- src/main/resources/lang/eng/lang.ini | 4 + src/main/resources/lang/eng/nukkit.yml | 2 +- src/main/resources/lang/fin/nukkit.yml | 2 +- src/main/resources/lang/idn/nukkit.yml | 2 +- src/main/resources/lang/jpn/nukkit.yml | 2 +- src/main/resources/lang/kor/nukkit.yml | 2 +- src/main/resources/lang/ltu/nukkit.yml | 2 +- src/main/resources/lang/pol/nukkit.yml | 2 +- src/main/resources/lang/rus/nukkit.yml | 2 +- src/main/resources/lang/spa/nukkit.yml | 2 +- src/main/resources/lang/tur/nukkit.yml | 2 +- src/main/resources/lang/ukr/nukkit.yml | 2 +- src/main/resources/runtime_block_states.dat | Bin 54161 -> 54234 bytes .../resources/runtime_block_states_662.dat | Bin 54217 -> 54297 bytes 82 files changed, 257 insertions(+), 1094 deletions(-) delete mode 100644 src/main/java/cn/nukkit/block/custom/comparator/AlphabetPaletteComparator.java delete mode 100644 src/main/resources/command_default.json diff --git a/src/main/java/cn/nukkit/AdventureSettings.java b/src/main/java/cn/nukkit/AdventureSettings.java index 11970b6ade4..40ac3636022 100644 --- a/src/main/java/cn/nukkit/AdventureSettings.java +++ b/src/main/java/cn/nukkit/AdventureSettings.java @@ -1,6 +1,5 @@ package cn.nukkit; -import cn.nukkit.network.protocol.AdventureSettingsPacket; import cn.nukkit.network.protocol.UpdateAbilitiesPacket; import cn.nukkit.network.protocol.UpdateAdventureSettingsPacket; import cn.nukkit.network.protocol.types.AbilityLayer; @@ -147,51 +146,40 @@ void update(boolean reset) { * List of adventure settings */ public enum Type { - WORLD_IMMUTABLE(AdventureSettingsPacket.WORLD_IMMUTABLE, null, false), - NO_PVM(AdventureSettingsPacket.NO_PVM, null, false), - NO_MVP(AdventureSettingsPacket.NO_MVP, PlayerAbility.INVULNERABLE, false), - SHOW_NAME_TAGS(AdventureSettingsPacket.SHOW_NAME_TAGS, null, false), - AUTO_JUMP(AdventureSettingsPacket.AUTO_JUMP, null, true), - ALLOW_FLIGHT(AdventureSettingsPacket.ALLOW_FLIGHT, PlayerAbility.MAY_FLY, false), - NO_CLIP(AdventureSettingsPacket.NO_CLIP, PlayerAbility.NO_CLIP, false), - WORLD_BUILDER(AdventureSettingsPacket.WORLD_BUILDER, PlayerAbility.WORLD_BUILDER, false), - FLYING(AdventureSettingsPacket.FLYING, PlayerAbility.FLYING, false), - MUTED(AdventureSettingsPacket.MUTED, PlayerAbility.MUTED, false), - MINE(AdventureSettingsPacket.MINE, PlayerAbility.MINE, true), - DOORS_AND_SWITCHED(AdventureSettingsPacket.DOORS_AND_SWITCHES, PlayerAbility.DOORS_AND_SWITCHES, true), - OPEN_CONTAINERS(AdventureSettingsPacket.OPEN_CONTAINERS, PlayerAbility.OPEN_CONTAINERS, true), - ATTACK_PLAYERS(AdventureSettingsPacket.ATTACK_PLAYERS, PlayerAbility.ATTACK_PLAYERS, true), - ATTACK_MOBS(AdventureSettingsPacket.ATTACK_MOBS, PlayerAbility.ATTACK_MOBS, true), - OPERATOR(AdventureSettingsPacket.OPERATOR, PlayerAbility.OPERATOR_COMMANDS, false), - TELEPORT(AdventureSettingsPacket.TELEPORT, PlayerAbility.TELEPORT, false), - BUILD(AdventureSettingsPacket.BUILD, PlayerAbility.BUILD, true), - PRIVILEGED_BUILDER(0, PlayerAbility.PRIVILEGED_BUILDER, false), + WORLD_IMMUTABLE(null, false), + NO_PVM(null, false), + NO_MVP(PlayerAbility.INVULNERABLE, false), + SHOW_NAME_TAGS(null, false), + AUTO_JUMP(null, true), + ALLOW_FLIGHT(PlayerAbility.MAY_FLY, false), + NO_CLIP(PlayerAbility.NO_CLIP, false), + WORLD_BUILDER(PlayerAbility.WORLD_BUILDER, false), + FLYING(PlayerAbility.FLYING, false), + MUTED(PlayerAbility.MUTED, false), + MINE(PlayerAbility.MINE, true), + DOORS_AND_SWITCHED(PlayerAbility.DOORS_AND_SWITCHES, true), + OPEN_CONTAINERS(PlayerAbility.OPEN_CONTAINERS, true), + ATTACK_PLAYERS(PlayerAbility.ATTACK_PLAYERS, true), + ATTACK_MOBS(PlayerAbility.ATTACK_MOBS, true), + OPERATOR(PlayerAbility.OPERATOR_COMMANDS, false), + TELEPORT(PlayerAbility.TELEPORT, false), + BUILD(PlayerAbility.BUILD, true), + PRIVILEGED_BUILDER(PlayerAbility.PRIVILEGED_BUILDER, false), // For backwards compatibility @Deprecated - BUILD_AND_MINE(0, null, true), + BUILD_AND_MINE(null, true), @Deprecated - DEFAULT_LEVEL_PERMISSIONS(AdventureSettingsPacket.DEFAULT_LEVEL_PERMISSIONS, null, false); + DEFAULT_LEVEL_PERMISSIONS(null, false); - private final int id; private final PlayerAbility ability; private final boolean defaultValue; - Type(int id, PlayerAbility ability, boolean defaultValue) { - this.id = id; + Type(PlayerAbility ability, boolean defaultValue) { this.ability = ability; this.defaultValue = defaultValue; } - /** - * Legacy: Get adventure setting ID if available - * - * @return adventure setting ID - */ - public int getId() { - return this.id; - } - /** * Get default value * diff --git a/src/main/java/cn/nukkit/Nukkit.java b/src/main/java/cn/nukkit/Nukkit.java index 6cef1c2cdb3..bf5c0e5f929 100644 --- a/src/main/java/cn/nukkit/Nukkit.java +++ b/src/main/java/cn/nukkit/Nukkit.java @@ -130,7 +130,7 @@ public static void main(String[] args) { } } - ServerKiller killer = new ServerKiller(8); + ServerKiller killer = new ServerKiller(10); killer.start(); if (TITLE) { diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 1c9706e558b..9927d9b4afc 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -540,7 +540,7 @@ public void setAllowInteract(boolean value, boolean containers) { } /** - * Set auto jump adventure setting + * Set auto jump adventure setting (adventureSettings.set(Type.AUTO_JUMP) + adventureSettings.update()) * @param value auto jump enabled */ @Deprecated @@ -550,7 +550,7 @@ public void setAutoJump(boolean value) { } /** - * Check wether auto jump adventure setting is enabled + * Check whether auto jump adventure setting is enabled (adventureSettings.get(Type.AUTO_JUMP)) * @return auto jump enabled */ @Deprecated @@ -1638,7 +1638,7 @@ public boolean setGamemode(int gamemode, boolean clientSide, AdventureSettings n } /** - * Send adventure settings + * Send adventure settings (adventureSettings.update()) */ @Deprecated public void sendSettings() { diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 839168172fc..449a694a8db 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -2530,7 +2530,7 @@ public boolean isWhitelisted(String name) { * @return is operator */ public boolean isOp(String name) { - return this.operators.exists(name, true); + return name != null && this.operators.exists(name, true); } /** @@ -2836,7 +2836,7 @@ private void loadSettings() { this.queryPlugins = this.getConfig("settings.query-plugins", true); this.networkCompressionThreshold = this.getConfig("network.batch-threshold", 256); - this.networkCompressionLevel = Math.max(Math.min(this.getConfig("network.compression-level", 4), 9), 0); + this.networkCompressionLevel = Math.max(Math.min(this.getConfig("network.compression-level", 5), 9), 0); this.encryptionEnabled = this.getConfig("network.encryption", false); this.autoTickRate = this.getConfig("level-settings.auto-tick-rate", true); diff --git a/src/main/java/cn/nukkit/block/BlockItemFrame.java b/src/main/java/cn/nukkit/block/BlockItemFrame.java index df1f715c4b2..785c4c98481 100644 --- a/src/main/java/cn/nukkit/block/BlockItemFrame.java +++ b/src/main/java/cn/nukkit/block/BlockItemFrame.java @@ -16,10 +16,7 @@ */ public class BlockItemFrame extends BlockTransparentMeta implements Faceable { - protected final static int[] FACING = {4, 5, 3, 2, 1, 0}; - - private final static int FACING_BITMASK = 0b0111; - //private final static int MAP_BIT = 0b1000; + protected final static int[] FACING = {8, 9, 3, 2, 1, 0}; public BlockItemFrame() { this(0); @@ -142,22 +139,26 @@ public int getComparatorInputOverride() { } public BlockFace getFacing() { - switch (this.getDamage() & FACING_BITMASK) { + switch (this.getDamage()) { case 0: + case 4: return BlockFace.WEST; case 1: + case 5: return BlockFace.EAST; case 2: + case 6: return BlockFace.NORTH; case 3: + case 7: return BlockFace.SOUTH; - case 4: + case 8: return BlockFace.UP; - case 5: + case 9: return BlockFace.DOWN; } - return null; + return BlockFace.UP; } @Override diff --git a/src/main/java/cn/nukkit/block/custom/comparator/AlphabetPaletteComparator.java b/src/main/java/cn/nukkit/block/custom/comparator/AlphabetPaletteComparator.java deleted file mode 100644 index 419cdfed2e0..00000000000 --- a/src/main/java/cn/nukkit/block/custom/comparator/AlphabetPaletteComparator.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.nukkit.block.custom.comparator; - -import org.cloudburstmc.nbt.NbtMap; - -import java.util.Comparator; - -public class AlphabetPaletteComparator implements Comparator<NbtMap> { - public static final AlphabetPaletteComparator INSTANCE = new AlphabetPaletteComparator(); - - @Override - public int compare(NbtMap o1, NbtMap o2) { - return getIdentifier(o1).compareToIgnoreCase(getIdentifier(o2)); - } - - private String getIdentifier(NbtMap state) { - return state.getString("name"); - } -} diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java index 6c31564c036..bd5f512851a 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityShulkerBox.java @@ -129,11 +129,6 @@ public BaseInventory getInventory() { return this.inventory; } - @Deprecated - public ShulkerBoxInventory getRealInventory() { - return (ShulkerBoxInventory) this.getInventory(); - } - @Override public String getName() { return this.hasName() ? this.namedTag.getString("CustomName") : "Shulker Box"; diff --git a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java index 97595989101..bd2333ea268 100644 --- a/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/DefaultGamemodeCommand.java @@ -8,6 +8,7 @@ import cn.nukkit.command.data.CommandParameter; import cn.nukkit.lang.TranslationContainer; import cn.nukkit.network.protocol.SetDefaultGameTypePacket; +import cn.nukkit.utils.TextFormat; /** * Created on 2015/11/12 by xtypr. @@ -47,7 +48,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) gameTypePacket.gamemode = sender.getServer().getDefaultGamemode(); Server.broadcastPacket(sender.getServer().getOnlinePlayers().values(), gameTypePacket); } else { - sender.sendMessage("Unknown game mode"); + sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.gamemode.fail.invalid", args[0])); } return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java index 15d66b7e3ab..e22a00484ff 100644 --- a/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GamemodeCommand.java @@ -44,7 +44,7 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) int gameMode = Server.getGamemodeFromString(args[0]); if (gameMode == -1) { - sender.sendMessage("Unknown game mode"); + sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.gamemode.fail.invalid", args[0])); return true; } diff --git a/src/main/java/cn/nukkit/command/defaults/SummonCommand.java b/src/main/java/cn/nukkit/command/defaults/SummonCommand.java index 2516d528a99..d596c44fa2b 100644 --- a/src/main/java/cn/nukkit/command/defaults/SummonCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SummonCommand.java @@ -6,6 +6,7 @@ import cn.nukkit.command.data.CommandParamType; import cn.nukkit.command.data.CommandParameter; import cn.nukkit.entity.Entity; +import cn.nukkit.lang.TranslationContainer; import cn.nukkit.level.Position; import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.utils.TextFormat; @@ -36,16 +37,23 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (args.length == 0 || (args.length == 1 && !(sender instanceof Player))) { + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); return false; } + String mob = args[0]; + // Convert Minecraft format to the format what Nukkit uses - String mob = Character.toUpperCase(args[0].charAt(0)) + args[0].substring(1); - int max = mob.length() - 1; - for (int x = 2; x < max; x++) { - if (mob.charAt(x) == '_') { - mob = mob.substring(0, x) + Character.toUpperCase(mob.charAt(x + 1)) + mob.substring(x + 2); + if (!Entity.isKnown(mob)) { + mob = Character.toUpperCase(args[0].charAt(0)) + args[0].substring(1); + StringBuilder sb = new StringBuilder(mob); + for (int x = 2; x < sb.length() - 1; x++) { + if (sb.charAt(x) == '_') { + sb.setCharAt(x + 1, Character.toUpperCase(sb.charAt(x + 1))); + sb.deleteCharAt(x); + } } + mob = sb.toString(); } Player playerThatSpawns; @@ -57,16 +65,16 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) } if (playerThatSpawns != null) { - Position pos = playerThatSpawns.getPosition().floor().add(0.5, 0, 0.5); + Position pos = playerThatSpawns.floor().add(0.5, 0, 0.5); Entity ent; if ((ent = Entity.createEntity(mob, pos)) != null) { ent.spawnToAll(); - sender.sendMessage("§6Spawned " + mob + " to " + playerThatSpawns.getName()); + sender.sendMessage(new TranslationContainer("%commands.summon.success")); } else { - sender.sendMessage(TextFormat.RED + "Unable to spawn " + mob); + sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.summon.failed")); } } else { - sender.sendMessage(TextFormat.RED + "Unknown player " + (args.length == 2 ? args[1] : sender.getName())); + sender.sendMessage(new TranslationContainer(TextFormat.RED + "%commands.generic.player.notFound")); } return true; diff --git a/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java index 9281153a8d2..599251f29d0 100644 --- a/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java +++ b/src/main/java/cn/nukkit/dispenser/FlintAndSteelDispenseBehavior.java @@ -14,10 +14,11 @@ public Item dispense(BlockDispenser block, BlockFace face, Item item) { if (target.getId() == BlockID.AIR) { Block down = target.down(); - if (down.getId() != BlockID.OBSIDIAN || !down.level.createPortal(down, false)) { + if (down.getId() != BlockID.OBSIDIAN || !down.level.createPortal(down)) { boolean soulFire = down.getId() == Block.SOUL_SAND || down.getId() == Block.SOUL_SOIL; block.level.setBlock(target, Block.get(soulFire ? BlockID.SOUL_FIRE : BlockID.FIRE)); } + down.level.addSound(down, cn.nukkit.level.Sound.MOB_GHAST_FIREBALL); item.useOn(target); } else if (target.getId() == BlockID.TNT) { target.onActivate(item); diff --git a/src/main/java/cn/nukkit/entity/Entity.java b/src/main/java/cn/nukkit/entity/Entity.java index 51de82fca75..b0ed653d2bf 100644 --- a/src/main/java/cn/nukkit/entity/Entity.java +++ b/src/main/java/cn/nukkit/entity/Entity.java @@ -372,7 +372,12 @@ public abstract class Entity extends Location implements Metadatable { public double lastPitch; public double lastHeadYaw; - public double entityCollisionReduction = 0; // Higher than 0.9 will result a fast collisions + @Deprecated + public double PitchDelta; + @Deprecated + public double YawDelta; + + public double entityCollisionReduction; // Higher than 0.9 will result a fast collisions public AxisAlignedBB boundingBox; public boolean onGround; @Deprecated @@ -381,25 +386,25 @@ public abstract class Entity extends Location implements Metadatable { public boolean positionChanged; @Deprecated public boolean motionChanged; - public int deadTicks = 0; - public int age = 0; - public int ticksLived = 0; + public int deadTicks; + public int age; + public int ticksLived; protected int airTicks = 400; protected boolean noFallDamage; protected float health = 20; protected int maxHealth = 20; - protected float absorption = 0; + protected float absorption; - protected float ySize = 0; + protected float ySize; public boolean keepMovement; - public float fallDistance = 0; + public float fallDistance; public int lastUpdate; - public int fireTicks = 0; - public int inPortalTicks = 0; - public int inEndPortalTicks = 0; + public int fireTicks; + public int inPortalTicks; + public int inEndPortalTicks; protected Position portalPos; public boolean noClip; public float scale = 1; @@ -433,7 +438,8 @@ public abstract class Entity extends Location implements Metadatable { public boolean closed; - public final boolean isPlayer; + @Deprecated + public final boolean isPlayer = this instanceof Player; private volatile boolean init; private volatile boolean initEntity; @@ -481,8 +487,7 @@ protected float getBaseOffset() { } public Entity(FullChunk chunk, CompoundTag nbt) { - this.isPlayer = this instanceof Player; - if (!this.isPlayer) { + if (!(this instanceof Player)) { this.init(chunk, nbt); } } @@ -1044,6 +1049,10 @@ private static Entity createEntity0(Class<? extends Entity> clazz, FullChunk chu return entity; } + public static boolean isKnown(String name) { + return knownEntities.containsKey(name); + } + public static boolean registerEntity(String name, Class<? extends Entity> clazz) { return registerEntity(name, clazz, false); } @@ -2575,6 +2584,10 @@ public boolean teleport(Location location) { } public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + if (!this.server.isPrimaryThread()) { + this.server.getLogger().warning("Entity teleported asynchronously: " + this.getClass().getSimpleName()); + } + double yaw = location.yaw; double pitch = location.pitch; @@ -2845,23 +2858,6 @@ public int hashCode() { return (int) (203 + this.id); } - public static Entity create(Object type, Position source, Object... args) { - FullChunk chunk = source.getLevel().getChunk((int) source.x >> 4, (int) source.z >> 4, true); - if (!chunk.isGenerated()) { - chunk.setGenerated(); - } - if (!chunk.isPopulated()) { - chunk.setPopulated(); - } - - CompoundTag nbt = new CompoundTag().putList(new ListTag<DoubleTag>("Pos").add(new DoubleTag("", source.x)).add(new DoubleTag("", source.y)).add(new DoubleTag("", source.z))) - .putList(new ListTag<DoubleTag>("Motion").add(new DoubleTag("", 0)).add(new DoubleTag("", 0)).add(new DoubleTag("", 0))) - .putList(new ListTag<FloatTag>("Rotation").add(new FloatTag("", source instanceof Location ? (float) ((Location) source).yaw : 0)) - .add(new FloatTag("", source instanceof Location ? (float) ((Location) source).pitch : 0))); - - return Entity.createEntity(type.toString(), chunk, nbt, args); - } - public boolean isOnLadder() { int b = this.level.getBlockIdAt(this.chunk, this.getFloorX(), this.getFloorY(), this.getFloorZ()); return b == Block.LADDER || b == Block.VINES || b == Block.TWISTING_VINES || b == Block.WEEPING_VINES || b == Block.CAVE_VINES_BODY_WITH_BERRIES || b == Block.CAVE_VINES_HEAD_WITH_BERRIES; diff --git a/src/main/java/cn/nukkit/entity/EntityHumanType.java b/src/main/java/cn/nukkit/entity/EntityHumanType.java index 5d302e89f97..ab67441fbf5 100644 --- a/src/main/java/cn/nukkit/entity/EntityHumanType.java +++ b/src/main/java/cn/nukkit/entity/EntityHumanType.java @@ -54,6 +54,7 @@ protected void initEntity() { for (CompoundTag item : inventoryList.getAll()) { int slot = item.getByte("Slot"); if (slot >= 0 && slot < 9) { + // Old hotbar saving stuff, remove it (useless now) inventoryList.remove(item); } else if (slot >= 100 && slot < 104) { this.inventory.setItem(this.inventory.getSize() + slot - 100, NBTIO.getItemHelper(item)); @@ -96,7 +97,7 @@ public void saveNBT() { ); } - int slotCount = 45; + int slotCount = 45; // SURVIVAL_SLOTS + 9 for (int slot = 9; slot < slotCount; ++slot) { Item item = this.inventory.getItem(slot - 9); inventoryTag.add(NBTIO.putItemHelper(item, slot)); diff --git a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java index 33403c2fe31..ad5b7f9aef2 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java @@ -158,7 +158,7 @@ public boolean onUpdate(int currentTick) { if (onGround && !closed) { close(); - Block floorBlock = level.getBlock(this); + Block floorBlock = level.getBlock(this.add(0, 0.0001, 0)); if (this.getBlock() == Block.SNOW_LAYER && floorBlock.getId() == Block.SNOW_LAYER && (floorBlock.getDamage() & 0x7) != 0x7) { int mergedHeight = (floorBlock.getDamage() & 0x7) + 1 + (this.getDamage() & 0x7) + 1; if (mergedHeight > 8) { @@ -188,7 +188,7 @@ public boolean onUpdate(int currentTick) { if (this.getBlock() != Block.SNOW_LAYER ? this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS) : this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { getLevel().dropItem(this, Item.get(this.blockId, this.damage, 1)); } - } else { + } else if (floorBlock.getId() == 0) { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(blockId, damage)); server.getPluginManager().callEvent(event); if (!event.isCancelled()) { diff --git a/src/main/java/cn/nukkit/entity/weather/EntityLightning.java b/src/main/java/cn/nukkit/entity/weather/EntityLightning.java index 906e025a2a8..5228093a62c 100644 --- a/src/main/java/cn/nukkit/entity/weather/EntityLightning.java +++ b/src/main/java/cn/nukkit/entity/weather/EntityLightning.java @@ -20,7 +20,7 @@ public class EntityLightning extends Entity implements EntityLightningStrike { public static final int NETWORK_ID = 93; - private boolean isEffect = true; + protected boolean isEffect = true; public int state; public int liveTime; diff --git a/src/main/java/cn/nukkit/event/player/PlayerKickEvent.java b/src/main/java/cn/nukkit/event/player/PlayerKickEvent.java index 45e43b80c50..d1d2943e0e2 100644 --- a/src/main/java/cn/nukkit/event/player/PlayerKickEvent.java +++ b/src/main/java/cn/nukkit/event/player/PlayerKickEvent.java @@ -47,14 +47,14 @@ public PlayerKickEvent(Player player, String reason, TextContainer quitMessage) this(player, Reason.UNKNOWN, reason, quitMessage); } - public PlayerKickEvent(Player player, Reason reason, TextContainer quitMessage) { - this(player, reason, reason.toString(), quitMessage); - } - public PlayerKickEvent(Player player, Reason reason, String quitMessage) { this(player, reason, new TextContainer(quitMessage)); } + public PlayerKickEvent(Player player, Reason reason, TextContainer quitMessage) { + this(player, reason, reason.toString(), quitMessage); + } + public PlayerKickEvent(Player player, Reason reason, String reasonString, TextContainer quitMessage) { this.player = player; this.quitMessage = quitMessage; diff --git a/src/main/java/cn/nukkit/event/server/BatchPacketsEvent.java b/src/main/java/cn/nukkit/event/server/BatchPacketsEvent.java index 9aa06826ca7..efa2e162df3 100644 --- a/src/main/java/cn/nukkit/event/server/BatchPacketsEvent.java +++ b/src/main/java/cn/nukkit/event/server/BatchPacketsEvent.java @@ -16,12 +16,9 @@ public static HandlerList getHandlers() { private final Player[] players; private final DataPacket[] packets; - private final boolean forceSync; - public BatchPacketsEvent(Player[] players, DataPacket[] packets, boolean forceSync) { this.players = players; this.packets = packets; - this.forceSync = forceSync; } public Player[] getPlayers() { @@ -34,6 +31,6 @@ public DataPacket[] getPackets() { @Deprecated public boolean isForceSync() { - return forceSync; + return true; } } diff --git a/src/main/java/cn/nukkit/form/element/ElementDropdown.java b/src/main/java/cn/nukkit/form/element/ElementDropdown.java index 9b3a5fb5add..4cfd810e057 100644 --- a/src/main/java/cn/nukkit/form/element/ElementDropdown.java +++ b/src/main/java/cn/nukkit/form/element/ElementDropdown.java @@ -8,7 +8,7 @@ public class ElementDropdown extends Element { @SuppressWarnings("unused") - private final String type = "dropdown"; + private final String type = "dropdown"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text = ""; @SuppressWarnings("FieldMayBeFinal") private List<String> options; diff --git a/src/main/java/cn/nukkit/form/element/ElementInput.java b/src/main/java/cn/nukkit/form/element/ElementInput.java index c5a33043661..18b3f9c8bda 100644 --- a/src/main/java/cn/nukkit/form/element/ElementInput.java +++ b/src/main/java/cn/nukkit/form/element/ElementInput.java @@ -5,7 +5,7 @@ public class ElementInput extends Element { @SuppressWarnings("unused") - private final String type = "input"; + private final String type = "input"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text = ""; private String placeholder = ""; @SerializedName("default") diff --git a/src/main/java/cn/nukkit/form/element/ElementLabel.java b/src/main/java/cn/nukkit/form/element/ElementLabel.java index 2f3a165fe82..a14a322cc3a 100644 --- a/src/main/java/cn/nukkit/form/element/ElementLabel.java +++ b/src/main/java/cn/nukkit/form/element/ElementLabel.java @@ -3,7 +3,7 @@ public class ElementLabel extends Element { @SuppressWarnings("unused") - private final String type = "label"; + private final String type = "label"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text = ""; public ElementLabel(String text) { diff --git a/src/main/java/cn/nukkit/form/element/ElementSlider.java b/src/main/java/cn/nukkit/form/element/ElementSlider.java index 3b11f376c7d..1f1c6e39f45 100644 --- a/src/main/java/cn/nukkit/form/element/ElementSlider.java +++ b/src/main/java/cn/nukkit/form/element/ElementSlider.java @@ -5,7 +5,7 @@ public class ElementSlider extends Element { @SuppressWarnings("unused") - private final String type = "slider"; + private final String type = "slider"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text = ""; private float min = 0f; private float max = 100f; @@ -25,8 +25,8 @@ public ElementSlider(String text, float min, float max, int step, float defaultV this.text = text; this.min = Math.max(min, 0f); this.max = Math.max(max, this.min); - if (step != -1.0f && step > 0) this.step = step; - if (defaultValue != -1.0f) this.defaultValue = defaultValue; + if (step != -1f && step > 0) this.step = step; + if (defaultValue != -1f) this.defaultValue = defaultValue; } public String getText() { diff --git a/src/main/java/cn/nukkit/form/element/ElementStepSlider.java b/src/main/java/cn/nukkit/form/element/ElementStepSlider.java index 7f510f3360f..ef942764cc6 100644 --- a/src/main/java/cn/nukkit/form/element/ElementStepSlider.java +++ b/src/main/java/cn/nukkit/form/element/ElementStepSlider.java @@ -8,7 +8,7 @@ public class ElementStepSlider extends Element { @SuppressWarnings("unused") - private final String type = "step_slider"; + private final String type = "step_slider"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text = ""; @SuppressWarnings("FieldMayBeFinal") private List<String> steps; diff --git a/src/main/java/cn/nukkit/form/element/ElementToggle.java b/src/main/java/cn/nukkit/form/element/ElementToggle.java index c2fc4f55754..eb24b24fa16 100644 --- a/src/main/java/cn/nukkit/form/element/ElementToggle.java +++ b/src/main/java/cn/nukkit/form/element/ElementToggle.java @@ -5,7 +5,7 @@ public class ElementToggle extends Element { @SuppressWarnings("unused") - private final String type = "toggle"; + private final String type = "toggle"; //This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String text; @SerializedName("default") private boolean defaultValue; diff --git a/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java b/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java index effc95ca7d7..5d52099c67a 100644 --- a/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java +++ b/src/main/java/cn/nukkit/form/handler/FormResponseHandler.java @@ -6,11 +6,9 @@ public interface FormResponseHandler { - static FormResponseHandler withoutPlayer(IntConsumer formIDConsumer) { return (player, formID) -> formIDConsumer.accept(formID); } - void handle(Player player, int formID); } diff --git a/src/main/java/cn/nukkit/form/window/FormWindowCustom.java b/src/main/java/cn/nukkit/form/window/FormWindowCustom.java index f7682ee7ba5..756b16eaab4 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowCustom.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowCustom.java @@ -12,7 +12,7 @@ public class FormWindowCustom extends FormWindow { @SuppressWarnings("unused") - private final String type = "custom_form"; + private final String type = "custom_form"; // This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String title = ""; private ElementButtonImageData icon; @SuppressWarnings("FieldMayBeFinal") diff --git a/src/main/java/cn/nukkit/form/window/FormWindowModal.java b/src/main/java/cn/nukkit/form/window/FormWindowModal.java index cb58679c9d9..63da91229d8 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowModal.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowModal.java @@ -5,7 +5,7 @@ public class FormWindowModal extends FormWindow { @SuppressWarnings("unused") - private final String type = "modal"; + private final String type = "modal"; // This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String title = ""; private String content = ""; private String button1 = ""; diff --git a/src/main/java/cn/nukkit/form/window/FormWindowSimple.java b/src/main/java/cn/nukkit/form/window/FormWindowSimple.java index 5731a7745ae..0b4e63f73f7 100644 --- a/src/main/java/cn/nukkit/form/window/FormWindowSimple.java +++ b/src/main/java/cn/nukkit/form/window/FormWindowSimple.java @@ -9,7 +9,7 @@ public class FormWindowSimple extends FormWindow { @SuppressWarnings("unused") - private final String type = "form"; + private final String type = "form"; // This variable is used for JSON import operations. Do NOT delete :) -- @Snake1999 private String title = ""; private String content = ""; @SuppressWarnings("FieldMayBeFinal") diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java index 46fc91701d7..137b86839ea 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/CraftingTransferMaterialAction.java @@ -10,12 +10,8 @@ */ public class CraftingTransferMaterialAction extends InventoryAction { - //private int slot; - public CraftingTransferMaterialAction(Item sourceItem, Item targetItem, int slot) { super(sourceItem, targetItem); - - //this.slot = slot; } @Override diff --git a/src/main/java/cn/nukkit/item/Item.java b/src/main/java/cn/nukkit/item/Item.java index 66fef103a53..ed302006196 100644 --- a/src/main/java/cn/nukkit/item/Item.java +++ b/src/main/java/cn/nukkit/item/Item.java @@ -399,12 +399,12 @@ public static void addCreativeItem(Item item) { public static void removeCreativeItem(Item item) { int index = getCreativeItemIndex(item); if (index != -1) { - getCreativeItems().remove(index); + Item.CREATIVE_ITEMS.remove(index); } } public static boolean isCreativeItem(Item item) { - for (Item aCreative : getCreativeItems()) { + for (Item aCreative : Item.CREATIVE_ITEMS) { if (item.equals(aCreative, !item.isTool())) { return true; } @@ -413,12 +413,12 @@ public static boolean isCreativeItem(Item item) { } public static Item getCreativeItem(int index) { - ArrayList<Item> items = getCreativeItems(); + List<Item> items = Item.CREATIVE_ITEMS; return (index >= 0 && index < items.size()) ? items.get(index) : null; } public static int getCreativeItemIndex(Item item) { - ArrayList<Item> items = getCreativeItems(); + List<Item> items = Item.CREATIVE_ITEMS; for (int i = 0; i < items.size(); i++) { if (item.equals(items.get(i), !item.isTool())) { return i; diff --git a/src/main/java/cn/nukkit/item/ItemBucket.java b/src/main/java/cn/nukkit/item/ItemBucket.java index 3c586fc6ab6..976cdf99bc2 100644 --- a/src/main/java/cn/nukkit/item/ItemBucket.java +++ b/src/main/java/cn/nukkit/item/ItemBucket.java @@ -292,7 +292,7 @@ public boolean onActivate(Level level, Player player, Block block, Block target, player.setNeedSendInventory(true); } } else if (targetBlock instanceof BlockPowderSnow) { - if (player.getLevel().getProvider() instanceof Anvil) { + if (player.getLevel().getProvider() instanceof Anvil || !block.canBeReplaced()) { return false; } diff --git a/src/main/java/cn/nukkit/item/ItemCoal.java b/src/main/java/cn/nukkit/item/ItemCoal.java index 46bf7064d85..7bd76f1bf17 100644 --- a/src/main/java/cn/nukkit/item/ItemCoal.java +++ b/src/main/java/cn/nukkit/item/ItemCoal.java @@ -15,9 +15,6 @@ public ItemCoal(Integer meta) { } public ItemCoal(Integer meta, int count) { - super(COAL, meta, count, "Coal"); - if (this.meta == 1) { - this.name = "Charcoal"; - } + super(COAL, meta, count, meta != null && meta == 1 ? "Charcoal" : "Coal"); } } diff --git a/src/main/java/cn/nukkit/item/ItemFireCharge.java b/src/main/java/cn/nukkit/item/ItemFireCharge.java index 092595faa60..4ef3be088d2 100644 --- a/src/main/java/cn/nukkit/item/ItemFireCharge.java +++ b/src/main/java/cn/nukkit/item/ItemFireCharge.java @@ -35,7 +35,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, if (block.getId() == AIR && (target instanceof BlockSolid || target instanceof BlockSolidMeta || target instanceof BlockLeaves)) { if (target.getId() == OBSIDIAN) { - if (level.createPortal(target, true)) { + if (level.createPortal(target)) { + level.addSound(target, Sound.MOB_GHAST_FIREBALL); return true; } } diff --git a/src/main/java/cn/nukkit/item/ItemFlintSteel.java b/src/main/java/cn/nukkit/item/ItemFlintSteel.java index a5dd0473275..e3170f6a6ce 100644 --- a/src/main/java/cn/nukkit/item/ItemFlintSteel.java +++ b/src/main/java/cn/nukkit/item/ItemFlintSteel.java @@ -42,7 +42,8 @@ public boolean onActivate(Level level, Player player, Block block, Block target, // 1.18 vanilla allows flint & steel to be used even if fire exists if ((block.getId() == AIR || block.getId() == FIRE || block.getId() == SOUL_FIRE) && (target instanceof BlockSolid || target instanceof BlockSolidMeta || target instanceof BlockLeaves)) { if (target.getId() == OBSIDIAN) { - if (level.createPortal(target, false)) { + if (level.createPortal(target)) { + level.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); return true; } } diff --git a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java index e5d8e1356b2..0fbfa0a3837 100644 --- a/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java +++ b/src/main/java/cn/nukkit/item/enchantment/EnchantmentSoulSpeed.java @@ -15,4 +15,9 @@ public int getMinEnchantAbility(int level) { public int getMaxLevel() { return 3; } + + @Override + public boolean isTreasure() { + return true; + } } diff --git a/src/main/java/cn/nukkit/level/BlockPalette.java b/src/main/java/cn/nukkit/level/BlockPalette.java index ea1d77cf425..19feea03d92 100644 --- a/src/main/java/cn/nukkit/level/BlockPalette.java +++ b/src/main/java/cn/nukkit/level/BlockPalette.java @@ -43,25 +43,6 @@ public void registerState(int blockId, int data, int runtimeId, CompoundTag bloc this.legacyToRuntimeId.put(legacyId, runtimeId); this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); this.stateToLegacy.putIfAbsent(blockState, legacyId); - - // Hack: Map IDs for item frame up & down states - if (blockId == BlockID.ITEM_FRAME_BLOCK || blockId == BlockID.GLOW_FRAME) { - if (data == 7) { - int offset = 5; - - runtimeId = runtimeId + offset; - legacyId = blockId << 6 | 5; // Up - this.legacyToRuntimeId.put(legacyId, runtimeId); - this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); - - int offset2 = 0; - - runtimeId = runtimeId + offset + offset2; - legacyId = blockId << 6 | 4; // Down - this.legacyToRuntimeId.put(legacyId, runtimeId); - this.runtimeIdToLegacy.putIfAbsent(runtimeId, legacyId); - } - } } public void lock() { diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 3cff8535ff8..742273e6345 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -66,7 +66,6 @@ import java.lang.ref.SoftReference; import java.util.*; import java.util.concurrent.*; -import java.util.function.Predicate; /** @@ -319,13 +318,13 @@ public Level(Server server, String name, String path, Class<? extends LevelProvi this.raining = this.provider.isRaining(); this.rainTime = this.provider.getRainTime(); if (this.rainTime <= 0) { - setRainTime(Utils.random.nextInt(168000) + 12000); + setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } this.thundering = this.provider.isThundering(); this.thunderTime = this.provider.getThunderTime(); if (this.thunderTime <= 0) { - setThunderTime(Utils.random.nextInt(168000) + 12000); + setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } this.levelCurrentTick = this.provider.getCurrentTick(); @@ -808,9 +807,9 @@ public void doTick(int currentTick) { if (this.rainTime <= 0) { if (!this.setRaining(!this.raining)) { if (this.raining) { - setRainTime(Utils.random.nextInt(12000) + 12000); + setRainTime(ThreadLocalRandom.current().nextInt(12000) + 12000); } else { - setRainTime(Utils.random.nextInt(168000) + 12000); + setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } } } @@ -819,9 +818,9 @@ public void doTick(int currentTick) { if (this.thunderTime <= 0) { if (!this.setThundering(!this.thundering)) { if (this.thundering) { - setThunderTime(Utils.random.nextInt(12000) + 3600); + setThunderTime(ThreadLocalRandom.current().nextInt(12000) + 3600); } else { - setThunderTime(Utils.random.nextInt(168000) + 12000); + setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } } } @@ -1003,19 +1002,19 @@ public void checkSleep() { return; } - int players = 0; - int sleeping = 0; + int playerCount = 0; + int sleepingPlayerCount = 0; for (Player p : this.players.values()) { if (p.isSpectator()) { continue; } - players++; + playerCount++; if (p.isSleeping()) { - sleeping++; + sleepingPlayerCount++; } } - if (players > 0 && sleeping / players * 100 >= gameRules.getInteger(GameRule.PLAYERS_SLEEPING_PERCENTAGE)) { + if (playerCount > 0 && sleepingPlayerCount / playerCount * 100 >= gameRules.getInteger(GameRule.PLAYERS_SLEEPING_PERCENTAGE)) { int time = this.getTime() % Level.TIME_FULL; if ((time >= Level.TIME_NIGHT && time < Level.TIME_SUNRISE) || this.isThundering()) { @@ -1107,9 +1106,7 @@ public void sendBlocks(Player[] target, Vector3[] blocks, int flags, boolean opt throw new IllegalStateException("Unable to create BlockUpdatePacket at (" + b.x + ", " + b.y + ", " + b.z + ") in " + getName()); } - for (Player player : target) { - player.dataPacket(packet); - } + Server.broadcastPacket(target, packet); } } @@ -1419,10 +1416,6 @@ public Block[] getCollisionBlocks(AxisAlignedBB bb, boolean targetFirst) { } public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targetFirst) { - return getCollisionBlocks(entity, bb, targetFirst, block -> block.getId() != 0); - } - - public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targetFirst, Predicate<Block> condition) { int minX = NukkitMath.floorDouble(bb.getMinX()); int minY = NukkitMath.floorDouble(bb.getMinY()); int minZ = NukkitMath.floorDouble(bb.getMinZ()); @@ -1437,7 +1430,7 @@ public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targe for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(entity == null ? null : entity.chunk, x, y, z, false); - if (block != null && condition.test(block) && block.collidesWithBB(bb)) { + if (block != null && block.getId() != 0 && block.collidesWithBB(bb)) { return new Block[]{block}; } } @@ -1448,7 +1441,7 @@ public Block[] getCollisionBlocks(Entity entity, AxisAlignedBB bb, boolean targe for (int x = minX; x <= maxX; ++x) { for (int y = minY; y <= maxY; ++y) { Block block = this.getBlock(entity == null ? null : entity.chunk, x, y, z, false); - if (block != null && condition.test(block) && block.collidesWithBB(bb)) { + if (block != null && block.getId() != 0 && block.collidesWithBB(bb)) { collides.add(block); } } @@ -2821,14 +2814,6 @@ public void setHeightMap(int x, int z, int value) { this.getChunk(x >> 4, z >> 4, true).setHeightMap(x & 0x0f, z & 0x0f, value & 0x0f); } - public int getBiomeColor(int x, int z) { - return this.getChunk(x >> 4, z >> 4, true).getBiomeColor(x & 0x0f, z & 0x0f); - } - - public void setBiomeColor(int x, int z, int R, int G, int B) { - this.getChunk(x >> 4, z >> 4, true).setBiomeColor(x & 0x0f, z & 0x0f, R, G, B); - } - public Map<Long, ? extends FullChunk> getChunks() { return provider.getLoadedChunks(); } @@ -3765,9 +3750,7 @@ public void addEntityMovement(Entity entity, double x, double y, double z, doubl pk.pitch = pitch; pk.onGround = entity.onGround; - for (Player p : entity.getViewers().values()) { - p.dataPacket(pk); - } + Server.broadcastPacket(entity.getViewers().values(), pk); } public boolean isRaining() { @@ -3794,7 +3777,7 @@ public boolean setRaining(boolean raining) { setRainTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_RAIN; - setRainTime(Utils.random.nextInt(168000) + 12000); + setRainTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); @@ -3837,7 +3820,7 @@ public boolean setThundering(boolean thundering) { setThunderTime(time); } else { pk.evid = LevelEventPacket.EVENT_STOP_THUNDER; - setThunderTime(Utils.random.nextInt(168000) + 12000); + setThunderTime(ThreadLocalRandom.current().nextInt(168000) + 12000); } Server.broadcastPacket(this.getPlayers().values(), pk); @@ -4008,7 +3991,7 @@ public boolean isAnimalSpawningAllowedByTime() { return time < 13184 || time > 22800; } - public boolean createPortal(Block target, boolean fireCharge) { + public boolean createPortal(Block target) { if (this.getDimension() == DIMENSION_THE_END) return false; final int maxPortalSize = 23; final int targX = target.getFloorX(); @@ -4135,11 +4118,6 @@ public boolean createPortal(Block target, boolean fireCharge) { } } - if (fireCharge) { - this.addSound(target, cn.nukkit.level.Sound.MOB_GHAST_FIREBALL); - } else { - this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); - } return true; } else if (sizeZ >= 2 && sizeZ <= maxPortalSize) { //start scan from 1 block above base @@ -4221,11 +4199,6 @@ public boolean createPortal(Block target, boolean fireCharge) { } } - if (fireCharge) { - this.addSound(target, cn.nukkit.level.Sound.MOB_GHAST_FIREBALL); - } else { - this.addLevelSoundEvent(target, LevelSoundEventPacket.SOUND_IGNITE); - } return true; } diff --git a/src/main/java/cn/nukkit/level/format/FullChunk.java b/src/main/java/cn/nukkit/level/format/FullChunk.java index 7678a47276c..a8ed539a1b4 100644 --- a/src/main/java/cn/nukkit/level/format/FullChunk.java +++ b/src/main/java/cn/nukkit/level/format/FullChunk.java @@ -124,9 +124,6 @@ default int getBiomeId(int x, int y, int z) { return this.getBiomeId(x, z); } - @Deprecated - void setBiomeIdAndColor(int x, int z, int idAndColor); - default void setBiomeId(int x, int y, int z, int biomeId) { this.setBiomeId(x, y, z, (byte) biomeId); } @@ -145,10 +142,6 @@ default void setBiome(int x, int z, cn.nukkit.level.biome.Biome biome) { setBiomeId(x, z, biome.getId()); } - int getBiomeColor(int x, int z); - - void setBiomeColor(int x, int z, int r, int g, int b); - boolean isLightPopulated(); void setLightPopulated(); @@ -199,9 +192,6 @@ default void setBiome(int x, int z, cn.nukkit.level.biome.Biome biome) { void setBiomeIdArray(byte[] biomeIdArray); - @Deprecated - int[] getBiomeColorArray(); - byte[] getHeightMapArray(); @Deprecated diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java index 0baf128db99..71a37a24696 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java @@ -231,19 +231,6 @@ public void setBiomeId(int x, int z, int biomeId) { this.biomes[(x << 4) | z] = (byte) biomeId; } - @Override - public int getBiomeColor(int x, int z) { - return 0; - } - - @Override - public void setBiomeIdAndColor(int x, int z, int idAndColor) { - } - - @Override - public void setBiomeColor(int x, int z, int r, int g, int b) { - } - @Override public int getHeightMap(int x, int z) { return this.heightMap[(z << 4) | x] & 0xFF; @@ -515,11 +502,6 @@ public void setBiomeIdArray(byte[] biomeIdArray) { this.biomes = biomeIdArray; } - @Override - public int[] getBiomeColorArray() { - return new int[0]; - } - @Override public byte[] getHeightMapArray() { return this.heightMap; diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java index d8eb0ef97e2..30d776da8a6 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java @@ -3,6 +3,7 @@ import cn.nukkit.block.Block; import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; +import cn.nukkit.level.biome.Biome; import cn.nukkit.math.NukkitRandom; /** @@ -49,6 +50,8 @@ public void placeLeaves(ChunkManager level, int topSize, int lRadius, int x, int int maxR = 1; int minR = 0; + boolean createSnow = this.canCreateSnow && Biome.getBiome(level.getChunk(x >> 4, z >> 4).getBiomeId(x & 0x0f, z & 0x0f)).isFreezing(); + for (int yy = 0; yy <= topSize; ++yy) { int yyy = y + this.treeHeight - yy; @@ -63,7 +66,7 @@ public void placeLeaves(ChunkManager level, int topSize, int lRadius, int x, int if (!Block.isBlockSolidById(level.getBlockIdAt(xx, yyy, zz))) { level.setBlockAt(xx, yyy, zz, this.getLeafBlock(), this.getType()); - if (this.canCreateSnow) { + if (createSnow) { if (level.getBlockIdAt(xx, yyy + 1, zz) == Block.AIR && level.getBlockIdAt(xx, yyy + 2, zz) == Block.AIR) { level.setBlockAt(xx, yyy + 1, zz, Block.SNOW_LAYER, 0); } diff --git a/src/main/java/cn/nukkit/math/MathHelper.java b/src/main/java/cn/nukkit/math/MathHelper.java index e9a6ed5380a..bb69aef37ec 100644 --- a/src/main/java/cn/nukkit/math/MathHelper.java +++ b/src/main/java/cn/nukkit/math/MathHelper.java @@ -90,7 +90,7 @@ public static int ceil(float floatNumber) { } public static int clamp(int check, int min, int max) { - return check > max ? max : (Math.max(check, min)); + return check > max ? max : (check < min ? min : check); } public static double denormalizeClamp(double lowerBnd, double upperBnd, double slide) { diff --git a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java index 7ab7dee8c6d..762bdbfb587 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ByteArrayTag.java @@ -21,7 +21,7 @@ public ByteArrayTag(String name, byte[] data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { if (data == null) { dos.writeInt(0); return; diff --git a/src/main/java/cn/nukkit/nbt/tag/ByteTag.java b/src/main/java/cn/nukkit/nbt/tag/ByteTag.java index b64fea4c379..2105ab5183c 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ByteTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ByteTag.java @@ -29,7 +29,7 @@ public ByteTag(String name, int data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeByte(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java index e3fa54c1522..8e4cfacfd42 100644 --- a/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/CompoundTag.java @@ -24,7 +24,7 @@ public CompoundTag(String name) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { for (Map.Entry<String, Tag> entry : this.tags.entrySet()) { Tag.writeNamedTag(entry.getValue(), entry.getKey(), dos); } diff --git a/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java b/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java index f9c592e6dd0..aa83d87bced 100644 --- a/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/DoubleTag.java @@ -29,7 +29,7 @@ public DoubleTag(String name, double data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeDouble(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/EndTag.java b/src/main/java/cn/nukkit/nbt/tag/EndTag.java index dde6093a682..9c5665e3939 100644 --- a/src/main/java/cn/nukkit/nbt/tag/EndTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/EndTag.java @@ -16,7 +16,7 @@ public void load(NBTInputStream dis) throws IOException { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { } @Override diff --git a/src/main/java/cn/nukkit/nbt/tag/FloatTag.java b/src/main/java/cn/nukkit/nbt/tag/FloatTag.java index 8c628fa322b..cb52a9d7508 100644 --- a/src/main/java/cn/nukkit/nbt/tag/FloatTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/FloatTag.java @@ -29,7 +29,7 @@ public FloatTag(String name, float data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeFloat(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java b/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java index 37303d7200c..dce99f7c1ed 100644 --- a/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/IntArrayTag.java @@ -20,7 +20,7 @@ public IntArrayTag(String name, int[] data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeInt(data.length); for (int aData : data) { dos.writeInt(aData); diff --git a/src/main/java/cn/nukkit/nbt/tag/IntTag.java b/src/main/java/cn/nukkit/nbt/tag/IntTag.java index 84a4edbfff7..d309322cc8d 100644 --- a/src/main/java/cn/nukkit/nbt/tag/IntTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/IntTag.java @@ -29,7 +29,7 @@ public IntTag(String name, int data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeInt(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/ListTag.java b/src/main/java/cn/nukkit/nbt/tag/ListTag.java index fd9f87a46f1..073326cdd9b 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ListTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ListTag.java @@ -25,7 +25,7 @@ public ListTag(String name) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { if (!list.isEmpty()) type = list.get(0).getId(); else type = 1; diff --git a/src/main/java/cn/nukkit/nbt/tag/LongTag.java b/src/main/java/cn/nukkit/nbt/tag/LongTag.java index de08af0b7b7..6f37370a14b 100644 --- a/src/main/java/cn/nukkit/nbt/tag/LongTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/LongTag.java @@ -29,7 +29,7 @@ public LongTag(String name, long data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeLong(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java index 9b9cc9b532f..a10ee20069c 100644 --- a/src/main/java/cn/nukkit/nbt/tag/ShortTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/ShortTag.java @@ -29,7 +29,7 @@ public ShortTag(String name, int data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeShort(data); } diff --git a/src/main/java/cn/nukkit/nbt/tag/StringTag.java b/src/main/java/cn/nukkit/nbt/tag/StringTag.java index 3c3d022b42d..7798406410e 100644 --- a/src/main/java/cn/nukkit/nbt/tag/StringTag.java +++ b/src/main/java/cn/nukkit/nbt/tag/StringTag.java @@ -20,7 +20,7 @@ public StringTag(String name, String data) { } @Override - public void write(NBTOutputStream dos) throws IOException { + void write(NBTOutputStream dos) throws IOException { dos.writeUTF(data); } diff --git a/src/main/java/cn/nukkit/network/CompressionProvider.java b/src/main/java/cn/nukkit/network/CompressionProvider.java index 17c4fa769b8..f8a78cc5f89 100644 --- a/src/main/java/cn/nukkit/network/CompressionProvider.java +++ b/src/main/java/cn/nukkit/network/CompressionProvider.java @@ -37,7 +37,7 @@ public byte[] compress(BinaryStream packet, int level) throws Exception { @Override public byte[] decompress(byte[] compressed) throws Exception { - return Zlib.inflate(compressed, 4194304); + return Zlib.inflate(compressed, 6291456); } @Override @@ -54,7 +54,7 @@ public byte[] compress(BinaryStream packet, int level) throws Exception { @Override public byte[] decompress(byte[] compressed) throws Exception { - return Zlib.inflateRaw(compressed, 4194304); + return Zlib.inflateRaw(compressed, 6291456); } @Override @@ -76,7 +76,7 @@ public byte[] compress(BinaryStream packet, int level) throws Exception { @Override public byte[] decompress(byte[] compressed) throws Exception { - return SnappyCompression.decompress(compressed, 4194304); + return SnappyCompression.decompress(compressed, 6291456); } @Override diff --git a/src/main/java/cn/nukkit/network/Network.java b/src/main/java/cn/nukkit/network/Network.java index 992b50f1f65..71e4b4506de 100644 --- a/src/main/java/cn/nukkit/network/Network.java +++ b/src/main/java/cn/nukkit/network/Network.java @@ -44,19 +44,23 @@ public Network(Server server) { this.server = server; } + @Deprecated public void addStatistics(double upload, double download) { this.upload += upload; this.download += download; } + @Deprecated public double getUpload() { return upload; } + @Deprecated public double getDownload() { return download; } + @Deprecated public void resetStatistics() { this.upload = 0; this.download = 0; diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index 47af7e3acca..1c3d89a4886 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -14,9 +14,6 @@ public abstract class DataPacket extends BinaryStream implements Cloneable { public volatile boolean isEncoded = false; - @Deprecated - private int channel; - public abstract byte pid(); public abstract void decode(); @@ -37,12 +34,11 @@ public DataPacket reset() { @Deprecated public void setChannel(int channel) { - this.channel = channel; } @Deprecated public int getChannel() { - return channel; + return 0; } public DataPacket clean() { diff --git a/src/main/java/cn/nukkit/scheduler/ServerScheduler.java b/src/main/java/cn/nukkit/scheduler/ServerScheduler.java index a3d23087a5c..7d4213a093e 100644 --- a/src/main/java/cn/nukkit/scheduler/ServerScheduler.java +++ b/src/main/java/cn/nukkit/scheduler/ServerScheduler.java @@ -3,10 +3,10 @@ import cn.nukkit.Server; import cn.nukkit.plugin.Plugin; import cn.nukkit.utils.PluginException; +import cn.nukkit.utils.Utils; +import java.util.ArrayDeque; import java.util.Map; -import java.util.Optional; -import java.util.PriorityQueue; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; @@ -22,22 +22,16 @@ public class ServerScheduler { private final AsyncPool asyncPool; private final Queue<TaskHandler> pending; - private final Queue<TaskHandler> queue; + private final Map<Integer, ArrayDeque<TaskHandler>> queueMap; private final Map<Integer, TaskHandler> taskMap; private final AtomicInteger currentTaskId; - private volatile int currentTick; + private volatile int currentTick = -1; public ServerScheduler() { this.pending = new ConcurrentLinkedQueue<>(); this.currentTaskId = new AtomicInteger(); - this.queue = new PriorityQueue<>(11, (left, right) -> { - int i = left.getNextRunTick() - right.getNextRunTick(); - if (i == 0) { - return left.getTaskId() - right.getTaskId(); - } - return i; - }); + this.queueMap = new ConcurrentHashMap<>(); this.taskMap = new ConcurrentHashMap<>(); this.asyncPool = new AsyncPool(Server.getInstance(), WORKERS); } @@ -224,7 +218,7 @@ public void cancelAllTasks() { } } this.taskMap.clear(); - this.queue.clear(); + this.queueMap .clear(); this.currentTaskId.set(0); } @@ -260,47 +254,67 @@ private TaskHandler addTask(Plugin plugin, Runnable task, int delay, int period, } public void mainThreadHeartbeat(int currentTick) { - this.currentTick = currentTick; - // Accepts pending - while (!pending.isEmpty()) { - queue.offer(pending.poll()); + // Accepts pending. + TaskHandler task; + while ((task = pending.poll()) != null) { + int tick = Math.max(currentTick, task.getNextRunTick()); // Do not schedule in the past + ArrayDeque<TaskHandler> queue = Utils.getOrCreate(queueMap, ArrayDeque.class, tick); + queue.add(task); } - // Main heart beat - while (isReady(currentTick)) { - TaskHandler taskHandler = queue.poll(); - if (taskHandler.isCancelled()) { - taskMap.remove(taskHandler.getTaskId()); - continue; - } else if (taskHandler.isAsynchronous()) { - asyncPool.execute(taskHandler.getTask()); - } else { - try { - taskHandler.run(currentTick); - } catch (Throwable e) { - Server.getInstance().getLogger().critical("Could not execute taskHandler " + taskHandler.getTaskId() + ": " + e.getMessage()); - Server.getInstance().getLogger().logException(e instanceof Exception ? e : new RuntimeException(e)); + if (currentTick - this.currentTick > queueMap.size()) { // A large number of ticks have passed since the last execution + for (Map.Entry<Integer, ArrayDeque<TaskHandler>> entry : queueMap.entrySet()) { + int tick = entry.getKey(); + if (tick <= currentTick) { + runTasks(tick); } } - if (taskHandler.isRepeating()) { - taskHandler.setNextRunTick(currentTick + taskHandler.getPeriod()); - pending.offer(taskHandler); - } else { - try { - Optional.ofNullable(taskMap.remove(taskHandler.getTaskId())).ifPresent(TaskHandler::cancel); - } catch (RuntimeException ex) { - Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); - } + } else { // Normal server tick + for (int i = this.currentTick + 1; i <= currentTick; i++) { + runTasks(currentTick); } } + this.currentTick = currentTick; AsyncTask.collectTask(); } - public int getQueueSize() { - return queue.size() + pending.size(); + private void runTasks(int currentTick) { + ArrayDeque<TaskHandler> queue = queueMap.remove(currentTick); + if (queue != null) { + for (TaskHandler taskHandler : queue) { + if (taskHandler.isCancelled()) { + taskMap.remove(taskHandler.getTaskId()); + continue; + } else if (taskHandler.isAsynchronous()) { + asyncPool.execute(taskHandler.getTask()); + } else { + try { + taskHandler.run(currentTick); + } catch (Throwable e) { + Server.getInstance().getLogger().critical("Could not execute taskHandler " + taskHandler.getTaskId() + ": " + e.getMessage(), + e instanceof Exception ? e : new RuntimeException(e)); + } + } + if (taskHandler.isRepeating()) { + taskHandler.setNextRunTick(currentTick + taskHandler.getPeriod()); + pending.offer(taskHandler); + } else { + try { + TaskHandler removed = taskMap.remove(taskHandler.getTaskId()); + if (removed != null) removed.cancel(); + } catch (RuntimeException ex) { + Server.getInstance().getLogger().critical("Exception while invoking onCancel", ex); + } + } + } + } } - private boolean isReady(int currentTick) { - return this.queue.peek() != null && this.queue.peek().getNextRunTick() <= currentTick; + public int getQueueSize() { + int size = pending.size(); + for (ArrayDeque<TaskHandler> queue : queueMap.values()) { + size += queue.size(); + } + return size; } private int nextTaskId() { diff --git a/src/main/java/cn/nukkit/utils/LogLevel.java b/src/main/java/cn/nukkit/utils/LogLevel.java index 0d4ee19399e..599d410798c 100644 --- a/src/main/java/cn/nukkit/utils/LogLevel.java +++ b/src/main/java/cn/nukkit/utils/LogLevel.java @@ -25,6 +25,7 @@ public enum LogLevel implements Comparable<LogLevel> { /** * Default logging level: INFO */ + @Deprecated public static final LogLevel DEFAULT_LEVEL = INFO; private final BiConsumer<MainLogger, String> logTo; diff --git a/src/main/java/cn/nukkit/utils/ServerKiller.java b/src/main/java/cn/nukkit/utils/ServerKiller.java index b03a8b8c6e5..8ff264c580e 100644 --- a/src/main/java/cn/nukkit/utils/ServerKiller.java +++ b/src/main/java/cn/nukkit/utils/ServerKiller.java @@ -26,6 +26,7 @@ public void run() { try { sleep(sleepTime); } catch (InterruptedException ignored) {} + System.out.println("Took too long to stop, server was killed forcefully!"); System.exit(1); } } diff --git a/src/main/java/cn/nukkit/utils/SimpleConfig.java b/src/main/java/cn/nukkit/utils/SimpleConfig.java index 985d7b324b3..01d188a4b99 100644 --- a/src/main/java/cn/nukkit/utils/SimpleConfig.java +++ b/src/main/java/cn/nukkit/utils/SimpleConfig.java @@ -19,6 +19,7 @@ * <p> * Added 11/02/2016 by fromgate */ +@SuppressWarnings("unused") public abstract class SimpleConfig { private final File configFile; diff --git a/src/main/java/cn/nukkit/utils/ThreadCache.java b/src/main/java/cn/nukkit/utils/ThreadCache.java index ac639a59860..c9546606b80 100644 --- a/src/main/java/cn/nukkit/utils/ThreadCache.java +++ b/src/main/java/cn/nukkit/utils/ThreadCache.java @@ -9,18 +9,29 @@ */ public class ThreadCache { + @Deprecated + public static void clean() { + } + + @Deprecated public static final ThreadLocal<byte[][]> idArray = ThreadLocal.withInitial(() -> new byte[16][]); + @Deprecated public static final ThreadLocal<byte[][]> dataArray = ThreadLocal.withInitial(() -> new byte[16][]); + @Deprecated public static final ThreadLocal<byte[]> byteCache6144 = ThreadLocal.withInitial(() -> new byte[6144]); + @Deprecated public static final ThreadLocal<byte[]> byteCache256 = ThreadLocal.withInitial(() -> new byte[256]); + @Deprecated public static final ThreadLocal<BitSet> boolCache4096 = ThreadLocal.withInitial(() -> new BitSet(4096)); + @Deprecated public static final ThreadLocal<char[]> charCache4096v2 = ThreadLocal.withInitial(() -> new char[4096]); + @Deprecated public static final ThreadLocal<char[]> charCache4096 = ThreadLocal.withInitial(() -> new char[4096]); public static final ThreadLocal<int[]> intCache256 = ThreadLocal.withInitial(() -> new int[256]); diff --git a/src/main/resources/command_default.json b/src/main/resources/command_default.json deleted file mode 100644 index 99f67e9c6f8..00000000000 --- a/src/main/resources/command_default.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "aliases": [], - "description": "insert_description_here", - "overloads": { - "default": { - "input": { - "parameters": [ - { - "name": "args", - "type": "rawtext", - "optional": true - } - ] - }, - "output": {} - } - }, - "permission": "any" -} \ No newline at end of file diff --git a/src/main/resources/item_tags.json b/src/main/resources/item_tags.json index 7786d739f7d..735fbac6b3d 100644 --- a/src/main/resources/item_tags.json +++ b/src/main/resources/item_tags.json @@ -1,748 +1 @@ -{ - "minecraft:arrow": [ - "minecraft:arrow" - ], - "minecraft:banner": [ - "minecraft:banner" - ], - "minecraft:boat": [ - "minecraft:acacia_boat", - "minecraft:acacia_chest_boat", - "minecraft:bamboo_chest_raft", - "minecraft:bamboo_raft", - "minecraft:birch_boat", - "minecraft:birch_chest_boat", - "minecraft:cherry_boat", - "minecraft:cherry_chest_boat", - "minecraft:dark_oak_boat", - "minecraft:dark_oak_chest_boat", - "minecraft:jungle_boat", - "minecraft:jungle_chest_boat", - "minecraft:mangrove_boat", - "minecraft:mangrove_chest_boat", - "minecraft:oak_boat", - "minecraft:oak_chest_boat", - "minecraft:spruce_boat", - "minecraft:spruce_chest_boat" - ], - "minecraft:boats": [ - "minecraft:acacia_boat", - "minecraft:acacia_chest_boat", - "minecraft:bamboo_chest_raft", - "minecraft:bamboo_raft", - "minecraft:birch_boat", - "minecraft:birch_chest_boat", - "minecraft:cherry_boat", - "minecraft:cherry_chest_boat", - "minecraft:dark_oak_boat", - "minecraft:dark_oak_chest_boat", - "minecraft:jungle_boat", - "minecraft:jungle_chest_boat", - "minecraft:mangrove_boat", - "minecraft:mangrove_chest_boat", - "minecraft:oak_boat", - "minecraft:oak_chest_boat", - "minecraft:spruce_boat", - "minecraft:spruce_chest_boat" - ], - "minecraft:bookshelf_books": [ - "minecraft:book", - "minecraft:enchanted_book", - "minecraft:writable_book", - "minecraft:written_book" - ], - "minecraft:chainmail_tier": [ - "minecraft:chainmail_boots", - "minecraft:chainmail_chestplate", - "minecraft:chainmail_helmet", - "minecraft:chainmail_leggings" - ], - "minecraft:coals": [ - "minecraft:charcoal", - "minecraft:coal" - ], - "minecraft:crimson_stems": [ - "minecraft:crimson_hyphae", - "minecraft:crimson_stem", - "minecraft:stripped_crimson_hyphae", - "minecraft:stripped_crimson_stem" - ], - "minecraft:decorated_pot_sherds": [ - "minecraft:angler_pottery_sherd", - "minecraft:archer_pottery_sherd", - "minecraft:arms_up_pottery_sherd", - "minecraft:blade_pottery_sherd", - "minecraft:brewer_pottery_sherd", - "minecraft:brick", - "minecraft:burn_pottery_sherd", - "minecraft:danger_pottery_sherd", - "minecraft:explorer_pottery_sherd", - "minecraft:friend_pottery_sherd", - "minecraft:heart_pottery_sherd", - "minecraft:heartbreak_pottery_sherd", - "minecraft:howl_pottery_sherd", - "minecraft:miner_pottery_sherd", - "minecraft:mourner_pottery_sherd", - "minecraft:plenty_pottery_sherd", - "minecraft:prize_pottery_sherd", - "minecraft:sheaf_pottery_sherd", - "minecraft:shelter_pottery_sherd", - "minecraft:skull_pottery_sherd", - "minecraft:snort_pottery_sherd" - ], - "minecraft:diamond_tier": [ - "minecraft:diamond_axe", - "minecraft:diamond_boots", - "minecraft:diamond_chestplate", - "minecraft:diamond_helmet", - "minecraft:diamond_hoe", - "minecraft:diamond_leggings", - "minecraft:diamond_pickaxe", - "minecraft:diamond_shovel", - "minecraft:diamond_sword" - ], - "minecraft:digger": [ - "minecraft:diamond_axe", - "minecraft:diamond_hoe", - "minecraft:diamond_pickaxe", - "minecraft:diamond_shovel", - "minecraft:golden_axe", - "minecraft:golden_hoe", - "minecraft:golden_pickaxe", - "minecraft:golden_shovel", - "minecraft:iron_axe", - "minecraft:iron_hoe", - "minecraft:iron_pickaxe", - "minecraft:iron_shovel", - "minecraft:netherite_axe", - "minecraft:netherite_hoe", - "minecraft:netherite_pickaxe", - "minecraft:netherite_shovel", - "minecraft:stone_axe", - "minecraft:stone_hoe", - "minecraft:stone_pickaxe", - "minecraft:stone_shovel", - "minecraft:wooden_axe", - "minecraft:wooden_hoe", - "minecraft:wooden_pickaxe", - "minecraft:wooden_shovel" - ], - "minecraft:door": [ - "minecraft:acacia_door", - "minecraft:bamboo_door", - "minecraft:birch_door", - "minecraft:cherry_door", - "minecraft:crimson_door", - "minecraft:dark_oak_door", - "minecraft:iron_door", - "minecraft:jungle_door", - "minecraft:mangrove_door", - "minecraft:spruce_door", - "minecraft:warped_door", - "minecraft:wooden_door" - ], - "minecraft:golden_tier": [ - "minecraft:golden_axe", - "minecraft:golden_boots", - "minecraft:golden_chestplate", - "minecraft:golden_helmet", - "minecraft:golden_hoe", - "minecraft:golden_leggings", - "minecraft:golden_pickaxe", - "minecraft:golden_shovel", - "minecraft:golden_sword" - ], - "minecraft:hanging_actor": [ - "minecraft:painting" - ], - "minecraft:hanging_sign": [ - "minecraft:acacia_hanging_sign", - "minecraft:bamboo_hanging_sign", - "minecraft:birch_hanging_sign", - "minecraft:cherry_hanging_sign", - "minecraft:crimson_hanging_sign", - "minecraft:dark_oak_hanging_sign", - "minecraft:jungle_hanging_sign", - "minecraft:mangrove_hanging_sign", - "minecraft:oak_hanging_sign", - "minecraft:spruce_hanging_sign", - "minecraft:warped_hanging_sign" - ], - "minecraft:horse_armor": [ - "minecraft:diamond_horse_armor", - "minecraft:golden_horse_armor", - "minecraft:iron_horse_armor", - "minecraft:leather_horse_armor" - ], - "minecraft:iron_tier": [ - "minecraft:iron_axe", - "minecraft:iron_boots", - "minecraft:iron_chestplate", - "minecraft:iron_helmet", - "minecraft:iron_hoe", - "minecraft:iron_leggings", - "minecraft:iron_pickaxe", - "minecraft:iron_shovel", - "minecraft:iron_sword" - ], - "minecraft:is_armor": [ - "minecraft:chainmail_boots", - "minecraft:chainmail_chestplate", - "minecraft:chainmail_helmet", - "minecraft:chainmail_leggings", - "minecraft:diamond_boots", - "minecraft:diamond_chestplate", - "minecraft:diamond_helmet", - "minecraft:diamond_leggings", - "minecraft:elytra", - "minecraft:golden_boots", - "minecraft:golden_chestplate", - "minecraft:golden_helmet", - "minecraft:golden_leggings", - "minecraft:iron_boots", - "minecraft:iron_chestplate", - "minecraft:iron_helmet", - "minecraft:iron_leggings", - "minecraft:leather_boots", - "minecraft:leather_chestplate", - "minecraft:leather_helmet", - "minecraft:leather_leggings", - "minecraft:netherite_boots", - "minecraft:netherite_chestplate", - "minecraft:netherite_helmet", - "minecraft:netherite_leggings", - "minecraft:turtle_helmet" - ], - "minecraft:is_axe": [ - "minecraft:diamond_axe", - "minecraft:golden_axe", - "minecraft:iron_axe", - "minecraft:netherite_axe", - "minecraft:stone_axe", - "minecraft:wooden_axe" - ], - "minecraft:is_cooked": [ - "minecraft:cooked_beef", - "minecraft:cooked_chicken", - "minecraft:cooked_cod", - "minecraft:cooked_mutton", - "minecraft:cooked_porkchop", - "minecraft:cooked_rabbit", - "minecraft:cooked_salmon", - "minecraft:rabbit_stew" - ], - "minecraft:is_fish": [ - "minecraft:cod", - "minecraft:cooked_cod", - "minecraft:cooked_salmon", - "minecraft:pufferfish", - "minecraft:salmon", - "minecraft:tropical_fish" - ], - "minecraft:is_food": [ - "minecraft:apple", - "minecraft:baked_potato", - "minecraft:beef", - "minecraft:beetroot", - "minecraft:beetroot_soup", - "minecraft:bread", - "minecraft:carrot", - "minecraft:chicken", - "minecraft:cooked_beef", - "minecraft:cooked_chicken", - "minecraft:cooked_mutton", - "minecraft:cooked_porkchop", - "minecraft:cooked_rabbit", - "minecraft:cookie", - "minecraft:dried_kelp", - "minecraft:enchanted_golden_apple", - "minecraft:golden_apple", - "minecraft:golden_carrot", - "minecraft:melon_slice", - "minecraft:mushroom_stew", - "minecraft:mutton", - "minecraft:porkchop", - "minecraft:potato", - "minecraft:pumpkin_pie", - "minecraft:rabbit", - "minecraft:rabbit_stew", - "minecraft:rotten_flesh", - "minecraft:sweet_berries" - ], - "minecraft:is_hoe": [ - "minecraft:diamond_hoe", - "minecraft:golden_hoe", - "minecraft:iron_hoe", - "minecraft:netherite_hoe", - "minecraft:stone_hoe", - "minecraft:wooden_hoe" - ], - "minecraft:is_meat": [ - "minecraft:beef", - "minecraft:chicken", - "minecraft:cooked_beef", - "minecraft:cooked_chicken", - "minecraft:cooked_mutton", - "minecraft:cooked_porkchop", - "minecraft:cooked_rabbit", - "minecraft:mutton", - "minecraft:porkchop", - "minecraft:rabbit", - "minecraft:rabbit_stew", - "minecraft:rotten_flesh" - ], - "minecraft:is_minecart": [ - "minecraft:chest_minecart", - "minecraft:command_block_minecart", - "minecraft:hopper_minecart", - "minecraft:minecart", - "minecraft:tnt_minecart" - ], - "minecraft:is_pickaxe": [ - "minecraft:diamond_pickaxe", - "minecraft:golden_pickaxe", - "minecraft:iron_pickaxe", - "minecraft:netherite_pickaxe", - "minecraft:stone_pickaxe", - "minecraft:wooden_pickaxe" - ], - "minecraft:is_shovel": [ - "minecraft:diamond_shovel", - "minecraft:golden_shovel", - "minecraft:iron_shovel", - "minecraft:netherite_shovel", - "minecraft:stone_shovel", - "minecraft:wooden_shovel" - ], - "minecraft:is_sword": [ - "minecraft:diamond_sword", - "minecraft:golden_sword", - "minecraft:iron_sword", - "minecraft:netherite_sword", - "minecraft:stone_sword", - "minecraft:wooden_sword" - ], - "minecraft:is_tool": [ - "minecraft:diamond_axe", - "minecraft:diamond_hoe", - "minecraft:diamond_pickaxe", - "minecraft:diamond_shovel", - "minecraft:diamond_sword", - "minecraft:golden_axe", - "minecraft:golden_hoe", - "minecraft:golden_pickaxe", - "minecraft:golden_shovel", - "minecraft:golden_sword", - "minecraft:iron_axe", - "minecraft:iron_hoe", - "minecraft:iron_pickaxe", - "minecraft:iron_shovel", - "minecraft:iron_sword", - "minecraft:netherite_axe", - "minecraft:netherite_hoe", - "minecraft:netherite_pickaxe", - "minecraft:netherite_shovel", - "minecraft:netherite_sword", - "minecraft:stone_axe", - "minecraft:stone_hoe", - "minecraft:stone_pickaxe", - "minecraft:stone_shovel", - "minecraft:stone_sword", - "minecraft:wooden_axe", - "minecraft:wooden_hoe", - "minecraft:wooden_pickaxe", - "minecraft:wooden_shovel", - "minecraft:wooden_sword" - ], - "minecraft:is_trident": [ - "minecraft:trident" - ], - "minecraft:leather_tier": [ - "minecraft:leather_boots", - "minecraft:leather_chestplate", - "minecraft:leather_helmet", - "minecraft:leather_leggings" - ], - "minecraft:lectern_books": [ - "minecraft:writable_book", - "minecraft:written_book" - ], - "minecraft:logs": [ - "minecraft:acacia_log", - "minecraft:birch_log", - "minecraft:cherry_log", - "minecraft:cherry_wood", - "minecraft:crimson_hyphae", - "minecraft:crimson_stem", - "minecraft:dark_oak_log", - "minecraft:jungle_log", - "minecraft:mangrove_log", - "minecraft:mangrove_wood", - "minecraft:oak_log", - "minecraft:spruce_log", - "minecraft:stripped_acacia_log", - "minecraft:stripped_birch_log", - "minecraft:stripped_cherry_log", - "minecraft:stripped_cherry_wood", - "minecraft:stripped_crimson_hyphae", - "minecraft:stripped_crimson_stem", - "minecraft:stripped_dark_oak_log", - "minecraft:stripped_jungle_log", - "minecraft:stripped_mangrove_log", - "minecraft:stripped_mangrove_wood", - "minecraft:stripped_oak_log", - "minecraft:stripped_spruce_log", - "minecraft:stripped_warped_hyphae", - "minecraft:stripped_warped_stem", - "minecraft:warped_hyphae", - "minecraft:warped_stem", - "minecraft:wood" - ], - "minecraft:logs_that_burn": [ - "minecraft:acacia_log", - "minecraft:birch_log", - "minecraft:cherry_log", - "minecraft:cherry_wood", - "minecraft:dark_oak_log", - "minecraft:jungle_log", - "minecraft:mangrove_log", - "minecraft:mangrove_wood", - "minecraft:oak_log", - "minecraft:spruce_log", - "minecraft:stripped_acacia_log", - "minecraft:stripped_birch_log", - "minecraft:stripped_cherry_log", - "minecraft:stripped_cherry_wood", - "minecraft:stripped_dark_oak_log", - "minecraft:stripped_jungle_log", - "minecraft:stripped_mangrove_log", - "minecraft:stripped_mangrove_wood", - "minecraft:stripped_oak_log", - "minecraft:stripped_spruce_log", - "minecraft:wood" - ], - "minecraft:mangrove_logs": [ - "minecraft:mangrove_log", - "minecraft:mangrove_wood", - "minecraft:stripped_mangrove_log", - "minecraft:stripped_mangrove_wood" - ], - "minecraft:music_disc": [ - "minecraft:music_disc_11", - "minecraft:music_disc_13", - "minecraft:music_disc_5", - "minecraft:music_disc_blocks", - "minecraft:music_disc_cat", - "minecraft:music_disc_chirp", - "minecraft:music_disc_far", - "minecraft:music_disc_mall", - "minecraft:music_disc_mellohi", - "minecraft:music_disc_otherside", - "minecraft:music_disc_pigstep", - "minecraft:music_disc_relic", - "minecraft:music_disc_stal", - "minecraft:music_disc_strad", - "minecraft:music_disc_wait", - "minecraft:music_disc_ward" - ], - "minecraft:netherite_tier": [ - "minecraft:netherite_axe", - "minecraft:netherite_boots", - "minecraft:netherite_chestplate", - "minecraft:netherite_helmet", - "minecraft:netherite_hoe", - "minecraft:netherite_leggings", - "minecraft:netherite_pickaxe", - "minecraft:netherite_shovel", - "minecraft:netherite_sword" - ], - "minecraft:planks": [ - "minecraft:bamboo_planks", - "minecraft:cherry_planks", - "minecraft:crimson_planks", - "minecraft:mangrove_planks", - "minecraft:planks", - "minecraft:warped_planks" - ], - "minecraft:sand": [ - "minecraft:sand" - ], - "minecraft:sign": [ - "minecraft:acacia_hanging_sign", - "minecraft:acacia_sign", - "minecraft:bamboo_hanging_sign", - "minecraft:bamboo_sign", - "minecraft:birch_hanging_sign", - "minecraft:birch_sign", - "minecraft:cherry_hanging_sign", - "minecraft:cherry_sign", - "minecraft:crimson_hanging_sign", - "minecraft:crimson_sign", - "minecraft:dark_oak_hanging_sign", - "minecraft:dark_oak_sign", - "minecraft:jungle_hanging_sign", - "minecraft:jungle_sign", - "minecraft:mangrove_hanging_sign", - "minecraft:mangrove_sign", - "minecraft:oak_hanging_sign", - "minecraft:oak_sign", - "minecraft:spruce_hanging_sign", - "minecraft:spruce_sign", - "minecraft:warped_hanging_sign", - "minecraft:warped_sign" - ], - "minecraft:soul_fire_base_blocks": [ - "minecraft:soul_sand", - "minecraft:soul_soil" - ], - "minecraft:spawn_egg": [ - "minecraft:agent_spawn_egg", - "minecraft:allay_spawn_egg", - "minecraft:axolotl_spawn_egg", - "minecraft:bat_spawn_egg", - "minecraft:bee_spawn_egg", - "minecraft:blaze_spawn_egg", - "minecraft:camel_spawn_egg", - "minecraft:cat_spawn_egg", - "minecraft:cave_spider_spawn_egg", - "minecraft:chicken_spawn_egg", - "minecraft:cod_spawn_egg", - "minecraft:cow_spawn_egg", - "minecraft:creeper_spawn_egg", - "minecraft:dolphin_spawn_egg", - "minecraft:donkey_spawn_egg", - "minecraft:drowned_spawn_egg", - "minecraft:elder_guardian_spawn_egg", - "minecraft:ender_dragon_spawn_egg", - "minecraft:enderman_spawn_egg", - "minecraft:endermite_spawn_egg", - "minecraft:evoker_spawn_egg", - "minecraft:fox_spawn_egg", - "minecraft:frog_spawn_egg", - "minecraft:ghast_spawn_egg", - "minecraft:glow_squid_spawn_egg", - "minecraft:goat_spawn_egg", - "minecraft:guardian_spawn_egg", - "minecraft:hoglin_spawn_egg", - "minecraft:horse_spawn_egg", - "minecraft:husk_spawn_egg", - "minecraft:iron_golem_spawn_egg", - "minecraft:llama_spawn_egg", - "minecraft:magma_cube_spawn_egg", - "minecraft:mooshroom_spawn_egg", - "minecraft:mule_spawn_egg", - "minecraft:npc_spawn_egg", - "minecraft:ocelot_spawn_egg", - "minecraft:panda_spawn_egg", - "minecraft:parrot_spawn_egg", - "minecraft:phantom_spawn_egg", - "minecraft:pig_spawn_egg", - "minecraft:piglin_brute_spawn_egg", - "minecraft:piglin_spawn_egg", - "minecraft:pillager_spawn_egg", - "minecraft:polar_bear_spawn_egg", - "minecraft:pufferfish_spawn_egg", - "minecraft:rabbit_spawn_egg", - "minecraft:ravager_spawn_egg", - "minecraft:salmon_spawn_egg", - "minecraft:sheep_spawn_egg", - "minecraft:shulker_spawn_egg", - "minecraft:silverfish_spawn_egg", - "minecraft:skeleton_horse_spawn_egg", - "minecraft:skeleton_spawn_egg", - "minecraft:slime_spawn_egg", - "minecraft:sniffer_spawn_egg", - "minecraft:snow_golem_spawn_egg", - "minecraft:spawn_egg", - "minecraft:spider_spawn_egg", - "minecraft:squid_spawn_egg", - "minecraft:stray_spawn_egg", - "minecraft:strider_spawn_egg", - "minecraft:tadpole_spawn_egg", - "minecraft:trader_llama_spawn_egg", - "minecraft:tropical_fish_spawn_egg", - "minecraft:turtle_spawn_egg", - "minecraft:vex_spawn_egg", - "minecraft:villager_spawn_egg", - "minecraft:vindicator_spawn_egg", - "minecraft:wandering_trader_spawn_egg", - "minecraft:warden_spawn_egg", - "minecraft:witch_spawn_egg", - "minecraft:wither_skeleton_spawn_egg", - "minecraft:wither_spawn_egg", - "minecraft:wolf_spawn_egg", - "minecraft:zoglin_spawn_egg", - "minecraft:zombie_horse_spawn_egg", - "minecraft:zombie_pigman_spawn_egg", - "minecraft:zombie_spawn_egg", - "minecraft:zombie_villager_spawn_egg" - ], - "minecraft:stone_bricks": [ - "minecraft:stonebrick" - ], - "minecraft:stone_crafting_materials": [ - "minecraft:blackstone", - "minecraft:cobbled_deepslate", - "minecraft:cobblestone" - ], - "minecraft:stone_tier": [ - "minecraft:stone_axe", - "minecraft:stone_hoe", - "minecraft:stone_pickaxe", - "minecraft:stone_shovel", - "minecraft:stone_sword" - ], - "minecraft:stone_tool_materials": [ - "minecraft:blackstone", - "minecraft:cobbled_deepslate", - "minecraft:cobblestone" - ], - "minecraft:transform_materials": [ - "minecraft:netherite_ingot" - ], - "minecraft:transform_templates": [ - "minecraft:netherite_upgrade_smithing_template" - ], - "minecraft:transformable_items": [ - "minecraft:diamond_axe", - "minecraft:diamond_boots", - "minecraft:diamond_chestplate", - "minecraft:diamond_helmet", - "minecraft:diamond_hoe", - "minecraft:diamond_leggings", - "minecraft:diamond_pickaxe", - "minecraft:diamond_shovel", - "minecraft:diamond_sword", - "minecraft:golden_boots" - ], - "minecraft:trim_materials": [ - "minecraft:amethyst_shard", - "minecraft:copper_ingot", - "minecraft:diamond", - "minecraft:emerald", - "minecraft:gold_ingot", - "minecraft:iron_ingot", - "minecraft:lapis_lazuli", - "minecraft:netherite_ingot", - "minecraft:quartz", - "minecraft:redstone" - ], - "minecraft:trim_templates": [ - "minecraft:coast_armor_trim_smithing_template", - "minecraft:dune_armor_trim_smithing_template", - "minecraft:eye_armor_trim_smithing_template", - "minecraft:host_armor_trim_smithing_template", - "minecraft:raiser_armor_trim_smithing_template", - "minecraft:rib_armor_trim_smithing_template", - "minecraft:sentry_armor_trim_smithing_template", - "minecraft:shaper_armor_trim_smithing_template", - "minecraft:silence_armor_trim_smithing_template", - "minecraft:snout_armor_trim_smithing_template", - "minecraft:spire_armor_trim_smithing_template", - "minecraft:tide_armor_trim_smithing_template", - "minecraft:vex_armor_trim_smithing_template", - "minecraft:ward_armor_trim_smithing_template", - "minecraft:wayfinder_armor_trim_smithing_template", - "minecraft:wild_armor_trim_smithing_template" - ], - "minecraft:trimmable_armors": [ - "minecraft:chainmail_boots", - "minecraft:chainmail_chestplate", - "minecraft:chainmail_helmet", - "minecraft:chainmail_leggings", - "minecraft:diamond_boots", - "minecraft:diamond_chestplate", - "minecraft:diamond_helmet", - "minecraft:diamond_leggings", - "minecraft:golden_boots", - "minecraft:golden_chestplate", - "minecraft:golden_helmet", - "minecraft:golden_leggings", - "minecraft:iron_boots", - "minecraft:iron_chestplate", - "minecraft:iron_helmet", - "minecraft:iron_leggings", - "minecraft:leather_boots", - "minecraft:leather_chestplate", - "minecraft:leather_helmet", - "minecraft:leather_leggings", - "minecraft:netherite_boots", - "minecraft:netherite_chestplate", - "minecraft:netherite_helmet", - "minecraft:netherite_leggings", - "minecraft:turtle_helmet" - ], - "minecraft:vibration_damper": [ - "minecraft:black_carpet", - "minecraft:black_wool", - "minecraft:blue_carpet", - "minecraft:blue_wool", - "minecraft:brown_carpet", - "minecraft:brown_wool", - "minecraft:cyan_carpet", - "minecraft:cyan_wool", - "minecraft:gray_carpet", - "minecraft:gray_wool", - "minecraft:green_carpet", - "minecraft:green_wool", - "minecraft:light_blue_carpet", - "minecraft:light_blue_wool", - "minecraft:light_gray_carpet", - "minecraft:light_gray_wool", - "minecraft:lime_carpet", - "minecraft:lime_wool", - "minecraft:magenta_carpet", - "minecraft:magenta_wool", - "minecraft:orange_carpet", - "minecraft:orange_wool", - "minecraft:pink_carpet", - "minecraft:pink_wool", - "minecraft:purple_carpet", - "minecraft:purple_wool", - "minecraft:red_carpet", - "minecraft:red_wool", - "minecraft:white_carpet", - "minecraft:white_wool", - "minecraft:yellow_carpet", - "minecraft:yellow_wool" - ], - "minecraft:warped_stems": [ - "minecraft:stripped_warped_hyphae", - "minecraft:stripped_warped_stem", - "minecraft:warped_hyphae", - "minecraft:warped_stem" - ], - "minecraft:wooden_slabs": [ - "minecraft:bamboo_slab", - "minecraft:cherry_slab", - "minecraft:crimson_slab", - "minecraft:mangrove_slab", - "minecraft:warped_slab", - "minecraft:wooden_slab" - ], - "minecraft:wooden_tier": [ - "minecraft:wooden_axe", - "minecraft:wooden_hoe", - "minecraft:wooden_pickaxe", - "minecraft:wooden_shovel", - "minecraft:wooden_sword" - ], - "minecraft:wool": [ - "minecraft:black_wool", - "minecraft:blue_wool", - "minecraft:brown_wool", - "minecraft:cyan_wool", - "minecraft:gray_wool", - "minecraft:green_wool", - "minecraft:light_blue_wool", - "minecraft:light_gray_wool", - "minecraft:lime_wool", - "minecraft:magenta_wool", - "minecraft:orange_wool", - "minecraft:pink_wool", - "minecraft:purple_wool", - "minecraft:red_wool", - "minecraft:white_wool", - "minecraft:yellow_wool" - ] -} +{"minecraft:arrow":["minecraft:arrow"],"minecraft:banner":["minecraft:banner"],"minecraft:boat":["minecraft:acacia_boat","minecraft:acacia_chest_boat","minecraft:bamboo_chest_raft","minecraft:bamboo_raft","minecraft:birch_boat","minecraft:birch_chest_boat","minecraft:cherry_boat","minecraft:cherry_chest_boat","minecraft:dark_oak_boat","minecraft:dark_oak_chest_boat","minecraft:jungle_boat","minecraft:jungle_chest_boat","minecraft:mangrove_boat","minecraft:mangrove_chest_boat","minecraft:oak_boat","minecraft:oak_chest_boat","minecraft:spruce_boat","minecraft:spruce_chest_boat"],"minecraft:boats":["minecraft:acacia_boat","minecraft:acacia_chest_boat","minecraft:bamboo_chest_raft","minecraft:bamboo_raft","minecraft:birch_boat","minecraft:birch_chest_boat","minecraft:cherry_boat","minecraft:cherry_chest_boat","minecraft:dark_oak_boat","minecraft:dark_oak_chest_boat","minecraft:jungle_boat","minecraft:jungle_chest_boat","minecraft:mangrove_boat","minecraft:mangrove_chest_boat","minecraft:oak_boat","minecraft:oak_chest_boat","minecraft:spruce_boat","minecraft:spruce_chest_boat"],"minecraft:bookshelf_books":["minecraft:book","minecraft:enchanted_book","minecraft:writable_book","minecraft:written_book"],"minecraft:chainmail_tier":["minecraft:chainmail_boots","minecraft:chainmail_chestplate","minecraft:chainmail_helmet","minecraft:chainmail_leggings"],"minecraft:coals":["minecraft:charcoal","minecraft:coal"],"minecraft:crimson_stems":["minecraft:crimson_hyphae","minecraft:crimson_stem","minecraft:stripped_crimson_hyphae","minecraft:stripped_crimson_stem"],"minecraft:decorated_pot_sherds":["minecraft:angler_pottery_sherd","minecraft:archer_pottery_sherd","minecraft:arms_up_pottery_sherd","minecraft:blade_pottery_sherd","minecraft:brewer_pottery_sherd","minecraft:brick","minecraft:burn_pottery_sherd","minecraft:danger_pottery_sherd","minecraft:explorer_pottery_sherd","minecraft:friend_pottery_sherd","minecraft:heart_pottery_sherd","minecraft:heartbreak_pottery_sherd","minecraft:howl_pottery_sherd","minecraft:miner_pottery_sherd","minecraft:mourner_pottery_sherd","minecraft:plenty_pottery_sherd","minecraft:prize_pottery_sherd","minecraft:sheaf_pottery_sherd","minecraft:shelter_pottery_sherd","minecraft:skull_pottery_sherd","minecraft:snort_pottery_sherd"],"minecraft:diamond_tier":["minecraft:diamond_axe","minecraft:diamond_boots","minecraft:diamond_chestplate","minecraft:diamond_helmet","minecraft:diamond_hoe","minecraft:diamond_leggings","minecraft:diamond_pickaxe","minecraft:diamond_shovel","minecraft:diamond_sword"],"minecraft:digger":["minecraft:diamond_axe","minecraft:diamond_hoe","minecraft:diamond_pickaxe","minecraft:diamond_shovel","minecraft:golden_axe","minecraft:golden_hoe","minecraft:golden_pickaxe","minecraft:golden_shovel","minecraft:iron_axe","minecraft:iron_hoe","minecraft:iron_pickaxe","minecraft:iron_shovel","minecraft:netherite_axe","minecraft:netherite_hoe","minecraft:netherite_pickaxe","minecraft:netherite_shovel","minecraft:stone_axe","minecraft:stone_hoe","minecraft:stone_pickaxe","minecraft:stone_shovel","minecraft:wooden_axe","minecraft:wooden_hoe","minecraft:wooden_pickaxe","minecraft:wooden_shovel"],"minecraft:door":["minecraft:acacia_door","minecraft:bamboo_door","minecraft:birch_door","minecraft:cherry_door","minecraft:crimson_door","minecraft:dark_oak_door","minecraft:iron_door","minecraft:jungle_door","minecraft:mangrove_door","minecraft:spruce_door","minecraft:warped_door","minecraft:wooden_door"],"minecraft:golden_tier":["minecraft:golden_axe","minecraft:golden_boots","minecraft:golden_chestplate","minecraft:golden_helmet","minecraft:golden_hoe","minecraft:golden_leggings","minecraft:golden_pickaxe","minecraft:golden_shovel","minecraft:golden_sword"],"minecraft:hanging_actor":["minecraft:painting"],"minecraft:hanging_sign":["minecraft:acacia_hanging_sign","minecraft:bamboo_hanging_sign","minecraft:birch_hanging_sign","minecraft:cherry_hanging_sign","minecraft:crimson_hanging_sign","minecraft:dark_oak_hanging_sign","minecraft:jungle_hanging_sign","minecraft:mangrove_hanging_sign","minecraft:oak_hanging_sign","minecraft:spruce_hanging_sign","minecraft:warped_hanging_sign"],"minecraft:horse_armor":["minecraft:diamond_horse_armor","minecraft:golden_horse_armor","minecraft:iron_horse_armor","minecraft:leather_horse_armor"],"minecraft:iron_tier":["minecraft:iron_axe","minecraft:iron_boots","minecraft:iron_chestplate","minecraft:iron_helmet","minecraft:iron_hoe","minecraft:iron_leggings","minecraft:iron_pickaxe","minecraft:iron_shovel","minecraft:iron_sword"],"minecraft:is_armor":["minecraft:chainmail_boots","minecraft:chainmail_chestplate","minecraft:chainmail_helmet","minecraft:chainmail_leggings","minecraft:diamond_boots","minecraft:diamond_chestplate","minecraft:diamond_helmet","minecraft:diamond_leggings","minecraft:elytra","minecraft:golden_boots","minecraft:golden_chestplate","minecraft:golden_helmet","minecraft:golden_leggings","minecraft:iron_boots","minecraft:iron_chestplate","minecraft:iron_helmet","minecraft:iron_leggings","minecraft:leather_boots","minecraft:leather_chestplate","minecraft:leather_helmet","minecraft:leather_leggings","minecraft:netherite_boots","minecraft:netherite_chestplate","minecraft:netherite_helmet","minecraft:netherite_leggings","minecraft:turtle_helmet"],"minecraft:is_axe":["minecraft:diamond_axe","minecraft:golden_axe","minecraft:iron_axe","minecraft:netherite_axe","minecraft:stone_axe","minecraft:wooden_axe"],"minecraft:is_cooked":["minecraft:cooked_beef","minecraft:cooked_chicken","minecraft:cooked_cod","minecraft:cooked_mutton","minecraft:cooked_porkchop","minecraft:cooked_rabbit","minecraft:cooked_salmon","minecraft:rabbit_stew"],"minecraft:is_fish":["minecraft:cod","minecraft:cooked_cod","minecraft:cooked_salmon","minecraft:pufferfish","minecraft:salmon","minecraft:tropical_fish"],"minecraft:is_food":["minecraft:apple","minecraft:baked_potato","minecraft:beef","minecraft:beetroot","minecraft:beetroot_soup","minecraft:bread","minecraft:carrot","minecraft:chicken","minecraft:cooked_beef","minecraft:cooked_chicken","minecraft:cooked_mutton","minecraft:cooked_porkchop","minecraft:cooked_rabbit","minecraft:cookie","minecraft:dried_kelp","minecraft:enchanted_golden_apple","minecraft:golden_apple","minecraft:golden_carrot","minecraft:melon_slice","minecraft:mushroom_stew","minecraft:mutton","minecraft:porkchop","minecraft:potato","minecraft:pumpkin_pie","minecraft:rabbit","minecraft:rabbit_stew","minecraft:rotten_flesh","minecraft:sweet_berries"],"minecraft:is_hoe":["minecraft:diamond_hoe","minecraft:golden_hoe","minecraft:iron_hoe","minecraft:netherite_hoe","minecraft:stone_hoe","minecraft:wooden_hoe"],"minecraft:is_meat":["minecraft:beef","minecraft:chicken","minecraft:cooked_beef","minecraft:cooked_chicken","minecraft:cooked_mutton","minecraft:cooked_porkchop","minecraft:cooked_rabbit","minecraft:mutton","minecraft:porkchop","minecraft:rabbit","minecraft:rabbit_stew","minecraft:rotten_flesh"],"minecraft:is_minecart":["minecraft:chest_minecart","minecraft:command_block_minecart","minecraft:hopper_minecart","minecraft:minecart","minecraft:tnt_minecart"],"minecraft:is_pickaxe":["minecraft:diamond_pickaxe","minecraft:golden_pickaxe","minecraft:iron_pickaxe","minecraft:netherite_pickaxe","minecraft:stone_pickaxe","minecraft:wooden_pickaxe"],"minecraft:is_shovel":["minecraft:diamond_shovel","minecraft:golden_shovel","minecraft:iron_shovel","minecraft:netherite_shovel","minecraft:stone_shovel","minecraft:wooden_shovel"],"minecraft:is_sword":["minecraft:diamond_sword","minecraft:golden_sword","minecraft:iron_sword","minecraft:netherite_sword","minecraft:stone_sword","minecraft:wooden_sword"],"minecraft:is_tool":["minecraft:diamond_axe","minecraft:diamond_hoe","minecraft:diamond_pickaxe","minecraft:diamond_shovel","minecraft:diamond_sword","minecraft:golden_axe","minecraft:golden_hoe","minecraft:golden_pickaxe","minecraft:golden_shovel","minecraft:golden_sword","minecraft:iron_axe","minecraft:iron_hoe","minecraft:iron_pickaxe","minecraft:iron_shovel","minecraft:iron_sword","minecraft:netherite_axe","minecraft:netherite_hoe","minecraft:netherite_pickaxe","minecraft:netherite_shovel","minecraft:netherite_sword","minecraft:stone_axe","minecraft:stone_hoe","minecraft:stone_pickaxe","minecraft:stone_shovel","minecraft:stone_sword","minecraft:wooden_axe","minecraft:wooden_hoe","minecraft:wooden_pickaxe","minecraft:wooden_shovel","minecraft:wooden_sword"],"minecraft:is_trident":["minecraft:trident"],"minecraft:leather_tier":["minecraft:leather_boots","minecraft:leather_chestplate","minecraft:leather_helmet","minecraft:leather_leggings"],"minecraft:lectern_books":["minecraft:writable_book","minecraft:written_book"],"minecraft:logs":["minecraft:acacia_log","minecraft:birch_log","minecraft:cherry_log","minecraft:cherry_wood","minecraft:crimson_hyphae","minecraft:crimson_stem","minecraft:dark_oak_log","minecraft:jungle_log","minecraft:mangrove_log","minecraft:mangrove_wood","minecraft:oak_log","minecraft:spruce_log","minecraft:stripped_acacia_log","minecraft:stripped_birch_log","minecraft:stripped_cherry_log","minecraft:stripped_cherry_wood","minecraft:stripped_crimson_hyphae","minecraft:stripped_crimson_stem","minecraft:stripped_dark_oak_log","minecraft:stripped_jungle_log","minecraft:stripped_mangrove_log","minecraft:stripped_mangrove_wood","minecraft:stripped_oak_log","minecraft:stripped_spruce_log","minecraft:stripped_warped_hyphae","minecraft:stripped_warped_stem","minecraft:warped_hyphae","minecraft:warped_stem","minecraft:wood"],"minecraft:logs_that_burn":["minecraft:acacia_log","minecraft:birch_log","minecraft:cherry_log","minecraft:cherry_wood","minecraft:dark_oak_log","minecraft:jungle_log","minecraft:mangrove_log","minecraft:mangrove_wood","minecraft:oak_log","minecraft:spruce_log","minecraft:stripped_acacia_log","minecraft:stripped_birch_log","minecraft:stripped_cherry_log","minecraft:stripped_cherry_wood","minecraft:stripped_dark_oak_log","minecraft:stripped_jungle_log","minecraft:stripped_mangrove_log","minecraft:stripped_mangrove_wood","minecraft:stripped_oak_log","minecraft:stripped_spruce_log","minecraft:wood"],"minecraft:mangrove_logs":["minecraft:mangrove_log","minecraft:mangrove_wood","minecraft:stripped_mangrove_log","minecraft:stripped_mangrove_wood"],"minecraft:music_disc":["minecraft:music_disc_11","minecraft:music_disc_13","minecraft:music_disc_5","minecraft:music_disc_blocks","minecraft:music_disc_cat","minecraft:music_disc_chirp","minecraft:music_disc_far","minecraft:music_disc_mall","minecraft:music_disc_mellohi","minecraft:music_disc_otherside","minecraft:music_disc_pigstep","minecraft:music_disc_relic","minecraft:music_disc_stal","minecraft:music_disc_strad","minecraft:music_disc_wait","minecraft:music_disc_ward"],"minecraft:netherite_tier":["minecraft:netherite_axe","minecraft:netherite_boots","minecraft:netherite_chestplate","minecraft:netherite_helmet","minecraft:netherite_hoe","minecraft:netherite_leggings","minecraft:netherite_pickaxe","minecraft:netherite_shovel","minecraft:netherite_sword"],"minecraft:planks":["minecraft:bamboo_planks","minecraft:cherry_planks","minecraft:crimson_planks","minecraft:mangrove_planks","minecraft:planks","minecraft:warped_planks"],"minecraft:sand":["minecraft:sand"],"minecraft:sign":["minecraft:acacia_hanging_sign","minecraft:acacia_sign","minecraft:bamboo_hanging_sign","minecraft:bamboo_sign","minecraft:birch_hanging_sign","minecraft:birch_sign","minecraft:cherry_hanging_sign","minecraft:cherry_sign","minecraft:crimson_hanging_sign","minecraft:crimson_sign","minecraft:dark_oak_hanging_sign","minecraft:dark_oak_sign","minecraft:jungle_hanging_sign","minecraft:jungle_sign","minecraft:mangrove_hanging_sign","minecraft:mangrove_sign","minecraft:oak_hanging_sign","minecraft:oak_sign","minecraft:spruce_hanging_sign","minecraft:spruce_sign","minecraft:warped_hanging_sign","minecraft:warped_sign"],"minecraft:soul_fire_base_blocks":["minecraft:soul_sand","minecraft:soul_soil"],"minecraft:spawn_egg":["minecraft:agent_spawn_egg","minecraft:allay_spawn_egg","minecraft:axolotl_spawn_egg","minecraft:bat_spawn_egg","minecraft:bee_spawn_egg","minecraft:blaze_spawn_egg","minecraft:camel_spawn_egg","minecraft:cat_spawn_egg","minecraft:cave_spider_spawn_egg","minecraft:chicken_spawn_egg","minecraft:cod_spawn_egg","minecraft:cow_spawn_egg","minecraft:creeper_spawn_egg","minecraft:dolphin_spawn_egg","minecraft:donkey_spawn_egg","minecraft:drowned_spawn_egg","minecraft:elder_guardian_spawn_egg","minecraft:ender_dragon_spawn_egg","minecraft:enderman_spawn_egg","minecraft:endermite_spawn_egg","minecraft:evoker_spawn_egg","minecraft:fox_spawn_egg","minecraft:frog_spawn_egg","minecraft:ghast_spawn_egg","minecraft:glow_squid_spawn_egg","minecraft:goat_spawn_egg","minecraft:guardian_spawn_egg","minecraft:hoglin_spawn_egg","minecraft:horse_spawn_egg","minecraft:husk_spawn_egg","minecraft:iron_golem_spawn_egg","minecraft:llama_spawn_egg","minecraft:magma_cube_spawn_egg","minecraft:mooshroom_spawn_egg","minecraft:mule_spawn_egg","minecraft:npc_spawn_egg","minecraft:ocelot_spawn_egg","minecraft:panda_spawn_egg","minecraft:parrot_spawn_egg","minecraft:phantom_spawn_egg","minecraft:pig_spawn_egg","minecraft:piglin_brute_spawn_egg","minecraft:piglin_spawn_egg","minecraft:pillager_spawn_egg","minecraft:polar_bear_spawn_egg","minecraft:pufferfish_spawn_egg","minecraft:rabbit_spawn_egg","minecraft:ravager_spawn_egg","minecraft:salmon_spawn_egg","minecraft:sheep_spawn_egg","minecraft:shulker_spawn_egg","minecraft:silverfish_spawn_egg","minecraft:skeleton_horse_spawn_egg","minecraft:skeleton_spawn_egg","minecraft:slime_spawn_egg","minecraft:sniffer_spawn_egg","minecraft:snow_golem_spawn_egg","minecraft:spawn_egg","minecraft:spider_spawn_egg","minecraft:squid_spawn_egg","minecraft:stray_spawn_egg","minecraft:strider_spawn_egg","minecraft:tadpole_spawn_egg","minecraft:trader_llama_spawn_egg","minecraft:tropical_fish_spawn_egg","minecraft:turtle_spawn_egg","minecraft:vex_spawn_egg","minecraft:villager_spawn_egg","minecraft:vindicator_spawn_egg","minecraft:wandering_trader_spawn_egg","minecraft:warden_spawn_egg","minecraft:witch_spawn_egg","minecraft:wither_skeleton_spawn_egg","minecraft:wither_spawn_egg","minecraft:wolf_spawn_egg","minecraft:zoglin_spawn_egg","minecraft:zombie_horse_spawn_egg","minecraft:zombie_pigman_spawn_egg","minecraft:zombie_spawn_egg","minecraft:zombie_villager_spawn_egg"],"minecraft:stone_bricks":["minecraft:stonebrick"],"minecraft:stone_crafting_materials":["minecraft:blackstone","minecraft:cobbled_deepslate","minecraft:cobblestone"],"minecraft:stone_tier":["minecraft:stone_axe","minecraft:stone_hoe","minecraft:stone_pickaxe","minecraft:stone_shovel","minecraft:stone_sword"],"minecraft:stone_tool_materials":["minecraft:blackstone","minecraft:cobbled_deepslate","minecraft:cobblestone"],"minecraft:transform_materials":["minecraft:netherite_ingot"],"minecraft:transform_templates":["minecraft:netherite_upgrade_smithing_template"],"minecraft:transformable_items":["minecraft:diamond_axe","minecraft:diamond_boots","minecraft:diamond_chestplate","minecraft:diamond_helmet","minecraft:diamond_hoe","minecraft:diamond_leggings","minecraft:diamond_pickaxe","minecraft:diamond_shovel","minecraft:diamond_sword","minecraft:golden_boots"],"minecraft:trim_materials":["minecraft:amethyst_shard","minecraft:copper_ingot","minecraft:diamond","minecraft:emerald","minecraft:gold_ingot","minecraft:iron_ingot","minecraft:lapis_lazuli","minecraft:netherite_ingot","minecraft:quartz","minecraft:redstone"],"minecraft:trim_templates":["minecraft:coast_armor_trim_smithing_template","minecraft:dune_armor_trim_smithing_template","minecraft:eye_armor_trim_smithing_template","minecraft:host_armor_trim_smithing_template","minecraft:raiser_armor_trim_smithing_template","minecraft:rib_armor_trim_smithing_template","minecraft:sentry_armor_trim_smithing_template","minecraft:shaper_armor_trim_smithing_template","minecraft:silence_armor_trim_smithing_template","minecraft:snout_armor_trim_smithing_template","minecraft:spire_armor_trim_smithing_template","minecraft:tide_armor_trim_smithing_template","minecraft:vex_armor_trim_smithing_template","minecraft:ward_armor_trim_smithing_template","minecraft:wayfinder_armor_trim_smithing_template","minecraft:wild_armor_trim_smithing_template"],"minecraft:trimmable_armors":["minecraft:chainmail_boots","minecraft:chainmail_chestplate","minecraft:chainmail_helmet","minecraft:chainmail_leggings","minecraft:diamond_boots","minecraft:diamond_chestplate","minecraft:diamond_helmet","minecraft:diamond_leggings","minecraft:golden_boots","minecraft:golden_chestplate","minecraft:golden_helmet","minecraft:golden_leggings","minecraft:iron_boots","minecraft:iron_chestplate","minecraft:iron_helmet","minecraft:iron_leggings","minecraft:leather_boots","minecraft:leather_chestplate","minecraft:leather_helmet","minecraft:leather_leggings","minecraft:netherite_boots","minecraft:netherite_chestplate","minecraft:netherite_helmet","minecraft:netherite_leggings","minecraft:turtle_helmet"],"minecraft:vibration_damper":["minecraft:black_carpet","minecraft:black_wool","minecraft:blue_carpet","minecraft:blue_wool","minecraft:brown_carpet","minecraft:brown_wool","minecraft:cyan_carpet","minecraft:cyan_wool","minecraft:gray_carpet","minecraft:gray_wool","minecraft:green_carpet","minecraft:green_wool","minecraft:light_blue_carpet","minecraft:light_blue_wool","minecraft:light_gray_carpet","minecraft:light_gray_wool","minecraft:lime_carpet","minecraft:lime_wool","minecraft:magenta_carpet","minecraft:magenta_wool","minecraft:orange_carpet","minecraft:orange_wool","minecraft:pink_carpet","minecraft:pink_wool","minecraft:purple_carpet","minecraft:purple_wool","minecraft:red_carpet","minecraft:red_wool","minecraft:white_carpet","minecraft:white_wool","minecraft:yellow_carpet","minecraft:yellow_wool"],"minecraft:warped_stems":["minecraft:stripped_warped_hyphae","minecraft:stripped_warped_stem","minecraft:warped_hyphae","minecraft:warped_stem"],"minecraft:wooden_slabs":["minecraft:bamboo_slab","minecraft:cherry_slab","minecraft:crimson_slab","minecraft:mangrove_slab","minecraft:warped_slab","minecraft:wooden_slab"],"minecraft:wooden_tier":["minecraft:wooden_axe","minecraft:wooden_hoe","minecraft:wooden_pickaxe","minecraft:wooden_shovel","minecraft:wooden_sword"],"minecraft:wool":["minecraft:black_wool","minecraft:blue_wool","minecraft:brown_wool","minecraft:cyan_wool","minecraft:gray_wool","minecraft:green_wool","minecraft:light_blue_wool","minecraft:light_gray_wool","minecraft:lime_wool","minecraft:magenta_wool","minecraft:orange_wool","minecraft:pink_wool","minecraft:purple_wool","minecraft:red_wool","minecraft:white_wool","minecraft:yellow_wool"]} \ No newline at end of file diff --git a/src/main/resources/lang/ara/nukkit.yml b/src/main/resources/lang/ara/nukkit.yml index 8e187200091..164795a7179 100644 --- a/src/main/resources/lang/ara/nukkit.yml +++ b/src/main/resources/lang/ara/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/bra/nukkit.yml b/src/main/resources/lang/bra/nukkit.yml index 55b0fb97a39..740a04d726f 100644 --- a/src/main/resources/lang/bra/nukkit.yml +++ b/src/main/resources/lang/bra/nukkit.yml @@ -22,7 +22,7 @@ network: #Coloque 0 para compactar tudo, -1 para desativar. batch-threshold: 256 #Nível de compressão usado pelo Zlib ao enviar packets compactados. Maior = mais uso na CPU, menos bandwidth utilizado. - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/chs/nukkit.yml b/src/main/resources/lang/chs/nukkit.yml index 1deb8ac54d3..df9b6d46c98 100644 --- a/src/main/resources/lang/chs/nukkit.yml +++ b/src/main/resources/lang/chs/nukkit.yml @@ -22,7 +22,7 @@ network: #设为 0 以压缩全部。设为 -1 以禁用此功能 batch-threshold: 256 #压缩等级。等级越高,CPU 占用越高,占用带宽越少 - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/cht/nukkit.yml b/src/main/resources/lang/cht/nukkit.yml index ea6e9e3b5c1..db1d466ba11 100644 --- a/src/main/resources/lang/cht/nukkit.yml +++ b/src/main/resources/lang/cht/nukkit.yml @@ -22,7 +22,7 @@ network: #設為0,壓縮全部。設為-1,停用功能 batch-threshold: 256 #壓縮等級,等級越高,CPU佔用越高,佔用頻寬越少 - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/cze/nukkit.yml b/src/main/resources/lang/cze/nukkit.yml index 3e8c5de924b..f7efc26879b 100644 --- a/src/main/resources/lang/cze/nukkit.yml +++ b/src/main/resources/lang/cze/nukkit.yml @@ -23,7 +23,7 @@ network: #Nastavte na 0, chcete-li vše komprimovat, -1 zakázat batch-threshold: 256 #Compression level používaný Zlib při odesílání dávkových paketů. Vyšší = více CPU, méně využití šířky pásma - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/deu/nukkit.yml b/src/main/resources/lang/deu/nukkit.yml index 6f7c3db40d1..785b6b05418 100644 --- a/src/main/resources/lang/deu/nukkit.yml +++ b/src/main/resources/lang/deu/nukkit.yml @@ -22,7 +22,7 @@ network: #Setze zu 0 to um alles zu komprimieren, -1 um die Komprimierung deaktivieren batch-threshold: 256 #Komprimierungslevel von Zlib wenn eine Menge Pakete/Daten gesendet werden. Höher = mehr CPU, weniger Bandbreitennutzung - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/eng/lang.ini b/src/main/resources/lang/eng/lang.ini index 59a0b3bf4ad..bdb21aded5a 100644 --- a/src/main/resources/lang/eng/lang.ini +++ b/src/main/resources/lang/eng/lang.ini @@ -134,6 +134,9 @@ commands.say.usage=/say <message ...> commands.seed.usage=/seed commands.seed.success=Seed: {%0} +commands.summon.success=Object successfully summoned +commands.summon.failed=Unable to summon object + commands.ban.success=Banned player {%0} commands.ban.usage=/ban <name> [reason ...] @@ -181,6 +184,7 @@ commands.whitelist.usage=/whitelist <on|off|list|add|remove|reload> commands.gamemode.success.self=Set own game mode to {%0} commands.gamemode.success.other=Set {%0}'s game mode to {%1} +commands.gamemode.fail.invalid=Game mode '{%0}' is invalid commands.gamemode.usage=/gamemode <mode> [player] commands.help.header=--- Showing help page {%0} of {%1} (/help <page>) --- diff --git a/src/main/resources/lang/eng/nukkit.yml b/src/main/resources/lang/eng/nukkit.yml index d1617ab9a08..f6cd617b524 100644 --- a/src/main/resources/lang/eng/nukkit.yml +++ b/src/main/resources/lang/eng/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/fin/nukkit.yml b/src/main/resources/lang/fin/nukkit.yml index 631a2492fff..700b9fb5646 100644 --- a/src/main/resources/lang/fin/nukkit.yml +++ b/src/main/resources/lang/fin/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/idn/nukkit.yml b/src/main/resources/lang/idn/nukkit.yml index d1617ab9a08..f6cd617b524 100644 --- a/src/main/resources/lang/idn/nukkit.yml +++ b/src/main/resources/lang/idn/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/jpn/nukkit.yml b/src/main/resources/lang/jpn/nukkit.yml index dcbee75870a..18893f9d637 100644 --- a/src/main/resources/lang/jpn/nukkit.yml +++ b/src/main/resources/lang/jpn/nukkit.yml @@ -22,7 +22,7 @@ network: #0にするとすべてのパケットを圧縮します, -1 で無効です. batch-threshold: 256 #バッチパケットを送信するときのzlibの圧縮レベルですす。 値を大きくするとcpuに多く負荷かけ、低くすると通信量が増えます。 - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/kor/nukkit.yml b/src/main/resources/lang/kor/nukkit.yml index 9579a00dd9e..a45a5339a12 100644 --- a/src/main/resources/lang/kor/nukkit.yml +++ b/src/main/resources/lang/kor/nukkit.yml @@ -22,7 +22,7 @@ network: #모두 압축하려면 0으로 설정하고, 비활성화하려면 -1으로 설정하세요 batch-threshold: 256 #일괄 처리된 패킷을 전송할 때 Zlib에 사용되는 압축 수준입니다. 높을수록 더 많은 CPU 사용량으로, 적은 대역폭을 사용합니다 - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/ltu/nukkit.yml b/src/main/resources/lang/ltu/nukkit.yml index b6160d57099..b2d8813a894 100644 --- a/src/main/resources/lang/ltu/nukkit.yml +++ b/src/main/resources/lang/ltu/nukkit.yml @@ -22,7 +22,7 @@ network: #Nustatykite 0, jeigu norite suspausti visus paketus, -1, kad išjungti batch-threshold: 256 #Suspaudimo lygis naudojant Zlib kai siunčiami suspausti paketai. Didesnė reikšmė = daugiau CPU, mažiau tinklo naudojimo - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/pol/nukkit.yml b/src/main/resources/lang/pol/nukkit.yml index c159af1e48b..b5268a8de5f 100644 --- a/src/main/resources/lang/pol/nukkit.yml +++ b/src/main/resources/lang/pol/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable. batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/rus/nukkit.yml b/src/main/resources/lang/rus/nukkit.yml index b20b2056beb..58fe745bf75 100644 --- a/src/main/resources/lang/rus/nukkit.yml +++ b/src/main/resources/lang/rus/nukkit.yml @@ -28,7 +28,7 @@ network: batch-threshold: 256 #Уровень сжатия используется Zlib при отправке сжатых пакетов #Чем больше - тем больше нагрузка на процессор, но меньше нагрузка на сеть - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/spa/nukkit.yml b/src/main/resources/lang/spa/nukkit.yml index 9b5a12e8102..9037352a85c 100644 --- a/src/main/resources/lang/spa/nukkit.yml +++ b/src/main/resources/lang/spa/nukkit.yml @@ -22,7 +22,7 @@ network: #Ajustar a 0 para comprimir todo, -1 para desactivar. batch-threshold: 256 #Nivel de compresion usada para el envió de paquetes. Alta = más CPU, menos banda ancha - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/tur/nukkit.yml b/src/main/resources/lang/tur/nukkit.yml index d1617ab9a08..f6cd617b524 100644 --- a/src/main/resources/lang/tur/nukkit.yml +++ b/src/main/resources/lang/tur/nukkit.yml @@ -22,7 +22,7 @@ network: #Set to 0 to compress everything, -1 to disable batch-threshold: 256 #Compression level used of Zlib when sending batched packets. Higher = more CPU, less bandwidth usage - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/lang/ukr/nukkit.yml b/src/main/resources/lang/ukr/nukkit.yml index 5c9196cc0a2..572dc8c342f 100644 --- a/src/main/resources/lang/ukr/nukkit.yml +++ b/src/main/resources/lang/ukr/nukkit.yml @@ -28,7 +28,7 @@ network: batch-threshold: 256 #Рівень стискання, який використовує Zlib при відправленні пакетів #Чим більше значення - тим більше навантаження на процесор, але менше навантаження на мережу - compression-level: 4 + compression-level: 5 #Enable network encryption encryption: true diff --git a/src/main/resources/runtime_block_states.dat b/src/main/resources/runtime_block_states.dat index 9bc804cc33746a6d48aa13f7c813106135a59ef1..4a7e10179626df61df797749ecd0e7b4496bcfcb 100644 GIT binary patch delta 49982 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ℑPP6kl+TvSiI=H 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 BOU?iQ delta 49900 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�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 bw`ek)kM7i*x^u`5?qH5TQSAkYaFPB8R=8iD diff --git a/src/main/resources/runtime_block_states_662.dat b/src/main/resources/runtime_block_states_662.dat index 7edb6d686bef12c25acda683799587bd8a80e6e5..74e5a477a94d135c0128b89b0178fb239f605645 100644 GIT binary patch delta 52325 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�iljB#kakDp=e@6<Nv<w2O);feigy}@ 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 I5`7Bye}T$VF8}}l delta 52268 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 JNE93C{{UY{BMbll From 89be986ab9a2d9f42dffea47a3179c4a6a121858 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:39:22 +0300 Subject: [PATCH 16/21] Missed these --- src/main/java/cn/nukkit/AdventureSettings.java | 3 --- src/main/java/cn/nukkit/level/Level.java | 12 +++--------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/main/java/cn/nukkit/AdventureSettings.java b/src/main/java/cn/nukkit/AdventureSettings.java index 40ac3636022..c5b2f0de7e2 100644 --- a/src/main/java/cn/nukkit/AdventureSettings.java +++ b/src/main/java/cn/nukkit/AdventureSettings.java @@ -166,9 +166,6 @@ public enum Type { BUILD(PlayerAbility.BUILD, true), PRIVILEGED_BUILDER(PlayerAbility.PRIVILEGED_BUILDER, false), - // For backwards compatibility - @Deprecated - BUILD_AND_MINE(null, true), @Deprecated DEFAULT_LEVEL_PERMISSIONS(null, false); diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 742273e6345..4e8f038a410 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -1667,9 +1667,7 @@ public synchronized Block getBlock(FullChunk chunk, int x, int y, int z, BlockLa fullState = 0; } - Block block; - int blockId = fullState >> Block.DATA_BITS; - block = Block.fullList[fullState].clone(); + Block block = Block.fullList[fullState].clone(); block.x = x; block.y = y; @@ -1697,9 +1695,7 @@ private Block getBlockAsyncIfLoaded(FullChunk chunk, int x, int y, int z, BlockL fullState = 0; } - Block block; - int blockId = fullState >> Block.DATA_BITS; - block = Block.fullList[fullState].clone(); + Block block = Block.fullList[fullState].clone(); block.x = x; block.y = y; @@ -1851,9 +1847,7 @@ public synchronized void setBlockFullIdAt(int x, int y, int z, BlockLayer layer, return; } - Block block; - int blockId = fullId >> Block.DATA_BITS; - block = Block.fullList[fullId]; + Block block = Block.fullList[fullId]; this.setBlock(x, y, z, layer, block, false, false); } From c6302d75fbd979dc9b27498f85b18cee85340dab Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 3 Oct 2024 19:17:00 +0300 Subject: [PATCH 17/21] These too and bump API to 1.1.0 --- src/main/java/cn/nukkit/Nukkit.java | 2 +- .../nukkit/command/data/CommandParameter.java | 39 +++++++++---------- .../nukkit/command/defaults/GiveCommand.java | 2 +- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/main/java/cn/nukkit/Nukkit.java b/src/main/java/cn/nukkit/Nukkit.java index bf5c0e5f929..14ccb42917d 100644 --- a/src/main/java/cn/nukkit/Nukkit.java +++ b/src/main/java/cn/nukkit/Nukkit.java @@ -38,7 +38,7 @@ public class Nukkit { public final static Properties GIT_INFO = getGitInfo(); public final static String VERSION = getVersion(); - public final static String API_VERSION = "1.0.22"; + public final static String API_VERSION = "1.1.0"; public final static String PATH = System.getProperty("user.dir") + '/'; public final static String DATA_PATH = System.getProperty("user.dir") + '/'; public final static String PLUGIN_PATH = DATA_PATH + "plugins"; diff --git a/src/main/java/cn/nukkit/command/data/CommandParameter.java b/src/main/java/cn/nukkit/command/data/CommandParameter.java index 4eaaed12f50..a657455d1b0 100644 --- a/src/main/java/cn/nukkit/command/data/CommandParameter.java +++ b/src/main/java/cn/nukkit/command/data/CommandParameter.java @@ -1,28 +1,9 @@ package cn.nukkit.command.data; - import java.util.ArrayList; -import java.util.Arrays; public class CommandParameter { - public final static String ARG_TYPE_STRING = "string"; - public final static String ARG_TYPE_STRING_ENUM = "stringenum"; - public final static String ARG_TYPE_BOOL = "bool"; - public final static String ARG_TYPE_TARGET = "target"; - public final static String ARG_TYPE_PLAYER = "target"; - public final static String ARG_TYPE_BLOCK_POS = "blockpos"; - public final static String ARG_TYPE_RAW_TEXT = "rawtext"; - public final static String ARG_TYPE_INT = "int"; - - public static final String ENUM_TYPE_ITEM_LIST = "Item"; - public static final String ENUM_TYPE_BLOCK_LIST = "Block"; - public static final String ENUM_TYPE_COMMAND_LIST = "commandName"; - public static final String ENUM_TYPE_ENCHANTMENT_LIST = "enchantmentType"; - public static final String ENUM_TYPE_ENTITY_LIST = "entityType"; - public static final String ENUM_TYPE_EFFECT_LIST = "effectType"; - public static final String ENUM_TYPE_PARTICLE_LIST = "particleType"; - public String name; public CommandParamType type; public boolean optional; @@ -39,6 +20,10 @@ public CommandParameter(String name, String type, boolean optional) { this(name, fromString(type), optional); } + /** + * @deprecated use {@link #newType(String, boolean, CommandParamType)} instead + */ + @Deprecated public CommandParameter(String name, CommandParamType type, boolean optional) { this.name = name; this.type = type; @@ -61,6 +46,10 @@ public CommandParameter(String name) { this(name, false); } + /** + * @deprecated use {@link #newEnum(String, boolean, String)} instead + */ + @Deprecated public CommandParameter(String name, boolean optional, String enumType) { this.name = name; this.type = CommandParamType.RAWTEXT; @@ -68,19 +57,29 @@ public CommandParameter(String name, boolean optional, String enumType) { this.enumData = new CommandEnum(enumType, new ArrayList<>()); } + /** + * @deprecated use {@link #newEnum(String, boolean, String[])} instead + */ @Deprecated public CommandParameter(String name, boolean optional, String[] enumValues) { this.name = name; this.type = CommandParamType.RAWTEXT; this.optional = optional; - this.enumData = new CommandEnum(name + "Enums", Arrays.asList(enumValues)); + this.enumData = new CommandEnum(name + "Enums", enumValues); } + /** + * @deprecated use {@link #newEnum(String, String)} instead + */ @Deprecated public CommandParameter(String name, String enumType) { this(name, false, enumType); } + /** + * @deprecated use {@link #newEnum(String, String[])} instead + */ + @Deprecated public CommandParameter(String name, String[] enumValues) { this(name, false, enumValues); } diff --git a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java index 73d8f0aa391..ea5eec19330 100644 --- a/src/main/java/cn/nukkit/command/defaults/GiveCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/GiveCommand.java @@ -27,7 +27,7 @@ public GiveCommand(String name) { this.commandParameters.clear(); this.commandParameters.put("default", new CommandParameter[]{ CommandParameter.newType("player", CommandParamType.TARGET), - new CommandParameter("itemName", false, CommandParameter.ENUM_TYPE_ITEM_LIST), + new CommandParameter("itemName", false, "Item"), CommandParameter.newType("amount", true, CommandParamType.INT), CommandParameter.newType("tags", true, CommandParamType.RAWTEXT) }); From 258f38c689b359094b38bc98b92c861e1ceb3b73 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Thu, 3 Oct 2024 22:39:40 +0300 Subject: [PATCH 18/21] Fix packet compression related bugs --- src/main/java/cn/nukkit/Server.java | 13 +-------- .../nbt/stream/FastByteArrayOutputStream.java | 1 - .../cn/nukkit/network/BatchingHelper.java | 27 ++++++++++++------- .../nukkit/network/CompressionProvider.java | 21 ++------------- .../nukkit/network/protocol/BatchPacket.java | 9 +++++++ .../protocol/ChunkRadiusUpdatedPacket.java | 2 +- .../nukkit/network/protocol/DataPacket.java | 18 +++++++------ .../protocol/InventoryContentPacket.java | 7 ----- .../network/session/RakNetPlayerSession.java | 3 +-- src/main/java/cn/nukkit/utils/Zlib.java | 6 ----- 10 files changed, 42 insertions(+), 65 deletions(-) diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 449a694a8db..47c912f9f8f 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -18,7 +18,6 @@ import cn.nukkit.event.HandlerList; import cn.nukkit.event.level.LevelInitEvent; import cn.nukkit.event.level.LevelLoadEvent; -import cn.nukkit.event.server.BatchPacketsEvent; import cn.nukkit.event.server.PlayerDataSerializeEvent; import cn.nukkit.event.server.QueryRegenerateEvent; import cn.nukkit.event.server.ServerStopEvent; @@ -724,17 +723,7 @@ public static void broadcastPacket(Player[] players, DataPacket packet) { } public void batchPackets(Player[] players, DataPacket[] packets) { - if (players == null || packets == null || players.length == 0 || packets.length == 0) { - return; - } - - BatchPacketsEvent ev = new BatchPacketsEvent(players, packets, true); - pluginManager.callEvent(ev); - if (ev.isCancelled()) { - return; - } - - this.batchingHelper.batchPackets(players, packets); + this.batchingHelper.batchPackets(this, players, packets); } /** diff --git a/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java b/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java index b64fd06c666..501e89de519 100644 --- a/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java +++ b/src/main/java/cn/nukkit/nbt/stream/FastByteArrayOutputStream.java @@ -132,7 +132,6 @@ public long length() throws IOException { } public byte[] toByteArray() { - if (position == array.length) return array; return Arrays.copyOfRange(array, 0, position); } } \ No newline at end of file diff --git a/src/main/java/cn/nukkit/network/BatchingHelper.java b/src/main/java/cn/nukkit/network/BatchingHelper.java index a4de2512848..fb731ea4238 100644 --- a/src/main/java/cn/nukkit/network/BatchingHelper.java +++ b/src/main/java/cn/nukkit/network/BatchingHelper.java @@ -2,6 +2,7 @@ import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.event.server.BatchPacketsEvent; import cn.nukkit.network.protocol.DataPacket; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -19,16 +20,24 @@ public BatchingHelper() { this.threadedExecutor = Executors.newSingleThreadExecutor(builder.build()); } - public void batchPackets(Player[] players, DataPacket[] packets) { - if (players.length > 0 && packets.length > 0) { - this.threadedExecutor.execute(() -> { - for (Player player : players) { - for (DataPacket packet : packets) { - player.getNetworkSession().sendPacket(packet); - } - } - }); + public void batchPackets(Server server, Player[] players, DataPacket[] packets) { + if (players == null || packets == null || players.length == 0 || packets.length == 0) { + return; + } + + BatchPacketsEvent ev = new BatchPacketsEvent(players, packets, true); + server.getPluginManager().callEvent(ev); + if (ev.isCancelled()) { + return; } + + this.threadedExecutor.execute(() -> { // Maybe players could have separate threads assigned to them? + for (Player player : players) { + for (DataPacket packet : packets) { + player.getNetworkSession().sendPacket(packet); + } + } + }); } public void shutdown() { diff --git a/src/main/java/cn/nukkit/network/CompressionProvider.java b/src/main/java/cn/nukkit/network/CompressionProvider.java index f8a78cc5f89..e1131b8ce30 100644 --- a/src/main/java/cn/nukkit/network/CompressionProvider.java +++ b/src/main/java/cn/nukkit/network/CompressionProvider.java @@ -29,23 +29,6 @@ public byte getPrefix() { } }; - CompressionProvider ZLIB = new CompressionProvider() { - @Override - public byte[] compress(BinaryStream packet, int level) throws Exception { - return Zlib.deflatePre16Packet(packet.getBuffer(), level); - } - - @Override - public byte[] decompress(byte[] compressed) throws Exception { - return Zlib.inflate(compressed, 6291456); - } - - @Override - public byte[] decompress(byte[] compressed, int maxSize) throws Exception { - return Zlib.inflate(compressed, maxSize); - } - }; - CompressionProvider ZLIB_RAW = new CompressionProvider() { @Override public byte[] compress(BinaryStream packet, int level) throws Exception { @@ -98,11 +81,11 @@ default byte[] decompress(byte[] compressed, int maxSize) throws Exception { return this.decompress(compressed); } - static CompressionProvider from(PacketCompressionAlgorithm algorithm, int raknetVersion) { + static CompressionProvider from(PacketCompressionAlgorithm algorithm) { if (algorithm == null) { return NONE; } else if (algorithm == PacketCompressionAlgorithm.ZLIB) { - return raknetVersion < 10 ? ZLIB : ZLIB_RAW; + return ZLIB_RAW; } else if (algorithm == PacketCompressionAlgorithm.SNAPPY) { return SNAPPY; } diff --git a/src/main/java/cn/nukkit/network/protocol/BatchPacket.java b/src/main/java/cn/nukkit/network/protocol/BatchPacket.java index fa739e6f371..5171ece01c8 100644 --- a/src/main/java/cn/nukkit/network/protocol/BatchPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/BatchPacket.java @@ -28,4 +28,13 @@ public void encode() { public void trim() { setBuffer(null); } + + @Override + public BatchPacket clone() { + BatchPacket packet = (BatchPacket) super.clone(); + if (this.payload != null) { + packet.payload = this.payload.clone(); + } + return packet; + } } diff --git a/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java b/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java index 13a4b4956ba..1948c667e2f 100644 --- a/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ChunkRadiusUpdatedPacket.java @@ -20,7 +20,7 @@ public void decode() { @Override public void encode() { - super.reset(); + this.reset(); this.putVarInt(this.radius); } diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index 1c3d89a4886..956d943b9f3 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -20,15 +20,24 @@ public abstract class DataPacket extends BinaryStream implements Cloneable { public abstract void encode(); + public final void tryEncode() { + if (!this.isEncoded) { + this.isEncoded = true; + this.encode(); + } + } + @Override public DataPacket reset() { super.reset(); + byte packetId = this.pid(); if (packetId < 0 && packetId >= -56) { // Hack: (byte) 200+ --> (int) 300+ this.putUnsignedVarInt(packetId + 356); } else { this.putUnsignedVarInt(packetId & 0xff); } + return this; } @@ -52,7 +61,7 @@ public DataPacket clean() { public DataPacket clone() { try { DataPacket packet = (DataPacket) super.clone(); - packet.setBuffer(this.count < 0 ? null : this.getBuffer()); // prevent reflecting same buffer instance + packet.setBuffer(this.getBuffer()); // prevent reflecting same buffer instance packet.offset = this.offset; packet.count = this.count; return packet; @@ -84,13 +93,6 @@ public BatchPacket compress(int level) { } } - public final void tryEncode() { - if (!this.isEncoded) { - this.isEncoded = true; - this.encode(); - } - } - void decodeUnsupported() { if (Nukkit.DEBUG > 1) { Server.getInstance().getLogger().debug("Warning: decode() not implemented for " + this.getClass().getName()); diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java index 717bd151408..e405839cc93 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java @@ -51,11 +51,4 @@ public void encode() { this.putBoolean(false); // fullContainerName.optional.present this.putUnsignedVarInt(0); // dynamicContainerSize } - - @Override - public InventoryContentPacket clone() { - InventoryContentPacket pk = (InventoryContentPacket) super.clone(); - pk.slots = this.slots.clone(); - return pk; - } } diff --git a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java index ecdb362cc25..8ea2953d2a3 100644 --- a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java @@ -8,7 +8,6 @@ import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.protocol.DisconnectPacket; -import cn.nukkit.network.protocol.ProtocolInfo; import cn.nukkit.utils.BinaryStream; import com.google.common.base.Preconditions; import com.nukkitx.natives.sha256.Sha256; @@ -144,7 +143,7 @@ public void sendPacket(DataPacket packet) { return; } - if (packet.pid() != ProtocolInfo.BATCH_PACKET) { + if (!(packet instanceof BatchPacket)) { packet.tryEncode(); } diff --git a/src/main/java/cn/nukkit/utils/Zlib.java b/src/main/java/cn/nukkit/utils/Zlib.java index 81ad00c778f..f9cef72b868 100644 --- a/src/main/java/cn/nukkit/utils/Zlib.java +++ b/src/main/java/cn/nukkit/utils/Zlib.java @@ -1,7 +1,5 @@ package cn.nukkit.utils; -import cn.nukkit.Server; - import java.io.IOException; import java.util.zip.Deflater; @@ -17,10 +15,6 @@ public static byte[] deflate(byte[] data, int level) throws Exception { return provider.deflate(data, level); } - public static byte[] deflatePre16Packet(byte[] data, int level) throws Exception { - return provider.deflate(data, data.length < Server.getInstance().networkCompressionThreshold ? 0 : level); - } - public static byte[] deflate(byte[][] data, int level) throws Exception { return provider.deflate(data, level); } From 26f1282bd67175c113d3b9159d080fc388fa26eb Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Fri, 4 Oct 2024 20:25:00 +0300 Subject: [PATCH 19/21] Cleanup --- src/main/java/cn/nukkit/Server.java | 8 ++++---- src/main/java/cn/nukkit/block/BlockBamboo.java | 6 ++++-- src/main/java/cn/nukkit/block/BlockBambooSapling.java | 6 ++++-- .../java/cn/nukkit/block/BlockDaylightDetector.java | 3 ++- .../nukkit/block/BlockDaylightDetectorInverted.java | 4 +++- .../cn/nukkit/command/defaults/EffectCommand.java | 2 +- .../java/cn/nukkit/command/simple/SimpleCommand.java | 3 ++- src/main/java/cn/nukkit/inventory/SmithingRecipe.java | 2 +- src/main/java/cn/nukkit/level/Level.java | 11 +---------- .../level/format/anvil/palette/BiomePalette.java | 1 + .../cn/nukkit/level/format/generic/BaseChunk.java | 2 +- .../cn/nukkit/level/format/generic/BaseFullChunk.java | 2 +- .../level/generator/object/tree/ObjectSpruceTree.java | 7 ++++++- 13 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/main/java/cn/nukkit/Server.java b/src/main/java/cn/nukkit/Server.java index 47c912f9f8f..725865c44a4 100644 --- a/src/main/java/cn/nukkit/Server.java +++ b/src/main/java/cn/nukkit/Server.java @@ -1071,10 +1071,6 @@ public void removePlayerListData(UUID uuid, Player[] players) { Server.broadcastPacket(players, pk); } - public void removePlayerListData(UUID uuid, Collection<Player> players) { - this.removePlayerListData(uuid, players.toArray(new Player[0])); - } - public void removePlayerListData(UUID uuid, Player player) { PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_REMOVE; @@ -1082,6 +1078,10 @@ public void removePlayerListData(UUID uuid, Player player) { player.dataPacket(pk); } + public void removePlayerListData(UUID uuid, Collection<Player> players) { + this.removePlayerListData(uuid, players.toArray(new Player[0])); + } + public void sendFullPlayerListData(Player player) { PlayerListPacket pk = new PlayerListPacket(); pk.type = PlayerListPacket.TYPE_ADD; diff --git a/src/main/java/cn/nukkit/block/BlockBamboo.java b/src/main/java/cn/nukkit/block/BlockBamboo.java index 73ccadb7b22..92de7f62e45 100644 --- a/src/main/java/cn/nukkit/block/BlockBamboo.java +++ b/src/main/java/cn/nukkit/block/BlockBamboo.java @@ -46,8 +46,10 @@ public int onUpdate(int type) { } else if (type == Level.BLOCK_UPDATE_SCHEDULED) { this.level.useBreakOn(this, null, null, true); } else if (type == Level.BLOCK_UPDATE_RANDOM) { - Block up = this.up(); - if (this.getAge() == 0 && up.getId() == AIR && level.isAnimalSpawningAllowedByTime()/*this.level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { + Block up; + int time = level.getTime() % Level.TIME_FULL; + boolean canGrow = time < 13184 || time > 22800; + if (this.getAge() == 0 && (up = this.up()).getId() == AIR && canGrow/*this.level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { this.grow(up); } return type; diff --git a/src/main/java/cn/nukkit/block/BlockBambooSapling.java b/src/main/java/cn/nukkit/block/BlockBambooSapling.java index 3b1800267d7..65925ee3c21 100644 --- a/src/main/java/cn/nukkit/block/BlockBambooSapling.java +++ b/src/main/java/cn/nukkit/block/BlockBambooSapling.java @@ -50,8 +50,10 @@ public int onUpdate(int type) { } return type; } else if (type == Level.BLOCK_UPDATE_RANDOM) { - Block up = up(); - if (getAge() == 0 && up.getId() == AIR && level.isAnimalSpawningAllowedByTime()/*level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { + Block up; + int time = level.getTime() % Level.TIME_FULL; + boolean canGrow = time < 13184 || time > 22800; + if (getAge() == 0 && (up = this.up()).getId() == AIR && canGrow/*level.getFullLight(up) >= BlockCrops.MINIMUM_LIGHT_LEVEL*/ && ThreadLocalRandom.current().nextInt(3) == 0) { BlockBamboo newState = (BlockBamboo) Block.get(Block.BAMBOO); newState.setLeafSize(BlockBamboo.LEAF_SIZE_SMALL); BlockGrowEvent blockGrowEvent = new BlockGrowEvent(up, newState); diff --git a/src/main/java/cn/nukkit/block/BlockDaylightDetector.java b/src/main/java/cn/nukkit/block/BlockDaylightDetector.java index 647630c9021..a327f55ec0f 100644 --- a/src/main/java/cn/nukkit/block/BlockDaylightDetector.java +++ b/src/main/java/cn/nukkit/block/BlockDaylightDetector.java @@ -56,7 +56,8 @@ public boolean isPowerSource() { @Override public int getWeakPower(BlockFace face) { - return this.level.isAnimalSpawningAllowedByTime() ? 15 : 0; + int time = level.getTime() % Level.TIME_FULL; + return time < 13184 || time > 22800 ? 15 : 0; } @Override diff --git a/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java b/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java index dfb25d16c9f..3169f1d88cf 100644 --- a/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java +++ b/src/main/java/cn/nukkit/block/BlockDaylightDetectorInverted.java @@ -3,6 +3,7 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; import cn.nukkit.item.ItemBlock; +import cn.nukkit.level.Level; import cn.nukkit.math.BlockFace; /** @@ -39,6 +40,7 @@ public boolean isPowerSource() { @Override public int getWeakPower(BlockFace face) { - return this.level.isAnimalSpawningAllowedByTime() ? 0 : 15; + int time = level.getTime() % Level.TIME_FULL; + return time < 13184 || time > 22800 ? 0 : 15; } } diff --git a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java index 9d9b2fdd14f..e348c2b5122 100644 --- a/src/main/java/cn/nukkit/command/defaults/EffectCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/EffectCommand.java @@ -17,7 +17,7 @@ * Created by Snake1999 and Pub4Game on 2016/1/23. * Package cn.nukkit.command.defaults in project nukkit. */ -public class EffectCommand extends Command { +public class EffectCommand extends VanillaCommand { public EffectCommand(String name) { super(name, "%nukkit.command.effect.description", "%commands.effect.usage"); diff --git a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java index 994274a41fb..e8ae2730c47 100644 --- a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java +++ b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java @@ -1,6 +1,7 @@ package cn.nukkit.command.simple; import cn.nukkit.Server; +import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.command.ConsoleCommandSender; import cn.nukkit.command.defaults.VanillaCommand; @@ -11,7 +12,7 @@ /** * @author Tee7even */ -public class SimpleCommand extends VanillaCommand { +public class SimpleCommand extends Command { private final Object object; private final Method method; private boolean forbidConsole; diff --git a/src/main/java/cn/nukkit/inventory/SmithingRecipe.java b/src/main/java/cn/nukkit/inventory/SmithingRecipe.java index 7b074dd6c37..1e9b9d2050a 100644 --- a/src/main/java/cn/nukkit/inventory/SmithingRecipe.java +++ b/src/main/java/cn/nukkit/inventory/SmithingRecipe.java @@ -109,7 +109,7 @@ public boolean matchItems(List<Item> inputList, int multiplier) { haveInputs.add(item.clone()); } List<Item> needInputs = new ArrayList<>(); - if(multiplier != 1){ + if (multiplier != 1) { for (Item item : ingredientsAggregate) { if (item.isNull()) continue; diff --git a/src/main/java/cn/nukkit/level/Level.java b/src/main/java/cn/nukkit/level/Level.java index 4e8f038a410..a67300cc8e8 100644 --- a/src/main/java/cn/nukkit/level/Level.java +++ b/src/main/java/cn/nukkit/level/Level.java @@ -388,14 +388,10 @@ public static Chunk.Entry getChunkXZ(long hash) { return new Chunk.Entry(getHashX(hash), getHashZ(hash)); } - public static int capWorldY(int y, DimensionData dimensionData) { + private static int capWorldY(int y, DimensionData dimensionData) { return Math.max(Math.min(y, dimensionData.getMaxHeight()), dimensionData.getMinHeight()); } - public int capWorldY(int y) { - return capWorldY(y, this.getDimensionData()); - } - public static int generateChunkLoaderId(ChunkLoader loader) { if (loader.getLoaderId() == 0) { return chunkLoaderCounter++; @@ -3980,11 +3976,6 @@ private int getUpdateLCG() { return (this.updateLCG = (this.updateLCG * 3) ^ LCG_CONSTANT); } - public boolean isAnimalSpawningAllowedByTime() { - int time = this.getTime() % TIME_FULL; - return time < 13184 || time > 22800; - } - public boolean createPortal(Block target) { if (this.getDimension() == DIMENSION_THE_END) return false; final int maxPortalSize = 23; diff --git a/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java b/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java index 3d58f2d3a82..b67aba1f8f9 100644 --- a/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java +++ b/src/main/java/cn/nukkit/level/format/anvil/palette/BiomePalette.java @@ -4,6 +4,7 @@ import cn.nukkit.utils.ThreadCache; import java.util.Arrays; +@Deprecated public final class BiomePalette { private int biome; diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java index 8c608fc4d0f..527e2bdc748 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseChunk.java @@ -20,7 +20,7 @@ public abstract class BaseChunk extends BaseFullChunk implements Chunk { - public ChunkSection[] sections; + protected ChunkSection[] sections; @SuppressWarnings("MismatchedReadAndWriteOfArray") private static final byte[] emptyIdArray = new byte[4096]; diff --git a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java index 71a37a24696..0a2e4864c14 100644 --- a/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java +++ b/src/main/java/cn/nukkit/level/format/generic/BaseFullChunk.java @@ -62,7 +62,7 @@ public abstract class BaseFullChunk implements FullChunk, ChunkManager { protected boolean isInit; - private BatchPacket chunkPacket; + protected BatchPacket chunkPacket; @Override public BaseFullChunk clone() { diff --git a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java index 30d776da8a6..0c8d490b394 100644 --- a/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java +++ b/src/main/java/cn/nukkit/level/generator/object/tree/ObjectSpruceTree.java @@ -4,6 +4,7 @@ import cn.nukkit.block.BlockWood; import cn.nukkit.level.ChunkManager; import cn.nukkit.level.biome.Biome; +import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.NukkitRandom; /** @@ -50,7 +51,11 @@ public void placeLeaves(ChunkManager level, int topSize, int lRadius, int x, int int maxR = 1; int minR = 0; - boolean createSnow = this.canCreateSnow && Biome.getBiome(level.getChunk(x >> 4, z >> 4).getBiomeId(x & 0x0f, z & 0x0f)).isFreezing(); + boolean createSnow = false; + if (this.canCreateSnow) { + FullChunk chunk = level.getChunk(x >> 4, z >> 4); + createSnow = chunk == null || Biome.getBiome(chunk.getBiomeId(x & 0x0f, z & 0x0f)).isFreezing(); + } for (int yy = 0; yy <= topSize; ++yy) { int yyy = y + this.treeHeight - yy; From 4ba1a10f7061b9ba6947de93e2a316caf188e7f7 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Sun, 6 Oct 2024 20:54:09 +0300 Subject: [PATCH 20/21] Refactor riptide --- src/main/java/cn/nukkit/Player.java | 66 +++---------------- .../nukkit/command/simple/SimpleCommand.java | 1 - .../entity/item/EntityFallingBlock.java | 35 ++-------- src/main/java/cn/nukkit/item/ItemTrident.java | 48 +++++++++++++- 4 files changed, 61 insertions(+), 89 deletions(-) diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 9927d9b4afc..57ee5449a29 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -279,6 +279,7 @@ public class Player extends EntityHuman implements CommandSender, InventoryHolde private int fireworkBoostTicks; private int fireworkBoostLevel; + @Setter private boolean needSendData; private boolean needSendAdventureSettings; private boolean needSendFoodLevel; @@ -370,6 +371,13 @@ public void onFireworkBoost(int boostLevel) { this.fireworkBoostTicks = boostLevel == 3 ? 44 : boostLevel == 2 ? 29 : 23; } + /** + * Set last spin attack tick to current tick + */ + public void onSpinAttack(int riptideLevel) { + this.riptideTicks = 50 + (riptideLevel << 5); + } + /** * Get ender chest the player is viewing * @return the ender chest player is viewing or null if player is not viewing an ender chest @@ -3415,64 +3423,8 @@ public void onCompletion(Server server) { break stopItemHold; case PlayerActionPacket.ACTION_STOP_SWIMMING: return; - case PlayerActionPacket.ACTION_START_SPIN_ATTACK: - if (this.inventory == null) { - break stopItemHold; - } - - PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, true); - - int riptideLevel = 0; - Item hand; - if ((hand = this.inventory.getItemInHandFast()).getId() != ItemID.TRIDENT) { - playerToggleSpinAttackEvent.setCancelled(true); - this.getServer().getLogger().debug(username + ": got ACTION_START_SPIN_ATTACK but hand item is not a trident"); - } else { - Enchantment riptide = hand.getEnchantment(Enchantment.ID_TRIDENT_RIPTIDE); - if (riptide == null) { - playerToggleSpinAttackEvent.setCancelled(true); - } else { - riptideLevel = riptide.getLevel(); - if (riptideLevel < 1) { - playerToggleSpinAttackEvent.setCancelled(true); - } else { - boolean inWater = false; - for (Block block : this.getCollisionBlocks()) { - if (block instanceof BlockWater) { - inWater = true; - break; - } - } - if (!(inWater || (this.getLevel().isRaining() && this.canSeeSky()))) { - playerToggleSpinAttackEvent.setCancelled(true); - } - } - } - } - - this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); - - if (playerToggleSpinAttackEvent.isCancelled()) { - this.needSendData = true; - } else { - this.setSpinAttack(true); - this.resetFallDistance(); - - this.riptideTicks = 50 + (riptideLevel << 5); - - int riptideSound; - if (riptideLevel >= 3) { - riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_3; - } else if (riptideLevel == 2) { - riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_2; - } else { - riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_1; - } - this.level.addLevelSoundEvent(this, riptideSound); - } - break stopItemHold; case PlayerActionPacket.ACTION_STOP_SPIN_ATTACK: - playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, false); + PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, false); this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); if (playerToggleSpinAttackEvent.isCancelled()) { this.needSendData = true; diff --git a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java index e8ae2730c47..d111773dcd0 100644 --- a/src/main/java/cn/nukkit/command/simple/SimpleCommand.java +++ b/src/main/java/cn/nukkit/command/simple/SimpleCommand.java @@ -4,7 +4,6 @@ import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; import cn.nukkit.command.ConsoleCommandSender; -import cn.nukkit.command.defaults.VanillaCommand; import cn.nukkit.lang.TranslationContainer; import java.lang.reflect.Method; diff --git a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java index ad5b7f9aef2..9340c02849d 100644 --- a/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java +++ b/src/main/java/cn/nukkit/entity/item/EntityFallingBlock.java @@ -1,6 +1,5 @@ package cn.nukkit.entity.item; -import cn.nukkit.Player; import cn.nukkit.block.Block; import cn.nukkit.block.BlockID; import cn.nukkit.block.BlockLiquid; @@ -14,12 +13,10 @@ import cn.nukkit.item.Item; import cn.nukkit.level.GameRule; import cn.nukkit.level.GlobalBlockPalette; -import cn.nukkit.level.Level; import cn.nukkit.level.format.FullChunk; import cn.nukkit.math.BlockFace; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; -import cn.nukkit.network.protocol.AddEntityPacket; import cn.nukkit.network.protocol.LevelEventPacket; /** @@ -94,31 +91,8 @@ protected void initEntity() { } this.fireProof = true; - } - @Override - public void spawnTo(Player player) { - if (!this.hasSpawned.containsKey(player.getLoaderId())) { - Boolean hasChunk = player.usedChunks.get(Level.chunkHash(this.chunk.getX(), this.chunk.getZ())); - if (hasChunk != null && hasChunk) { - AddEntityPacket addEntity = new AddEntityPacket(); - addEntity.type = this.getNetworkId(); - addEntity.entityUniqueId = this.id; - addEntity.entityRuntimeId = this.id; - addEntity.yaw = (float) this.yaw; - addEntity.headYaw = (float) this.yaw; - addEntity.pitch = (float) this.pitch; - addEntity.x = (float) this.x; - addEntity.y = (float) this.y; - addEntity.z = (float) this.z; - addEntity.speedX = (float) this.motionX; - addEntity.speedY = (float) this.motionY; - addEntity.speedZ = (float) this.motionZ; - addEntity.metadata = this.dataProperties.clone().put(new IntEntityData(DATA_VARIANT, GlobalBlockPalette.getOrCreateRuntimeId(this.blockId, this.damage))); - player.dataPacket(addEntity); - this.hasSpawned.put(player.getLoaderId(), player); - } - } + this.setDataProperty(new IntEntityData(DATA_VARIANT, GlobalBlockPalette.getOrCreateRuntimeId(this.blockId, this.damage))); } public boolean canCollideWith(Entity entity) { @@ -158,12 +132,14 @@ public boolean onUpdate(int currentTick) { if (onGround && !closed) { close(); + Block floorBlock = level.getBlock(this.add(0, 0.0001, 0)); if (this.getBlock() == Block.SNOW_LAYER && floorBlock.getId() == Block.SNOW_LAYER && (floorBlock.getDamage() & 0x7) != 0x7) { int mergedHeight = (floorBlock.getDamage() & 0x7) + 1 + (this.getDamage() & 0x7) + 1; if (mergedHeight > 8) { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, 0x7)); this.server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { this.level.setBlock(floorBlock, event.getTo(), true); @@ -172,6 +148,7 @@ public boolean onUpdate(int currentTick) { if (aboveBlock.getId() == Block.AIR) { EntityBlockChangeEvent event2 = new EntityBlockChangeEvent(this, aboveBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 9)); // -8-1 this.server.getPluginManager().callEvent(event2); + if (!event2.isCancelled()) { this.level.setBlock(abovePos, event2.getTo(), true); } @@ -180,6 +157,7 @@ public boolean onUpdate(int currentTick) { } else { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(Block.SNOW_LAYER, mergedHeight - 1)); this.server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { this.level.setBlock(floorBlock, event.getTo(), true); } @@ -188,9 +166,10 @@ public boolean onUpdate(int currentTick) { if (this.getBlock() != Block.SNOW_LAYER ? this.level.getGameRules().getBoolean(GameRule.DO_ENTITY_DROPS) : this.level.getGameRules().getBoolean(GameRule.DO_TILE_DROPS)) { getLevel().dropItem(this, Item.get(this.blockId, this.damage, 1)); } - } else if (floorBlock.getId() == 0) { + } else if (floorBlock.canBeReplaced()) { EntityBlockChangeEvent event = new EntityBlockChangeEvent(this, floorBlock, Block.get(blockId, damage)); server.getPluginManager().callEvent(event); + if (!event.isCancelled()) { int blockId = event.getTo().getId(); if (blockId != Item.POINTED_DRIPSTONE) { diff --git a/src/main/java/cn/nukkit/item/ItemTrident.java b/src/main/java/cn/nukkit/item/ItemTrident.java index 93296799a62..7b5aded44c4 100644 --- a/src/main/java/cn/nukkit/item/ItemTrident.java +++ b/src/main/java/cn/nukkit/item/ItemTrident.java @@ -2,10 +2,13 @@ import cn.nukkit.Player; import cn.nukkit.Server; +import cn.nukkit.block.Block; +import cn.nukkit.block.BlockWater; import cn.nukkit.entity.projectile.EntityProjectile; import cn.nukkit.entity.projectile.EntityThrownTrident; import cn.nukkit.event.entity.EntityShootBowEvent; import cn.nukkit.event.entity.ProjectileLaunchEvent; +import cn.nukkit.event.player.PlayerToggleSpinAttackEvent; import cn.nukkit.item.enchantment.Enchantment; import cn.nukkit.math.Vector3; import cn.nukkit.nbt.tag.CompoundTag; @@ -32,12 +35,12 @@ public ItemTrident(Integer meta, int count) { public int getMaxDurability() { return ItemTool.DURABILITY_TRIDENT; } - + @Override public boolean isSword() { return true; } - + @Override public int getAttackDamage() { return 9; @@ -50,7 +53,46 @@ public boolean onClickAir(Player player, Vector3 directionVector) { @Override public boolean onRelease(Player player, int ticksUsed) { - if (this.hasEnchantment(Enchantment.ID_TRIDENT_RIPTIDE)) { + Enchantment riptide = this.getEnchantment(Enchantment.ID_TRIDENT_RIPTIDE); + if (riptide != null) { + PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(player, true); + + int riptideLevel = riptide.getLevel(); + if (riptideLevel < 1) { + playerToggleSpinAttackEvent.setCancelled(true); + } else { + boolean inWater = false; + for (Block block : player.getCollisionBlocks()) { + if (block instanceof BlockWater) { + inWater = true; + break; + } + } + if (!(inWater || (player.getLevel().isRaining() && player.canSeeSky()))) { + playerToggleSpinAttackEvent.setCancelled(true); + } + } + + player.getServer().getPluginManager().callEvent(playerToggleSpinAttackEvent); + + if (playerToggleSpinAttackEvent.isCancelled()) { + player.setNeedSendData(true); + } else { + player.onSpinAttack(riptideLevel); + player.setSpinAttack(true); + player.resetFallDistance(); + + int riptideSound; + if (riptideLevel >= 3) { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_3; + } else if (riptideLevel == 2) { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_2; + } else { + riptideSound = LevelSoundEventPacket.SOUND_ITEM_TRIDENT_RIPTIDE_1; + } + + player.getLevel().addLevelSoundEvent(player, riptideSound); + } return true; } From 73768357681ee10a01aa27d87d68037e17c162d3 Mon Sep 17 00:00:00 2001 From: Petteri <26197131+PetteriM1@users.noreply.github.com> Date: Sun, 20 Oct 2024 20:24:08 +0300 Subject: [PATCH 21/21] Merge (1.21.40) --- build.gradle.kts | 1 - gradle/libs.versions.toml | 3 +- src/main/java/cn/nukkit/Player.java | 21 ++-- .../java/cn/nukkit/block/BlockComposter.java | 4 +- src/main/java/cn/nukkit/block/BlockCoral.java | 3 + .../java/cn/nukkit/block/BlockCoralBlock.java | 1 + .../java/cn/nukkit/block/BlockCoralFan.java | 1 + src/main/java/cn/nukkit/block/BlockID.java | 2 +- .../java/cn/nukkit/block/BlockMushroom.java | 2 +- .../cn/nukkit/block/BlockRedstoneWire.java | 2 +- .../java/cn/nukkit/block/BlockSeaPickle.java | 1 + src/main/java/cn/nukkit/block/BlockTorch.java | 2 +- src/main/java/cn/nukkit/block/BlockTypes.java | 1 + .../cn/nukkit/block/BlockVinesNether.java | 4 +- .../nukkit/blockentity/BlockEntityBell.java | 8 +- .../cn/nukkit/command/SimpleCommandMap.java | 4 +- .../nukkit/command/defaults/SaveCommand.java | 25 +++++ .../command/defaults/SaveOffCommand.java | 33 ------ .../command/defaults/SaveOnCommand.java | 33 ------ .../cn/nukkit/entity/data/EntityMetadata.java | 17 +-- .../cn/nukkit/inventory/LoomInventory.java | 26 ++++- .../nukkit/inventory/SmithingInventory.java | 13 +++ .../transaction/InventoryTransaction.java | 2 +- .../transaction/LoomTransaction.java | 48 ++++---- .../transaction/RepairItemTransaction.java | 4 + .../transaction/action/LoomItemAction.java | 17 +-- src/main/java/cn/nukkit/item/ItemTrident.java | 2 +- .../leveldb/structure/BlockStateSnapshot.java | 12 ++ .../leveldb/structure/StateBlockStorage.java | 8 +- .../level/generator/SimpleChunkManager.java | 2 +- .../network/encryption/EncryptionUtils.java | 103 ------------------ .../cn/nukkit/network/encryption/Sha256.java | 34 ++++++ .../ClientboundMapItemDataPacket.java | 28 ++--- .../nukkit/network/protocol/DataPacket.java | 9 -- .../network/protocol/DimensionDataPacket.java | 9 +- .../protocol/InventoryContentPacket.java | 4 +- .../network/protocol/InventorySlotPacket.java | 4 +- .../protocol/InventoryTransactionPacket.java | 2 +- .../network/protocol/MobEffectPacket.java | 2 +- .../protocol/PlayerAuthInputPacket.java | 22 ++-- .../nukkit/network/protocol/ProtocolInfo.java | 6 +- .../protocol/ResourcePackStackPacket.java | 9 +- .../protocol/ResourcePacksInfoPacket.java | 28 +---- .../network/protocol/StartGamePacket.java | 2 +- .../protocol/UpdateAbilitiesPacket.java | 2 +- .../protocol/types/AuthInputAction.java | 22 +++- .../types/NetworkInventoryAction.java | 11 -- .../network/session/RakNetPlayerSession.java | 5 +- .../cn/nukkit/resourcepacks/ResourcePack.java | 22 +++- .../java/cn/nukkit/utils/BinaryStream.java | 7 +- src/main/resources/creative_items.json | 2 +- src/main/resources/item_mappings.json | 2 +- src/main/resources/lang/ara/lang.ini | 7 +- src/main/resources/lang/bra/lang.ini | 4 +- src/main/resources/lang/chs/lang.ini | 4 +- src/main/resources/lang/cht/lang.ini | 4 +- src/main/resources/lang/cze/lang.ini | 4 +- src/main/resources/lang/deu/lang.ini | 4 +- src/main/resources/lang/eng/lang.ini | 7 +- src/main/resources/lang/fin/lang.ini | 4 +- src/main/resources/lang/idn/lang.ini | 7 +- src/main/resources/lang/jpn/lang.ini | 4 +- src/main/resources/lang/kor/lang.ini | 4 +- src/main/resources/lang/ltu/lang.ini | 4 +- src/main/resources/lang/pol/lang.ini | 4 +- src/main/resources/lang/rus/lang.ini | 4 +- src/main/resources/lang/spa/lang.ini | 4 +- src/main/resources/lang/tur/lang.ini | 7 +- src/main/resources/lang/ukr/lang.ini | 4 +- src/main/resources/legacy_item_ids.json | 2 +- src/main/resources/runtime_block_states.dat | Bin 54234 -> 54984 bytes .../resources/runtime_block_states_662.dat | Bin 54297 -> 54898 bytes src/main/resources/runtime_item_states.json | 2 +- 73 files changed, 313 insertions(+), 408 deletions(-) delete mode 100644 src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java delete mode 100644 src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java create mode 100644 src/main/java/cn/nukkit/network/encryption/Sha256.java diff --git a/build.gradle.kts b/build.gradle.kts index f3274d357ac..7f4ac433fc1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,6 @@ repositories { dependencies { api(libs.network) api(libs.epoll) - api(libs.natives) api(libs.fastutil) api(libs.bundles.fastutilmaps) api(libs.guava) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a372e9acd22..69c8c43f68f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,6 @@ fastutilmaps = "8.5.13-SNAPSHOT" [libraries] network = { group = "org.cloudburstmc.netty", name = "netty-transport-raknet", version = "1.0.0.CR3-SNAPSHOT" } epoll = { group = "io.netty", name = "netty-transport-native-epoll", version = "4.1.101.Final" } -natives = { group = "com.nukkitx", name = "natives", version = "1.0.3" } fastutil = { group = "com.nukkitx", name = "fastutil-lite", version = "8.1.1" } fastutil-long-long-maps = { group = "org.cloudburstmc.fastutil.maps", name = "long-long-maps", version.ref = "fastutilmaps" } fastutil-int-short-maps = { group = "org.cloudburstmc.fastutil.maps", name = "int-short-maps", version.ref = "fastutilmaps" } @@ -16,7 +15,7 @@ fastutil-object-object-maps = { group = "org.cloudburstmc.fastutil.maps", name = guava = { group = "com.google.guava", name = "guava", version = "33.2.1-jre" } gson = { group = "com.google.code.gson", name = "gson", version = "2.10.1" } snakeyaml = { group = "org.yaml", name = "snakeyaml", version = "1.33" } -leveldb = { group = "org.iq80.leveldb", name = "leveldb", version = "0.11-SNAPSHOT" } +leveldb = { group = "org.iq80.leveldb", name = "leveldb", version = "0.11.1-SNAPSHOT" } leveldbjni = { group = "net.daporkchop", name = "leveldb-mcpe-jni", version = "0.0.10-SNAPSHOT" } snappy = { group = "org.xerial.snappy", name = "snappy-java", version = "1.1.10.7" } jwt = { group = "com.nimbusds", name = "nimbus-jose-jwt", version = "9.23" } diff --git a/src/main/java/cn/nukkit/Player.java b/src/main/java/cn/nukkit/Player.java index 57ee5449a29..a8024ccac01 100644 --- a/src/main/java/cn/nukkit/Player.java +++ b/src/main/java/cn/nukkit/Player.java @@ -3290,6 +3290,16 @@ public void onCompletion(Server server) { } } + if (authPacket.getInputData().contains(AuthInputAction.STOP_SPIN_ATTACK)) { + PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, false); + this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); + if (playerToggleSpinAttackEvent.isCancelled()) { + this.needSendData = true; + } else { + this.setSpinAttack(false); + } + } + if (authPacket.getInputData().contains(AuthInputAction.START_FLYING)) { if (!server.getAllowFlight() && !this.adventureSettings.get(Type.ALLOW_FLIGHT)) { this.kick(PlayerKickEvent.Reason.FLYING_DISABLED, MSG_FLYING_NOT_ENABLED, true); @@ -3423,15 +3433,6 @@ public void onCompletion(Server server) { break stopItemHold; case PlayerActionPacket.ACTION_STOP_SWIMMING: return; - case PlayerActionPacket.ACTION_STOP_SPIN_ATTACK: - PlayerToggleSpinAttackEvent playerToggleSpinAttackEvent = new PlayerToggleSpinAttackEvent(this, false); - this.server.getPluginManager().callEvent(playerToggleSpinAttackEvent); - if (playerToggleSpinAttackEvent.isCancelled()) { - this.needSendData = true; - } else { - this.setSpinAttack(false); - } - return; } this.setUsingItem(false); @@ -4832,7 +4833,7 @@ private void onBlockBreakStart(BlockVector3 blockPos, BlockFace face) { break; } - int bid = this.level.getBlockIdAt(blockPos.x + face.getXOffset(), blockPos.y + face.getYOffset(), blockPos.z + face.getZOffset()); + int bid = this.level.getBlockIdAt(this.chunk, blockPos.x + face.getXOffset(), blockPos.y + face.getYOffset(), blockPos.z + face.getZOffset()); if (bid == Block.FIRE || bid == Block.SOUL_FIRE) { Vector3 block = this.temporalVector.setComponents(blockPos.x + face.getXOffset(), blockPos.y + face.getYOffset(), blockPos.z + face.getZOffset()); this.level.setBlock(block, Block.get(BlockID.AIR), true); diff --git a/src/main/java/cn/nukkit/block/BlockComposter.java b/src/main/java/cn/nukkit/block/BlockComposter.java index 4237ab52a92..36e1febeee1 100644 --- a/src/main/java/cn/nukkit/block/BlockComposter.java +++ b/src/main/java/cn/nukkit/block/BlockComposter.java @@ -21,8 +21,8 @@ public class BlockComposter extends BlockTransparentMeta implements ItemID { registerItems(100, CAKE, PUMPKIN_PIE); registerBlocks(30, BLOCK_KELP, LEAVES, LEAVES2, SAPLINGS, SEAGRASS, SWEET_BERRY_BUSH); registerBlocks(50, GRASS, CACTUS, DRIED_KELP_BLOCK, VINES, NETHER_SPROUTS_BLOCK); - registerBlocks(65, DANDELION, RED_FLOWER, DOUBLE_PLANT, WITHER_ROSE, LILY_PAD, MELON_BLOCK, PUMPKIN, CARVED_PUMPKIN, SEA_PICKLE, BROWN_MUSHROOM, RED_MUSHROOM, SHROOMLIGHT, CRIMSON_FUNGUS, WARPED_FUNGUS); - registerBlocks(85, HAY_BALE, BROWN_MUSHROOM_BLOCK, RED_MUSHROOM_BLOCK, MUSHROOM_STEW, BLOCK_NETHER_WART_BLOCK, WARPED_WART_BLOCK); + registerBlocks(65, DANDELION, RED_FLOWER, DOUBLE_PLANT, WITHER_ROSE, LILY_PAD, MELON_BLOCK, PUMPKIN, CARVED_PUMPKIN, SEA_PICKLE, BROWN_MUSHROOM, RED_MUSHROOM, SHROOMLIGHT, CRIMSON_FUNGUS, WARPED_FUNGUS, MUSHROOM_STEW); + registerBlocks(85, HAY_BALE, BROWN_MUSHROOM_BLOCK, RED_MUSHROOM_BLOCK, BLOCK_NETHER_WART_BLOCK, WARPED_WART_BLOCK); registerBlocks(100, CAKE_BLOCK); registerBlock(50, TALL_GRASS, 0); registerBlock(50, TALL_GRASS, 1); diff --git a/src/main/java/cn/nukkit/block/BlockCoral.java b/src/main/java/cn/nukkit/block/BlockCoral.java index 65fb7b21a77..55303fb7bcd 100644 --- a/src/main/java/cn/nukkit/block/BlockCoral.java +++ b/src/main/java/cn/nukkit/block/BlockCoral.java @@ -86,6 +86,9 @@ public boolean place(Item item, Block block, Block target, BlockFace face, doubl if (this.down().isTransparent()) { return false; } + if (!(block instanceof BlockWater || block.level.isBlockWaterloggedAt(block.getChunk(), (int) block.x, (int) block.y, (int) block.z))) { + this.setDamage(8 + this.getDamage()); // Dead + } if (this.getLevel().setBlock(this, this, true, true)) { if (block instanceof BlockWater) { this.getLevel().setBlock((int) this.x, (int) this.y, (int) this.z, Block.LAYER_WATERLOGGED, Block.get(Block.STILL_WATER), true, true); diff --git a/src/main/java/cn/nukkit/block/BlockCoralBlock.java b/src/main/java/cn/nukkit/block/BlockCoralBlock.java index 4d99a455c05..71fdbc8ea82 100644 --- a/src/main/java/cn/nukkit/block/BlockCoralBlock.java +++ b/src/main/java/cn/nukkit/block/BlockCoralBlock.java @@ -53,6 +53,7 @@ public int onUpdate(int type) { } BlockFadeEvent event = new BlockFadeEvent(this, Block.get(CORAL_BLOCK, this.getDamage() | 0x8)); + level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { this.setDead(true); this.getLevel().setBlock(this, event.getNewState(), true, true); diff --git a/src/main/java/cn/nukkit/block/BlockCoralFan.java b/src/main/java/cn/nukkit/block/BlockCoralFan.java index 7648136baed..2ba5330c0a2 100644 --- a/src/main/java/cn/nukkit/block/BlockCoralFan.java +++ b/src/main/java/cn/nukkit/block/BlockCoralFan.java @@ -47,6 +47,7 @@ public int onUpdate(int type) { if (!this.isDead() && !(this.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockWater) && !(this.getLevelBlock(BlockLayer.WATERLOGGED) instanceof BlockIceFrosted)) { BlockFadeEvent event = new BlockFadeEvent(this, Block.get(CORAL_FAN_DEAD, this.getDamage())); + level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { this.getLevel().setBlock(this, event.getNewState(), true, true); } diff --git a/src/main/java/cn/nukkit/block/BlockID.java b/src/main/java/cn/nukkit/block/BlockID.java index d3bc900a972..01797ea24ac 100644 --- a/src/main/java/cn/nukkit/block/BlockID.java +++ b/src/main/java/cn/nukkit/block/BlockID.java @@ -694,7 +694,7 @@ public interface BlockID { int MANGROVE_PRESSURE_PLATE = 745; int MANGROVE_FENCE = 746; int MANGROVE_FENCE_GATE = 747; - // + int MANGROVE_DOOR = 748; int MANGROVE_STANDING_SIGN = 749; int MANGROVE_WALL_SIGN = 750; int MANGROVE_TRAPDOOR = 751; diff --git a/src/main/java/cn/nukkit/block/BlockMushroom.java b/src/main/java/cn/nukkit/block/BlockMushroom.java index f4ff9eed556..5ff2af6a8e0 100644 --- a/src/main/java/cn/nukkit/block/BlockMushroom.java +++ b/src/main/java/cn/nukkit/block/BlockMushroom.java @@ -37,7 +37,7 @@ public int onUpdate(int type) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (block instanceof BlockWater) { + if (block instanceof BlockWater || block.level.isBlockWaterloggedAt(block.getChunk(), (int) block.x, (int) block.y, (int) block.z)) { return false; } diff --git a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java index def1f0f126f..ec2f96e0cd7 100644 --- a/src/main/java/cn/nukkit/block/BlockRedstoneWire.java +++ b/src/main/java/cn/nukkit/block/BlockRedstoneWire.java @@ -40,7 +40,7 @@ public int getId() { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (block instanceof BlockWater) { + if (block instanceof BlockWater || block.level.isBlockWaterloggedAt(block.getChunk(), (int) block.x, (int) block.y, (int) block.z)) { return false; } diff --git a/src/main/java/cn/nukkit/block/BlockSeaPickle.java b/src/main/java/cn/nukkit/block/BlockSeaPickle.java index 47630a2e9eb..16dc90bbf00 100644 --- a/src/main/java/cn/nukkit/block/BlockSeaPickle.java +++ b/src/main/java/cn/nukkit/block/BlockSeaPickle.java @@ -71,6 +71,7 @@ public int onUpdate(int type) { } } else if (!this.isDead()) { BlockFadeEvent event = new BlockFadeEvent(this, Block.get(SEA_PICKLE, this.getDamage() ^ 0x4)); + level.getServer().getPluginManager().callEvent(event); if (!event.isCancelled()) { this.getLevel().setBlock(this, event.getNewState(), true, true); } diff --git a/src/main/java/cn/nukkit/block/BlockTorch.java b/src/main/java/cn/nukkit/block/BlockTorch.java index 1a663461346..3d839c1d885 100644 --- a/src/main/java/cn/nukkit/block/BlockTorch.java +++ b/src/main/java/cn/nukkit/block/BlockTorch.java @@ -75,7 +75,7 @@ public int onUpdate(int type) { @Override public boolean place(Item item, Block block, Block target, BlockFace face, double fx, double fy, double fz, Player player) { - if (block instanceof BlockWater) { + if (block instanceof BlockWater || block.level.isBlockWaterloggedAt(block.getChunk(), (int) block.x, (int) block.y, (int) block.z)) { return false; } diff --git a/src/main/java/cn/nukkit/block/BlockTypes.java b/src/main/java/cn/nukkit/block/BlockTypes.java index f48bc364784..82b3d4f795d 100644 --- a/src/main/java/cn/nukkit/block/BlockTypes.java +++ b/src/main/java/cn/nukkit/block/BlockTypes.java @@ -677,6 +677,7 @@ public class BlockTypes { public static final BlockType MANGROVE_PRESSURE_PLATE = register("minecraft:mangrove_pressure_plate", BlockID.MANGROVE_PRESSURE_PLATE); public static final BlockType MANGROVE_FENCE = register("minecraft:mangrove_fence", BlockID.MANGROVE_FENCE); public static final BlockType MANGROVE_FENCE_GATE = register("minecraft:mangrove_fence_gate", BlockID.MANGROVE_FENCE_GATE); + public static final BlockType MANGROVE_DOOR = register("minecraft:mangrove_door", BlockID.MANGROVE_DOOR); public static final BlockType MANGROVE_STANDING_SIGN = register("minecraft:mangrove_standing_sign", BlockID.MANGROVE_STANDING_SIGN); public static final BlockType MANGROVE_WALL_SIGN = register("minecraft:mangrove_wall_sign", BlockID.MANGROVE_WALL_SIGN); public static final BlockType MANGROVE_TRAPDOOR = register("minecraft:mangrove_trapdoor", BlockID.MANGROVE_TRAPDOOR); diff --git a/src/main/java/cn/nukkit/block/BlockVinesNether.java b/src/main/java/cn/nukkit/block/BlockVinesNether.java index 2bb0840824a..0a6adeea540 100644 --- a/src/main/java/cn/nukkit/block/BlockVinesNether.java +++ b/src/main/java/cn/nukkit/block/BlockVinesNether.java @@ -122,7 +122,7 @@ && findVineAge(true).orElse(maxVineAge) < maxVineAge) { */ public boolean grow() { Block pos = getSide(getGrowthDirection()); - if (pos.getId() != AIR || pos.y < 0 || 255 < pos.y) { + if (pos.getId() != AIR || pos.y < level.getMinBlockY() || pos.y > level.getMaxBlockY()) { return false; } @@ -163,7 +163,7 @@ public int growMultiple() { int grew = 0; for (int distance = 1; distance <= blocksToGrow; distance++) { Block pos = getSide(growthDirection, distance); - if (pos.getId() != AIR || pos.y < 0 || 255 < pos.y) { + if (pos.getId() != AIR || pos.y < level.getMinBlockY() || pos.y > level.getMaxBlockY()) { break; } diff --git a/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java b/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java index 8b7169e7419..30e788bf4d6 100644 --- a/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java +++ b/src/main/java/cn/nukkit/blockentity/BlockEntityBell.java @@ -54,6 +54,10 @@ public void saveNBT() { @Override public boolean onUpdate() { + if (this.closed) { + return false; + } + if (ringing) { if (ticks == 0) { this.level.addLevelSoundEvent(this, LevelSoundEventPacket.SOUND_BLOCK_BELL_HIT); @@ -79,10 +83,6 @@ public boolean onUpdate() { } private void spawnToAllWithExceptions() { - if (this.closed) { - return; - } - for (Player player : this.getLevel().getChunkPlayers(this.chunk.getX(), this.chunk.getZ()).values()) { if (player.spawned && !spawnExceptions.contains(player.getId())) { this.spawnTo(player); diff --git a/src/main/java/cn/nukkit/command/SimpleCommandMap.java b/src/main/java/cn/nukkit/command/SimpleCommandMap.java index a821b53b315..cf8402e9123 100644 --- a/src/main/java/cn/nukkit/command/SimpleCommandMap.java +++ b/src/main/java/cn/nukkit/command/SimpleCommandMap.java @@ -43,7 +43,7 @@ private void setDefaultCommands() { this.register("nukkit", new KickCommand("kick")); this.register("nukkit", new OpCommand("op")); this.register("nukkit", new DeopCommand("deop")); - this.register("nukkit", new SaveCommand("save-all")); + this.register("nukkit", new SaveCommand("save")); this.register("nukkit", new GiveCommand("give")); this.register("nukkit", new ClearCommand("clear")); this.register("nukkit", new EffectCommand("effect")); @@ -64,8 +64,6 @@ private void setDefaultCommands() { this.register("nukkit", new DefaultGamemodeCommand("defaultgamemode")); this.register("nukkit", new SayCommand("say")); this.register("nukkit", new MeCommand("me")); - this.register("nukkit", new SaveOnCommand("save-on")); - this.register("nukkit", new SaveOffCommand("save-off")); this.register("nukkit", new DifficultyCommand("difficulty")); this.register("nukkit", new ParticleCommand("particle")); this.register("nukkit", new SpawnpointCommand("spawnpoint")); diff --git a/src/main/java/cn/nukkit/command/defaults/SaveCommand.java b/src/main/java/cn/nukkit/command/defaults/SaveCommand.java index 9e119f2d104..ac4d768ebf5 100644 --- a/src/main/java/cn/nukkit/command/defaults/SaveCommand.java +++ b/src/main/java/cn/nukkit/command/defaults/SaveCommand.java @@ -15,6 +15,7 @@ public class SaveCommand extends VanillaCommand { public SaveCommand(String name) { super(name, "%nukkit.command.save.description", "%commands.save.usage"); this.setPermission("nukkit.command.save.perform"); + this.setAliases(new String[]{"save-all"}); this.commandParameters.clear(); } @@ -24,6 +25,30 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } + if (args.length > 0) { + switch (args[0].toLowerCase()) { + case "on": + sender.getServer().setAutoSave(true); + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.enabled")); + return true; + case "off": + sender.getServer().setAutoSave(false); + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.disabled")); + return true; + case "hold": + sender.getServer().holdWorldSave = true; + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-on")); + return true; + case "resume": + sender.getServer().holdWorldSave = false; + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-off")); + return true; + default: + sender.sendMessage(new TranslationContainer("commands.generic.usage", this.usageMessage)); + return false; + } + } + Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.start")); for (Player player : sender.getServer().getOnlinePlayers().values()) { diff --git a/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java b/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java deleted file mode 100644 index 4967c22d8a0..00000000000 --- a/src/main/java/cn/nukkit/command/defaults/SaveOffCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.nukkit.command.defaults; - -import cn.nukkit.command.Command; -import cn.nukkit.command.CommandSender; -import cn.nukkit.lang.TranslationContainer; - -/** - * Created on 2015/11/13 by xtypr. - * Package cn.nukkit.command.defaults in project Nukkit . - */ -public class SaveOffCommand extends VanillaCommand { - - public SaveOffCommand(String name) { - super(name, "%nukkit.command.saveoff.description", "%commands.save-off.usage"); - this.setPermission("nukkit.command.save.disable"); - this.commandParameters.clear(); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (!this.testPermission(sender)) { - return true; - } - if (args.length == 1 && args[0].equalsIgnoreCase("hold")) { - sender.getServer().holdWorldSave = true; - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-on")); - return true; - } - sender.getServer().setAutoSave(false); - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.disabled")); - return true; - } -} diff --git a/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java b/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java deleted file mode 100644 index d9818a32cc3..00000000000 --- a/src/main/java/cn/nukkit/command/defaults/SaveOnCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.nukkit.command.defaults; - -import cn.nukkit.command.Command; -import cn.nukkit.command.CommandSender; -import cn.nukkit.lang.TranslationContainer; - -/** - * Created on 2015/11/13 by xtypr. - * Package cn.nukkit.command.defaults in project Nukkit . - */ -public class SaveOnCommand extends VanillaCommand { - - public SaveOnCommand(String name) { - super(name, "%nukkit.command.saveon.description", "%commands.save-on.usage"); - this.setPermission("nukkit.command.save.enable"); - this.commandParameters.clear(); - } - - @Override - public boolean execute(CommandSender sender, String commandLabel, String[] args) { - if (!this.testPermission(sender)) { - return true; - } - if (args.length == 1 && args[0].equalsIgnoreCase("hold")) { - sender.getServer().holdWorldSave = false; - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.hold-off")); - return true; - } - sender.getServer().setAutoSave(true); - Command.broadcastCommandMessage(sender, new TranslationContainer("commands.save.enabled")); - return true; - } -} diff --git a/src/main/java/cn/nukkit/entity/data/EntityMetadata.java b/src/main/java/cn/nukkit/entity/data/EntityMetadata.java index a228ca58857..bea49e0e10f 100644 --- a/src/main/java/cn/nukkit/entity/data/EntityMetadata.java +++ b/src/main/java/cn/nukkit/entity/data/EntityMetadata.java @@ -16,7 +16,15 @@ */ public class EntityMetadata { - private Int2ObjectMap<EntityData> map = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap<EntityData> map; + + public EntityMetadata() { + this.map = new Int2ObjectOpenHashMap<>(); + } + + private EntityMetadata(Int2ObjectMap<EntityData> map) { + this.map = map; + } public EntityData get(int id) { return this.getOrDefault(id, null); @@ -123,12 +131,7 @@ public Map<Integer, EntityData> getMap() { return new TreeMap<>(this.map); // Ordered } - private EntityMetadata replace(Int2ObjectMap<EntityData> map) { - this.map = map; - return this; - } - public EntityMetadata clone() { - return new EntityMetadata().replace(new Int2ObjectOpenHashMap<>(this.map)); + return new EntityMetadata(new Int2ObjectOpenHashMap<>(this.map)); } } diff --git a/src/main/java/cn/nukkit/inventory/LoomInventory.java b/src/main/java/cn/nukkit/inventory/LoomInventory.java index e67c5866957..f0900c80c45 100644 --- a/src/main/java/cn/nukkit/inventory/LoomInventory.java +++ b/src/main/java/cn/nukkit/inventory/LoomInventory.java @@ -2,12 +2,17 @@ import cn.nukkit.Player; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.level.Position; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; public class LoomInventory extends FakeBlockUIComponent { public static final int OFFSET = 9; + public static final IntSet ITEMS = new IntOpenHashSet(new int[]{Item.AIR, ItemID.BANNER, ItemID.DYE, ItemID.BANNER_PATTERN}); + public LoomInventory(PlayerUIInventory playerUI, Position position) { super(playerUI, InventoryType.LOOM, OFFSET, position); } @@ -18,19 +23,32 @@ public void onOpen(Player who) { who.craftingType = Player.CRAFTING_LOOM; } - public Item getFirstItem() { + public Item getBanner() { return getItem(0); } - public Item getSecondItem() { + public Item getDye() { return getItem(1); } - public void setFirstItem(Item item) { + public Item getPattern() { + return getItem(2); + } + + public void setBanner(Item item) { this.setItem(0, item); } - public void setSecondItem(Item item) { + public void setDye(Item item) { this.setItem(1, item); } + + public void setPattern(Item item) { + this.setItem(2, item); + } + + @Override + public boolean allowedToAdd(Item item) { + return ITEMS.contains(item.getId()); + } } diff --git a/src/main/java/cn/nukkit/inventory/SmithingInventory.java b/src/main/java/cn/nukkit/inventory/SmithingInventory.java index 5cbbd876a80..cfbbd9e1bac 100644 --- a/src/main/java/cn/nukkit/inventory/SmithingInventory.java +++ b/src/main/java/cn/nukkit/inventory/SmithingInventory.java @@ -3,7 +3,10 @@ import cn.nukkit.Player; import cn.nukkit.Server; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import cn.nukkit.level.Position; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; /** * @author joserobjr @@ -19,6 +22,11 @@ public class SmithingInventory extends FakeBlockUIComponent { private Item currentResult = Item.get(0); + private static final IntSet ITEMS = new IntOpenHashSet(new int[]{ + Item.AIR, ItemID.NETHERITE_INGOT, ItemID.DIAMOND_SWORD, ItemID.DIAMOND_SHOVEL, ItemID.DIAMOND_PICKAXE, ItemID.DIAMOND_AXE, + ItemID.DIAMOND_HOE, ItemID.DIAMOND_HELMET, ItemID.DIAMOND_CHESTPLATE, ItemID.DIAMOND_LEGGINGS, ItemID.DIAMOND_BOOTS, + ItemID.NETHERITE_SWORD, ItemID.NETHERITE_SHOVEL, ItemID.NETHERITE_PICKAXE, ItemID.NETHERITE_AXE, ItemID.NETHERITE_HOE, + ItemID.NETHERITE_HELMET, ItemID.NETHERITE_CHESTPLATE, ItemID.NETHERITE_LEGGINGS, ItemID.NETHERITE_BOOTS}); public SmithingInventory(PlayerUIInventory playerUI, Position position) { super(playerUI, InventoryType.SMITHING_TABLE, 51, position); @@ -84,4 +92,9 @@ public void onOpen(Player who) { public Item getCurrentResult() { return currentResult; } + + @Override + public boolean allowedToAdd(Item item) { + return ITEMS.contains(item.getId()); + } } diff --git a/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java index bb3e5e7c152..634599db43b 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/InventoryTransaction.java @@ -84,7 +84,7 @@ public void addAction(InventoryAction action) { if (!slotChangeAction.getInventory().allowedToAdd(targetItem)) { invalid = true; - Server.getInstance().getLogger().debug("Failed to add SlotChangeAction for " + source.getName() + ": " + slotChangeAction.getInventory().getTitle() + " inventory doesn't allow item " + targetItem.getId()); + Server.getInstance().getLogger().debug("Failed to add SlotChangeAction for " + source.getName() + ": " + slotChangeAction.getInventory().getName() + " inventory doesn't allow item " + targetItem.getId()); return; } diff --git a/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java index 8dda85bb7ac..2b5073499e2 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/LoomTransaction.java @@ -7,6 +7,7 @@ import cn.nukkit.inventory.transaction.action.InventoryAction; import cn.nukkit.inventory.transaction.action.LoomItemAction; import cn.nukkit.item.Item; +import cn.nukkit.item.ItemID; import java.util.List; @@ -29,26 +30,32 @@ public void addAction(InventoryAction action) { @Override public boolean canExecute() { + if (!super.canExecute()) { + return false; + } + Inventory inventory = getSource().getWindowById(Player.LOOM_WINDOW_ID); if (!(inventory instanceof LoomInventory)) { return false; } - LoomInventory loomInventory = (LoomInventory) inventory; if (outputItem == null) { return false; } - Item first = loomInventory.getFirstItem(); - Item second = loomInventory.getSecondItem(); - if (first.getId() != Item.BANNER || second.getId() != Item.DYE || first.getDamage() != outputItem.getDamage()) { + LoomInventory loomInventory = (LoomInventory) inventory; + Item banner = loomInventory.getBanner(); + Item dye = loomInventory.getDye(); + if (banner.getId() != Item.BANNER || dye.getId() != Item.DYE || banner.getDamage() != outputItem.getDamage()) { return false; } + if (!outputItem.hasCompoundTag()) { return false; } + int patternCount = outputItem.getNamedTag().getList("Patterns").size(); - if (first.getNamedTag() == null) { + if (banner.getNamedTag() == null) { return patternCount == 1; } @@ -56,36 +63,33 @@ public boolean canExecute() { return false; } - return first.getNamedTag().getList("Patterns").size() + 1 == patternCount; - } - - @Override - public boolean execute() { - if (this.hasExecuted() || !this.canExecute()) { - this.source.removeAllWindows(false); - this.sendInventories(); + Item pattern = loomInventory.getPattern(); + if (pattern.getId() != 0 && pattern.getId() != ItemID.BANNER_PATTERN) { return false; } + return banner.getNamedTag().getList("Patterns").size() + 1 == patternCount; + } + + @Override + protected boolean callExecuteEvent() { LoomInventory inventory = (LoomInventory) getSource().getWindowById(Player.LOOM_WINDOW_ID); LoomItemEvent event = new LoomItemEvent(inventory, this.outputItem, this.source); this.source.getServer().getPluginManager().callEvent(event); if (event.isCancelled()) { this.source.removeAllWindows(false); this.sendInventories(); - return true; - } - - for (InventoryAction action : this.actions) { - if (action.execute(this.source)) { - action.onExecuteSuccess(this.source); - } else { - action.onExecuteFail(this.source); - } + return false; } return true; } + @Override + protected void sendInventories() { + this.source.removeAllWindows(false); + super.sendInventories(); + } + public Item getOutputItem() { return this.outputItem; } diff --git a/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java index 1e986a2a7d1..9ef9c0e96f0 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/RepairItemTransaction.java @@ -46,6 +46,10 @@ public RepairItemTransaction(Player source, List<InventoryAction> actions) { @Override public boolean canExecute() { + if (!super.canExecute()) { + return false; + } + Inventory inventory = getSource().getWindowById(Player.ANVIL_WINDOW_ID); if (!(inventory instanceof AnvilInventory)) { return false; diff --git a/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java b/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java index 17956901c80..8a433b0baad 100644 --- a/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java +++ b/src/main/java/cn/nukkit/inventory/transaction/action/LoomItemAction.java @@ -25,16 +25,17 @@ public boolean execute(Player source) { @Override public void onExecuteSuccess(Player source) { - Item first = inventory.getFirstItem(); - Item second = inventory.getSecondItem(); - if (first != null && !first.isNull()) { - first.count--; - inventory.setFirstItem(first); + Item banner = inventory.getBanner(); + Item dye = inventory.getDye(); + if (banner != null && !banner.isNull()) { + banner.count -= sourceItem.getCount(); + inventory.setBanner(banner); } - if (second != null && !second.isNull()) { - second.count--; - inventory.setSecondItem(second); + if (dye != null && !dye.isNull()) { + dye.count -= sourceItem.getCount(); + inventory.setDye(dye); } + // Pattern not consumed } @Override diff --git a/src/main/java/cn/nukkit/item/ItemTrident.java b/src/main/java/cn/nukkit/item/ItemTrident.java index 7b5aded44c4..5af7c72fcef 100644 --- a/src/main/java/cn/nukkit/item/ItemTrident.java +++ b/src/main/java/cn/nukkit/item/ItemTrident.java @@ -63,7 +63,7 @@ public boolean onRelease(Player player, int ticksUsed) { } else { boolean inWater = false; for (Block block : player.getCollisionBlocks()) { - if (block instanceof BlockWater) { + if (block instanceof BlockWater || block.level.isBlockWaterloggedAt(player.chunk, (int) block.x, (int) block.y, (int) block.z)) { inWater = true; break; } diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java index 7a4e7ddb982..5c66838ccc2 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/BlockStateSnapshot.java @@ -1,5 +1,6 @@ package cn.nukkit.level.format.leveldb.structure; +import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.format.leveldb.BlockStateMapping; import org.cloudburstmc.nbt.NbtMap; import lombok.Builder; @@ -22,6 +23,9 @@ public class BlockStateSnapshot { @Builder.Default private int legacyData = -1; + @Builder.Default + private int runtimeIdNetworkProtocol = -1; + public int getLegacyId() { if (this.legacyId != -1) { return this.legacyId; @@ -48,4 +52,12 @@ public int getLegacyData() { } return data; } + + int getRuntimeIdNetworkProtocol() { + if (this.runtimeIdNetworkProtocol == -1) { + this.runtimeIdNetworkProtocol = GlobalBlockPalette.getOrCreateRuntimeId(this.getLegacyId(), this.getLegacyData()); + } + + return this.runtimeIdNetworkProtocol; + } } diff --git a/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java index 4898ab7f52d..d42dfd033af 100644 --- a/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java +++ b/src/main/java/cn/nukkit/level/format/leveldb/structure/StateBlockStorage.java @@ -2,7 +2,6 @@ import cn.nukkit.Nukkit; import cn.nukkit.block.Block; -import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.level.format.leveldb.BlockStateMapping; import cn.nukkit.level.util.BitArray; import cn.nukkit.level.util.BitArrayVersion; @@ -21,7 +20,6 @@ import java.io.IOException; import java.util.List; -import java.util.function.Function; public class StateBlockStorage { private static final Logger log = LogManager.getLogger("LevelDB-Logger"); @@ -211,10 +209,6 @@ public void setFullBlock(int index, int value) { } public void writeTo(BinaryStream stream) { - this.writeTo(stream, state -> GlobalBlockPalette.getOrCreateRuntimeId(state.getLegacyId(), state.getLegacyData())); - } - - private void writeTo(BinaryStream stream, Function<BlockStateSnapshot, Integer> mapping) { BitArray bitArray = this.bitArray; stream.putByte((byte) this.getPaletteHeader(bitArray.getVersion(), true)); @@ -226,7 +220,7 @@ private void writeTo(BinaryStream stream, Function<BlockStateSnapshot, Integer> } for (BlockStateSnapshot state : this.palette) { - stream.putVarInt(mapping.apply(state)); + stream.putVarInt(state.getRuntimeIdNetworkProtocol()); } } diff --git a/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java b/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java index 731385ca9bc..2eff112ffdd 100644 --- a/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java +++ b/src/main/java/cn/nukkit/level/generator/SimpleChunkManager.java @@ -78,7 +78,7 @@ public int getBlockDataAt(int x, int y, int z, BlockLayer layer) { public void setBlockDataAt(int x, int y, int z, BlockLayer layer, int data) { FullChunk chunk = this.getChunk(x >> 4, z >> 4); if (chunk != null) { - chunk.setBlockData(x & 0xf, y & 0xff, z & 0xf, layer, data); + chunk.setBlockData(x & 0xf, y, z & 0xf, layer, data); } } diff --git a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java index 9e68611b63c..cce7d08c513 100644 --- a/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java +++ b/src/main/java/cn/nukkit/network/encryption/EncryptionUtils.java @@ -1,20 +1,13 @@ package cn.nukkit.network.encryption; -import com.google.common.base.Preconditions; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.crypto.ECDSASigner; -import com.nimbusds.jose.crypto.ECDSAVerifier; import com.nimbusds.jose.jwk.Curve; -import com.nimbusds.jose.shaded.json.JSONArray; -import com.nimbusds.jose.shaded.json.JSONObject; -import com.nimbusds.jose.shaded.json.JSONValue; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import com.nukkitx.natives.aes.AesFactory; -import com.nukkitx.natives.util.Natives; import lombok.experimental.UtilityClass; import javax.crypto.Cipher; @@ -30,10 +23,8 @@ import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; -import java.text.ParseException; import java.util.Arrays; import java.util.Base64; -import java.util.Iterator; /** * <a href="https://github.com/CloudburstMC/Protocol/blob/6b48673067d5c0e60f5e0a5a7e889dbf2aafa1a1/bedrock/bedrock-common/src/main/java/com/nukkitx/protocol/bedrock/util/EncryptionUtils.java">...</a> @@ -41,7 +32,6 @@ @UtilityClass public class EncryptionUtils { - private static final AesFactory AES_FACTORY; private static final ECPublicKey MOJANG_PUBLIC_KEY; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final String MOJANG_PUBLIC_KEY_BASE64 = @@ -54,14 +44,6 @@ public class EncryptionUtils { String namedGroups = System.getProperty("jdk.tls.namedGroups"); System.setProperty("jdk.tls.namedGroups", namedGroups == null || namedGroups.isEmpty() ? "secp384r1" : ", secp384r1"); - AesFactory aesFactory; - try { - aesFactory = Natives.AES_CFB8.get(); - } catch (NullPointerException | IllegalStateException e) { - aesFactory = null; - } - AES_FACTORY = aesFactory; - try { KEY_PAIR_GEN = KeyPairGenerator.getInstance("EC"); KEY_PAIR_GEN.initialize(new ECGenParameterSpec("secp384r1")); @@ -83,15 +65,6 @@ public static ECPublicKey generateKey(String b64) throws NoSuchAlgorithmExceptio return (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(b64))); } - /** - * Create EC key pair to be used for handshake and encryption - * - * @return EC KeyPair - */ - public static KeyPair createKeyPair() { - return KEY_PAIR_GEN.generateKeyPair(); - } - /** * Sign JWS object with a given private key. * @@ -103,73 +76,6 @@ public static void signJwt(JWSObject jws, ECPrivateKey key) throws JOSEException jws.sign(new ECDSASigner(key, Curve.P_384)); } - /** - * Check whether a JWS object is valid for a given public key. - * - * @param jws object to be verified - * @param key key to verify object with - * @return true if the JWS object is valid - * @throws JOSEException invalid key provided - */ - public static boolean verifyJwt(JWSObject jws, ECPublicKey key) throws JOSEException { - return jws.verify(new ECDSAVerifier(key)); - } - - /** - * Verify the validity of the login chain data from the LoginPacket - * - * @param chain array of JWS objects - * @return chain validity - * @throws JOSEException invalid JWS algorithm used - * @throws ParseException invalid JWS object - * @throws InvalidKeySpecException invalid EC key provided - * @throws NoSuchAlgorithmException runtime does not support EC spec - */ - public static boolean verifyChain(JSONArray chain) throws JOSEException, ParseException, InvalidKeySpecException, NoSuchAlgorithmException { - ECPublicKey lastKey = null; - boolean validChain = false; - Iterator<Object> iterator = chain.iterator(); - while (iterator.hasNext()) { - Object node = iterator.next(); - Preconditions.checkArgument(node instanceof String, "Chain node is not a string"); - JWSObject jwt = JWSObject.parse((String) node); - - // x509 cert is expected in every claim - URI x5u = jwt.getHeader().getX509CertURL(); - if (x5u == null) { - return false; - } - - ECPublicKey expectedKey = EncryptionUtils.generateKey(jwt.getHeader().getX509CertURL().toString()); - // First key is self-signed - if (lastKey == null) { - lastKey = expectedKey; - } else if (!lastKey.equals(expectedKey)) { - return false; - } - - if (!verifyJwt(jwt, lastKey)) { - return false; - } - - if (validChain) { - return !iterator.hasNext(); - } - - if (lastKey.equals(EncryptionUtils.MOJANG_PUBLIC_KEY)) { - validChain = true; - } - - Object payload = JSONValue.parse(jwt.getPayload().toString()); - Preconditions.checkArgument(payload instanceof JSONObject, "Payload is not a object"); - - Object identityPublicKey = ((JSONObject) payload).get("identityPublicKey"); - Preconditions.checkArgument(identityPublicKey instanceof String, "identityPublicKey node is missing in chain"); - lastKey = generateKey((String) identityPublicKey); - } - return validChain; - } - /** * Generate the secret key used to encrypt the connection * @@ -239,15 +145,6 @@ public static byte[] generateRandomToken() { return token; } - /** - * Check whether java supports the encryption required to authenticate sessions. - * - * @return can use encryption - */ - public static boolean canUseEncryption() { - return AES_FACTORY != null; - } - /** * Mojang's public key used to verify the JWT during login. * diff --git a/src/main/java/cn/nukkit/network/encryption/Sha256.java b/src/main/java/cn/nukkit/network/encryption/Sha256.java new file mode 100644 index 00000000000..d5b00637217 --- /dev/null +++ b/src/main/java/cn/nukkit/network/encryption/Sha256.java @@ -0,0 +1,34 @@ +package cn.nukkit.network.encryption; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Sha256 { + + private final MessageDigest digest; + + public Sha256() { + try { + this.digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ex) { + throw new AssertionError(ex); + } + } + + public void update(byte[] input, int offset, int len) { + this.digest.update(input, offset, len); + } + + public void update(ByteBuffer buffer) { + this.digest.update(buffer); + } + + public byte[] digest() { + return this.digest.digest(); + } + + public void reset() { + this.digest.reset(); + } +} diff --git a/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java b/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java index 60be9eeadbf..86b4dd587f3 100644 --- a/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ClientboundMapItemDataPacket.java @@ -15,8 +15,6 @@ public class ClientboundMapItemDataPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.CLIENTBOUND_MAP_ITEM_DATA_PACKET; - public long[] eids = new long[0]; - public long mapId; public int update; public byte scale; @@ -25,14 +23,19 @@ public class ClientboundMapItemDataPacket extends DataPacket { public int height; public int offsetX; public int offsetZ; - public byte dimensionId; - public BlockVector3 origin = new BlockVector3(); - - public MapDecorator[] decorators = new MapDecorator[0]; - public MapTrackedObject[] trackedEntities = new MapTrackedObject[0]; - public int[] colors = new int[0]; - public BufferedImage image = null; + public BlockVector3 origin = EMPTY_BlockVector3; + public MapDecorator[] decorators = EMPTY_MapDecorator; + public MapTrackedObject[] trackedEntities = EMPTY_MapTrackedObject; + public long[] eids = EMPTY_long; + public int[] colors = EMPTY_int; + public BufferedImage image; + + private static final BlockVector3 EMPTY_BlockVector3 = new BlockVector3(); + private static final MapDecorator[] EMPTY_MapDecorator = new MapDecorator[0]; + private static final MapTrackedObject[] EMPTY_MapTrackedObject = new MapTrackedObject[0]; + private static final long[] EMPTY_long = new long[0]; + private static final int[] EMPTY_int = new int[0]; public static final int TEXTURE_UPDATE = 0x02; public static final int DECORATIONS_UPDATE = 0x04; @@ -68,7 +71,6 @@ public void encode() { this.putUnsignedVarInt(update); this.putByte(this.dimensionId); this.putBoolean(this.isLocked); - this.putSignedBlockPosition(origin); if ((update & ENTITIES_UPDATE) != 0) { @@ -102,7 +104,7 @@ public void encode() { this.putByte(decorator.offsetX); this.putByte(decorator.offsetZ); this.putString(decorator.label); - this.putUnsignedVarInt(decorator.color.getRGB()); //toABGR? + this.putUnsignedVarInt(decorator.color.getRGB()); } } @@ -122,9 +124,9 @@ public void encode() { } image.flush(); - } else if (colors.length > 0) { + } else { for (int color : colors) { - this.putUnsignedVarInt(color); //toABGR? + this.putUnsignedVarInt(color); } } } diff --git a/src/main/java/cn/nukkit/network/protocol/DataPacket.java b/src/main/java/cn/nukkit/network/protocol/DataPacket.java index 956d943b9f3..2d9852bd083 100644 --- a/src/main/java/cn/nukkit/network/protocol/DataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DataPacket.java @@ -41,15 +41,6 @@ public DataPacket reset() { return this; } - @Deprecated - public void setChannel(int channel) { - } - - @Deprecated - public int getChannel() { - return 0; - } - public DataPacket clean() { this.setBuffer(null); this.setOffset(0); diff --git a/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java index aca511f7baf..1e2407ff3eb 100644 --- a/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/DimensionDataPacket.java @@ -1,7 +1,6 @@ package cn.nukkit.network.protocol; import cn.nukkit.network.protocol.types.DimensionDefinition; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.ToString; import java.util.List; @@ -11,13 +10,7 @@ public class DimensionDataPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.DIMENSION_DATA_PACKET; - private static final List<DimensionDefinition> DEFAULT_DEFINITIONS = new ObjectArrayList<DimensionDefinition>() { - { - add(new DimensionDefinition("minecraft:overworld", 319, -64, 1)); - } - }; - - public List<DimensionDefinition> definitions = DEFAULT_DEFINITIONS; + public List<DimensionDefinition> definitions; @Override public byte pid() { diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java index e405839cc93..376f89252cb 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryContentPacket.java @@ -24,6 +24,8 @@ public byte pid() { public static final int SPECIAL_HOTBAR = 0x7a; public static final int SPECIAL_FIXED_INVENTORY = 0x7b; + private static final Item EMPTY_STORAGE_ITEM = Item.get(Item.AIR); + public int inventoryId; public int networkId; public Item[] slots = new Item[0]; @@ -49,6 +51,6 @@ public void encode() { } this.putByte((byte) 0); // fullContainerName.id this.putBoolean(false); // fullContainerName.optional.present - this.putUnsignedVarInt(0); // dynamicContainerSize + this.putSlot(EMPTY_STORAGE_ITEM); } } diff --git a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java index eac733be895..29a14e0fa03 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventorySlotPacket.java @@ -21,6 +21,8 @@ public byte pid() { public int slot; public Item item; + private static final Item EMPTY_STORAGE_ITEM = Item.get(Item.AIR); + @Override public void decode() { this.decodeUnsupported(); @@ -33,7 +35,7 @@ public void encode() { this.putUnsignedVarInt(this.slot); this.putByte((byte) 0); // fullContainerName.id this.putBoolean(false); // fullContainerName.optional.present - this.putUnsignedVarInt(0); // dynamicContainerSize + this.putSlot(EMPTY_STORAGE_ITEM); this.putSlot(this.item); } } diff --git a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java index c671536fbb1..7af1b5aae7e 100644 --- a/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/InventoryTransactionPacket.java @@ -182,6 +182,6 @@ public void decode() { } private Vector3 getVector3fAsVector3() { - return new Vector3(this.getLFloat(4), this.getLFloat(4), this.getLFloat(4)); + return new Vector3(this.getLFloat(), this.getLFloat(), this.getLFloat()); } } diff --git a/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java b/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java index 9951924d937..42d279be769 100644 --- a/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/MobEffectPacket.java @@ -42,6 +42,6 @@ public void encode() { this.putVarInt(this.amplifier); this.putBoolean(this.particles); this.putVarInt(this.duration); - this.putLLong(this.tick); + this.putUnsignedVarLong(this.tick); } } diff --git a/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java b/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java index 092af07acbb..5c4bfeb23f5 100644 --- a/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/PlayerAuthInputPacket.java @@ -22,19 +22,19 @@ public class PlayerAuthInputPacket extends DataPacket { private float pitch; private float headYaw; private Vector3f position; - private Vector2 motion; + private Vector2 motion; // Vector2 for backwards compatibility private final Set<AuthInputAction> inputData = EnumSet.noneOf(AuthInputAction.class); private InputMode inputMode; private ClientPlayMode playMode; private AuthInteractionModel interactionModel; - private Vector3f vrGazeDirection; private long tick; private Vector3f delta; - private Vector2 analogMoveVector; - //private ItemStackRequest itemStackRequest; private final Map<PlayerActionType, PlayerBlockActionData> blockActionData = new EnumMap<>(PlayerActionType.class); private long predictedVehicle; + private Vector2f analogMoveVector; private Vector2f vehicleRotation; + private Vector2f interactRotation; + private Vector3f cameraOrientation; @Override public byte pid() { @@ -61,18 +61,11 @@ public void decode() { this.interactionModel = AuthInteractionModel.fromOrdinal((int) this.getUnsignedVarInt()); - if (this.playMode == ClientPlayMode.REALITY) { - this.vrGazeDirection = this.getVector3f(); - } + this.interactRotation = this.getVector2f(); this.tick = this.getUnsignedVarLong(); this.delta = this.getVector3f(); - //if (this.inputData.contains(AuthInputAction.PERFORM_ITEM_STACK_REQUEST)) { - // TODO: this.itemStackRequest = readItemStackRequest(buf, protocolVersion); - // We are safe to leave this for later, since it is only sent with ServerAuthInventories - //} - if (this.inputData.contains(AuthInputAction.PERFORM_BLOCK_ACTIONS)) { int arraySize = this.getVarInt(); if (arraySize > 256) throw new IllegalArgumentException("PlayerAuthInputPacket PERFORM_BLOCK_ACTIONS is too long: " + arraySize); @@ -93,11 +86,12 @@ public void decode() { } if (this.inputData.contains(AuthInputAction.IN_CLIENT_PREDICTED_IN_VEHICLE)) { - this.vehicleRotation = new Vector2f(this.getLFloat(), this.getLFloat()); + this.vehicleRotation = this.getVector2f(); this.predictedVehicle = this.getVarLong(); } - this.analogMoveVector = new Vector2(this.getLFloat(), this.getLFloat()); + this.analogMoveVector = this.getVector2f(); + this.cameraOrientation = this.getVector3f(); } @Override diff --git a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java index 2bdc239c5d6..9586b3e1b04 100644 --- a/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java +++ b/src/main/java/cn/nukkit/network/protocol/ProtocolInfo.java @@ -14,11 +14,11 @@ public interface ProtocolInfo { * Actual Minecraft protocol version */ @SuppressWarnings("UnnecessaryBoxing") - int CURRENT_PROTOCOL = Integer.valueOf("729"); // DO NOT REMOVE BOXING + int CURRENT_PROTOCOL = Integer.valueOf("748"); // DO NOT REMOVE BOXING List<Integer> SUPPORTED_PROTOCOLS = Ints.asList(CURRENT_PROTOCOL); - String MINECRAFT_VERSION_NETWORK = "1.21.30"; + String MINECRAFT_VERSION_NETWORK = "1.21.40"; String MINECRAFT_VERSION = 'v' + MINECRAFT_VERSION_NETWORK; byte BATCH_PACKET = (byte) 0xff; @@ -236,4 +236,6 @@ public interface ProtocolInfo { byte __INTERNAL__SERVERBOUND_DIAGNOSTICS_PACKET = (byte) 215; byte __INTERNAL__CAMERA_AIM_ASSIST_PACKET = (byte) 216; byte __INTERNAL__CONTAINER_REGISTRY_CLEANUP_PACKET = (byte) 217; + byte __INTERNAL__MOVEMENT_EFFECT_PACKET = (byte) 218; + byte __INTERNAL__SET_MOVEMENT_AUTHORITY_PACKET = (byte) 219; } diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java index b49a2cfe08a..a9714d1e905 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePackStackPacket.java @@ -12,12 +12,11 @@ public class ResourcePackStackPacket extends DataPacket { public static final byte NETWORK_ID = ProtocolInfo.RESOURCE_PACK_STACK_PACKET; - public boolean mustAccept = false; - @Deprecated - public String gameVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK; // For plugin compatibility + public boolean mustAccept; + public String gameVersion = ProtocolInfo.MINECRAFT_VERSION_NETWORK; public ResourcePack[] behaviourPackStack = ResourcePack.EMPTY_ARRAY; public ResourcePack[] resourcePackStack = ResourcePack.EMPTY_ARRAY; - public final List<ExperimentData> experiments = new ObjectArrayList<>(); + public final List<ExperimentData> experiments = new ObjectArrayList<>(1); @Override public void decode() { @@ -40,7 +39,7 @@ public void encode() { this.putString(entry.getPackVersion()); this.putString(""); } - this.putString(ProtocolInfo.MINECRAFT_VERSION_NETWORK); + this.putString(this.gameVersion); this.putExperiments(this.experiments); this.putBoolean(false); // Has editor packs } diff --git a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java index 9543fbccb49..07502f22e59 100644 --- a/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/ResourcePacksInfoPacket.java @@ -1,11 +1,7 @@ package cn.nukkit.network.protocol; import cn.nukkit.resourcepacks.ResourcePack; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.ToString; -import lombok.Value; - -import java.util.List; @ToString public class ResourcePacksInfoPacket extends DataPacket { @@ -14,12 +10,9 @@ public class ResourcePacksInfoPacket extends DataPacket { public boolean mustAccept; public boolean scripting; - @Deprecated - public boolean forceServerPacks; // pre 1.21.30 public boolean hasAddonPacks; public ResourcePack[] behaviourPackEntries = ResourcePack.EMPTY_ARRAY; public ResourcePack[] resourcePackEntries = ResourcePack.EMPTY_ARRAY; - public List<CDNEntry> CDNEntries = new ObjectArrayList<>(); @Override public void decode() { @@ -34,12 +27,6 @@ public void encode() { this.putBoolean(this.scripting); this.encodeResourcePacks(this.resourcePackEntries); - - this.putUnsignedVarInt(this.CDNEntries.size()); - this.CDNEntries.forEach((entry) -> { - this.putString(entry.getPackId()); - this.putString(entry.getRemoteUrl()); - }); } private void encodeResourcePacks(ResourcePack[] packs) { @@ -49,11 +36,12 @@ private void encodeResourcePacks(ResourcePack[] packs) { this.putString(entry.getPackVersion()); this.putLLong(entry.getPackSize()); this.putString(entry.getEncryptionKey()); - this.putString(""); // sub-pack name + this.putString(entry.getSubPackName()); this.putString(!entry.getEncryptionKey().isEmpty() ? entry.getPackId().toString() : ""); - this.putBoolean(false); // scripting - this.putBoolean(false); // isAddonPack - this.putBoolean(false); // raytracing capable + this.putBoolean(entry.usesScripting()); + this.putBoolean(entry.isAddonPack()); + this.putBoolean(entry.isRaytracingCapable()); + this.putString(entry.getCDNUrl()); } } @@ -61,10 +49,4 @@ private void encodeResourcePacks(ResourcePack[] packs) { public byte pid() { return NETWORK_ID; } - - @Value - public static class CDNEntry { - String packId; - String remoteUrl; - } } diff --git a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java index 57f75246f28..41cde605b0d 100644 --- a/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java +++ b/src/main/java/cn/nukkit/network/protocol/StartGamePacket.java @@ -88,7 +88,7 @@ public byte pid() { public boolean disablePlayerInteractions; public boolean emoteChatMuted; public boolean hardcore; - public final List<ExperimentData> experiments = new ObjectArrayList<>(); + public final List<ExperimentData> experiments = new ObjectArrayList<>(1); @Override public void decode() { diff --git a/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java b/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java index c2f35ad37b1..712d7985fe6 100644 --- a/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java +++ b/src/main/java/cn/nukkit/network/protocol/UpdateAbilitiesPacket.java @@ -52,7 +52,7 @@ public class UpdateAbilitiesPacket extends DataPacket { private long entityId; private PlayerPermission playerPermission; private CommandPermission commandPermission; - private final List<AbilityLayer> abilityLayers = new ObjectArrayList<>(); + private final List<AbilityLayer> abilityLayers = new ObjectArrayList<>(2); @Override public void decode() { diff --git a/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java b/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java index 1bff0bc502c..2695692b3c4 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/AuthInputAction.java @@ -102,7 +102,27 @@ public enum AuthInputAction { /** * @since v729 */ - DOWN_RIGHT; + DOWN_RIGHT, + /** + * @since v748 + */ + START_USING_ITEM, + /** + * @since v748 + */ + IS_CAMERA_RELATIVE_MOVEMENT_ENABLED, + /** + * @since v748 + */ + IS_ROT_CONTROLLED_BY_MOVE_DIRECTION, + /** + * @since v748 + */ + START_SPIN_ATTACK, + /** + * @since v748 + */ + STOP_SPIN_ATTACK; private static final AuthInputAction[] VALUES = values(); diff --git a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java index 520747dbf7e..4016b47a6d1 100644 --- a/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java +++ b/src/main/java/cn/nukkit/network/protocol/types/NetworkInventoryAction.java @@ -7,8 +7,6 @@ import cn.nukkit.item.Item; import cn.nukkit.item.ItemID; import cn.nukkit.network.protocol.InventoryTransactionPacket; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; import lombok.ToString; import java.util.Optional; @@ -68,7 +66,6 @@ public class NetworkInventoryAction { public int inventorySlot; public Item oldItem; public Item newItem; - public int stackNetworkId; public NetworkInventoryAction read(InventoryTransactionPacket packet) { this.sourceType = (int) packet.getUnsignedVarInt(); @@ -135,10 +132,6 @@ public void write(InventoryTransactionPacket packet) { packet.putSlot(this.newItem); } - private static final IntSet VALID_SMITHING_EQUIPMENT = new IntOpenHashSet(new int[]{ - Item.AIR, ItemID.DIAMOND_SWORD, ItemID.DIAMOND_SHOVEL, ItemID.DIAMOND_PICKAXE, ItemID.DIAMOND_AXE, ItemID.DIAMOND_HOE, ItemID.DIAMOND_HELMET, ItemID.DIAMOND_CHESTPLATE, ItemID.DIAMOND_LEGGINGS, ItemID.DIAMOND_BOOTS - }); - public InventoryAction createInventoryAction(Player player) { switch (this.sourceType) { case SOURCE_CONTAINER: @@ -202,10 +195,6 @@ public InventoryAction createInventoryAction(Player player) { player.getServer().getLogger().debug(player.getName() + " does not have smithing table window open"); return null; } - if (!(this.oldItem == null || VALID_SMITHING_EQUIPMENT.contains(this.oldItem.getId())) || !(this.newItem == null || VALID_SMITHING_EQUIPMENT.contains(this.oldItem.getId()))) { - player.getServer().getLogger().debug(player.getName() + " had invalid smithing equipment"); - return null; - } this.windowId = Player.SMITHING_WINDOW_ID; this.inventorySlot = 0; break; diff --git a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java index 8ea2953d2a3..6b706060a92 100644 --- a/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java +++ b/src/main/java/cn/nukkit/network/session/RakNetPlayerSession.java @@ -5,13 +5,12 @@ import cn.nukkit.Server; import cn.nukkit.network.CompressionProvider; import cn.nukkit.network.RakNetInterface; +import cn.nukkit.network.encryption.Sha256; import cn.nukkit.network.protocol.BatchPacket; import cn.nukkit.network.protocol.DataPacket; import cn.nukkit.network.protocol.DisconnectPacket; import cn.nukkit.utils.BinaryStream; import com.google.common.base.Preconditions; -import com.nukkitx.natives.sha256.Sha256; -import com.nukkitx.natives.util.Natives; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; @@ -319,7 +318,7 @@ public long getPing() { return -1; } - private static final ThreadLocal<Sha256> HASH_LOCAL = ThreadLocal.withInitial(Natives.SHA_256); + private static final ThreadLocal<Sha256> HASH_LOCAL = ThreadLocal.withInitial(Sha256::new); private byte[] generateTrailer(ByteBuf buf) { Sha256 hash = HASH_LOCAL.get(); diff --git a/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java b/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java index fe1a18f6c85..2d125a8f18f 100644 --- a/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java +++ b/src/main/java/cn/nukkit/resourcepacks/ResourcePack.java @@ -18,7 +18,27 @@ public interface ResourcePack { byte[] getPackChunk(int off, int len); - default String getEncryptionKey() { // Default for backwards compatibility + default String getEncryptionKey() { + return ""; + } + + default String getSubPackName() { + return ""; + } + + default boolean usesScripting() { + return false; + } + + default boolean isAddonPack() { + return false; + } + + default boolean isRaytracingCapable() { + return false; + } + + default String getCDNUrl() { return ""; } } diff --git a/src/main/java/cn/nukkit/utils/BinaryStream.java b/src/main/java/cn/nukkit/utils/BinaryStream.java index 46b23e47a15..e372261b4af 100644 --- a/src/main/java/cn/nukkit/utils/BinaryStream.java +++ b/src/main/java/cn/nukkit/utils/BinaryStream.java @@ -11,6 +11,7 @@ import cn.nukkit.level.GlobalBlockPalette; import cn.nukkit.math.BlockFace; import cn.nukkit.math.BlockVector3; +import cn.nukkit.math.Vector2f; import cn.nukkit.math.Vector3f; import cn.nukkit.nbt.NBTIO; import cn.nukkit.nbt.tag.CompoundTag; @@ -745,7 +746,7 @@ public void putBlockVector3(int x, int y, int z) { } public Vector3f getVector3f() { - return new Vector3f(this.getLFloat(4), this.getLFloat(4), this.getLFloat(4)); + return new Vector3f(this.getLFloat(), this.getLFloat(), this.getLFloat()); } public void putVector3f(Vector3f v) { @@ -758,6 +759,10 @@ public void putVector3f(float x, float y, float z) { this.putLFloat(z); } + public Vector2f getVector2f() { + return new Vector2f(this.getLFloat(), this.getLFloat()); + } + public void putGameRules(GameRules gameRules) { Map<GameRule, GameRules.Value> rulesToSend = new HashMap<>(gameRules.getGameRules()); this.putUnsignedVarInt(rulesToSend.size()); diff --git a/src/main/resources/creative_items.json b/src/main/resources/creative_items.json index 8c1d7a1188b..a63cd633366 100644 --- a/src/main/resources/creative_items.json +++ b/src/main/resources/creative_items.json @@ -1 +1 @@ -{"items":[{"id":"minecraft:oak_planks","blockRuntimeId":12910},{"id":"minecraft:spruce_planks","blockRuntimeId":13268},{"id":"minecraft:birch_planks","blockRuntimeId":8000},{"id":"minecraft:jungle_planks","blockRuntimeId":11184},{"id":"minecraft:acacia_planks","blockRuntimeId":6034},{"id":"minecraft:dark_oak_planks","blockRuntimeId":4686},{"id":"minecraft:mangrove_planks","blockRuntimeId":2960},{"id":"minecraft:cherry_planks","blockRuntimeId":13133},{"id":"minecraft:bamboo_planks","blockRuntimeId":8658},{"id":"minecraft:bamboo_mosaic","blockRuntimeId":14080},{"id":"minecraft:crimson_planks","blockRuntimeId":7342},{"id":"minecraft:warped_planks","blockRuntimeId":2934},{"id":"minecraft:cobblestone_wall","blockRuntimeId":3512},{"id":"minecraft:mossy_cobblestone_wall","blockRuntimeId":3322},{"id":"minecraft:granite_wall","blockRuntimeId":8838},{"id":"minecraft:diorite_wall","blockRuntimeId":2963},{"id":"minecraft:andesite_wall","blockRuntimeId":8449},{"id":"minecraft:sandstone_wall","blockRuntimeId":13525},{"id":"minecraft:red_sandstone_wall","blockRuntimeId":948},{"id":"minecraft:stone_brick_wall","blockRuntimeId":8260},{"id":"minecraft:mossy_stone_brick_wall","blockRuntimeId":13882},{"id":"minecraft:brick_wall","blockRuntimeId":723},{"id":"minecraft:nether_brick_wall","blockRuntimeId":11579},{"id":"minecraft:red_nether_brick_wall","blockRuntimeId":12586},{"id":"minecraft:end_stone_brick_wall","blockRuntimeId":77},{"id":"minecraft:prismarine_wall","blockRuntimeId":7406},{"id":"minecraft:blackstone_wall","blockRuntimeId":5463},{"id":"minecraft:polished_blackstone_wall","blockRuntimeId":11270},{"id":"minecraft:polished_blackstone_brick_wall","blockRuntimeId":3139},{"id":"minecraft:cobbled_deepslate_wall","blockRuntimeId":13716},{"id":"minecraft:deepslate_tile_wall","blockRuntimeId":7757},{"id":"minecraft:polished_deepslate_wall","blockRuntimeId":13285},{"id":"minecraft:deepslate_brick_wall","blockRuntimeId":1707},{"id":"minecraft:mud_brick_wall","blockRuntimeId":2724},{"id":"minecraft:oak_fence","blockRuntimeId":8793},{"id":"minecraft:spruce_fence","blockRuntimeId":1657},{"id":"minecraft:birch_fence","blockRuntimeId":13687},{"id":"minecraft:jungle_fence","blockRuntimeId":1648},{"id":"minecraft:acacia_fence","blockRuntimeId":13714},{"id":"minecraft:dark_oak_fence","blockRuntimeId":11859},{"id":"minecraft:mangrove_fence","blockRuntimeId":11187},{"id":"minecraft:cherry_fence","blockRuntimeId":2959},{"id":"minecraft:bamboo_fence","blockRuntimeId":1896},{"id":"minecraft:nether_brick_fence","blockRuntimeId":5812},{"id":"minecraft:crimson_fence","blockRuntimeId":13451},{"id":"minecraft:warped_fence","blockRuntimeId":9444},{"id":"minecraft:fence_gate","blockRuntimeId":241},{"id":"minecraft:spruce_fence_gate","blockRuntimeId":11152},{"id":"minecraft:birch_fence_gate","blockRuntimeId":4915},{"id":"minecraft:jungle_fence_gate","blockRuntimeId":8050},{"id":"minecraft:acacia_fence_gate","blockRuntimeId":12961},{"id":"minecraft:dark_oak_fence_gate","blockRuntimeId":5684},{"id":"minecraft:mangrove_fence_gate","blockRuntimeId":6248},{"id":"minecraft:cherry_fence_gate","blockRuntimeId":14051},{"id":"minecraft:bamboo_fence_gate","blockRuntimeId":7731},{"id":"minecraft:crimson_fence_gate","blockRuntimeId":6702},{"id":"minecraft:warped_fence_gate","blockRuntimeId":8088},{"id":"minecraft:normal_stone_stairs","blockRuntimeId":1897},{"id":"minecraft:stone_stairs","blockRuntimeId":4827},{"id":"minecraft:mossy_cobblestone_stairs","blockRuntimeId":5626},{"id":"minecraft:oak_stairs","blockRuntimeId":1223},{"id":"minecraft:spruce_stairs","blockRuntimeId":297},{"id":"minecraft:birch_stairs","blockRuntimeId":11571},{"id":"minecraft:jungle_stairs","blockRuntimeId":11531},{"id":"minecraft:acacia_stairs","blockRuntimeId":10720},{"id":"minecraft:dark_oak_stairs","blockRuntimeId":7749},{"id":"minecraft:mangrove_stairs","blockRuntimeId":6210},{"id":"minecraft:cherry_stairs","blockRuntimeId":11912},{"id":"minecraft:bamboo_stairs","blockRuntimeId":2709},{"id":"minecraft:bamboo_mosaic_stairs","blockRuntimeId":10728},{"id":"minecraft:stone_brick_stairs","blockRuntimeId":2942},{"id":"minecraft:mossy_stone_brick_stairs","blockRuntimeId":9879},{"id":"minecraft:sandstone_stairs","blockRuntimeId":4719},{"id":"minecraft:smooth_sandstone_stairs","blockRuntimeId":4757},{"id":"minecraft:red_sandstone_stairs","blockRuntimeId":8031},{"id":"minecraft:smooth_red_sandstone_stairs","blockRuntimeId":8422},{"id":"minecraft:granite_stairs","blockRuntimeId":4262},{"id":"minecraft:polished_granite_stairs","blockRuntimeId":5649},{"id":"minecraft:diorite_stairs","blockRuntimeId":5971},{"id":"minecraft:polished_diorite_stairs","blockRuntimeId":11249},{"id":"minecraft:andesite_stairs","blockRuntimeId":7992},{"id":"minecraft:polished_andesite_stairs","blockRuntimeId":11763},{"id":"minecraft:brick_stairs","blockRuntimeId":11075},{"id":"minecraft:nether_brick_stairs","blockRuntimeId":272},{"id":"minecraft:red_nether_brick_stairs","blockRuntimeId":11170},{"id":"minecraft:end_brick_stairs","blockRuntimeId":10912},{"id":"minecraft:quartz_stairs","blockRuntimeId":6868},{"id":"minecraft:smooth_quartz_stairs","blockRuntimeId":13075},{"id":"minecraft:purpur_stairs","blockRuntimeId":13138},{"id":"minecraft:prismarine_stairs","blockRuntimeId":12471},{"id":"minecraft:dark_prismarine_stairs","blockRuntimeId":12806},{"id":"minecraft:prismarine_bricks_stairs","blockRuntimeId":1110},{"id":"minecraft:crimson_stairs","blockRuntimeId":10801},{"id":"minecraft:warped_stairs","blockRuntimeId":4840},{"id":"minecraft:blackstone_stairs","blockRuntimeId":11752},{"id":"minecraft:polished_blackstone_stairs","blockRuntimeId":5866},{"id":"minecraft:polished_blackstone_brick_stairs","blockRuntimeId":6070},{"id":"minecraft:cut_copper_stairs","blockRuntimeId":6221},{"id":"minecraft:exposed_cut_copper_stairs","blockRuntimeId":6202},{"id":"minecraft:weathered_cut_copper_stairs","blockRuntimeId":5874},{"id":"minecraft:oxidized_cut_copper_stairs","blockRuntimeId":1605},{"id":"minecraft:waxed_cut_copper_stairs","blockRuntimeId":1664},{"id":"minecraft:waxed_exposed_cut_copper_stairs","blockRuntimeId":5429},{"id":"minecraft:waxed_weathered_cut_copper_stairs","blockRuntimeId":10703},{"id":"minecraft:waxed_oxidized_cut_copper_stairs","blockRuntimeId":9397},{"id":"minecraft:cobbled_deepslate_stairs","blockRuntimeId":885},{"id":"minecraft:deepslate_tile_stairs","blockRuntimeId":6694},{"id":"minecraft:polished_deepslate_stairs","blockRuntimeId":1542},{"id":"minecraft:deepslate_brick_stairs","blockRuntimeId":12798},{"id":"minecraft:mud_brick_stairs","blockRuntimeId":8236},{"id":"minecraft:wooden_door"},{"id":"minecraft:spruce_door"},{"id":"minecraft:birch_door"},{"id":"minecraft:jungle_door"},{"id":"minecraft:acacia_door"},{"id":"minecraft:dark_oak_door"},{"id":"minecraft:mangrove_door"},{"id":"minecraft:cherry_door"},{"id":"minecraft:bamboo_door"},{"id":"minecraft:iron_door"},{"id":"minecraft:crimson_door"},{"id":"minecraft:warped_door"},{"id":"minecraft:trapdoor","blockRuntimeId":1149},{"id":"minecraft:spruce_trapdoor","blockRuntimeId":11120},{"id":"minecraft:birch_trapdoor","blockRuntimeId":11193},{"id":"minecraft:jungle_trapdoor","blockRuntimeId":8069},{"id":"minecraft:acacia_trapdoor","blockRuntimeId":8625},{"id":"minecraft:dark_oak_trapdoor","blockRuntimeId":12889},{"id":"minecraft:mangrove_trapdoor","blockRuntimeId":6078},{"id":"minecraft:cherry_trapdoor","blockRuntimeId":2908},{"id":"minecraft:bamboo_trapdoor","blockRuntimeId":7941},{"id":"minecraft:iron_trapdoor","blockRuntimeId":1570},{"id":"minecraft:crimson_trapdoor","blockRuntimeId":5907},{"id":"minecraft:warped_trapdoor","blockRuntimeId":6818},{"id":"minecraft:iron_bars","blockRuntimeId":6903},{"id":"minecraft:glass","blockRuntimeId":10698},{"id":"minecraft:white_stained_glass","blockRuntimeId":7347},{"id":"minecraft:light_gray_stained_glass","blockRuntimeId":1702},{"id":"minecraft:gray_stained_glass","blockRuntimeId":4836},{"id":"minecraft:black_stained_glass","blockRuntimeId":9924},{"id":"minecraft:brown_stained_glass","blockRuntimeId":2316},{"id":"minecraft:red_stained_glass","blockRuntimeId":7923},{"id":"minecraft:orange_stained_glass","blockRuntimeId":6133},{"id":"minecraft:yellow_stained_glass","blockRuntimeId":12952},{"id":"minecraft:lime_stained_glass","blockRuntimeId":283},{"id":"minecraft:green_stained_glass","blockRuntimeId":5809},{"id":"minecraft:cyan_stained_glass","blockRuntimeId":9923},{"id":"minecraft:light_blue_stained_glass","blockRuntimeId":8835},{"id":"minecraft:blue_stained_glass","blockRuntimeId":8797},{"id":"minecraft:purple_stained_glass","blockRuntimeId":3500},{"id":"minecraft:magenta_stained_glass","blockRuntimeId":12430},{"id":"minecraft:pink_stained_glass","blockRuntimeId":5890},{"id":"minecraft:tinted_glass","blockRuntimeId":10055},{"id":"minecraft:glass_pane","blockRuntimeId":7919},{"id":"minecraft:white_stained_glass_pane","blockRuntimeId":6134},{"id":"minecraft:light_gray_stained_glass_pane","blockRuntimeId":1704},{"id":"minecraft:gray_stained_glass_pane","blockRuntimeId":11457},{"id":"minecraft:black_stained_glass_pane","blockRuntimeId":3511},{"id":"minecraft:brown_stained_glass_pane","blockRuntimeId":1235},{"id":"minecraft:red_stained_glass_pane","blockRuntimeId":11851},{"id":"minecraft:orange_stained_glass_pane","blockRuntimeId":1139},{"id":"minecraft:yellow_stained_glass_pane","blockRuntimeId":718},{"id":"minecraft:lime_stained_glass_pane","blockRuntimeId":12950},{"id":"minecraft:green_stained_glass_pane","blockRuntimeId":5648},{"id":"minecraft:cyan_stained_glass_pane","blockRuntimeId":11025},{"id":"minecraft:light_blue_stained_glass_pane","blockRuntimeId":5411},{"id":"minecraft:blue_stained_glass_pane","blockRuntimeId":240},{"id":"minecraft:purple_stained_glass_pane","blockRuntimeId":7355},{"id":"minecraft:magenta_stained_glass_pane","blockRuntimeId":6902},{"id":"minecraft:pink_stained_glass_pane","blockRuntimeId":11856},{"id":"minecraft:ladder","blockRuntimeId":14086},{"id":"minecraft:scaffolding","blockRuntimeId":4703},{"id":"minecraft:smooth_stone_slab","blockRuntimeId":12914},{"id":"minecraft:normal_stone_slab","blockRuntimeId":5715},{"id":"minecraft:cobblestone_slab","blockRuntimeId":5704},{"id":"minecraft:mossy_cobblestone_slab","blockRuntimeId":5700},{"id":"minecraft:oak_slab","blockRuntimeId":5814},{"id":"minecraft:spruce_slab","blockRuntimeId":11750},{"id":"minecraft:birch_slab","blockRuntimeId":5351},{"id":"minecraft:jungle_slab","blockRuntimeId":5409},{"id":"minecraft:acacia_slab","blockRuntimeId":12937},{"id":"minecraft:dark_oak_slab","blockRuntimeId":1705},{"id":"minecraft:mangrove_slab","blockRuntimeId":3301},{"id":"minecraft:cherry_slab","blockRuntimeId":10737},{"id":"minecraft:bamboo_slab","blockRuntimeId":11072},{"id":"minecraft:bamboo_mosaic_slab","blockRuntimeId":3684},{"id":"minecraft:stone_brick_slab","blockRuntimeId":10799},{"id":"minecraft:mossy_stone_brick_slab","blockRuntimeId":1617},{"id":"minecraft:sandstone_slab","blockRuntimeId":1615},{"id":"minecraft:cut_sandstone_slab","blockRuntimeId":8104},{"id":"minecraft:smooth_sandstone_slab","blockRuntimeId":2320},{"id":"minecraft:red_sandstone_slab","blockRuntimeId":2906},{"id":"minecraft:cut_red_sandstone_slab","blockRuntimeId":12796},{"id":"minecraft:smooth_red_sandstone_slab","blockRuntimeId":11105},{"id":"minecraft:granite_slab","blockRuntimeId":6192},{"id":"minecraft:polished_granite_slab","blockRuntimeId":10781},{"id":"minecraft:diorite_slab","blockRuntimeId":1120},{"id":"minecraft:polished_diorite_slab","blockRuntimeId":6218},{"id":"minecraft:andesite_slab","blockRuntimeId":10880},{"id":"minecraft:polished_andesite_slab","blockRuntimeId":12782},{"id":"minecraft:brick_slab","blockRuntimeId":12907},{"id":"minecraft:nether_brick_slab","blockRuntimeId":10540},{"id":"minecraft:red_nether_brick_slab","blockRuntimeId":11149},{"id":"minecraft:end_stone_brick_slab","blockRuntimeId":1907},{"id":"minecraft:quartz_slab","blockRuntimeId":11215},{"id":"minecraft:smooth_quartz_slab","blockRuntimeId":12943},{"id":"minecraft:purpur_slab","blockRuntimeId":2488},{"id":"minecraft:prismarine_slab","blockRuntimeId":5821},{"id":"minecraft:dark_prismarine_slab","blockRuntimeId":9421},{"id":"minecraft:prismarine_brick_slab","blockRuntimeId":4856},{"id":"minecraft:crimson_slab","blockRuntimeId":9899},{"id":"minecraft:warped_slab","blockRuntimeId":11031},{"id":"minecraft:blackstone_slab","blockRuntimeId":2924},{"id":"minecraft:polished_blackstone_slab","blockRuntimeId":10484},{"id":"minecraft:polished_blackstone_brick_slab","blockRuntimeId":5713},{"id":"minecraft:cut_copper_slab","blockRuntimeId":7921},{"id":"minecraft:exposed_cut_copper_slab","blockRuntimeId":11168},{"id":"minecraft:weathered_cut_copper_slab","blockRuntimeId":10534},{"id":"minecraft:oxidized_cut_copper_slab","blockRuntimeId":7965},{"id":"minecraft:waxed_cut_copper_slab","blockRuntimeId":13283},{"id":"minecraft:waxed_exposed_cut_copper_slab","blockRuntimeId":1173},{"id":"minecraft:waxed_weathered_cut_copper_slab","blockRuntimeId":11116},{"id":"minecraft:waxed_oxidized_cut_copper_slab","blockRuntimeId":2537},{"id":"minecraft:cobbled_deepslate_slab","blockRuntimeId":12515},{"id":"minecraft:polished_deepslate_slab","blockRuntimeId":1244},{"id":"minecraft:deepslate_tile_slab","blockRuntimeId":5816},{"id":"minecraft:deepslate_brick_slab","blockRuntimeId":4838},{"id":"minecraft:mud_brick_slab","blockRuntimeId":5438},{"id":"minecraft:brick_block","blockRuntimeId":6850},{"id":"minecraft:chiseled_nether_bricks","blockRuntimeId":12449},{"id":"minecraft:cracked_nether_bricks","blockRuntimeId":6138},{"id":"minecraft:quartz_bricks","blockRuntimeId":10879},{"id":"minecraft:stone_bricks","blockRuntimeId":5625},{"id":"minecraft:mossy_stone_bricks","blockRuntimeId":4853},{"id":"minecraft:cracked_stone_bricks","blockRuntimeId":4748},{"id":"minecraft:chiseled_stone_bricks","blockRuntimeId":271},{"id":"minecraft:end_bricks","blockRuntimeId":1236},{"id":"minecraft:prismarine_bricks","blockRuntimeId":13508},{"id":"minecraft:polished_blackstone_bricks","blockRuntimeId":6721},{"id":"minecraft:cracked_polished_blackstone_bricks","blockRuntimeId":12396},{"id":"minecraft:gilded_blackstone","blockRuntimeId":6199},{"id":"minecraft:chiseled_polished_blackstone","blockRuntimeId":7748},{"id":"minecraft:deepslate_tiles","blockRuntimeId":6172},{"id":"minecraft:cracked_deepslate_tiles","blockRuntimeId":5667},{"id":"minecraft:deepslate_bricks","blockRuntimeId":8175},{"id":"minecraft:cracked_deepslate_bricks","blockRuntimeId":8045},{"id":"minecraft:chiseled_deepslate","blockRuntimeId":7920},{"id":"minecraft:cobblestone","blockRuntimeId":4751},{"id":"minecraft:mossy_cobblestone","blockRuntimeId":1176},{"id":"minecraft:cobbled_deepslate","blockRuntimeId":11214},{"id":"minecraft:smooth_stone","blockRuntimeId":6173},{"id":"minecraft:sandstone","blockRuntimeId":4784},{"id":"minecraft:chiseled_sandstone","blockRuntimeId":10505},{"id":"minecraft:cut_sandstone","blockRuntimeId":10088},{"id":"minecraft:smooth_sandstone","blockRuntimeId":893},{"id":"minecraft:red_sandstone","blockRuntimeId":11148},{"id":"minecraft:chiseled_red_sandstone","blockRuntimeId":12882},{"id":"minecraft:cut_red_sandstone","blockRuntimeId":5452},{"id":"minecraft:smooth_red_sandstone","blockRuntimeId":13137},{"id":"minecraft:coal_block","blockRuntimeId":8086},{"id":"minecraft:dried_kelp_block","blockRuntimeId":13449},{"id":"minecraft:gold_block","blockRuntimeId":1283},{"id":"minecraft:iron_block","blockRuntimeId":14085},{"id":"minecraft:copper_block","blockRuntimeId":6692},{"id":"minecraft:copper_door"},{"id":"minecraft:copper_trapdoor","blockRuntimeId":9423},{"id":"minecraft:copper_grate","blockRuntimeId":1622},{"id":"minecraft:exposed_copper","blockRuntimeId":1872},{"id":"minecraft:exposed_copper_door"},{"id":"minecraft:exposed_copper_trapdoor","blockRuntimeId":10590},{"id":"minecraft:exposed_copper_grate","blockRuntimeId":8066},{"id":"minecraft:weathered_copper","blockRuntimeId":13881},{"id":"minecraft:weathered_copper_door"},{"id":"minecraft:weathered_copper_trapdoor","blockRuntimeId":11089},{"id":"minecraft:weathered_copper_grate","blockRuntimeId":4849},{"id":"minecraft:oxidized_copper","blockRuntimeId":4684},{"id":"minecraft:oxidized_copper_door"},{"id":"minecraft:oxidized_copper_trapdoor","blockRuntimeId":6176},{"id":"minecraft:oxidized_copper_grate","blockRuntimeId":11188},{"id":"minecraft:waxed_copper","blockRuntimeId":13106},{"id":"minecraft:waxed_copper_door"},{"id":"minecraft:waxed_copper_trapdoor","blockRuntimeId":4668},{"id":"minecraft:waxed_copper_grate","blockRuntimeId":8067},{"id":"minecraft:waxed_exposed_copper","blockRuntimeId":2513},{"id":"minecraft:waxed_exposed_copper_door"},{"id":"minecraft:waxed_exposed_copper_trapdoor","blockRuntimeId":9855},{"id":"minecraft:waxed_exposed_copper_grate","blockRuntimeId":1869},{"id":"minecraft:waxed_weathered_copper","blockRuntimeId":2535},{"id":"minecraft:waxed_weathered_copper_door"},{"id":"minecraft:waxed_weathered_copper_trapdoor","blockRuntimeId":6852},{"id":"minecraft:waxed_weathered_copper_grate","blockRuntimeId":5891},{"id":"minecraft:waxed_oxidized_copper","blockRuntimeId":12909},{"id":"minecraft:waxed_oxidized_copper_door"},{"id":"minecraft:waxed_oxidized_copper_trapdoor","blockRuntimeId":7356},{"id":"minecraft:waxed_oxidized_copper_grate","blockRuntimeId":13690},{"id":"minecraft:cut_copper","blockRuntimeId":6730},{"id":"minecraft:exposed_cut_copper","blockRuntimeId":10702},{"id":"minecraft:weathered_cut_copper","blockRuntimeId":12377},{"id":"minecraft:oxidized_cut_copper","blockRuntimeId":8191},{"id":"minecraft:waxed_cut_copper","blockRuntimeId":12513},{"id":"minecraft:waxed_exposed_cut_copper","blockRuntimeId":4950},{"id":"minecraft:waxed_weathered_cut_copper","blockRuntimeId":7346},{"id":"minecraft:waxed_oxidized_cut_copper","blockRuntimeId":1118},{"id":"minecraft:chiseled_copper","blockRuntimeId":8758},{"id":"minecraft:exposed_chiseled_copper","blockRuntimeId":11151},{"id":"minecraft:weathered_chiseled_copper","blockRuntimeId":1551},{"id":"minecraft:oxidized_chiseled_copper","blockRuntimeId":7957},{"id":"minecraft:waxed_chiseled_copper","blockRuntimeId":13282},{"id":"minecraft:waxed_exposed_chiseled_copper","blockRuntimeId":1198},{"id":"minecraft:waxed_oxidized_chiseled_copper","blockRuntimeId":75},{"id":"minecraft:waxed_weathered_chiseled_copper","blockRuntimeId":4835},{"id":"minecraft:copper_bulb","blockRuntimeId":5967},{"id":"minecraft:exposed_copper_bulb","blockRuntimeId":10794},{"id":"minecraft:weathered_copper_bulb","blockRuntimeId":12928},{"id":"minecraft:oxidized_copper_bulb","blockRuntimeId":3126},{"id":"minecraft:waxed_copper_bulb","blockRuntimeId":1889},{"id":"minecraft:waxed_exposed_copper_bulb","blockRuntimeId":5709},{"id":"minecraft:waxed_weathered_copper_bulb","blockRuntimeId":9405},{"id":"minecraft:waxed_oxidized_copper_bulb","blockRuntimeId":10812},{"id":"minecraft:emerald_block","blockRuntimeId":3313},{"id":"minecraft:diamond_block","blockRuntimeId":1220},{"id":"minecraft:lapis_block","blockRuntimeId":5799},{"id":"minecraft:raw_iron_block","blockRuntimeId":14082},{"id":"minecraft:raw_copper_block","blockRuntimeId":7964},{"id":"minecraft:raw_gold_block","blockRuntimeId":1619},{"id":"minecraft:quartz_block","blockRuntimeId":4824},{"id":"minecraft:quartz_pillar","blockRuntimeId":4947},{"id":"minecraft:chiseled_quartz_block","blockRuntimeId":12519},{"id":"minecraft:smooth_quartz","blockRuntimeId":6123},{"id":"minecraft:prismarine","blockRuntimeId":10587},{"id":"minecraft:dark_prismarine","blockRuntimeId":5938},{"id":"minecraft:slime","blockRuntimeId":5759},{"id":"minecraft:honey_block","blockRuntimeId":2886},{"id":"minecraft:honeycomb_block","blockRuntimeId":6069},{"id":"minecraft:hay_block","blockRuntimeId":2520},{"id":"minecraft:bone_block","blockRuntimeId":5760},{"id":"minecraft:nether_brick","blockRuntimeId":12491},{"id":"minecraft:red_nether_brick","blockRuntimeId":722},{"id":"minecraft:netherite_block","blockRuntimeId":4912},{"id":"minecraft:lodestone","blockRuntimeId":14079},{"id":"minecraft:white_wool","blockRuntimeId":8087},{"id":"minecraft:light_gray_wool","blockRuntimeId":13499},{"id":"minecraft:gray_wool","blockRuntimeId":1138},{"id":"minecraft:black_wool","blockRuntimeId":1623},{"id":"minecraft:brown_wool","blockRuntimeId":1197},{"id":"minecraft:red_wool","blockRuntimeId":284},{"id":"minecraft:orange_wool","blockRuntimeId":2494},{"id":"minecraft:yellow_wool","blockRuntimeId":711},{"id":"minecraft:lime_wool","blockRuntimeId":10473},{"id":"minecraft:green_wool","blockRuntimeId":4854},{"id":"minecraft:cyan_wool","blockRuntimeId":8013},{"id":"minecraft:light_blue_wool","blockRuntimeId":11857},{"id":"minecraft:blue_wool","blockRuntimeId":8192},{"id":"minecraft:purple_wool","blockRuntimeId":14084},{"id":"minecraft:magenta_wool","blockRuntimeId":3130},{"id":"minecraft:pink_wool","blockRuntimeId":4913},{"id":"minecraft:white_carpet","blockRuntimeId":12479},{"id":"minecraft:light_gray_carpet","blockRuntimeId":14083},{"id":"minecraft:gray_carpet","blockRuntimeId":1141},{"id":"minecraft:black_carpet","blockRuntimeId":10515},{"id":"minecraft:brown_carpet","blockRuntimeId":2941},{"id":"minecraft:red_carpet","blockRuntimeId":12794},{"id":"minecraft:orange_carpet","blockRuntimeId":11814},{"id":"minecraft:yellow_carpet","blockRuntimeId":9922},{"id":"minecraft:lime_carpet","blockRuntimeId":11260},{"id":"minecraft:green_carpet","blockRuntimeId":4855},{"id":"minecraft:cyan_carpet","blockRuntimeId":4754},{"id":"minecraft:light_blue_carpet","blockRuntimeId":6908},{"id":"minecraft:blue_carpet","blockRuntimeId":920},{"id":"minecraft:purple_carpet","blockRuntimeId":13129},{"id":"minecraft:magenta_carpet","blockRuntimeId":1180},{"id":"minecraft:pink_carpet","blockRuntimeId":13074},{"id":"minecraft:white_concrete_powder","blockRuntimeId":7293},{"id":"minecraft:light_gray_concrete_powder","blockRuntimeId":12463},{"id":"minecraft:gray_concrete_powder","blockRuntimeId":12574},{"id":"minecraft:black_concrete_powder","blockRuntimeId":1661},{"id":"minecraft:brown_concrete_powder","blockRuntimeId":9470},{"id":"minecraft:red_concrete_powder","blockRuntimeId":12462},{"id":"minecraft:orange_concrete_powder","blockRuntimeId":13878},{"id":"minecraft:yellow_concrete_powder","blockRuntimeId":12945},{"id":"minecraft:lime_concrete_powder","blockRuntimeId":13453},{"id":"minecraft:green_concrete_powder","blockRuntimeId":11747},{"id":"minecraft:cyan_concrete_powder","blockRuntimeId":4657},{"id":"minecraft:light_blue_concrete_powder","blockRuntimeId":65},{"id":"minecraft:blue_concrete_powder","blockRuntimeId":11257},{"id":"minecraft:purple_concrete_powder","blockRuntimeId":11108},{"id":"minecraft:magenta_concrete_powder","blockRuntimeId":6036},{"id":"minecraft:pink_concrete_powder","blockRuntimeId":5818},{"id":"minecraft:white_concrete","blockRuntimeId":13697},{"id":"minecraft:light_gray_concrete","blockRuntimeId":2504},{"id":"minecraft:gray_concrete","blockRuntimeId":12577},{"id":"minecraft:black_concrete","blockRuntimeId":11241},{"id":"minecraft:brown_concrete","blockRuntimeId":10736},{"id":"minecraft:red_concrete","blockRuntimeId":13073},{"id":"minecraft:orange_concrete","blockRuntimeId":11258},{"id":"minecraft:yellow_concrete","blockRuntimeId":4753},{"id":"minecraft:lime_concrete","blockRuntimeId":6195},{"id":"minecraft:green_concrete","blockRuntimeId":9896},{"id":"minecraft:cyan_concrete","blockRuntimeId":12480},{"id":"minecraft:light_blue_concrete","blockRuntimeId":12815},{"id":"minecraft:blue_concrete","blockRuntimeId":12490},{"id":"minecraft:purple_concrete","blockRuntimeId":5708},{"id":"minecraft:magenta_concrete","blockRuntimeId":5937},{"id":"minecraft:pink_concrete","blockRuntimeId":4270},{"id":"minecraft:clay","blockRuntimeId":11911},{"id":"minecraft:hardened_clay","blockRuntimeId":1909},{"id":"minecraft:white_terracotta","blockRuntimeId":6904},{"id":"minecraft:light_gray_terracotta","blockRuntimeId":2311},{"id":"minecraft:gray_terracotta","blockRuntimeId":6175},{"id":"minecraft:black_terracotta","blockRuntimeId":10606},{"id":"minecraft:brown_terracotta","blockRuntimeId":13479},{"id":"minecraft:red_terracotta","blockRuntimeId":2962},{"id":"minecraft:orange_terracotta","blockRuntimeId":12906},{"id":"minecraft:yellow_terracotta","blockRuntimeId":5720},{"id":"minecraft:lime_terracotta","blockRuntimeId":14050},{"id":"minecraft:green_terracotta","blockRuntimeId":4837},{"id":"minecraft:cyan_terracotta"},{"id":"minecraft:light_blue_terracotta","blockRuntimeId":5931},{"id":"minecraft:blue_terracotta","blockRuntimeId":4783},{"id":"minecraft:purple_terracotta","blockRuntimeId":10920},{"id":"minecraft:magenta_terracotta","blockRuntimeId":6201},{"id":"minecraft:pink_terracotta","blockRuntimeId":5661},{"id":"minecraft:white_glazed_terracotta","blockRuntimeId":8611},{"id":"minecraft:silver_glazed_terracotta","blockRuntimeId":4256},{"id":"minecraft:gray_glazed_terracotta","blockRuntimeId":14067},{"id":"minecraft:black_glazed_terracotta","blockRuntimeId":9391},{"id":"minecraft:brown_glazed_terracotta","blockRuntimeId":4662},{"id":"minecraft:red_glazed_terracotta","blockRuntimeId":5678},{"id":"minecraft:orange_glazed_terracotta","blockRuntimeId":3303},{"id":"minecraft:yellow_glazed_terracotta","blockRuntimeId":2927},{"id":"minecraft:lime_glazed_terracotta","blockRuntimeId":1143},{"id":"minecraft:green_glazed_terracotta","blockRuntimeId":11178},{"id":"minecraft:cyan_glazed_terracotta","blockRuntimeId":8039},{"id":"minecraft:light_blue_glazed_terracotta","blockRuntimeId":8182},{"id":"minecraft:blue_glazed_terracotta","blockRuntimeId":8176},{"id":"minecraft:purple_glazed_terracotta","blockRuntimeId":11741},{"id":"minecraft:magenta_glazed_terracotta","blockRuntimeId":3131},{"id":"minecraft:pink_glazed_terracotta","blockRuntimeId":11109},{"id":"minecraft:purpur_block","blockRuntimeId":13089},{"id":"minecraft:purpur_pillar","blockRuntimeId":8723},{"id":"minecraft:packed_mud","blockRuntimeId":1239},{"id":"minecraft:mud_bricks","blockRuntimeId":11441},{"id":"minecraft:nether_wart_block","blockRuntimeId":5820},{"id":"minecraft:warped_wart_block","blockRuntimeId":9904},{"id":"minecraft:shroomlight","blockRuntimeId":7730},{"id":"minecraft:crimson_nylium","blockRuntimeId":5706},{"id":"minecraft:warped_nylium","blockRuntimeId":10875},{"id":"minecraft:netherrack","blockRuntimeId":11777},{"id":"minecraft:basalt","blockRuntimeId":5923},{"id":"minecraft:polished_basalt","blockRuntimeId":29},{"id":"minecraft:smooth_basalt","blockRuntimeId":3310},{"id":"minecraft:soul_soil","blockRuntimeId":8836},{"id":"minecraft:dirt","blockRuntimeId":8796},{"id":"minecraft:coarse_dirt","blockRuntimeId":6126},{"id":"minecraft:farmland","blockRuntimeId":5440},{"id":"minecraft:grass_block","blockRuntimeId":9960},{"id":"minecraft:grass_path","blockRuntimeId":13715},{"id":"minecraft:podzol","blockRuntimeId":6691},{"id":"minecraft:mycelium","blockRuntimeId":4811},{"id":"minecraft:mud","blockRuntimeId":11218},{"id":"minecraft:stone","blockRuntimeId":2319},{"id":"minecraft:iron_ore","blockRuntimeId":6731},{"id":"minecraft:gold_ore","blockRuntimeId":2926},{"id":"minecraft:diamond_ore","blockRuntimeId":5935},{"id":"minecraft:lapis_ore","blockRuntimeId":13072},{"id":"minecraft:redstone_ore","blockRuntimeId":5806},{"id":"minecraft:coal_ore","blockRuntimeId":5800},{"id":"minecraft:copper_ore","blockRuntimeId":4685},{"id":"minecraft:emerald_ore","blockRuntimeId":12557},{"id":"minecraft:quartz_ore","blockRuntimeId":6094},{"id":"minecraft:nether_gold_ore","blockRuntimeId":32},{"id":"minecraft:ancient_debris","blockRuntimeId":10608},{"id":"minecraft:deepslate_iron_ore","blockRuntimeId":12492},{"id":"minecraft:deepslate_gold_ore","blockRuntimeId":10607},{"id":"minecraft:deepslate_diamond_ore","blockRuntimeId":13482},{"id":"minecraft:deepslate_lapis_ore","blockRuntimeId":12464},{"id":"minecraft:deepslate_redstone_ore","blockRuntimeId":11185},{"id":"minecraft:deepslate_emerald_ore","blockRuntimeId":10876},{"id":"minecraft:deepslate_coal_ore","blockRuntimeId":12376},{"id":"minecraft:deepslate_copper_ore","blockRuntimeId":270},{"id":"minecraft:gravel","blockRuntimeId":14111},{"id":"minecraft:granite","blockRuntimeId":239},{"id":"minecraft:diorite","blockRuntimeId":306},{"id":"minecraft:andesite","blockRuntimeId":2317},{"id":"minecraft:blackstone","blockRuntimeId":12960},{"id":"minecraft:deepslate","blockRuntimeId":1177},{"id":"minecraft:polished_granite","blockRuntimeId":1673},{"id":"minecraft:polished_diorite","blockRuntimeId":9384},{"id":"minecraft:polished_andesite","blockRuntimeId":12916},{"id":"minecraft:polished_blackstone","blockRuntimeId":4810},{"id":"minecraft:polished_deepslate","blockRuntimeId":13134},{"id":"minecraft:sand","blockRuntimeId":5722},{"id":"minecraft:red_sand","blockRuntimeId":2519},{"id":"minecraft:cactus","blockRuntimeId":11549},{"id":"minecraft:oak_log","blockRuntimeId":1232},{"id":"minecraft:stripped_oak_log","blockRuntimeId":12911},{"id":"minecraft:spruce_log","blockRuntimeId":5796},{"id":"minecraft:stripped_spruce_log","blockRuntimeId":10809},{"id":"minecraft:birch_log","blockRuntimeId":2322},{"id":"minecraft:stripped_birch_log","blockRuntimeId":10052},{"id":"minecraft:jungle_log","blockRuntimeId":1128},{"id":"minecraft:stripped_jungle_log","blockRuntimeId":2294},{"id":"minecraft:acacia_log","blockRuntimeId":5904},{"id":"minecraft:stripped_acacia_log","blockRuntimeId":9439},{"id":"minecraft:dark_oak_log","blockRuntimeId":3686},{"id":"minecraft:stripped_dark_oak_log","blockRuntimeId":1122},{"id":"minecraft:mangrove_log","blockRuntimeId":1602},{"id":"minecraft:stripped_mangrove_log","blockRuntimeId":14108},{"id":"minecraft:cherry_log","blockRuntimeId":12468},{"id":"minecraft:stripped_cherry_log","blockRuntimeId":6019},{"id":"minecraft:crimson_stem","blockRuntimeId":9893},{"id":"minecraft:stripped_crimson_stem","blockRuntimeId":11511},{"id":"minecraft:warped_stem","blockRuntimeId":11033},{"id":"minecraft:stripped_warped_stem","blockRuntimeId":12771},{"id":"minecraft:oak_wood","blockRuntimeId":7352},{"id":"minecraft:stripped_oak_wood","blockRuntimeId":6905},{"id":"minecraft:spruce_wood","blockRuntimeId":12957},{"id":"minecraft:stripped_spruce_wood","blockRuntimeId":2491},{"id":"minecraft:birch_wood","blockRuntimeId":2516},{"id":"minecraft:stripped_birch_wood","blockRuntimeId":6135},{"id":"minecraft:jungle_wood","blockRuntimeId":2532},{"id":"minecraft:stripped_jungle_wood","blockRuntimeId":1597},{"id":"minecraft:acacia_wood","blockRuntimeId":11454},{"id":"minecraft:stripped_acacia_wood","blockRuntimeId":1217},{"id":"minecraft:dark_oak_wood","blockRuntimeId":10},{"id":"minecraft:stripped_dark_oak_wood","blockRuntimeId":7343},{"id":"minecraft:mangrove_wood","blockRuntimeId":5672},{"id":"minecraft:stripped_mangrove_wood","blockRuntimeId":5755},{"id":"minecraft:cherry_wood","blockRuntimeId":11902},{"id":"minecraft:stripped_cherry_wood","blockRuntimeId":7379},{"id":"minecraft:crimson_hyphae","blockRuntimeId":5863},{"id":"minecraft:stripped_crimson_hyphae","blockRuntimeId":11041},{"id":"minecraft:warped_hyphae","blockRuntimeId":9901},{"id":"minecraft:stripped_warped_hyphae","blockRuntimeId":8617},{"id":"minecraft:bamboo_block","blockRuntimeId":66},{"id":"minecraft:stripped_bamboo_block","blockRuntimeId":4729},{"id":"minecraft:oak_leaves","blockRuntimeId":2539},{"id":"minecraft:spruce_leaves","blockRuntimeId":5979},{"id":"minecraft:birch_leaves","blockRuntimeId":5657},{"id":"minecraft:jungle_leaves","blockRuntimeId":8026},{"id":"minecraft:acacia_leaves","blockRuntimeId":3507},{"id":"minecraft:dark_oak_leaves","blockRuntimeId":10536},{"id":"minecraft:mangrove_leaves","blockRuntimeId":11210},{"id":"minecraft:cherry_leaves","blockRuntimeId":9387},{"id":"minecraft:azalea_leaves","blockRuntimeId":13085},{"id":"minecraft:azalea_leaves_flowered","blockRuntimeId":10864},{"id":"minecraft:oak_sapling","blockRuntimeId":2307},{"id":"minecraft:spruce_sapling","blockRuntimeId":5718},{"id":"minecraft:birch_sapling","blockRuntimeId":12887},{"id":"minecraft:jungle_sapling","blockRuntimeId":10503},{"id":"minecraft:acacia_sapling","blockRuntimeId":10877},{"id":"minecraft:dark_oak_sapling","blockRuntimeId":1658},{"id":"minecraft:mangrove_propagule","blockRuntimeId":11539},{"id":"minecraft:cherry_sapling","blockRuntimeId":12466},{"id":"minecraft:bee_nest","blockRuntimeId":8799},{"id":"minecraft:wheat_seeds"},{"id":"minecraft:pumpkin_seeds"},{"id":"minecraft:melon_seeds"},{"id":"minecraft:beetroot_seeds"},{"id":"minecraft:torchflower_seeds"},{"id":"minecraft:pitcher_pod"},{"id":"minecraft:wheat"},{"id":"minecraft:beetroot"},{"id":"minecraft:potato"},{"id":"minecraft:poisonous_potato"},{"id":"minecraft:carrot"},{"id":"minecraft:golden_carrot"},{"id":"minecraft:apple"},{"id":"minecraft:golden_apple"},{"id":"minecraft:enchanted_golden_apple"},{"id":"minecraft:melon_block","blockRuntimeId":1660},{"id":"minecraft:melon_slice"},{"id":"minecraft:glistering_melon_slice"},{"id":"minecraft:sweet_berries"},{"id":"minecraft:glow_berries"},{"id":"minecraft:pumpkin","blockRuntimeId":6164},{"id":"minecraft:carved_pumpkin","blockRuntimeId":12749},{"id":"minecraft:lit_pumpkin","blockRuntimeId":11219},{"id":"minecraft:honeycomb"},{"id":"minecraft:fern","blockRuntimeId":10558},{"id":"minecraft:large_fern","blockRuntimeId":11039},{"id":"minecraft:short_grass","blockRuntimeId":11115},{"id":"minecraft:tall_grass","blockRuntimeId":11016},{"id":"minecraft:nether_sprouts"},{"id":"minecraft:fire_coral","blockRuntimeId":2318},{"id":"minecraft:brain_coral","blockRuntimeId":2490},{"id":"minecraft:bubble_coral","blockRuntimeId":10739},{"id":"minecraft:tube_coral","blockRuntimeId":13146},{"id":"minecraft:horn_coral","blockRuntimeId":4752},{"id":"minecraft:dead_fire_coral","blockRuntimeId":10798},{"id":"minecraft:dead_brain_coral","blockRuntimeId":12375},{"id":"minecraft:dead_bubble_coral","blockRuntimeId":12465},{"id":"minecraft:dead_tube_coral","blockRuntimeId":5819},{"id":"minecraft:dead_horn_coral","blockRuntimeId":9957},{"id":"minecraft:fire_coral_fan","blockRuntimeId":11118},{"id":"minecraft:brain_coral_fan","blockRuntimeId":2543},{"id":"minecraft:bubble_coral_fan","blockRuntimeId":1131},{"id":"minecraft:tube_coral_fan","blockRuntimeId":1171},{"id":"minecraft:horn_coral_fan","blockRuntimeId":11036},{"id":"minecraft:dead_fire_coral_fan","blockRuntimeId":11261},{"id":"minecraft:dead_brain_coral_fan","blockRuntimeId":1165},{"id":"minecraft:dead_bubble_coral_fan","blockRuntimeId":1126},{"id":"minecraft:dead_tube_coral_fan","blockRuntimeId":11247},{"id":"minecraft:dead_horn_coral_fan","blockRuntimeId":11815},{"id":"minecraft:crimson_roots","blockRuntimeId":12936},{"id":"minecraft:warped_roots","blockRuntimeId":5936},{"id":"minecraft:dandelion","blockRuntimeId":14149},{"id":"minecraft:poppy","blockRuntimeId":4850},{"id":"minecraft:blue_orchid","blockRuntimeId":5647},{"id":"minecraft:allium","blockRuntimeId":1620},{"id":"minecraft:azure_bluet","blockRuntimeId":720},{"id":"minecraft:red_tulip","blockRuntimeId":12816},{"id":"minecraft:orange_tulip","blockRuntimeId":11438},{"id":"minecraft:white_tulip","blockRuntimeId":6194},{"id":"minecraft:pink_tulip","blockRuntimeId":5813},{"id":"minecraft:oxeye_daisy","blockRuntimeId":12503},{"id":"minecraft:cornflower","blockRuntimeId":7747},{"id":"minecraft:lily_of_the_valley","blockRuntimeId":1142},{"id":"minecraft:sunflower","blockRuntimeId":6264},{"id":"minecraft:lilac","blockRuntimeId":12517},{"id":"minecraft:rose_bush","blockRuntimeId":8188},{"id":"minecraft:peony","blockRuntimeId":13194},{"id":"minecraft:pitcher_plant","blockRuntimeId":4890},{"id":"minecraft:pink_petals","blockRuntimeId":6266},{"id":"minecraft:wither_rose","blockRuntimeId":10699},{"id":"minecraft:torchflower","blockRuntimeId":10561},{"id":"minecraft:white_dye"},{"id":"minecraft:light_gray_dye"},{"id":"minecraft:gray_dye"},{"id":"minecraft:black_dye"},{"id":"minecraft:brown_dye"},{"id":"minecraft:red_dye"},{"id":"minecraft:orange_dye"},{"id":"minecraft:yellow_dye"},{"id":"minecraft:lime_dye"},{"id":"minecraft:green_dye"},{"id":"minecraft:cyan_dye"},{"id":"minecraft:light_blue_dye"},{"id":"minecraft:blue_dye"},{"id":"minecraft:purple_dye"},{"id":"minecraft:magenta_dye"},{"id":"minecraft:pink_dye"},{"id":"minecraft:ink_sac"},{"id":"minecraft:glow_ink_sac"},{"id":"minecraft:cocoa_beans"},{"id":"minecraft:lapis_lazuli"},{"id":"minecraft:bone_meal"},{"id":"minecraft:vine","blockRuntimeId":2890},{"id":"minecraft:weeping_vines","blockRuntimeId":8193},{"id":"minecraft:twisting_vines","blockRuntimeId":8732},{"id":"minecraft:waterlily","blockRuntimeId":3311},{"id":"minecraft:seagrass","blockRuntimeId":1168},{"id":"minecraft:kelp"},{"id":"minecraft:deadbush","blockRuntimeId":6718},{"id":"minecraft:bamboo","blockRuntimeId":4812},{"id":"minecraft:snow","blockRuntimeId":5721},{"id":"minecraft:ice","blockRuntimeId":11223},{"id":"minecraft:packed_ice","blockRuntimeId":1238},{"id":"minecraft:blue_ice","blockRuntimeId":11760},{"id":"minecraft:snow_layer","blockRuntimeId":894},{"id":"minecraft:pointed_dripstone","blockRuntimeId":12789},{"id":"minecraft:dripstone_block","blockRuntimeId":2889},{"id":"minecraft:moss_carpet","blockRuntimeId":1242},{"id":"minecraft:moss_block","blockRuntimeId":11107},{"id":"minecraft:dirt_with_roots","blockRuntimeId":8085},{"id":"minecraft:hanging_roots","blockRuntimeId":947},{"id":"minecraft:mangrove_roots","blockRuntimeId":10711},{"id":"minecraft:muddy_mangrove_roots","blockRuntimeId":1594},{"id":"minecraft:big_dripleaf","blockRuntimeId":10060},{"id":"minecraft:small_dripleaf_block","blockRuntimeId":5889},{"id":"minecraft:spore_blossom","blockRuntimeId":12522},{"id":"minecraft:azalea","blockRuntimeId":11440},{"id":"minecraft:flowering_azalea","blockRuntimeId":8190},{"id":"minecraft:glow_lichen","blockRuntimeId":8722},{"id":"minecraft:amethyst_block","blockRuntimeId":1278},{"id":"minecraft:budding_amethyst","blockRuntimeId":11565},{"id":"minecraft:amethyst_cluster","blockRuntimeId":13277},{"id":"minecraft:large_amethyst_bud","blockRuntimeId":6773},{"id":"minecraft:medium_amethyst_bud","blockRuntimeId":5950},{"id":"minecraft:small_amethyst_bud","blockRuntimeId":1553},{"id":"minecraft:tuff","blockRuntimeId":1601},{"id":"minecraft:tuff_stairs","blockRuntimeId":9914},{"id":"minecraft:tuff_slab","blockRuntimeId":1199},{"id":"minecraft:tuff_wall","blockRuntimeId":4076},{"id":"minecraft:chiseled_tuff","blockRuntimeId":13452},{"id":"minecraft:polished_tuff","blockRuntimeId":12429},{"id":"minecraft:polished_tuff_stairs","blockRuntimeId":12481},{"id":"minecraft:polished_tuff_slab","blockRuntimeId":309},{"id":"minecraft:polished_tuff_wall","blockRuntimeId":2547},{"id":"minecraft:tuff_bricks","blockRuntimeId":11209},{"id":"minecraft:tuff_brick_stairs","blockRuntimeId":10785},{"id":"minecraft:tuff_brick_slab","blockRuntimeId":4851},{"id":"minecraft:tuff_brick_wall","blockRuntimeId":2326},{"id":"minecraft:chiseled_tuff_bricks","blockRuntimeId":12814},{"id":"minecraft:calcite","blockRuntimeId":1119},{"id":"minecraft:chicken"},{"id":"minecraft:porkchop"},{"id":"minecraft:beef"},{"id":"minecraft:mutton"},{"id":"minecraft:rabbit"},{"id":"minecraft:cod"},{"id":"minecraft:salmon"},{"id":"minecraft:tropical_fish"},{"id":"minecraft:pufferfish"},{"id":"minecraft:brown_mushroom","blockRuntimeId":4656},{"id":"minecraft:red_mushroom","blockRuntimeId":6198},{"id":"minecraft:crimson_fungus","blockRuntimeId":13132},{"id":"minecraft:warped_fungus","blockRuntimeId":1243},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":12572},{"id":"minecraft:red_mushroom_block","blockRuntimeId":4746},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":12573},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":12558},{"id":"minecraft:egg"},{"id":"minecraft:sugar_cane"},{"id":"minecraft:sugar"},{"id":"minecraft:rotten_flesh"},{"id":"minecraft:bone"},{"id":"minecraft:web","blockRuntimeId":11246},{"id":"minecraft:spider_eye"},{"id":"minecraft:mob_spawner","blockRuntimeId":1672},{"id":"minecraft:trial_spawner","blockRuntimeId":13702},{"id":"minecraft:vault","blockRuntimeId":10609},{"id":"minecraft:end_portal_frame","blockRuntimeId":10563},{"id":"minecraft:infested_stone","blockRuntimeId":10562},{"id":"minecraft:infested_cobblestone","blockRuntimeId":5664},{"id":"minecraft:infested_stone_bricks","blockRuntimeId":7924},{"id":"minecraft:infested_mossy_stone_bricks","blockRuntimeId":2717},{"id":"minecraft:infested_cracked_stone_bricks","blockRuntimeId":2536},{"id":"minecraft:infested_chiseled_stone_bricks","blockRuntimeId":5811},{"id":"minecraft:infested_deepslate","blockRuntimeId":6682},{"id":"minecraft:dragon_egg","blockRuntimeId":12489},{"id":"minecraft:turtle_egg","blockRuntimeId":13454},{"id":"minecraft:sniffer_egg","blockRuntimeId":11566},{"id":"minecraft:frog_spawn","blockRuntimeId":5983},{"id":"minecraft:pearlescent_froglight","blockRuntimeId":11001},{"id":"minecraft:verdant_froglight","blockRuntimeId":11027},{"id":"minecraft:ochre_froglight","blockRuntimeId":4073},{"id":"minecraft:chicken_spawn_egg"},{"id":"minecraft:bee_spawn_egg"},{"id":"minecraft:cow_spawn_egg"},{"id":"minecraft:pig_spawn_egg"},{"id":"minecraft:sheep_spawn_egg"},{"id":"minecraft:wolf_spawn_egg"},{"id":"minecraft:polar_bear_spawn_egg"},{"id":"minecraft:ocelot_spawn_egg"},{"id":"minecraft:cat_spawn_egg"},{"id":"minecraft:mooshroom_spawn_egg"},{"id":"minecraft:bat_spawn_egg"},{"id":"minecraft:parrot_spawn_egg"},{"id":"minecraft:rabbit_spawn_egg"},{"id":"minecraft:llama_spawn_egg"},{"id":"minecraft:horse_spawn_egg"},{"id":"minecraft:donkey_spawn_egg"},{"id":"minecraft:mule_spawn_egg"},{"id":"minecraft:skeleton_horse_spawn_egg"},{"id":"minecraft:zombie_horse_spawn_egg"},{"id":"minecraft:tropical_fish_spawn_egg"},{"id":"minecraft:cod_spawn_egg"},{"id":"minecraft:pufferfish_spawn_egg"},{"id":"minecraft:salmon_spawn_egg"},{"id":"minecraft:dolphin_spawn_egg"},{"id":"minecraft:turtle_spawn_egg"},{"id":"minecraft:panda_spawn_egg"},{"id":"minecraft:fox_spawn_egg"},{"id":"minecraft:creeper_spawn_egg"},{"id":"minecraft:enderman_spawn_egg"},{"id":"minecraft:silverfish_spawn_egg"},{"id":"minecraft:skeleton_spawn_egg"},{"id":"minecraft:wither_skeleton_spawn_egg"},{"id":"minecraft:stray_spawn_egg"},{"id":"minecraft:slime_spawn_egg"},{"id":"minecraft:spider_spawn_egg"},{"id":"minecraft:zombie_spawn_egg"},{"id":"minecraft:zombie_pigman_spawn_egg"},{"id":"minecraft:husk_spawn_egg"},{"id":"minecraft:drowned_spawn_egg"},{"id":"minecraft:squid_spawn_egg"},{"id":"minecraft:glow_squid_spawn_egg"},{"id":"minecraft:cave_spider_spawn_egg"},{"id":"minecraft:witch_spawn_egg"},{"id":"minecraft:guardian_spawn_egg"},{"id":"minecraft:elder_guardian_spawn_egg"},{"id":"minecraft:endermite_spawn_egg"},{"id":"minecraft:magma_cube_spawn_egg"},{"id":"minecraft:strider_spawn_egg"},{"id":"minecraft:hoglin_spawn_egg"},{"id":"minecraft:piglin_spawn_egg"},{"id":"minecraft:zoglin_spawn_egg"},{"id":"minecraft:piglin_brute_spawn_egg"},{"id":"minecraft:goat_spawn_egg"},{"id":"minecraft:axolotl_spawn_egg"},{"id":"minecraft:warden_spawn_egg"},{"id":"minecraft:allay_spawn_egg"},{"id":"minecraft:frog_spawn_egg"},{"id":"minecraft:tadpole_spawn_egg"},{"id":"minecraft:trader_llama_spawn_egg"},{"id":"minecraft:camel_spawn_egg"},{"id":"minecraft:ghast_spawn_egg"},{"id":"minecraft:blaze_spawn_egg"},{"id":"minecraft:shulker_spawn_egg"},{"id":"minecraft:vindicator_spawn_egg"},{"id":"minecraft:evoker_spawn_egg"},{"id":"minecraft:vex_spawn_egg"},{"id":"minecraft:villager_spawn_egg"},{"id":"minecraft:wandering_trader_spawn_egg"},{"id":"minecraft:zombie_villager_spawn_egg"},{"id":"minecraft:phantom_spawn_egg"},{"id":"minecraft:pillager_spawn_egg"},{"id":"minecraft:ravager_spawn_egg"},{"id":"minecraft:iron_golem_spawn_egg"},{"id":"minecraft:snow_golem_spawn_egg"},{"id":"minecraft:sniffer_spawn_egg"},{"id":"minecraft:breeze_spawn_egg"},{"id":"minecraft:armadillo_spawn_egg"},{"id":"minecraft:bogged_spawn_egg"},{"id":"minecraft:obsidian","blockRuntimeId":1703},{"id":"minecraft:crying_obsidian","blockRuntimeId":11259},{"id":"minecraft:bedrock","blockRuntimeId":11748},{"id":"minecraft:soul_sand","blockRuntimeId":8837},{"id":"minecraft:magma","blockRuntimeId":13466},{"id":"minecraft:nether_wart"},{"id":"minecraft:end_stone","blockRuntimeId":5358},{"id":"minecraft:chorus_flower","blockRuntimeId":6127},{"id":"minecraft:chorus_plant","blockRuntimeId":8219},{"id":"minecraft:chorus_fruit"},{"id":"minecraft:popped_chorus_fruit"},{"id":"minecraft:sponge","blockRuntimeId":1893},{"id":"minecraft:wet_sponge","blockRuntimeId":76},{"id":"minecraft:tube_coral_block","blockRuntimeId":12881},{"id":"minecraft:brain_coral_block","blockRuntimeId":8641},{"id":"minecraft:bubble_coral_block","blockRuntimeId":5810},{"id":"minecraft:fire_coral_block","blockRuntimeId":6035},{"id":"minecraft:horn_coral_block","blockRuntimeId":7967},{"id":"minecraft:dead_tube_coral_block","blockRuntimeId":5357},{"id":"minecraft:dead_brain_coral_block","blockRuntimeId":11186},{"id":"minecraft:dead_bubble_coral_block","blockRuntimeId":2950},{"id":"minecraft:dead_fire_coral_block","blockRuntimeId":3125},{"id":"minecraft:dead_horn_coral_block","blockRuntimeId":11026},{"id":"minecraft:sculk","blockRuntimeId":11775},{"id":"minecraft:sculk_vein","blockRuntimeId":12311},{"id":"minecraft:sculk_catalyst","blockRuntimeId":4749},{"id":"minecraft:sculk_shrieker","blockRuntimeId":1134},{"id":"minecraft:sculk_sensor","blockRuntimeId":5964},{"id":"minecraft:calibrated_sculk_sensor","blockRuntimeId":9409},{"id":"minecraft:reinforced_deepslate","blockRuntimeId":9385},{"id":"minecraft:leather_helmet"},{"id":"minecraft:chainmail_helmet"},{"id":"minecraft:iron_helmet"},{"id":"minecraft:golden_helmet"},{"id":"minecraft:diamond_helmet"},{"id":"minecraft:netherite_helmet"},{"id":"minecraft:leather_chestplate"},{"id":"minecraft:chainmail_chestplate"},{"id":"minecraft:iron_chestplate"},{"id":"minecraft:golden_chestplate"},{"id":"minecraft:diamond_chestplate"},{"id":"minecraft:netherite_chestplate"},{"id":"minecraft:leather_leggings"},{"id":"minecraft:chainmail_leggings"},{"id":"minecraft:iron_leggings"},{"id":"minecraft:golden_leggings"},{"id":"minecraft:diamond_leggings"},{"id":"minecraft:netherite_leggings"},{"id":"minecraft:leather_boots"},{"id":"minecraft:chainmail_boots"},{"id":"minecraft:iron_boots"},{"id":"minecraft:golden_boots"},{"id":"minecraft:diamond_boots"},{"id":"minecraft:netherite_boots"},{"id":"minecraft:wooden_sword"},{"id":"minecraft:stone_sword"},{"id":"minecraft:iron_sword"},{"id":"minecraft:golden_sword"},{"id":"minecraft:diamond_sword"},{"id":"minecraft:netherite_sword"},{"id":"minecraft:wooden_axe"},{"id":"minecraft:stone_axe"},{"id":"minecraft:iron_axe"},{"id":"minecraft:golden_axe"},{"id":"minecraft:diamond_axe"},{"id":"minecraft:netherite_axe"},{"id":"minecraft:wooden_pickaxe"},{"id":"minecraft:stone_pickaxe"},{"id":"minecraft:iron_pickaxe"},{"id":"minecraft:golden_pickaxe"},{"id":"minecraft:diamond_pickaxe"},{"id":"minecraft:netherite_pickaxe"},{"id":"minecraft:wooden_shovel"},{"id":"minecraft:stone_shovel"},{"id":"minecraft:iron_shovel"},{"id":"minecraft:golden_shovel"},{"id":"minecraft:diamond_shovel"},{"id":"minecraft:netherite_shovel"},{"id":"minecraft:wooden_hoe"},{"id":"minecraft:stone_hoe"},{"id":"minecraft:iron_hoe"},{"id":"minecraft:golden_hoe"},{"id":"minecraft:diamond_hoe"},{"id":"minecraft:netherite_hoe"},{"id":"minecraft:bow"},{"id":"minecraft:crossbow"},{"id":"minecraft:mace"},{"id":"minecraft:arrow"},{"id":"minecraft:arrow","damage":6},{"id":"minecraft:arrow","damage":7},{"id":"minecraft:arrow","damage":8},{"id":"minecraft:arrow","damage":9},{"id":"minecraft:arrow","damage":10},{"id":"minecraft:arrow","damage":11},{"id":"minecraft:arrow","damage":12},{"id":"minecraft:arrow","damage":13},{"id":"minecraft:arrow","damage":14},{"id":"minecraft:arrow","damage":15},{"id":"minecraft:arrow","damage":16},{"id":"minecraft:arrow","damage":17},{"id":"minecraft:arrow","damage":18},{"id":"minecraft:arrow","damage":19},{"id":"minecraft:arrow","damage":20},{"id":"minecraft:arrow","damage":21},{"id":"minecraft:arrow","damage":22},{"id":"minecraft:arrow","damage":23},{"id":"minecraft:arrow","damage":24},{"id":"minecraft:arrow","damage":25},{"id":"minecraft:arrow","damage":26},{"id":"minecraft:arrow","damage":27},{"id":"minecraft:arrow","damage":28},{"id":"minecraft:arrow","damage":29},{"id":"minecraft:arrow","damage":30},{"id":"minecraft:arrow","damage":31},{"id":"minecraft:arrow","damage":32},{"id":"minecraft:arrow","damage":33},{"id":"minecraft:arrow","damage":34},{"id":"minecraft:arrow","damage":35},{"id":"minecraft:arrow","damage":36},{"id":"minecraft:arrow","damage":37},{"id":"minecraft:arrow","damage":38},{"id":"minecraft:arrow","damage":39},{"id":"minecraft:arrow","damage":40},{"id":"minecraft:arrow","damage":41},{"id":"minecraft:arrow","damage":42},{"id":"minecraft:arrow","damage":43},{"id":"minecraft:arrow","damage":44},{"id":"minecraft:arrow","damage":45},{"id":"minecraft:arrow","damage":46},{"id":"minecraft:arrow","damage":47},{"id":"minecraft:ominous_bottle"},{"id":"minecraft:ominous_bottle","damage":1},{"id":"minecraft:ominous_bottle","damage":2},{"id":"minecraft:ominous_bottle","damage":3},{"id":"minecraft:ominous_bottle","damage":4},{"id":"minecraft:shield"},{"id":"minecraft:cooked_chicken"},{"id":"minecraft:cooked_porkchop"},{"id":"minecraft:cooked_beef"},{"id":"minecraft:cooked_mutton"},{"id":"minecraft:cooked_rabbit"},{"id":"minecraft:cooked_cod"},{"id":"minecraft:cooked_salmon"},{"id":"minecraft:bread"},{"id":"minecraft:mushroom_stew"},{"id":"minecraft:beetroot_soup"},{"id":"minecraft:rabbit_stew"},{"id":"minecraft:baked_potato"},{"id":"minecraft:cookie"},{"id":"minecraft:pumpkin_pie"},{"id":"minecraft:cake"},{"id":"minecraft:dried_kelp"},{"id":"minecraft:fishing_rod"},{"id":"minecraft:carrot_on_a_stick"},{"id":"minecraft:warped_fungus_on_a_stick"},{"id":"minecraft:snowball"},{"id":"minecraft:wind_charge"},{"id":"minecraft:shears"},{"id":"minecraft:flint_and_steel"},{"id":"minecraft:lead"},{"id":"minecraft:clock"},{"id":"minecraft:compass"},{"id":"minecraft:recovery_compass"},{"id":"minecraft:goat_horn"},{"id":"minecraft:goat_horn","damage":1},{"id":"minecraft:goat_horn","damage":2},{"id":"minecraft:goat_horn","damage":3},{"id":"minecraft:goat_horn","damage":4},{"id":"minecraft:goat_horn","damage":5},{"id":"minecraft:goat_horn","damage":6},{"id":"minecraft:goat_horn","damage":7},{"id":"minecraft:empty_map"},{"id":"minecraft:empty_map","damage":2},{"id":"minecraft:saddle"},{"id":"minecraft:leather_horse_armor"},{"id":"minecraft:iron_horse_armor"},{"id":"minecraft:golden_horse_armor"},{"id":"minecraft:diamond_horse_armor"},{"id":"minecraft:wolf_armor"},{"id":"minecraft:trident"},{"id":"minecraft:turtle_helmet"},{"id":"minecraft:elytra"},{"id":"minecraft:totem_of_undying"},{"id":"minecraft:glass_bottle"},{"id":"minecraft:experience_bottle"},{"id":"minecraft:potion"},{"id":"minecraft:potion","damage":1},{"id":"minecraft:potion","damage":2},{"id":"minecraft:potion","damage":3},{"id":"minecraft:potion","damage":4},{"id":"minecraft:potion","damage":5},{"id":"minecraft:potion","damage":6},{"id":"minecraft:potion","damage":7},{"id":"minecraft:potion","damage":8},{"id":"minecraft:potion","damage":9},{"id":"minecraft:potion","damage":10},{"id":"minecraft:potion","damage":11},{"id":"minecraft:potion","damage":12},{"id":"minecraft:potion","damage":13},{"id":"minecraft:potion","damage":14},{"id":"minecraft:potion","damage":15},{"id":"minecraft:potion","damage":16},{"id":"minecraft:potion","damage":17},{"id":"minecraft:potion","damage":18},{"id":"minecraft:potion","damage":19},{"id":"minecraft:potion","damage":20},{"id":"minecraft:potion","damage":21},{"id":"minecraft:potion","damage":22},{"id":"minecraft:potion","damage":23},{"id":"minecraft:potion","damage":24},{"id":"minecraft:potion","damage":25},{"id":"minecraft:potion","damage":26},{"id":"minecraft:potion","damage":27},{"id":"minecraft:potion","damage":28},{"id":"minecraft:potion","damage":29},{"id":"minecraft:potion","damage":30},{"id":"minecraft:potion","damage":31},{"id":"minecraft:potion","damage":32},{"id":"minecraft:potion","damage":33},{"id":"minecraft:potion","damage":34},{"id":"minecraft:potion","damage":35},{"id":"minecraft:potion","damage":36},{"id":"minecraft:potion","damage":37},{"id":"minecraft:potion","damage":38},{"id":"minecraft:potion","damage":39},{"id":"minecraft:potion","damage":40},{"id":"minecraft:potion","damage":41},{"id":"minecraft:potion","damage":42},{"id":"minecraft:potion","damage":43},{"id":"minecraft:potion","damage":44},{"id":"minecraft:potion","damage":45},{"id":"minecraft:potion","damage":46},{"id":"minecraft:splash_potion"},{"id":"minecraft:splash_potion","damage":1},{"id":"minecraft:splash_potion","damage":2},{"id":"minecraft:splash_potion","damage":3},{"id":"minecraft:splash_potion","damage":4},{"id":"minecraft:splash_potion","damage":5},{"id":"minecraft:splash_potion","damage":6},{"id":"minecraft:splash_potion","damage":7},{"id":"minecraft:splash_potion","damage":8},{"id":"minecraft:splash_potion","damage":9},{"id":"minecraft:splash_potion","damage":10},{"id":"minecraft:splash_potion","damage":11},{"id":"minecraft:splash_potion","damage":12},{"id":"minecraft:splash_potion","damage":13},{"id":"minecraft:splash_potion","damage":14},{"id":"minecraft:splash_potion","damage":15},{"id":"minecraft:splash_potion","damage":16},{"id":"minecraft:splash_potion","damage":17},{"id":"minecraft:splash_potion","damage":18},{"id":"minecraft:splash_potion","damage":19},{"id":"minecraft:splash_potion","damage":20},{"id":"minecraft:splash_potion","damage":21},{"id":"minecraft:splash_potion","damage":22},{"id":"minecraft:splash_potion","damage":23},{"id":"minecraft:splash_potion","damage":24},{"id":"minecraft:splash_potion","damage":25},{"id":"minecraft:splash_potion","damage":26},{"id":"minecraft:splash_potion","damage":27},{"id":"minecraft:splash_potion","damage":28},{"id":"minecraft:splash_potion","damage":29},{"id":"minecraft:splash_potion","damage":30},{"id":"minecraft:splash_potion","damage":31},{"id":"minecraft:splash_potion","damage":32},{"id":"minecraft:splash_potion","damage":33},{"id":"minecraft:splash_potion","damage":34},{"id":"minecraft:splash_potion","damage":35},{"id":"minecraft:splash_potion","damage":36},{"id":"minecraft:splash_potion","damage":37},{"id":"minecraft:splash_potion","damage":38},{"id":"minecraft:splash_potion","damage":39},{"id":"minecraft:splash_potion","damage":40},{"id":"minecraft:splash_potion","damage":41},{"id":"minecraft:splash_potion","damage":42},{"id":"minecraft:splash_potion","damage":43},{"id":"minecraft:splash_potion","damage":44},{"id":"minecraft:splash_potion","damage":45},{"id":"minecraft:splash_potion","damage":46},{"id":"minecraft:lingering_potion"},{"id":"minecraft:lingering_potion","damage":1},{"id":"minecraft:lingering_potion","damage":2},{"id":"minecraft:lingering_potion","damage":3},{"id":"minecraft:lingering_potion","damage":4},{"id":"minecraft:lingering_potion","damage":5},{"id":"minecraft:lingering_potion","damage":6},{"id":"minecraft:lingering_potion","damage":7},{"id":"minecraft:lingering_potion","damage":8},{"id":"minecraft:lingering_potion","damage":9},{"id":"minecraft:lingering_potion","damage":10},{"id":"minecraft:lingering_potion","damage":11},{"id":"minecraft:lingering_potion","damage":12},{"id":"minecraft:lingering_potion","damage":13},{"id":"minecraft:lingering_potion","damage":14},{"id":"minecraft:lingering_potion","damage":15},{"id":"minecraft:lingering_potion","damage":16},{"id":"minecraft:lingering_potion","damage":17},{"id":"minecraft:lingering_potion","damage":18},{"id":"minecraft:lingering_potion","damage":19},{"id":"minecraft:lingering_potion","damage":20},{"id":"minecraft:lingering_potion","damage":21},{"id":"minecraft:lingering_potion","damage":22},{"id":"minecraft:lingering_potion","damage":23},{"id":"minecraft:lingering_potion","damage":24},{"id":"minecraft:lingering_potion","damage":25},{"id":"minecraft:lingering_potion","damage":26},{"id":"minecraft:lingering_potion","damage":27},{"id":"minecraft:lingering_potion","damage":28},{"id":"minecraft:lingering_potion","damage":29},{"id":"minecraft:lingering_potion","damage":30},{"id":"minecraft:lingering_potion","damage":31},{"id":"minecraft:lingering_potion","damage":32},{"id":"minecraft:lingering_potion","damage":33},{"id":"minecraft:lingering_potion","damage":34},{"id":"minecraft:lingering_potion","damage":35},{"id":"minecraft:lingering_potion","damage":36},{"id":"minecraft:lingering_potion","damage":37},{"id":"minecraft:lingering_potion","damage":38},{"id":"minecraft:lingering_potion","damage":39},{"id":"minecraft:lingering_potion","damage":40},{"id":"minecraft:lingering_potion","damage":41},{"id":"minecraft:lingering_potion","damage":42},{"id":"minecraft:lingering_potion","damage":43},{"id":"minecraft:lingering_potion","damage":44},{"id":"minecraft:lingering_potion","damage":45},{"id":"minecraft:lingering_potion","damage":46},{"id":"minecraft:spyglass"},{"id":"minecraft:brush"},{"id":"minecraft:stick"},{"id":"minecraft:bed"},{"id":"minecraft:bed","damage":8},{"id":"minecraft:bed","damage":7},{"id":"minecraft:bed","damage":15},{"id":"minecraft:bed","damage":12},{"id":"minecraft:bed","damage":14},{"id":"minecraft:bed","damage":1},{"id":"minecraft:bed","damage":4},{"id":"minecraft:bed","damage":5},{"id":"minecraft:bed","damage":13},{"id":"minecraft:bed","damage":9},{"id":"minecraft:bed","damage":3},{"id":"minecraft:bed","damage":11},{"id":"minecraft:bed","damage":10},{"id":"minecraft:bed","damage":2},{"id":"minecraft:bed","damage":6},{"id":"minecraft:torch","blockRuntimeId":2718},{"id":"minecraft:soul_torch","blockRuntimeId":6685},{"id":"minecraft:sea_pickle","blockRuntimeId":9446},{"id":"minecraft:lantern","blockRuntimeId":11817},{"id":"minecraft:soul_lantern","blockRuntimeId":8794},{"id":"minecraft:candle","blockRuntimeId":12774},{"id":"minecraft:white_candle","blockRuntimeId":7984},{"id":"minecraft:orange_candle","blockRuntimeId":1624},{"id":"minecraft:magenta_candle","blockRuntimeId":1690},{"id":"minecraft:light_blue_candle","blockRuntimeId":6155},{"id":"minecraft:yellow_candle","blockRuntimeId":10712},{"id":"minecraft:lime_candle","blockRuntimeId":10898},{"id":"minecraft:pink_candle","blockRuntimeId":12578},{"id":"minecraft:gray_candle","blockRuntimeId":2951},{"id":"minecraft:light_gray_candle","blockRuntimeId":10741},{"id":"minecraft:cyan_candle","blockRuntimeId":13098},{"id":"minecraft:purple_candle","blockRuntimeId":11778},{"id":"minecraft:blue_candle","blockRuntimeId":2},{"id":"minecraft:brown_candle","blockRuntimeId":9871},{"id":"minecraft:green_candle","blockRuntimeId":2505},{"id":"minecraft:red_candle","blockRuntimeId":6722},{"id":"minecraft:black_candle","blockRuntimeId":912},{"id":"minecraft:crafting_table","blockRuntimeId":9445},{"id":"minecraft:cartography_table","blockRuntimeId":14112},{"id":"minecraft:fletching_table","blockRuntimeId":9386},{"id":"minecraft:smithing_table","blockRuntimeId":4848},{"id":"minecraft:beehive","blockRuntimeId":10641},{"id":"minecraft:suspicious_sand","blockRuntimeId":3315},{"id":"minecraft:suspicious_gravel","blockRuntimeId":6811},{"id":"minecraft:campfire"},{"id":"minecraft:soul_campfire"},{"id":"minecraft:furnace","blockRuntimeId":13273},{"id":"minecraft:blast_furnace","blockRuntimeId":12934},{"id":"minecraft:smoker","blockRuntimeId":2314},{"id":"minecraft:respawn_anchor","blockRuntimeId":2499},{"id":"minecraft:brewing_stand"},{"id":"minecraft:anvil","blockRuntimeId":11189},{"id":"minecraft:chipped_anvil","blockRuntimeId":6768},{"id":"minecraft:damaged_anvil","blockRuntimeId":13691},{"id":"minecraft:grindstone","blockRuntimeId":13483},{"id":"minecraft:enchanting_table","blockRuntimeId":11269},{"id":"minecraft:bookshelf","blockRuntimeId":11217},{"id":"minecraft:chiseled_bookshelf","blockRuntimeId":1286},{"id":"minecraft:lectern","blockRuntimeId":11503},{"id":"minecraft:cauldron"},{"id":"minecraft:composter","blockRuntimeId":8106},{"id":"minecraft:chest","blockRuntimeId":11900},{"id":"minecraft:trapped_chest","blockRuntimeId":8623},{"id":"minecraft:ender_chest","blockRuntimeId":5947},{"id":"minecraft:barrel","blockRuntimeId":6111},{"id":"minecraft:undyed_shulker_box","blockRuntimeId":4809},{"id":"minecraft:white_shulker_box","blockRuntimeId":1621},{"id":"minecraft:light_gray_shulker_box","blockRuntimeId":9905},{"id":"minecraft:gray_shulker_box","blockRuntimeId":8030},{"id":"minecraft:black_shulker_box","blockRuntimeId":10502},{"id":"minecraft:brown_shulker_box","blockRuntimeId":11439},{"id":"minecraft:red_shulker_box","blockRuntimeId":6018},{"id":"minecraft:orange_shulker_box","blockRuntimeId":10740},{"id":"minecraft:yellow_shulker_box","blockRuntimeId":280},{"id":"minecraft:lime_shulker_box","blockRuntimeId":1550},{"id":"minecraft:green_shulker_box","blockRuntimeId":11038},{"id":"minecraft:cyan_shulker_box","blockRuntimeId":11761},{"id":"minecraft:light_blue_shulker_box","blockRuntimeId":11530},{"id":"minecraft:blue_shulker_box","blockRuntimeId":10793},{"id":"minecraft:purple_shulker_box","blockRuntimeId":12748},{"id":"minecraft:magenta_shulker_box","blockRuntimeId":1237},{"id":"minecraft:pink_shulker_box","blockRuntimeId":5955},{"id":"minecraft:armor_stand"},{"id":"minecraft:noteblock","blockRuntimeId":1600},{"id":"minecraft:jukebox","blockRuntimeId":7378},{"id":"minecraft:music_disc_13"},{"id":"minecraft:music_disc_cat"},{"id":"minecraft:music_disc_blocks"},{"id":"minecraft:music_disc_chirp"},{"id":"minecraft:music_disc_far"},{"id":"minecraft:music_disc_mall"},{"id":"minecraft:music_disc_mellohi"},{"id":"minecraft:music_disc_stal"},{"id":"minecraft:music_disc_strad"},{"id":"minecraft:music_disc_ward"},{"id":"minecraft:music_disc_11"},{"id":"minecraft:music_disc_wait"},{"id":"minecraft:music_disc_otherside"},{"id":"minecraft:music_disc_5"},{"id":"minecraft:music_disc_pigstep"},{"id":"minecraft:music_disc_relic"},{"id":"minecraft:music_disc_creator"},{"id":"minecraft:music_disc_creator_music_box"},{"id":"minecraft:music_disc_precipice"},{"id":"minecraft:disc_fragment_5"},{"id":"minecraft:glowstone_dust"},{"id":"minecraft:glowstone","blockRuntimeId":5412},{"id":"minecraft:redstone_lamp","blockRuntimeId":1175},{"id":"minecraft:sea_lantern","blockRuntimeId":12917},{"id":"minecraft:oak_sign"},{"id":"minecraft:spruce_sign"},{"id":"minecraft:birch_sign"},{"id":"minecraft:jungle_sign"},{"id":"minecraft:acacia_sign"},{"id":"minecraft:dark_oak_sign"},{"id":"minecraft:mangrove_sign"},{"id":"minecraft:cherry_sign"},{"id":"minecraft:bamboo_sign"},{"id":"minecraft:crimson_sign"},{"id":"minecraft:warped_sign"},{"id":"minecraft:oak_hanging_sign"},{"id":"minecraft:spruce_hanging_sign"},{"id":"minecraft:birch_hanging_sign"},{"id":"minecraft:jungle_hanging_sign"},{"id":"minecraft:acacia_hanging_sign"},{"id":"minecraft:dark_oak_hanging_sign"},{"id":"minecraft:mangrove_hanging_sign"},{"id":"minecraft:cherry_hanging_sign"},{"id":"minecraft:bamboo_hanging_sign"},{"id":"minecraft:crimson_hanging_sign"},{"id":"minecraft:warped_hanging_sign"},{"id":"minecraft:painting"},{"id":"minecraft:frame"},{"id":"minecraft:glow_frame"},{"id":"minecraft:honey_bottle"},{"id":"minecraft:flower_pot"},{"id":"minecraft:bowl"},{"id":"minecraft:bucket"},{"id":"minecraft:milk_bucket"},{"id":"minecraft:water_bucket"},{"id":"minecraft:lava_bucket"},{"id":"minecraft:cod_bucket"},{"id":"minecraft:salmon_bucket"},{"id":"minecraft:tropical_fish_bucket"},{"id":"minecraft:pufferfish_bucket"},{"id":"minecraft:powder_snow_bucket"},{"id":"minecraft:axolotl_bucket"},{"id":"minecraft:tadpole_bucket"},{"id":"minecraft:skull","damage":3},{"id":"minecraft:skull","damage":2},{"id":"minecraft:skull","damage":4},{"id":"minecraft:skull","damage":5},{"id":"minecraft:skull"},{"id":"minecraft:skull","damage":1},{"id":"minecraft:skull","damage":6},{"id":"minecraft:beacon","blockRuntimeId":721},{"id":"minecraft:bell","blockRuntimeId":11471},{"id":"minecraft:conduit","blockRuntimeId":5758},{"id":"minecraft:stonecutter_block","blockRuntimeId":12941},{"id":"minecraft:coal"},{"id":"minecraft:charcoal"},{"id":"minecraft:diamond"},{"id":"minecraft:iron_nugget"},{"id":"minecraft:raw_iron"},{"id":"minecraft:raw_gold"},{"id":"minecraft:raw_copper"},{"id":"minecraft:copper_ingot"},{"id":"minecraft:iron_ingot"},{"id":"minecraft:netherite_scrap"},{"id":"minecraft:netherite_ingot"},{"id":"minecraft:gold_nugget"},{"id":"minecraft:gold_ingot"},{"id":"minecraft:emerald"},{"id":"minecraft:quartz"},{"id":"minecraft:clay_ball"},{"id":"minecraft:brick"},{"id":"minecraft:netherbrick"},{"id":"minecraft:prismarine_shard"},{"id":"minecraft:amethyst_shard"},{"id":"minecraft:prismarine_crystals"},{"id":"minecraft:nautilus_shell"},{"id":"minecraft:heart_of_the_sea"},{"id":"minecraft:turtle_scute"},{"id":"minecraft:armadillo_scute"},{"id":"minecraft:phantom_membrane"},{"id":"minecraft:string"},{"id":"minecraft:feather"},{"id":"minecraft:flint"},{"id":"minecraft:gunpowder"},{"id":"minecraft:leather"},{"id":"minecraft:rabbit_hide"},{"id":"minecraft:rabbit_foot"},{"id":"minecraft:fire_charge"},{"id":"minecraft:blaze_rod"},{"id":"minecraft:breeze_rod"},{"id":"minecraft:heavy_core","blockRuntimeId":12514},{"id":"minecraft:blaze_powder"},{"id":"minecraft:magma_cream"},{"id":"minecraft:fermented_spider_eye"},{"id":"minecraft:echo_shard"},{"id":"minecraft:dragon_breath"},{"id":"minecraft:shulker_shell"},{"id":"minecraft:ghast_tear"},{"id":"minecraft:slime_ball"},{"id":"minecraft:ender_pearl"},{"id":"minecraft:ender_eye"},{"id":"minecraft:nether_star"},{"id":"minecraft:end_rod","blockRuntimeId":9887},{"id":"minecraft:lightning_rod","blockRuntimeId":3501},{"id":"minecraft:end_crystal"},{"id":"minecraft:paper"},{"id":"minecraft:book"},{"id":"minecraft:writable_book"},{"id":"minecraft:trial_key"},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAQAAAA="},{"id":"minecraft:oak_boat"},{"id":"minecraft:spruce_boat"},{"id":"minecraft:birch_boat"},{"id":"minecraft:jungle_boat"},{"id":"minecraft:acacia_boat"},{"id":"minecraft:dark_oak_boat"},{"id":"minecraft:mangrove_boat"},{"id":"minecraft:cherry_boat"},{"id":"minecraft:bamboo_raft"},{"id":"minecraft:oak_chest_boat"},{"id":"minecraft:spruce_chest_boat"},{"id":"minecraft:birch_chest_boat"},{"id":"minecraft:jungle_chest_boat"},{"id":"minecraft:acacia_chest_boat"},{"id":"minecraft:dark_oak_chest_boat"},{"id":"minecraft:mangrove_chest_boat"},{"id":"minecraft:cherry_chest_boat"},{"id":"minecraft:bamboo_chest_raft"},{"id":"minecraft:rail","blockRuntimeId":5453},{"id":"minecraft:golden_rail","blockRuntimeId":8001},{"id":"minecraft:detector_rail","blockRuntimeId":5635},{"id":"minecraft:activator_rail","blockRuntimeId":1558},{"id":"minecraft:minecart"},{"id":"minecraft:chest_minecart"},{"id":"minecraft:hopper_minecart"},{"id":"minecraft:tnt_minecart"},{"id":"minecraft:redstone"},{"id":"minecraft:redstone_block","blockRuntimeId":4914},{"id":"minecraft:redstone_torch","blockRuntimeId":4250},{"id":"minecraft:lever","blockRuntimeId":11056},{"id":"minecraft:wooden_button","blockRuntimeId":10922},{"id":"minecraft:spruce_button","blockRuntimeId":5892},{"id":"minecraft:birch_button","blockRuntimeId":13182},{"id":"minecraft:jungle_button","blockRuntimeId":285},{"id":"minecraft:acacia_button","blockRuntimeId":12432},{"id":"minecraft:dark_oak_button","blockRuntimeId":258},{"id":"minecraft:mangrove_button","blockRuntimeId":11802},{"id":"minecraft:cherry_button","blockRuntimeId":6234},{"id":"minecraft:bamboo_button","blockRuntimeId":11004},{"id":"minecraft:stone_button","blockRuntimeId":1875},{"id":"minecraft:crimson_button","blockRuntimeId":6022},{"id":"minecraft:warped_button","blockRuntimeId":12450},{"id":"minecraft:polished_blackstone_button","blockRuntimeId":13208},{"id":"minecraft:tripwire_hook","blockRuntimeId":9961},{"id":"minecraft:wooden_pressure_plate","blockRuntimeId":13509},{"id":"minecraft:spruce_pressure_plate","blockRuntimeId":4896},{"id":"minecraft:birch_pressure_plate","blockRuntimeId":4687},{"id":"minecraft:jungle_pressure_plate","blockRuntimeId":4765},{"id":"minecraft:acacia_pressure_plate","blockRuntimeId":7925},{"id":"minecraft:dark_oak_pressure_plate","blockRuntimeId":10003},{"id":"minecraft:mangrove_pressure_plate","blockRuntimeId":5393},{"id":"minecraft:cherry_pressure_plate","blockRuntimeId":311},{"id":"minecraft:bamboo_pressure_plate","blockRuntimeId":10571},{"id":"minecraft:crimson_pressure_plate","blockRuntimeId":14092},{"id":"minecraft:warped_pressure_plate","blockRuntimeId":1201},{"id":"minecraft:stone_pressure_plate","blockRuntimeId":5413},{"id":"minecraft:light_weighted_pressure_plate","blockRuntimeId":4793},{"id":"minecraft:heavy_weighted_pressure_plate","blockRuntimeId":3484},{"id":"minecraft:polished_blackstone_pressure_plate","blockRuntimeId":10749},{"id":"minecraft:observer","blockRuntimeId":4238},{"id":"minecraft:daylight_detector","blockRuntimeId":5723},{"id":"minecraft:repeater"},{"id":"minecraft:comparator"},{"id":"minecraft:hopper"},{"id":"minecraft:dropper","blockRuntimeId":12756},{"id":"minecraft:dispenser","blockRuntimeId":13470},{"id":"minecraft:crafter","blockRuntimeId":13220},{"id":"minecraft:piston","blockRuntimeId":2936},{"id":"minecraft:sticky_piston","blockRuntimeId":5940},{"id":"minecraft:tnt","blockRuntimeId":11242},{"id":"minecraft:name_tag"},{"id":"minecraft:loom","blockRuntimeId":5353},{"id":"minecraft:banner","nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":8,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":7,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":12,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":14,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":1,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":4,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":5,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":13,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":9,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":3,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":11,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":10,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":2,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":6,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQEAAAAA"},{"id":"minecraft:creeper_banner_pattern"},{"id":"minecraft:skull_banner_pattern"},{"id":"minecraft:flower_banner_pattern"},{"id":"minecraft:mojang_banner_pattern"},{"id":"minecraft:field_masoned_banner_pattern"},{"id":"minecraft:bordure_indented_banner_pattern"},{"id":"minecraft:piglin_banner_pattern"},{"id":"minecraft:globe_banner_pattern"},{"id":"minecraft:flow_banner_pattern"},{"id":"minecraft:guster_banner_pattern"},{"id":"minecraft:angler_pottery_sherd"},{"id":"minecraft:archer_pottery_sherd"},{"id":"minecraft:arms_up_pottery_sherd"},{"id":"minecraft:blade_pottery_sherd"},{"id":"minecraft:brewer_pottery_sherd"},{"id":"minecraft:burn_pottery_sherd"},{"id":"minecraft:danger_pottery_sherd"},{"id":"minecraft:explorer_pottery_sherd"},{"id":"minecraft:flow_pottery_sherd"},{"id":"minecraft:friend_pottery_sherd"},{"id":"minecraft:guster_pottery_sherd"},{"id":"minecraft:heart_pottery_sherd"},{"id":"minecraft:heartbreak_pottery_sherd"},{"id":"minecraft:howl_pottery_sherd"},{"id":"minecraft:miner_pottery_sherd"},{"id":"minecraft:mourner_pottery_sherd"},{"id":"minecraft:plenty_pottery_sherd"},{"id":"minecraft:prize_pottery_sherd"},{"id":"minecraft:scrape_pottery_sherd"},{"id":"minecraft:sheaf_pottery_sherd"},{"id":"minecraft:shelter_pottery_sherd"},{"id":"minecraft:skull_pottery_sherd"},{"id":"minecraft:snort_pottery_sherd"},{"id":"minecraft:netherite_upgrade_smithing_template"},{"id":"minecraft:sentry_armor_trim_smithing_template"},{"id":"minecraft:vex_armor_trim_smithing_template"},{"id":"minecraft:wild_armor_trim_smithing_template"},{"id":"minecraft:coast_armor_trim_smithing_template"},{"id":"minecraft:dune_armor_trim_smithing_template"},{"id":"minecraft:wayfinder_armor_trim_smithing_template"},{"id":"minecraft:shaper_armor_trim_smithing_template"},{"id":"minecraft:raiser_armor_trim_smithing_template"},{"id":"minecraft:host_armor_trim_smithing_template"},{"id":"minecraft:ward_armor_trim_smithing_template"},{"id":"minecraft:silence_armor_trim_smithing_template"},{"id":"minecraft:tide_armor_trim_smithing_template"},{"id":"minecraft:snout_armor_trim_smithing_template"},{"id":"minecraft:rib_armor_trim_smithing_template"},{"id":"minecraft:eye_armor_trim_smithing_template"},{"id":"minecraft:spire_armor_trim_smithing_template"},{"id":"minecraft:flow_armor_trim_smithing_template"},{"id":"minecraft:bolt_armor_trim_smithing_template"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_star","nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA="},{"id":"minecraft:firework_star","damage":8,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA="},{"id":"minecraft:firework_star","damage":7,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA="},{"id":"minecraft:firework_star","damage":15,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA="},{"id":"minecraft:firework_star","damage":12,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA="},{"id":"minecraft:firework_star","damage":14,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA="},{"id":"minecraft:firework_star","damage":1,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA="},{"id":"minecraft:firework_star","damage":4,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA="},{"id":"minecraft:firework_star","damage":5,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA="},{"id":"minecraft:firework_star","damage":13,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA="},{"id":"minecraft:firework_star","damage":9,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA="},{"id":"minecraft:firework_star","damage":3,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA="},{"id":"minecraft:firework_star","damage":11,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA="},{"id":"minecraft:firework_star","damage":10,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA="},{"id":"minecraft:firework_star","damage":2,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA="},{"id":"minecraft:firework_star","damage":6,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA="},{"id":"minecraft:chain"},{"id":"minecraft:target","blockRuntimeId":10921},{"id":"minecraft:decorated_pot","blockRuntimeId":11263},{"id":"minecraft:lodestone_compass"},{"id":"minecraft:wither_spawn_egg"},{"id":"minecraft:ender_dragon_spawn_egg"},{"id":"minecraft:ominous_trial_key"}]} \ No newline at end of file +{"items":[{"id":"minecraft:oak_planks","blockRuntimeId":12956},{"id":"minecraft:spruce_planks","blockRuntimeId":13314},{"id":"minecraft:birch_planks","blockRuntimeId":8003},{"id":"minecraft:jungle_planks","blockRuntimeId":11221},{"id":"minecraft:acacia_planks","blockRuntimeId":6043},{"id":"minecraft:dark_oak_planks","blockRuntimeId":4692},{"id":"minecraft:mangrove_planks","blockRuntimeId":2966},{"id":"minecraft:cherry_planks","blockRuntimeId":13179},{"id":"minecraft:bamboo_planks","blockRuntimeId":8667},{"id":"minecraft:bamboo_mosaic","blockRuntimeId":14126},{"id":"minecraft:crimson_planks","blockRuntimeId":7351},{"id":"minecraft:warped_planks","blockRuntimeId":2940},{"id":"minecraft:cobblestone_wall","blockRuntimeId":3518},{"id":"minecraft:mossy_cobblestone_wall","blockRuntimeId":3328},{"id":"minecraft:granite_wall","blockRuntimeId":8847},{"id":"minecraft:diorite_wall","blockRuntimeId":2969},{"id":"minecraft:andesite_wall","blockRuntimeId":8458},{"id":"minecraft:sandstone_wall","blockRuntimeId":13571},{"id":"minecraft:red_sandstone_wall","blockRuntimeId":954},{"id":"minecraft:stone_brick_wall","blockRuntimeId":8269},{"id":"minecraft:mossy_stone_brick_wall","blockRuntimeId":13928},{"id":"minecraft:brick_wall","blockRuntimeId":729},{"id":"minecraft:nether_brick_wall","blockRuntimeId":11616},{"id":"minecraft:red_nether_brick_wall","blockRuntimeId":12632},{"id":"minecraft:end_stone_brick_wall","blockRuntimeId":83},{"id":"minecraft:prismarine_wall","blockRuntimeId":7415},{"id":"minecraft:blackstone_wall","blockRuntimeId":5475},{"id":"minecraft:polished_blackstone_wall","blockRuntimeId":11307},{"id":"minecraft:polished_blackstone_brick_wall","blockRuntimeId":3145},{"id":"minecraft:cobbled_deepslate_wall","blockRuntimeId":13762},{"id":"minecraft:deepslate_tile_wall","blockRuntimeId":7766},{"id":"minecraft:polished_deepslate_wall","blockRuntimeId":13331},{"id":"minecraft:deepslate_brick_wall","blockRuntimeId":1713},{"id":"minecraft:tuff_wall","blockRuntimeId":4082},{"id":"minecraft:tuff_brick_wall","blockRuntimeId":2332},{"id":"minecraft:polished_tuff_wall","blockRuntimeId":2553},{"id":"minecraft:mud_brick_wall","blockRuntimeId":2730},{"id":"minecraft:oak_fence","blockRuntimeId":8802},{"id":"minecraft:spruce_fence","blockRuntimeId":1663},{"id":"minecraft:birch_fence","blockRuntimeId":13733},{"id":"minecraft:jungle_fence","blockRuntimeId":1654},{"id":"minecraft:acacia_fence","blockRuntimeId":13760},{"id":"minecraft:dark_oak_fence","blockRuntimeId":11902},{"id":"minecraft:mangrove_fence","blockRuntimeId":11224},{"id":"minecraft:cherry_fence","blockRuntimeId":2965},{"id":"minecraft:bamboo_fence","blockRuntimeId":1902},{"id":"minecraft:nether_brick_fence","blockRuntimeId":5821},{"id":"minecraft:crimson_fence","blockRuntimeId":13497},{"id":"minecraft:warped_fence","blockRuntimeId":9465},{"id":"minecraft:fence_gate","blockRuntimeId":247},{"id":"minecraft:spruce_fence_gate","blockRuntimeId":11189},{"id":"minecraft:birch_fence_gate","blockRuntimeId":4927},{"id":"minecraft:jungle_fence_gate","blockRuntimeId":8053},{"id":"minecraft:acacia_fence_gate","blockRuntimeId":13007},{"id":"minecraft:dark_oak_fence_gate","blockRuntimeId":5693},{"id":"minecraft:mangrove_fence_gate","blockRuntimeId":6257},{"id":"minecraft:cherry_fence_gate","blockRuntimeId":14097},{"id":"minecraft:bamboo_fence_gate","blockRuntimeId":7740},{"id":"minecraft:crimson_fence_gate","blockRuntimeId":6711},{"id":"minecraft:warped_fence_gate","blockRuntimeId":8091},{"id":"minecraft:normal_stone_stairs","blockRuntimeId":1903},{"id":"minecraft:stone_stairs","blockRuntimeId":4833},{"id":"minecraft:mossy_cobblestone_stairs","blockRuntimeId":5638},{"id":"minecraft:oak_stairs","blockRuntimeId":1229},{"id":"minecraft:spruce_stairs","blockRuntimeId":303},{"id":"minecraft:birch_stairs","blockRuntimeId":11608},{"id":"minecraft:jungle_stairs","blockRuntimeId":11568},{"id":"minecraft:acacia_stairs","blockRuntimeId":10757},{"id":"minecraft:dark_oak_stairs","blockRuntimeId":7758},{"id":"minecraft:mangrove_stairs","blockRuntimeId":6219},{"id":"minecraft:cherry_stairs","blockRuntimeId":11952},{"id":"minecraft:bamboo_stairs","blockRuntimeId":2715},{"id":"minecraft:bamboo_mosaic_stairs","blockRuntimeId":10765},{"id":"minecraft:stone_brick_stairs","blockRuntimeId":2948},{"id":"minecraft:mossy_stone_brick_stairs","blockRuntimeId":9900},{"id":"minecraft:sandstone_stairs","blockRuntimeId":4725},{"id":"minecraft:smooth_sandstone_stairs","blockRuntimeId":4763},{"id":"minecraft:red_sandstone_stairs","blockRuntimeId":8034},{"id":"minecraft:smooth_red_sandstone_stairs","blockRuntimeId":8431},{"id":"minecraft:granite_stairs","blockRuntimeId":4268},{"id":"minecraft:polished_granite_stairs","blockRuntimeId":5661},{"id":"minecraft:diorite_stairs","blockRuntimeId":5980},{"id":"minecraft:polished_diorite_stairs","blockRuntimeId":11286},{"id":"minecraft:andesite_stairs","blockRuntimeId":7995},{"id":"minecraft:polished_andesite_stairs","blockRuntimeId":11800},{"id":"minecraft:brick_stairs","blockRuntimeId":11112},{"id":"minecraft:nether_brick_stairs","blockRuntimeId":278},{"id":"minecraft:red_nether_brick_stairs","blockRuntimeId":11207},{"id":"minecraft:end_brick_stairs","blockRuntimeId":10949},{"id":"minecraft:quartz_stairs","blockRuntimeId":6877},{"id":"minecraft:smooth_quartz_stairs","blockRuntimeId":13121},{"id":"minecraft:purpur_stairs","blockRuntimeId":13184},{"id":"minecraft:prismarine_stairs","blockRuntimeId":12517},{"id":"minecraft:dark_prismarine_stairs","blockRuntimeId":12852},{"id":"minecraft:prismarine_bricks_stairs","blockRuntimeId":1116},{"id":"minecraft:crimson_stairs","blockRuntimeId":10838},{"id":"minecraft:warped_stairs","blockRuntimeId":4846},{"id":"minecraft:blackstone_stairs","blockRuntimeId":11789},{"id":"minecraft:polished_blackstone_stairs","blockRuntimeId":5875},{"id":"minecraft:polished_blackstone_brick_stairs","blockRuntimeId":6079},{"id":"minecraft:cobbled_deepslate_stairs","blockRuntimeId":891},{"id":"minecraft:deepslate_tile_stairs","blockRuntimeId":6703},{"id":"minecraft:polished_deepslate_stairs","blockRuntimeId":1548},{"id":"minecraft:deepslate_brick_stairs","blockRuntimeId":12844},{"id":"minecraft:tuff_stairs","blockRuntimeId":9935},{"id":"minecraft:polished_tuff_stairs","blockRuntimeId":12527},{"id":"minecraft:tuff_brick_stairs","blockRuntimeId":10822},{"id":"minecraft:mud_brick_stairs","blockRuntimeId":8245},{"id":"minecraft:cut_copper_stairs","blockRuntimeId":6230},{"id":"minecraft:exposed_cut_copper_stairs","blockRuntimeId":6211},{"id":"minecraft:weathered_cut_copper_stairs","blockRuntimeId":5883},{"id":"minecraft:oxidized_cut_copper_stairs","blockRuntimeId":1611},{"id":"minecraft:waxed_cut_copper_stairs","blockRuntimeId":1670},{"id":"minecraft:waxed_exposed_cut_copper_stairs","blockRuntimeId":5441},{"id":"minecraft:waxed_weathered_cut_copper_stairs","blockRuntimeId":10740},{"id":"minecraft:waxed_oxidized_cut_copper_stairs","blockRuntimeId":9412},{"id":"minecraft:wooden_door"},{"id":"minecraft:spruce_door"},{"id":"minecraft:birch_door"},{"id":"minecraft:jungle_door"},{"id":"minecraft:acacia_door"},{"id":"minecraft:dark_oak_door"},{"id":"minecraft:mangrove_door"},{"id":"minecraft:cherry_door"},{"id":"minecraft:bamboo_door"},{"id":"minecraft:iron_door"},{"id":"minecraft:crimson_door"},{"id":"minecraft:warped_door"},{"id":"minecraft:copper_door"},{"id":"minecraft:exposed_copper_door"},{"id":"minecraft:weathered_copper_door"},{"id":"minecraft:oxidized_copper_door"},{"id":"minecraft:waxed_copper_door"},{"id":"minecraft:waxed_exposed_copper_door"},{"id":"minecraft:waxed_weathered_copper_door"},{"id":"minecraft:waxed_oxidized_copper_door"},{"id":"minecraft:trapdoor","blockRuntimeId":1155},{"id":"minecraft:spruce_trapdoor","blockRuntimeId":11157},{"id":"minecraft:birch_trapdoor","blockRuntimeId":11230},{"id":"minecraft:jungle_trapdoor","blockRuntimeId":8072},{"id":"minecraft:acacia_trapdoor","blockRuntimeId":8634},{"id":"minecraft:dark_oak_trapdoor","blockRuntimeId":12935},{"id":"minecraft:mangrove_trapdoor","blockRuntimeId":6087},{"id":"minecraft:cherry_trapdoor","blockRuntimeId":2914},{"id":"minecraft:bamboo_trapdoor","blockRuntimeId":7950},{"id":"minecraft:iron_trapdoor","blockRuntimeId":1576},{"id":"minecraft:crimson_trapdoor","blockRuntimeId":5916},{"id":"minecraft:warped_trapdoor","blockRuntimeId":6827},{"id":"minecraft:copper_trapdoor","blockRuntimeId":9444},{"id":"minecraft:exposed_copper_trapdoor","blockRuntimeId":10611},{"id":"minecraft:weathered_copper_trapdoor","blockRuntimeId":11126},{"id":"minecraft:oxidized_copper_trapdoor","blockRuntimeId":6185},{"id":"minecraft:waxed_copper_trapdoor","blockRuntimeId":4674},{"id":"minecraft:waxed_exposed_copper_trapdoor","blockRuntimeId":9876},{"id":"minecraft:waxed_weathered_copper_trapdoor","blockRuntimeId":6861},{"id":"minecraft:waxed_oxidized_copper_trapdoor","blockRuntimeId":7365},{"id":"minecraft:iron_bars","blockRuntimeId":6912},{"id":"minecraft:glass","blockRuntimeId":10735},{"id":"minecraft:white_stained_glass","blockRuntimeId":7356},{"id":"minecraft:light_gray_stained_glass","blockRuntimeId":1708},{"id":"minecraft:gray_stained_glass","blockRuntimeId":4842},{"id":"minecraft:black_stained_glass","blockRuntimeId":9945},{"id":"minecraft:brown_stained_glass","blockRuntimeId":2322},{"id":"minecraft:red_stained_glass","blockRuntimeId":7932},{"id":"minecraft:orange_stained_glass","blockRuntimeId":6142},{"id":"minecraft:yellow_stained_glass","blockRuntimeId":12998},{"id":"minecraft:lime_stained_glass","blockRuntimeId":289},{"id":"minecraft:green_stained_glass","blockRuntimeId":5818},{"id":"minecraft:cyan_stained_glass","blockRuntimeId":9944},{"id":"minecraft:light_blue_stained_glass","blockRuntimeId":8844},{"id":"minecraft:blue_stained_glass","blockRuntimeId":8806},{"id":"minecraft:purple_stained_glass","blockRuntimeId":3506},{"id":"minecraft:magenta_stained_glass","blockRuntimeId":12476},{"id":"minecraft:pink_stained_glass","blockRuntimeId":5899},{"id":"minecraft:tinted_glass","blockRuntimeId":10076},{"id":"minecraft:glass_pane","blockRuntimeId":7928},{"id":"minecraft:white_stained_glass_pane","blockRuntimeId":6143},{"id":"minecraft:light_gray_stained_glass_pane","blockRuntimeId":1710},{"id":"minecraft:gray_stained_glass_pane","blockRuntimeId":11494},{"id":"minecraft:black_stained_glass_pane","blockRuntimeId":3517},{"id":"minecraft:brown_stained_glass_pane","blockRuntimeId":1241},{"id":"minecraft:red_stained_glass_pane","blockRuntimeId":11894},{"id":"minecraft:orange_stained_glass_pane","blockRuntimeId":1145},{"id":"minecraft:yellow_stained_glass_pane","blockRuntimeId":724},{"id":"minecraft:lime_stained_glass_pane","blockRuntimeId":12996},{"id":"minecraft:green_stained_glass_pane","blockRuntimeId":5660},{"id":"minecraft:cyan_stained_glass_pane","blockRuntimeId":11062},{"id":"minecraft:light_blue_stained_glass_pane","blockRuntimeId":5423},{"id":"minecraft:blue_stained_glass_pane","blockRuntimeId":246},{"id":"minecraft:purple_stained_glass_pane","blockRuntimeId":7364},{"id":"minecraft:magenta_stained_glass_pane","blockRuntimeId":6911},{"id":"minecraft:pink_stained_glass_pane","blockRuntimeId":11899},{"id":"minecraft:ladder","blockRuntimeId":14132},{"id":"minecraft:scaffolding","blockRuntimeId":4709},{"id":"minecraft:brick_block","blockRuntimeId":6859},{"id":"minecraft:smooth_stone_slab","blockRuntimeId":12960},{"id":"minecraft:normal_stone_slab","blockRuntimeId":5724},{"id":"minecraft:cobblestone_slab","blockRuntimeId":5713},{"id":"minecraft:mossy_cobblestone_slab","blockRuntimeId":5709},{"id":"minecraft:oak_slab","blockRuntimeId":5823},{"id":"minecraft:spruce_slab","blockRuntimeId":11787},{"id":"minecraft:birch_slab","blockRuntimeId":5363},{"id":"minecraft:jungle_slab","blockRuntimeId":5421},{"id":"minecraft:acacia_slab","blockRuntimeId":12983},{"id":"minecraft:dark_oak_slab","blockRuntimeId":1711},{"id":"minecraft:mangrove_slab","blockRuntimeId":3307},{"id":"minecraft:cherry_slab","blockRuntimeId":10774},{"id":"minecraft:bamboo_slab","blockRuntimeId":11109},{"id":"minecraft:bamboo_mosaic_slab","blockRuntimeId":3690},{"id":"minecraft:stone_brick_slab","blockRuntimeId":10836},{"id":"minecraft:mossy_stone_brick_slab","blockRuntimeId":1623},{"id":"minecraft:sandstone_slab","blockRuntimeId":1621},{"id":"minecraft:cut_sandstone_slab","blockRuntimeId":8107},{"id":"minecraft:smooth_sandstone_slab","blockRuntimeId":2326},{"id":"minecraft:red_sandstone_slab","blockRuntimeId":2912},{"id":"minecraft:cut_red_sandstone_slab","blockRuntimeId":12842},{"id":"minecraft:smooth_red_sandstone_slab","blockRuntimeId":11142},{"id":"minecraft:granite_slab","blockRuntimeId":6201},{"id":"minecraft:polished_granite_slab","blockRuntimeId":10818},{"id":"minecraft:diorite_slab","blockRuntimeId":1126},{"id":"minecraft:polished_diorite_slab","blockRuntimeId":6227},{"id":"minecraft:andesite_slab","blockRuntimeId":10917},{"id":"minecraft:polished_andesite_slab","blockRuntimeId":12828},{"id":"minecraft:brick_slab","blockRuntimeId":12953},{"id":"minecraft:nether_brick_slab","blockRuntimeId":10561},{"id":"minecraft:red_nether_brick_slab","blockRuntimeId":11186},{"id":"minecraft:end_stone_brick_slab","blockRuntimeId":1913},{"id":"minecraft:quartz_slab","blockRuntimeId":11252},{"id":"minecraft:smooth_quartz_slab","blockRuntimeId":12989},{"id":"minecraft:purpur_slab","blockRuntimeId":2494},{"id":"minecraft:prismarine_slab","blockRuntimeId":5830},{"id":"minecraft:dark_prismarine_slab","blockRuntimeId":9442},{"id":"minecraft:prismarine_brick_slab","blockRuntimeId":4868},{"id":"minecraft:crimson_slab","blockRuntimeId":9920},{"id":"minecraft:warped_slab","blockRuntimeId":11068},{"id":"minecraft:blackstone_slab","blockRuntimeId":2930},{"id":"minecraft:polished_blackstone_slab","blockRuntimeId":10505},{"id":"minecraft:polished_blackstone_brick_slab","blockRuntimeId":5722},{"id":"minecraft:cobbled_deepslate_slab","blockRuntimeId":12561},{"id":"minecraft:polished_deepslate_slab","blockRuntimeId":1250},{"id":"minecraft:deepslate_tile_slab","blockRuntimeId":5825},{"id":"minecraft:deepslate_brick_slab","blockRuntimeId":4844},{"id":"minecraft:tuff_slab","blockRuntimeId":1205},{"id":"minecraft:polished_tuff_slab","blockRuntimeId":315},{"id":"minecraft:tuff_brick_slab","blockRuntimeId":4863},{"id":"minecraft:mud_brick_slab","blockRuntimeId":5450},{"id":"minecraft:cut_copper_slab","blockRuntimeId":7930},{"id":"minecraft:exposed_cut_copper_slab","blockRuntimeId":11205},{"id":"minecraft:weathered_cut_copper_slab","blockRuntimeId":10555},{"id":"minecraft:oxidized_cut_copper_slab","blockRuntimeId":7974},{"id":"minecraft:waxed_cut_copper_slab","blockRuntimeId":13329},{"id":"minecraft:waxed_exposed_cut_copper_slab","blockRuntimeId":1179},{"id":"minecraft:waxed_weathered_cut_copper_slab","blockRuntimeId":11153},{"id":"minecraft:waxed_oxidized_cut_copper_slab","blockRuntimeId":2543},{"id":"minecraft:stone_bricks","blockRuntimeId":5637},{"id":"minecraft:mossy_stone_bricks","blockRuntimeId":4865},{"id":"minecraft:cracked_stone_bricks","blockRuntimeId":4754},{"id":"minecraft:chiseled_stone_bricks","blockRuntimeId":277},{"id":"minecraft:smooth_stone","blockRuntimeId":6182},{"id":"minecraft:end_bricks","blockRuntimeId":1242},{"id":"minecraft:polished_blackstone_bricks","blockRuntimeId":6730},{"id":"minecraft:cracked_polished_blackstone_bricks","blockRuntimeId":12436},{"id":"minecraft:gilded_blackstone","blockRuntimeId":6208},{"id":"minecraft:chiseled_polished_blackstone","blockRuntimeId":7757},{"id":"minecraft:deepslate_tiles","blockRuntimeId":6181},{"id":"minecraft:cracked_deepslate_tiles","blockRuntimeId":5679},{"id":"minecraft:deepslate_bricks","blockRuntimeId":8184},{"id":"minecraft:tuff_bricks","blockRuntimeId":11246},{"id":"minecraft:cracked_deepslate_bricks","blockRuntimeId":8048},{"id":"minecraft:chiseled_deepslate","blockRuntimeId":7929},{"id":"minecraft:chiseled_tuff","blockRuntimeId":13498},{"id":"minecraft:chiseled_tuff_bricks","blockRuntimeId":12860},{"id":"minecraft:cobblestone","blockRuntimeId":4757},{"id":"minecraft:mossy_cobblestone","blockRuntimeId":1182},{"id":"minecraft:cobbled_deepslate","blockRuntimeId":11251},{"id":"minecraft:sandstone","blockRuntimeId":4790},{"id":"minecraft:chiseled_sandstone","blockRuntimeId":10526},{"id":"minecraft:cut_sandstone","blockRuntimeId":10109},{"id":"minecraft:smooth_sandstone","blockRuntimeId":899},{"id":"minecraft:red_sandstone","blockRuntimeId":11185},{"id":"minecraft:chiseled_red_sandstone","blockRuntimeId":12928},{"id":"minecraft:cut_red_sandstone","blockRuntimeId":5464},{"id":"minecraft:smooth_red_sandstone","blockRuntimeId":13183},{"id":"minecraft:coal_block","blockRuntimeId":8089},{"id":"minecraft:dried_kelp_block","blockRuntimeId":13495},{"id":"minecraft:copper_block","blockRuntimeId":6701},{"id":"minecraft:weathered_copper","blockRuntimeId":13927},{"id":"minecraft:exposed_copper","blockRuntimeId":1878},{"id":"minecraft:oxidized_copper","blockRuntimeId":4690},{"id":"minecraft:waxed_copper","blockRuntimeId":13152},{"id":"minecraft:waxed_exposed_copper","blockRuntimeId":2519},{"id":"minecraft:waxed_weathered_copper","blockRuntimeId":2541},{"id":"minecraft:waxed_oxidized_copper","blockRuntimeId":12955},{"id":"minecraft:copper_grate","blockRuntimeId":1628},{"id":"minecraft:exposed_copper_grate","blockRuntimeId":8069},{"id":"minecraft:weathered_copper_grate","blockRuntimeId":4861},{"id":"minecraft:oxidized_copper_grate","blockRuntimeId":11225},{"id":"minecraft:waxed_copper_grate","blockRuntimeId":8070},{"id":"minecraft:waxed_exposed_copper_grate","blockRuntimeId":1875},{"id":"minecraft:waxed_weathered_copper_grate","blockRuntimeId":5900},{"id":"minecraft:waxed_oxidized_copper_grate","blockRuntimeId":13736},{"id":"minecraft:cut_copper","blockRuntimeId":6739},{"id":"minecraft:exposed_cut_copper","blockRuntimeId":10739},{"id":"minecraft:weathered_cut_copper","blockRuntimeId":12417},{"id":"minecraft:oxidized_cut_copper","blockRuntimeId":8200},{"id":"minecraft:waxed_cut_copper","blockRuntimeId":12559},{"id":"minecraft:waxed_exposed_cut_copper","blockRuntimeId":4962},{"id":"minecraft:waxed_weathered_cut_copper","blockRuntimeId":7355},{"id":"minecraft:waxed_oxidized_cut_copper","blockRuntimeId":1124},{"id":"minecraft:chiseled_copper","blockRuntimeId":8767},{"id":"minecraft:exposed_chiseled_copper","blockRuntimeId":11188},{"id":"minecraft:weathered_chiseled_copper","blockRuntimeId":1557},{"id":"minecraft:oxidized_chiseled_copper","blockRuntimeId":7966},{"id":"minecraft:waxed_chiseled_copper","blockRuntimeId":13328},{"id":"minecraft:waxed_exposed_chiseled_copper","blockRuntimeId":1204},{"id":"minecraft:waxed_oxidized_chiseled_copper","blockRuntimeId":81},{"id":"minecraft:waxed_weathered_chiseled_copper","blockRuntimeId":4841},{"id":"minecraft:copper_bulb","blockRuntimeId":5976},{"id":"minecraft:exposed_copper_bulb","blockRuntimeId":10831},{"id":"minecraft:weathered_copper_bulb","blockRuntimeId":12974},{"id":"minecraft:oxidized_copper_bulb","blockRuntimeId":3132},{"id":"minecraft:waxed_copper_bulb","blockRuntimeId":1895},{"id":"minecraft:waxed_exposed_copper_bulb","blockRuntimeId":5718},{"id":"minecraft:waxed_weathered_copper_bulb","blockRuntimeId":9420},{"id":"minecraft:waxed_oxidized_copper_bulb","blockRuntimeId":10849},{"id":"minecraft:iron_block","blockRuntimeId":14131},{"id":"minecraft:gold_block","blockRuntimeId":1289},{"id":"minecraft:emerald_block","blockRuntimeId":3319},{"id":"minecraft:diamond_block","blockRuntimeId":1226},{"id":"minecraft:lapis_block","blockRuntimeId":5808},{"id":"minecraft:raw_copper_block","blockRuntimeId":7973},{"id":"minecraft:raw_iron_block","blockRuntimeId":14128},{"id":"minecraft:raw_gold_block","blockRuntimeId":1625},{"id":"minecraft:quartz_block","blockRuntimeId":4830},{"id":"minecraft:quartz_bricks","blockRuntimeId":10916},{"id":"minecraft:quartz_pillar","blockRuntimeId":4959},{"id":"minecraft:chiseled_quartz_block","blockRuntimeId":12565},{"id":"minecraft:smooth_quartz","blockRuntimeId":6132},{"id":"minecraft:prismarine","blockRuntimeId":10608},{"id":"minecraft:prismarine_bricks","blockRuntimeId":13554},{"id":"minecraft:dark_prismarine","blockRuntimeId":5947},{"id":"minecraft:slime","blockRuntimeId":5768},{"id":"minecraft:honey_block","blockRuntimeId":2892},{"id":"minecraft:honeycomb_block","blockRuntimeId":6078},{"id":"minecraft:hay_block","blockRuntimeId":2526},{"id":"minecraft:bone_block","blockRuntimeId":5769},{"id":"minecraft:nether_brick","blockRuntimeId":12537},{"id":"minecraft:red_nether_brick","blockRuntimeId":728},{"id":"minecraft:chiseled_nether_bricks","blockRuntimeId":12495},{"id":"minecraft:cracked_nether_bricks","blockRuntimeId":6147},{"id":"minecraft:netherite_block","blockRuntimeId":4924},{"id":"minecraft:lodestone","blockRuntimeId":14125},{"id":"minecraft:white_wool","blockRuntimeId":8090},{"id":"minecraft:light_gray_wool","blockRuntimeId":13545},{"id":"minecraft:gray_wool","blockRuntimeId":1144},{"id":"minecraft:black_wool","blockRuntimeId":1629},{"id":"minecraft:brown_wool","blockRuntimeId":1203},{"id":"minecraft:red_wool","blockRuntimeId":290},{"id":"minecraft:orange_wool","blockRuntimeId":2500},{"id":"minecraft:yellow_wool","blockRuntimeId":717},{"id":"minecraft:lime_wool","blockRuntimeId":10494},{"id":"minecraft:green_wool","blockRuntimeId":4866},{"id":"minecraft:cyan_wool","blockRuntimeId":8016},{"id":"minecraft:light_blue_wool","blockRuntimeId":11900},{"id":"minecraft:blue_wool","blockRuntimeId":8201},{"id":"minecraft:purple_wool","blockRuntimeId":14130},{"id":"minecraft:magenta_wool","blockRuntimeId":3136},{"id":"minecraft:pink_wool","blockRuntimeId":4925},{"id":"minecraft:white_carpet","blockRuntimeId":12525},{"id":"minecraft:light_gray_carpet","blockRuntimeId":14129},{"id":"minecraft:gray_carpet","blockRuntimeId":1147},{"id":"minecraft:black_carpet","blockRuntimeId":10536},{"id":"minecraft:brown_carpet","blockRuntimeId":2947},{"id":"minecraft:red_carpet","blockRuntimeId":12840},{"id":"minecraft:orange_carpet","blockRuntimeId":11857},{"id":"minecraft:yellow_carpet","blockRuntimeId":9943},{"id":"minecraft:lime_carpet","blockRuntimeId":11297},{"id":"minecraft:green_carpet","blockRuntimeId":4867},{"id":"minecraft:cyan_carpet","blockRuntimeId":4760},{"id":"minecraft:light_blue_carpet","blockRuntimeId":6917},{"id":"minecraft:blue_carpet","blockRuntimeId":926},{"id":"minecraft:purple_carpet","blockRuntimeId":13175},{"id":"minecraft:magenta_carpet","blockRuntimeId":1186},{"id":"minecraft:pink_carpet","blockRuntimeId":13120},{"id":"minecraft:white_concrete_powder","blockRuntimeId":7302},{"id":"minecraft:light_gray_concrete_powder","blockRuntimeId":12509},{"id":"minecraft:gray_concrete_powder","blockRuntimeId":12620},{"id":"minecraft:black_concrete_powder","blockRuntimeId":1667},{"id":"minecraft:brown_concrete_powder","blockRuntimeId":9491},{"id":"minecraft:red_concrete_powder","blockRuntimeId":12508},{"id":"minecraft:orange_concrete_powder","blockRuntimeId":13924},{"id":"minecraft:yellow_concrete_powder","blockRuntimeId":12991},{"id":"minecraft:lime_concrete_powder","blockRuntimeId":13499},{"id":"minecraft:green_concrete_powder","blockRuntimeId":11784},{"id":"minecraft:cyan_concrete_powder","blockRuntimeId":4663},{"id":"minecraft:light_blue_concrete_powder","blockRuntimeId":71},{"id":"minecraft:blue_concrete_powder","blockRuntimeId":11294},{"id":"minecraft:purple_concrete_powder","blockRuntimeId":11145},{"id":"minecraft:magenta_concrete_powder","blockRuntimeId":6045},{"id":"minecraft:pink_concrete_powder","blockRuntimeId":5827},{"id":"minecraft:white_concrete","blockRuntimeId":13743},{"id":"minecraft:light_gray_concrete","blockRuntimeId":2510},{"id":"minecraft:gray_concrete","blockRuntimeId":12623},{"id":"minecraft:black_concrete","blockRuntimeId":11278},{"id":"minecraft:brown_concrete","blockRuntimeId":10773},{"id":"minecraft:red_concrete","blockRuntimeId":13119},{"id":"minecraft:orange_concrete","blockRuntimeId":11295},{"id":"minecraft:yellow_concrete","blockRuntimeId":4759},{"id":"minecraft:lime_concrete","blockRuntimeId":6204},{"id":"minecraft:green_concrete","blockRuntimeId":9917},{"id":"minecraft:cyan_concrete","blockRuntimeId":12526},{"id":"minecraft:light_blue_concrete","blockRuntimeId":12861},{"id":"minecraft:blue_concrete","blockRuntimeId":12536},{"id":"minecraft:purple_concrete","blockRuntimeId":5717},{"id":"minecraft:magenta_concrete","blockRuntimeId":5946},{"id":"minecraft:pink_concrete","blockRuntimeId":4276},{"id":"minecraft:hardened_clay","blockRuntimeId":1915},{"id":"minecraft:white_terracotta","blockRuntimeId":6913},{"id":"minecraft:light_gray_terracotta","blockRuntimeId":2317},{"id":"minecraft:gray_terracotta","blockRuntimeId":6184},{"id":"minecraft:black_terracotta","blockRuntimeId":10643},{"id":"minecraft:brown_terracotta","blockRuntimeId":13525},{"id":"minecraft:red_terracotta","blockRuntimeId":2968},{"id":"minecraft:orange_terracotta","blockRuntimeId":12952},{"id":"minecraft:yellow_terracotta","blockRuntimeId":5729},{"id":"minecraft:lime_terracotta","blockRuntimeId":14096},{"id":"minecraft:green_terracotta","blockRuntimeId":4843},{"id":"minecraft:cyan_terracotta"},{"id":"minecraft:light_blue_terracotta","blockRuntimeId":5940},{"id":"minecraft:blue_terracotta","blockRuntimeId":4789},{"id":"minecraft:purple_terracotta","blockRuntimeId":10957},{"id":"minecraft:magenta_terracotta","blockRuntimeId":6210},{"id":"minecraft:pink_terracotta","blockRuntimeId":5673},{"id":"minecraft:white_glazed_terracotta","blockRuntimeId":8620},{"id":"minecraft:silver_glazed_terracotta","blockRuntimeId":4262},{"id":"minecraft:gray_glazed_terracotta","blockRuntimeId":14113},{"id":"minecraft:black_glazed_terracotta","blockRuntimeId":9406},{"id":"minecraft:brown_glazed_terracotta","blockRuntimeId":4668},{"id":"minecraft:red_glazed_terracotta","blockRuntimeId":5687},{"id":"minecraft:orange_glazed_terracotta","blockRuntimeId":3309},{"id":"minecraft:yellow_glazed_terracotta","blockRuntimeId":2933},{"id":"minecraft:lime_glazed_terracotta","blockRuntimeId":1149},{"id":"minecraft:green_glazed_terracotta","blockRuntimeId":11215},{"id":"minecraft:cyan_glazed_terracotta","blockRuntimeId":8042},{"id":"minecraft:light_blue_glazed_terracotta","blockRuntimeId":8191},{"id":"minecraft:blue_glazed_terracotta","blockRuntimeId":8185},{"id":"minecraft:purple_glazed_terracotta","blockRuntimeId":11778},{"id":"minecraft:magenta_glazed_terracotta","blockRuntimeId":3137},{"id":"minecraft:pink_glazed_terracotta","blockRuntimeId":11146},{"id":"minecraft:purpur_block","blockRuntimeId":13135},{"id":"minecraft:purpur_pillar","blockRuntimeId":8732},{"id":"minecraft:packed_mud","blockRuntimeId":1245},{"id":"minecraft:mud_bricks","blockRuntimeId":11478},{"id":"minecraft:nether_wart_block","blockRuntimeId":5829},{"id":"minecraft:warped_wart_block","blockRuntimeId":9925},{"id":"minecraft:shroomlight","blockRuntimeId":7739},{"id":"minecraft:crimson_nylium","blockRuntimeId":5715},{"id":"minecraft:warped_nylium","blockRuntimeId":10912},{"id":"minecraft:netherrack","blockRuntimeId":11820},{"id":"minecraft:soul_soil","blockRuntimeId":8845},{"id":"minecraft:grass_block","blockRuntimeId":9981},{"id":"minecraft:podzol","blockRuntimeId":6700},{"id":"minecraft:mycelium","blockRuntimeId":4817},{"id":"minecraft:grass_path","blockRuntimeId":13761},{"id":"minecraft:dirt","blockRuntimeId":8805},{"id":"minecraft:coarse_dirt","blockRuntimeId":6135},{"id":"minecraft:dirt_with_roots","blockRuntimeId":8088},{"id":"minecraft:farmland","blockRuntimeId":5452},{"id":"minecraft:mud","blockRuntimeId":11255},{"id":"minecraft:clay","blockRuntimeId":11951},{"id":"minecraft:iron_ore","blockRuntimeId":6740},{"id":"minecraft:gold_ore","blockRuntimeId":2932},{"id":"minecraft:diamond_ore","blockRuntimeId":5944},{"id":"minecraft:lapis_ore","blockRuntimeId":13118},{"id":"minecraft:redstone_ore","blockRuntimeId":5815},{"id":"minecraft:coal_ore","blockRuntimeId":5809},{"id":"minecraft:copper_ore","blockRuntimeId":4691},{"id":"minecraft:emerald_ore","blockRuntimeId":12603},{"id":"minecraft:quartz_ore","blockRuntimeId":6103},{"id":"minecraft:nether_gold_ore","blockRuntimeId":32},{"id":"minecraft:ancient_debris","blockRuntimeId":10645},{"id":"minecraft:deepslate_iron_ore","blockRuntimeId":12538},{"id":"minecraft:deepslate_gold_ore","blockRuntimeId":10644},{"id":"minecraft:deepslate_diamond_ore","blockRuntimeId":13528},{"id":"minecraft:deepslate_lapis_ore","blockRuntimeId":12510},{"id":"minecraft:deepslate_redstone_ore","blockRuntimeId":11222},{"id":"minecraft:deepslate_emerald_ore","blockRuntimeId":10913},{"id":"minecraft:deepslate_coal_ore","blockRuntimeId":12416},{"id":"minecraft:deepslate_copper_ore","blockRuntimeId":276},{"id":"minecraft:stone","blockRuntimeId":2325},{"id":"minecraft:granite","blockRuntimeId":245},{"id":"minecraft:diorite","blockRuntimeId":312},{"id":"minecraft:andesite","blockRuntimeId":2323},{"id":"minecraft:blackstone","blockRuntimeId":13006},{"id":"minecraft:deepslate","blockRuntimeId":1183},{"id":"minecraft:tuff","blockRuntimeId":1607},{"id":"minecraft:basalt","blockRuntimeId":5932},{"id":"minecraft:polished_granite","blockRuntimeId":1679},{"id":"minecraft:polished_diorite","blockRuntimeId":9393},{"id":"minecraft:polished_andesite","blockRuntimeId":12962},{"id":"minecraft:polished_blackstone","blockRuntimeId":4816},{"id":"minecraft:polished_deepslate","blockRuntimeId":13180},{"id":"minecraft:polished_tuff","blockRuntimeId":12475},{"id":"minecraft:polished_basalt","blockRuntimeId":29},{"id":"minecraft:smooth_basalt","blockRuntimeId":3316},{"id":"minecraft:gravel","blockRuntimeId":14157},{"id":"minecraft:sand","blockRuntimeId":5731},{"id":"minecraft:red_sand","blockRuntimeId":2525},{"id":"minecraft:cactus","blockRuntimeId":11586},{"id":"minecraft:oak_log","blockRuntimeId":1238},{"id":"minecraft:stripped_oak_log","blockRuntimeId":12957},{"id":"minecraft:spruce_log","blockRuntimeId":5805},{"id":"minecraft:stripped_spruce_log","blockRuntimeId":10846},{"id":"minecraft:birch_log","blockRuntimeId":2328},{"id":"minecraft:stripped_birch_log","blockRuntimeId":10073},{"id":"minecraft:jungle_log","blockRuntimeId":1134},{"id":"minecraft:stripped_jungle_log","blockRuntimeId":2300},{"id":"minecraft:acacia_log","blockRuntimeId":5913},{"id":"minecraft:stripped_acacia_log","blockRuntimeId":9460},{"id":"minecraft:dark_oak_log","blockRuntimeId":3692},{"id":"minecraft:stripped_dark_oak_log","blockRuntimeId":1128},{"id":"minecraft:mangrove_log","blockRuntimeId":1608},{"id":"minecraft:stripped_mangrove_log","blockRuntimeId":14154},{"id":"minecraft:cherry_log","blockRuntimeId":12514},{"id":"minecraft:stripped_cherry_log","blockRuntimeId":6028},{"id":"minecraft:crimson_stem","blockRuntimeId":9914},{"id":"minecraft:stripped_crimson_stem","blockRuntimeId":11548},{"id":"minecraft:warped_stem","blockRuntimeId":11070},{"id":"minecraft:stripped_warped_stem","blockRuntimeId":12817},{"id":"minecraft:oak_wood","blockRuntimeId":7361},{"id":"minecraft:stripped_oak_wood","blockRuntimeId":6914},{"id":"minecraft:spruce_wood","blockRuntimeId":13003},{"id":"minecraft:stripped_spruce_wood","blockRuntimeId":2497},{"id":"minecraft:birch_wood","blockRuntimeId":2522},{"id":"minecraft:stripped_birch_wood","blockRuntimeId":6144},{"id":"minecraft:jungle_wood","blockRuntimeId":2538},{"id":"minecraft:stripped_jungle_wood","blockRuntimeId":1603},{"id":"minecraft:acacia_wood","blockRuntimeId":11491},{"id":"minecraft:stripped_acacia_wood","blockRuntimeId":1223},{"id":"minecraft:dark_oak_wood","blockRuntimeId":10},{"id":"minecraft:stripped_dark_oak_wood","blockRuntimeId":7352},{"id":"minecraft:mangrove_wood","blockRuntimeId":5684},{"id":"minecraft:stripped_mangrove_wood","blockRuntimeId":5764},{"id":"minecraft:cherry_wood","blockRuntimeId":11945},{"id":"minecraft:stripped_cherry_wood","blockRuntimeId":7388},{"id":"minecraft:crimson_hyphae","blockRuntimeId":5872},{"id":"minecraft:stripped_crimson_hyphae","blockRuntimeId":11078},{"id":"minecraft:warped_hyphae","blockRuntimeId":9922},{"id":"minecraft:stripped_warped_hyphae","blockRuntimeId":8626},{"id":"minecraft:bamboo_block","blockRuntimeId":72},{"id":"minecraft:stripped_bamboo_block","blockRuntimeId":4735},{"id":"minecraft:oak_leaves","blockRuntimeId":2545},{"id":"minecraft:spruce_leaves","blockRuntimeId":5988},{"id":"minecraft:birch_leaves","blockRuntimeId":5669},{"id":"minecraft:jungle_leaves","blockRuntimeId":8029},{"id":"minecraft:acacia_leaves","blockRuntimeId":3513},{"id":"minecraft:dark_oak_leaves","blockRuntimeId":10557},{"id":"minecraft:mangrove_leaves","blockRuntimeId":11247},{"id":"minecraft:cherry_leaves","blockRuntimeId":9396},{"id":"minecraft:azalea_leaves","blockRuntimeId":13131},{"id":"minecraft:azalea_leaves_flowered","blockRuntimeId":10901},{"id":"minecraft:oak_sapling","blockRuntimeId":2313},{"id":"minecraft:spruce_sapling","blockRuntimeId":5727},{"id":"minecraft:birch_sapling","blockRuntimeId":12933},{"id":"minecraft:jungle_sapling","blockRuntimeId":10524},{"id":"minecraft:acacia_sapling","blockRuntimeId":10914},{"id":"minecraft:dark_oak_sapling","blockRuntimeId":1664},{"id":"minecraft:mangrove_propagule","blockRuntimeId":11576},{"id":"minecraft:cherry_sapling","blockRuntimeId":12512},{"id":"minecraft:bee_nest","blockRuntimeId":8808},{"id":"minecraft:wheat_seeds"},{"id":"minecraft:pumpkin_seeds"},{"id":"minecraft:melon_seeds"},{"id":"minecraft:beetroot_seeds"},{"id":"minecraft:torchflower_seeds"},{"id":"minecraft:pitcher_pod"},{"id":"minecraft:wheat"},{"id":"minecraft:beetroot"},{"id":"minecraft:potato"},{"id":"minecraft:poisonous_potato"},{"id":"minecraft:carrot"},{"id":"minecraft:golden_carrot"},{"id":"minecraft:apple"},{"id":"minecraft:golden_apple"},{"id":"minecraft:enchanted_golden_apple"},{"id":"minecraft:melon_block","blockRuntimeId":1666},{"id":"minecraft:melon_slice"},{"id":"minecraft:glistering_melon_slice"},{"id":"minecraft:sweet_berries"},{"id":"minecraft:glow_berries"},{"id":"minecraft:pumpkin","blockRuntimeId":6173},{"id":"minecraft:carved_pumpkin","blockRuntimeId":12795},{"id":"minecraft:lit_pumpkin","blockRuntimeId":11256},{"id":"minecraft:honeycomb"},{"id":"minecraft:fern","blockRuntimeId":10579},{"id":"minecraft:large_fern","blockRuntimeId":11076},{"id":"minecraft:short_grass","blockRuntimeId":11152},{"id":"minecraft:tall_grass","blockRuntimeId":11053},{"id":"minecraft:nether_sprouts"},{"id":"minecraft:fire_coral","blockRuntimeId":2324},{"id":"minecraft:brain_coral","blockRuntimeId":2496},{"id":"minecraft:bubble_coral","blockRuntimeId":10776},{"id":"minecraft:tube_coral","blockRuntimeId":13192},{"id":"minecraft:horn_coral","blockRuntimeId":4758},{"id":"minecraft:dead_fire_coral","blockRuntimeId":10835},{"id":"minecraft:dead_brain_coral","blockRuntimeId":12415},{"id":"minecraft:dead_bubble_coral","blockRuntimeId":12511},{"id":"minecraft:dead_tube_coral","blockRuntimeId":5828},{"id":"minecraft:dead_horn_coral","blockRuntimeId":9978},{"id":"minecraft:fire_coral_fan","blockRuntimeId":11155},{"id":"minecraft:brain_coral_fan","blockRuntimeId":2549},{"id":"minecraft:bubble_coral_fan","blockRuntimeId":1137},{"id":"minecraft:tube_coral_fan","blockRuntimeId":1177},{"id":"minecraft:horn_coral_fan","blockRuntimeId":11073},{"id":"minecraft:dead_fire_coral_fan","blockRuntimeId":11298},{"id":"minecraft:dead_brain_coral_fan","blockRuntimeId":1171},{"id":"minecraft:dead_bubble_coral_fan","blockRuntimeId":1132},{"id":"minecraft:dead_tube_coral_fan","blockRuntimeId":11284},{"id":"minecraft:dead_horn_coral_fan","blockRuntimeId":11858},{"id":"minecraft:crimson_roots","blockRuntimeId":12982},{"id":"minecraft:warped_roots","blockRuntimeId":5945},{"id":"minecraft:dandelion","blockRuntimeId":14195},{"id":"minecraft:poppy","blockRuntimeId":4862},{"id":"minecraft:blue_orchid","blockRuntimeId":5659},{"id":"minecraft:allium","blockRuntimeId":1626},{"id":"minecraft:azure_bluet","blockRuntimeId":726},{"id":"minecraft:red_tulip","blockRuntimeId":12862},{"id":"minecraft:orange_tulip","blockRuntimeId":11475},{"id":"minecraft:white_tulip","blockRuntimeId":6203},{"id":"minecraft:pink_tulip","blockRuntimeId":5822},{"id":"minecraft:oxeye_daisy","blockRuntimeId":12549},{"id":"minecraft:cornflower","blockRuntimeId":7756},{"id":"minecraft:lily_of_the_valley","blockRuntimeId":1148},{"id":"minecraft:sunflower","blockRuntimeId":6273},{"id":"minecraft:lilac","blockRuntimeId":12563},{"id":"minecraft:rose_bush","blockRuntimeId":8197},{"id":"minecraft:peony","blockRuntimeId":13240},{"id":"minecraft:pitcher_plant","blockRuntimeId":4902},{"id":"minecraft:pink_petals","blockRuntimeId":6275},{"id":"minecraft:wither_rose","blockRuntimeId":10736},{"id":"minecraft:torchflower","blockRuntimeId":10582},{"id":"minecraft:white_dye"},{"id":"minecraft:light_gray_dye"},{"id":"minecraft:gray_dye"},{"id":"minecraft:black_dye"},{"id":"minecraft:brown_dye"},{"id":"minecraft:red_dye"},{"id":"minecraft:orange_dye"},{"id":"minecraft:yellow_dye"},{"id":"minecraft:lime_dye"},{"id":"minecraft:green_dye"},{"id":"minecraft:cyan_dye"},{"id":"minecraft:light_blue_dye"},{"id":"minecraft:blue_dye"},{"id":"minecraft:purple_dye"},{"id":"minecraft:magenta_dye"},{"id":"minecraft:pink_dye"},{"id":"minecraft:ink_sac"},{"id":"minecraft:glow_ink_sac"},{"id":"minecraft:cocoa_beans"},{"id":"minecraft:lapis_lazuli"},{"id":"minecraft:bone_meal"},{"id":"minecraft:vine","blockRuntimeId":2896},{"id":"minecraft:weeping_vines","blockRuntimeId":8202},{"id":"minecraft:twisting_vines","blockRuntimeId":8741},{"id":"minecraft:waterlily","blockRuntimeId":3317},{"id":"minecraft:seagrass","blockRuntimeId":1174},{"id":"minecraft:kelp"},{"id":"minecraft:deadbush","blockRuntimeId":6727},{"id":"minecraft:bamboo","blockRuntimeId":4818},{"id":"minecraft:snow","blockRuntimeId":5730},{"id":"minecraft:ice","blockRuntimeId":11260},{"id":"minecraft:packed_ice","blockRuntimeId":1244},{"id":"minecraft:blue_ice","blockRuntimeId":11797},{"id":"minecraft:snow_layer","blockRuntimeId":900},{"id":"minecraft:pointed_dripstone","blockRuntimeId":12835},{"id":"minecraft:dripstone_block","blockRuntimeId":2895},{"id":"minecraft:moss_carpet","blockRuntimeId":1248},{"id":"minecraft:moss_block","blockRuntimeId":11144},{"id":"minecraft:hanging_roots","blockRuntimeId":953},{"id":"minecraft:mangrove_roots","blockRuntimeId":10748},{"id":"minecraft:muddy_mangrove_roots","blockRuntimeId":1600},{"id":"minecraft:big_dripleaf","blockRuntimeId":10081},{"id":"minecraft:small_dripleaf_block","blockRuntimeId":5898},{"id":"minecraft:spore_blossom","blockRuntimeId":12568},{"id":"minecraft:azalea","blockRuntimeId":11477},{"id":"minecraft:flowering_azalea","blockRuntimeId":8199},{"id":"minecraft:glow_lichen","blockRuntimeId":8731},{"id":"minecraft:amethyst_block","blockRuntimeId":1284},{"id":"minecraft:budding_amethyst","blockRuntimeId":11602},{"id":"minecraft:amethyst_cluster","blockRuntimeId":13323},{"id":"minecraft:large_amethyst_bud","blockRuntimeId":6782},{"id":"minecraft:medium_amethyst_bud","blockRuntimeId":5959},{"id":"minecraft:small_amethyst_bud","blockRuntimeId":1559},{"id":"minecraft:calcite","blockRuntimeId":1125},{"id":"minecraft:chicken"},{"id":"minecraft:porkchop"},{"id":"minecraft:beef"},{"id":"minecraft:mutton"},{"id":"minecraft:rabbit"},{"id":"minecraft:cod"},{"id":"minecraft:salmon"},{"id":"minecraft:tropical_fish"},{"id":"minecraft:pufferfish"},{"id":"minecraft:brown_mushroom","blockRuntimeId":4662},{"id":"minecraft:red_mushroom","blockRuntimeId":6207},{"id":"minecraft:crimson_fungus","blockRuntimeId":13178},{"id":"minecraft:warped_fungus","blockRuntimeId":1249},{"id":"minecraft:brown_mushroom_block","blockRuntimeId":12618},{"id":"minecraft:red_mushroom_block","blockRuntimeId":4752},{"id":"minecraft:mushroom_stem","blockRuntimeId":10642},{"id":"minecraft:egg"},{"id":"minecraft:sugar_cane"},{"id":"minecraft:sugar"},{"id":"minecraft:rotten_flesh"},{"id":"minecraft:bone"},{"id":"minecraft:web","blockRuntimeId":11283},{"id":"minecraft:spider_eye"},{"id":"minecraft:mob_spawner","blockRuntimeId":1678},{"id":"minecraft:trial_spawner","blockRuntimeId":13748},{"id":"minecraft:vault","blockRuntimeId":10646},{"id":"minecraft:end_portal_frame","blockRuntimeId":10584},{"id":"minecraft:infested_stone","blockRuntimeId":10583},{"id":"minecraft:infested_cobblestone","blockRuntimeId":5676},{"id":"minecraft:infested_stone_bricks","blockRuntimeId":7933},{"id":"minecraft:infested_mossy_stone_bricks","blockRuntimeId":2723},{"id":"minecraft:infested_cracked_stone_bricks","blockRuntimeId":2542},{"id":"minecraft:infested_chiseled_stone_bricks","blockRuntimeId":5820},{"id":"minecraft:infested_deepslate","blockRuntimeId":6691},{"id":"minecraft:dragon_egg","blockRuntimeId":12535},{"id":"minecraft:turtle_egg","blockRuntimeId":13500},{"id":"minecraft:sniffer_egg","blockRuntimeId":11603},{"id":"minecraft:frog_spawn","blockRuntimeId":5992},{"id":"minecraft:pearlescent_froglight","blockRuntimeId":11038},{"id":"minecraft:verdant_froglight","blockRuntimeId":11064},{"id":"minecraft:ochre_froglight","blockRuntimeId":4079},{"id":"minecraft:chicken_spawn_egg"},{"id":"minecraft:bee_spawn_egg"},{"id":"minecraft:cow_spawn_egg"},{"id":"minecraft:pig_spawn_egg"},{"id":"minecraft:sheep_spawn_egg"},{"id":"minecraft:wolf_spawn_egg"},{"id":"minecraft:polar_bear_spawn_egg"},{"id":"minecraft:ocelot_spawn_egg"},{"id":"minecraft:cat_spawn_egg"},{"id":"minecraft:mooshroom_spawn_egg"},{"id":"minecraft:bat_spawn_egg"},{"id":"minecraft:parrot_spawn_egg"},{"id":"minecraft:rabbit_spawn_egg"},{"id":"minecraft:llama_spawn_egg"},{"id":"minecraft:horse_spawn_egg"},{"id":"minecraft:donkey_spawn_egg"},{"id":"minecraft:mule_spawn_egg"},{"id":"minecraft:skeleton_horse_spawn_egg"},{"id":"minecraft:zombie_horse_spawn_egg"},{"id":"minecraft:tropical_fish_spawn_egg"},{"id":"minecraft:cod_spawn_egg"},{"id":"minecraft:pufferfish_spawn_egg"},{"id":"minecraft:salmon_spawn_egg"},{"id":"minecraft:dolphin_spawn_egg"},{"id":"minecraft:turtle_spawn_egg"},{"id":"minecraft:panda_spawn_egg"},{"id":"minecraft:fox_spawn_egg"},{"id":"minecraft:creeper_spawn_egg"},{"id":"minecraft:enderman_spawn_egg"},{"id":"minecraft:silverfish_spawn_egg"},{"id":"minecraft:skeleton_spawn_egg"},{"id":"minecraft:wither_skeleton_spawn_egg"},{"id":"minecraft:stray_spawn_egg"},{"id":"minecraft:slime_spawn_egg"},{"id":"minecraft:spider_spawn_egg"},{"id":"minecraft:zombie_spawn_egg"},{"id":"minecraft:zombie_pigman_spawn_egg"},{"id":"minecraft:husk_spawn_egg"},{"id":"minecraft:drowned_spawn_egg"},{"id":"minecraft:squid_spawn_egg"},{"id":"minecraft:glow_squid_spawn_egg"},{"id":"minecraft:cave_spider_spawn_egg"},{"id":"minecraft:witch_spawn_egg"},{"id":"minecraft:guardian_spawn_egg"},{"id":"minecraft:elder_guardian_spawn_egg"},{"id":"minecraft:endermite_spawn_egg"},{"id":"minecraft:magma_cube_spawn_egg"},{"id":"minecraft:strider_spawn_egg"},{"id":"minecraft:hoglin_spawn_egg"},{"id":"minecraft:piglin_spawn_egg"},{"id":"minecraft:zoglin_spawn_egg"},{"id":"minecraft:piglin_brute_spawn_egg"},{"id":"minecraft:goat_spawn_egg"},{"id":"minecraft:axolotl_spawn_egg"},{"id":"minecraft:warden_spawn_egg"},{"id":"minecraft:allay_spawn_egg"},{"id":"minecraft:frog_spawn_egg"},{"id":"minecraft:tadpole_spawn_egg"},{"id":"minecraft:trader_llama_spawn_egg"},{"id":"minecraft:camel_spawn_egg"},{"id":"minecraft:ghast_spawn_egg"},{"id":"minecraft:blaze_spawn_egg"},{"id":"minecraft:shulker_spawn_egg"},{"id":"minecraft:vindicator_spawn_egg"},{"id":"minecraft:evoker_spawn_egg"},{"id":"minecraft:vex_spawn_egg"},{"id":"minecraft:villager_spawn_egg"},{"id":"minecraft:wandering_trader_spawn_egg"},{"id":"minecraft:zombie_villager_spawn_egg"},{"id":"minecraft:phantom_spawn_egg"},{"id":"minecraft:pillager_spawn_egg"},{"id":"minecraft:ravager_spawn_egg"},{"id":"minecraft:iron_golem_spawn_egg"},{"id":"minecraft:snow_golem_spawn_egg"},{"id":"minecraft:sniffer_spawn_egg"},{"id":"minecraft:breeze_spawn_egg"},{"id":"minecraft:armadillo_spawn_egg"},{"id":"minecraft:bogged_spawn_egg"},{"id":"minecraft:obsidian","blockRuntimeId":1709},{"id":"minecraft:crying_obsidian","blockRuntimeId":11296},{"id":"minecraft:bedrock","blockRuntimeId":11785},{"id":"minecraft:soul_sand","blockRuntimeId":8846},{"id":"minecraft:magma","blockRuntimeId":13512},{"id":"minecraft:nether_wart"},{"id":"minecraft:end_stone","blockRuntimeId":5370},{"id":"minecraft:chorus_flower","blockRuntimeId":6136},{"id":"minecraft:chorus_plant","blockRuntimeId":8228},{"id":"minecraft:chorus_fruit"},{"id":"minecraft:popped_chorus_fruit"},{"id":"minecraft:sponge","blockRuntimeId":1899},{"id":"minecraft:wet_sponge","blockRuntimeId":82},{"id":"minecraft:tube_coral_block","blockRuntimeId":12927},{"id":"minecraft:brain_coral_block","blockRuntimeId":8650},{"id":"minecraft:bubble_coral_block","blockRuntimeId":5819},{"id":"minecraft:fire_coral_block","blockRuntimeId":6044},{"id":"minecraft:horn_coral_block","blockRuntimeId":7976},{"id":"minecraft:dead_tube_coral_block","blockRuntimeId":5369},{"id":"minecraft:dead_brain_coral_block","blockRuntimeId":11223},{"id":"minecraft:dead_bubble_coral_block","blockRuntimeId":2956},{"id":"minecraft:dead_fire_coral_block","blockRuntimeId":3131},{"id":"minecraft:dead_horn_coral_block","blockRuntimeId":11063},{"id":"minecraft:sculk","blockRuntimeId":11818},{"id":"minecraft:sculk_vein","blockRuntimeId":12351},{"id":"minecraft:sculk_catalyst","blockRuntimeId":4755},{"id":"minecraft:sculk_shrieker","blockRuntimeId":1140},{"id":"minecraft:sculk_sensor","blockRuntimeId":5973},{"id":"minecraft:calibrated_sculk_sensor","blockRuntimeId":9430},{"id":"minecraft:reinforced_deepslate","blockRuntimeId":9394},{"id":"minecraft:leather_helmet"},{"id":"minecraft:chainmail_helmet"},{"id":"minecraft:iron_helmet"},{"id":"minecraft:golden_helmet"},{"id":"minecraft:diamond_helmet"},{"id":"minecraft:netherite_helmet"},{"id":"minecraft:leather_chestplate"},{"id":"minecraft:chainmail_chestplate"},{"id":"minecraft:iron_chestplate"},{"id":"minecraft:golden_chestplate"},{"id":"minecraft:diamond_chestplate"},{"id":"minecraft:netherite_chestplate"},{"id":"minecraft:leather_leggings"},{"id":"minecraft:chainmail_leggings"},{"id":"minecraft:iron_leggings"},{"id":"minecraft:golden_leggings"},{"id":"minecraft:diamond_leggings"},{"id":"minecraft:netherite_leggings"},{"id":"minecraft:leather_boots"},{"id":"minecraft:chainmail_boots"},{"id":"minecraft:iron_boots"},{"id":"minecraft:golden_boots"},{"id":"minecraft:diamond_boots"},{"id":"minecraft:netherite_boots"},{"id":"minecraft:wooden_sword"},{"id":"minecraft:stone_sword"},{"id":"minecraft:iron_sword"},{"id":"minecraft:golden_sword"},{"id":"minecraft:diamond_sword"},{"id":"minecraft:netherite_sword"},{"id":"minecraft:wooden_axe"},{"id":"minecraft:stone_axe"},{"id":"minecraft:iron_axe"},{"id":"minecraft:golden_axe"},{"id":"minecraft:diamond_axe"},{"id":"minecraft:netherite_axe"},{"id":"minecraft:wooden_pickaxe"},{"id":"minecraft:stone_pickaxe"},{"id":"minecraft:iron_pickaxe"},{"id":"minecraft:golden_pickaxe"},{"id":"minecraft:diamond_pickaxe"},{"id":"minecraft:netherite_pickaxe"},{"id":"minecraft:wooden_shovel"},{"id":"minecraft:stone_shovel"},{"id":"minecraft:iron_shovel"},{"id":"minecraft:golden_shovel"},{"id":"minecraft:diamond_shovel"},{"id":"minecraft:netherite_shovel"},{"id":"minecraft:wooden_hoe"},{"id":"minecraft:stone_hoe"},{"id":"minecraft:iron_hoe"},{"id":"minecraft:golden_hoe"},{"id":"minecraft:diamond_hoe"},{"id":"minecraft:netherite_hoe"},{"id":"minecraft:bow"},{"id":"minecraft:crossbow"},{"id":"minecraft:mace"},{"id":"minecraft:arrow"},{"id":"minecraft:arrow","damage":6},{"id":"minecraft:arrow","damage":7},{"id":"minecraft:arrow","damage":8},{"id":"minecraft:arrow","damage":9},{"id":"minecraft:arrow","damage":10},{"id":"minecraft:arrow","damage":11},{"id":"minecraft:arrow","damage":12},{"id":"minecraft:arrow","damage":13},{"id":"minecraft:arrow","damage":14},{"id":"minecraft:arrow","damage":15},{"id":"minecraft:arrow","damage":16},{"id":"minecraft:arrow","damage":17},{"id":"minecraft:arrow","damage":18},{"id":"minecraft:arrow","damage":19},{"id":"minecraft:arrow","damage":20},{"id":"minecraft:arrow","damage":21},{"id":"minecraft:arrow","damage":22},{"id":"minecraft:arrow","damage":23},{"id":"minecraft:arrow","damage":24},{"id":"minecraft:arrow","damage":25},{"id":"minecraft:arrow","damage":26},{"id":"minecraft:arrow","damage":27},{"id":"minecraft:arrow","damage":28},{"id":"minecraft:arrow","damage":29},{"id":"minecraft:arrow","damage":30},{"id":"minecraft:arrow","damage":31},{"id":"minecraft:arrow","damage":32},{"id":"minecraft:arrow","damage":33},{"id":"minecraft:arrow","damage":34},{"id":"minecraft:arrow","damage":35},{"id":"minecraft:arrow","damage":36},{"id":"minecraft:arrow","damage":37},{"id":"minecraft:arrow","damage":38},{"id":"minecraft:arrow","damage":39},{"id":"minecraft:arrow","damage":40},{"id":"minecraft:arrow","damage":41},{"id":"minecraft:arrow","damage":42},{"id":"minecraft:arrow","damage":43},{"id":"minecraft:arrow","damage":44},{"id":"minecraft:arrow","damage":45},{"id":"minecraft:arrow","damage":46},{"id":"minecraft:arrow","damage":47},{"id":"minecraft:shield"},{"id":"minecraft:cooked_chicken"},{"id":"minecraft:cooked_porkchop"},{"id":"minecraft:cooked_beef"},{"id":"minecraft:cooked_mutton"},{"id":"minecraft:cooked_rabbit"},{"id":"minecraft:cooked_cod"},{"id":"minecraft:cooked_salmon"},{"id":"minecraft:bread"},{"id":"minecraft:mushroom_stew"},{"id":"minecraft:beetroot_soup"},{"id":"minecraft:rabbit_stew"},{"id":"minecraft:baked_potato"},{"id":"minecraft:cookie"},{"id":"minecraft:pumpkin_pie"},{"id":"minecraft:cake"},{"id":"minecraft:dried_kelp"},{"id":"minecraft:fishing_rod"},{"id":"minecraft:carrot_on_a_stick"},{"id":"minecraft:warped_fungus_on_a_stick"},{"id":"minecraft:snowball"},{"id":"minecraft:wind_charge"},{"id":"minecraft:shears"},{"id":"minecraft:flint_and_steel"},{"id":"minecraft:lead"},{"id":"minecraft:clock"},{"id":"minecraft:compass"},{"id":"minecraft:recovery_compass"},{"id":"minecraft:goat_horn"},{"id":"minecraft:goat_horn","damage":1},{"id":"minecraft:goat_horn","damage":2},{"id":"minecraft:goat_horn","damage":3},{"id":"minecraft:goat_horn","damage":4},{"id":"minecraft:goat_horn","damage":5},{"id":"minecraft:goat_horn","damage":6},{"id":"minecraft:goat_horn","damage":7},{"id":"minecraft:empty_map"},{"id":"minecraft:empty_map","damage":2},{"id":"minecraft:saddle"},{"id":"minecraft:bundle"},{"id":"minecraft:white_bundle"},{"id":"minecraft:light_gray_bundle"},{"id":"minecraft:gray_bundle"},{"id":"minecraft:black_bundle"},{"id":"minecraft:brown_bundle"},{"id":"minecraft:red_bundle"},{"id":"minecraft:orange_bundle"},{"id":"minecraft:yellow_bundle"},{"id":"minecraft:lime_bundle"},{"id":"minecraft:green_bundle"},{"id":"minecraft:cyan_bundle"},{"id":"minecraft:light_blue_bundle"},{"id":"minecraft:blue_bundle"},{"id":"minecraft:purple_bundle"},{"id":"minecraft:magenta_bundle"},{"id":"minecraft:pink_bundle"},{"id":"minecraft:leather_horse_armor"},{"id":"minecraft:iron_horse_armor"},{"id":"minecraft:golden_horse_armor"},{"id":"minecraft:diamond_horse_armor"},{"id":"minecraft:wolf_armor"},{"id":"minecraft:trident"},{"id":"minecraft:turtle_helmet"},{"id":"minecraft:elytra"},{"id":"minecraft:totem_of_undying"},{"id":"minecraft:glass_bottle"},{"id":"minecraft:experience_bottle"},{"id":"minecraft:potion"},{"id":"minecraft:potion","damage":1},{"id":"minecraft:potion","damage":2},{"id":"minecraft:potion","damage":3},{"id":"minecraft:potion","damage":4},{"id":"minecraft:potion","damage":5},{"id":"minecraft:potion","damage":6},{"id":"minecraft:potion","damage":7},{"id":"minecraft:potion","damage":8},{"id":"minecraft:potion","damage":9},{"id":"minecraft:potion","damage":10},{"id":"minecraft:potion","damage":11},{"id":"minecraft:potion","damage":12},{"id":"minecraft:potion","damage":13},{"id":"minecraft:potion","damage":14},{"id":"minecraft:potion","damage":15},{"id":"minecraft:potion","damage":16},{"id":"minecraft:potion","damage":17},{"id":"minecraft:potion","damage":18},{"id":"minecraft:potion","damage":19},{"id":"minecraft:potion","damage":20},{"id":"minecraft:potion","damage":21},{"id":"minecraft:potion","damage":22},{"id":"minecraft:potion","damage":23},{"id":"minecraft:potion","damage":24},{"id":"minecraft:potion","damage":25},{"id":"minecraft:potion","damage":26},{"id":"minecraft:potion","damage":27},{"id":"minecraft:potion","damage":28},{"id":"minecraft:potion","damage":29},{"id":"minecraft:potion","damage":30},{"id":"minecraft:potion","damage":31},{"id":"minecraft:potion","damage":32},{"id":"minecraft:potion","damage":33},{"id":"minecraft:potion","damage":34},{"id":"minecraft:potion","damage":35},{"id":"minecraft:potion","damage":36},{"id":"minecraft:potion","damage":37},{"id":"minecraft:potion","damage":38},{"id":"minecraft:potion","damage":39},{"id":"minecraft:potion","damage":40},{"id":"minecraft:potion","damage":41},{"id":"minecraft:potion","damage":42},{"id":"minecraft:potion","damage":43},{"id":"minecraft:potion","damage":44},{"id":"minecraft:potion","damage":45},{"id":"minecraft:potion","damage":46},{"id":"minecraft:splash_potion"},{"id":"minecraft:splash_potion","damage":1},{"id":"minecraft:splash_potion","damage":2},{"id":"minecraft:splash_potion","damage":3},{"id":"minecraft:splash_potion","damage":4},{"id":"minecraft:splash_potion","damage":5},{"id":"minecraft:splash_potion","damage":6},{"id":"minecraft:splash_potion","damage":7},{"id":"minecraft:splash_potion","damage":8},{"id":"minecraft:splash_potion","damage":9},{"id":"minecraft:splash_potion","damage":10},{"id":"minecraft:splash_potion","damage":11},{"id":"minecraft:splash_potion","damage":12},{"id":"minecraft:splash_potion","damage":13},{"id":"minecraft:splash_potion","damage":14},{"id":"minecraft:splash_potion","damage":15},{"id":"minecraft:splash_potion","damage":16},{"id":"minecraft:splash_potion","damage":17},{"id":"minecraft:splash_potion","damage":18},{"id":"minecraft:splash_potion","damage":19},{"id":"minecraft:splash_potion","damage":20},{"id":"minecraft:splash_potion","damage":21},{"id":"minecraft:splash_potion","damage":22},{"id":"minecraft:splash_potion","damage":23},{"id":"minecraft:splash_potion","damage":24},{"id":"minecraft:splash_potion","damage":25},{"id":"minecraft:splash_potion","damage":26},{"id":"minecraft:splash_potion","damage":27},{"id":"minecraft:splash_potion","damage":28},{"id":"minecraft:splash_potion","damage":29},{"id":"minecraft:splash_potion","damage":30},{"id":"minecraft:splash_potion","damage":31},{"id":"minecraft:splash_potion","damage":32},{"id":"minecraft:splash_potion","damage":33},{"id":"minecraft:splash_potion","damage":34},{"id":"minecraft:splash_potion","damage":35},{"id":"minecraft:splash_potion","damage":36},{"id":"minecraft:splash_potion","damage":37},{"id":"minecraft:splash_potion","damage":38},{"id":"minecraft:splash_potion","damage":39},{"id":"minecraft:splash_potion","damage":40},{"id":"minecraft:splash_potion","damage":41},{"id":"minecraft:splash_potion","damage":42},{"id":"minecraft:splash_potion","damage":43},{"id":"minecraft:splash_potion","damage":44},{"id":"minecraft:splash_potion","damage":45},{"id":"minecraft:splash_potion","damage":46},{"id":"minecraft:lingering_potion"},{"id":"minecraft:lingering_potion","damage":1},{"id":"minecraft:lingering_potion","damage":2},{"id":"minecraft:lingering_potion","damage":3},{"id":"minecraft:lingering_potion","damage":4},{"id":"minecraft:lingering_potion","damage":5},{"id":"minecraft:lingering_potion","damage":6},{"id":"minecraft:lingering_potion","damage":7},{"id":"minecraft:lingering_potion","damage":8},{"id":"minecraft:lingering_potion","damage":9},{"id":"minecraft:lingering_potion","damage":10},{"id":"minecraft:lingering_potion","damage":11},{"id":"minecraft:lingering_potion","damage":12},{"id":"minecraft:lingering_potion","damage":13},{"id":"minecraft:lingering_potion","damage":14},{"id":"minecraft:lingering_potion","damage":15},{"id":"minecraft:lingering_potion","damage":16},{"id":"minecraft:lingering_potion","damage":17},{"id":"minecraft:lingering_potion","damage":18},{"id":"minecraft:lingering_potion","damage":19},{"id":"minecraft:lingering_potion","damage":20},{"id":"minecraft:lingering_potion","damage":21},{"id":"minecraft:lingering_potion","damage":22},{"id":"minecraft:lingering_potion","damage":23},{"id":"minecraft:lingering_potion","damage":24},{"id":"minecraft:lingering_potion","damage":25},{"id":"minecraft:lingering_potion","damage":26},{"id":"minecraft:lingering_potion","damage":27},{"id":"minecraft:lingering_potion","damage":28},{"id":"minecraft:lingering_potion","damage":29},{"id":"minecraft:lingering_potion","damage":30},{"id":"minecraft:lingering_potion","damage":31},{"id":"minecraft:lingering_potion","damage":32},{"id":"minecraft:lingering_potion","damage":33},{"id":"minecraft:lingering_potion","damage":34},{"id":"minecraft:lingering_potion","damage":35},{"id":"minecraft:lingering_potion","damage":36},{"id":"minecraft:lingering_potion","damage":37},{"id":"minecraft:lingering_potion","damage":38},{"id":"minecraft:lingering_potion","damage":39},{"id":"minecraft:lingering_potion","damage":40},{"id":"minecraft:lingering_potion","damage":41},{"id":"minecraft:lingering_potion","damage":42},{"id":"minecraft:lingering_potion","damage":43},{"id":"minecraft:lingering_potion","damage":44},{"id":"minecraft:lingering_potion","damage":45},{"id":"minecraft:lingering_potion","damage":46},{"id":"minecraft:ominous_bottle"},{"id":"minecraft:ominous_bottle","damage":1},{"id":"minecraft:ominous_bottle","damage":2},{"id":"minecraft:ominous_bottle","damage":3},{"id":"minecraft:ominous_bottle","damage":4},{"id":"minecraft:spyglass"},{"id":"minecraft:brush"},{"id":"minecraft:stick"},{"id":"minecraft:bed"},{"id":"minecraft:bed","damage":8},{"id":"minecraft:bed","damage":7},{"id":"minecraft:bed","damage":15},{"id":"minecraft:bed","damage":12},{"id":"minecraft:bed","damage":14},{"id":"minecraft:bed","damage":1},{"id":"minecraft:bed","damage":4},{"id":"minecraft:bed","damage":5},{"id":"minecraft:bed","damage":13},{"id":"minecraft:bed","damage":9},{"id":"minecraft:bed","damage":3},{"id":"minecraft:bed","damage":11},{"id":"minecraft:bed","damage":10},{"id":"minecraft:bed","damage":2},{"id":"minecraft:bed","damage":6},{"id":"minecraft:torch","blockRuntimeId":2724},{"id":"minecraft:soul_torch","blockRuntimeId":6694},{"id":"minecraft:sea_pickle","blockRuntimeId":9467},{"id":"minecraft:lantern","blockRuntimeId":11860},{"id":"minecraft:soul_lantern","blockRuntimeId":8803},{"id":"minecraft:candle","blockRuntimeId":12820},{"id":"minecraft:white_candle","blockRuntimeId":7987},{"id":"minecraft:orange_candle","blockRuntimeId":1630},{"id":"minecraft:magenta_candle","blockRuntimeId":1696},{"id":"minecraft:light_blue_candle","blockRuntimeId":6164},{"id":"minecraft:yellow_candle","blockRuntimeId":10749},{"id":"minecraft:lime_candle","blockRuntimeId":10935},{"id":"minecraft:pink_candle","blockRuntimeId":12624},{"id":"minecraft:gray_candle","blockRuntimeId":2957},{"id":"minecraft:light_gray_candle","blockRuntimeId":10778},{"id":"minecraft:cyan_candle","blockRuntimeId":13144},{"id":"minecraft:purple_candle","blockRuntimeId":11821},{"id":"minecraft:blue_candle","blockRuntimeId":2},{"id":"minecraft:brown_candle","blockRuntimeId":9892},{"id":"minecraft:green_candle","blockRuntimeId":2511},{"id":"minecraft:red_candle","blockRuntimeId":6731},{"id":"minecraft:black_candle","blockRuntimeId":918},{"id":"minecraft:crafting_table","blockRuntimeId":9466},{"id":"minecraft:cartography_table","blockRuntimeId":14158},{"id":"minecraft:fletching_table","blockRuntimeId":9395},{"id":"minecraft:smithing_table","blockRuntimeId":4854},{"id":"minecraft:beehive","blockRuntimeId":10678},{"id":"minecraft:suspicious_sand","blockRuntimeId":3321},{"id":"minecraft:suspicious_gravel","blockRuntimeId":6820},{"id":"minecraft:campfire"},{"id":"minecraft:soul_campfire"},{"id":"minecraft:furnace","blockRuntimeId":13319},{"id":"minecraft:blast_furnace","blockRuntimeId":12980},{"id":"minecraft:smoker","blockRuntimeId":2320},{"id":"minecraft:respawn_anchor","blockRuntimeId":2505},{"id":"minecraft:brewing_stand"},{"id":"minecraft:anvil","blockRuntimeId":11226},{"id":"minecraft:chipped_anvil","blockRuntimeId":6777},{"id":"minecraft:damaged_anvil","blockRuntimeId":13737},{"id":"minecraft:grindstone","blockRuntimeId":13529},{"id":"minecraft:enchanting_table","blockRuntimeId":11306},{"id":"minecraft:bookshelf","blockRuntimeId":11254},{"id":"minecraft:chiseled_bookshelf","blockRuntimeId":1292},{"id":"minecraft:lectern","blockRuntimeId":11540},{"id":"minecraft:cauldron"},{"id":"minecraft:composter","blockRuntimeId":8115},{"id":"minecraft:chest","blockRuntimeId":11943},{"id":"minecraft:trapped_chest","blockRuntimeId":8632},{"id":"minecraft:ender_chest","blockRuntimeId":5956},{"id":"minecraft:barrel","blockRuntimeId":6120},{"id":"minecraft:undyed_shulker_box","blockRuntimeId":4815},{"id":"minecraft:white_shulker_box","blockRuntimeId":1627},{"id":"minecraft:light_gray_shulker_box","blockRuntimeId":9926},{"id":"minecraft:gray_shulker_box","blockRuntimeId":8033},{"id":"minecraft:black_shulker_box","blockRuntimeId":10523},{"id":"minecraft:brown_shulker_box","blockRuntimeId":11476},{"id":"minecraft:red_shulker_box","blockRuntimeId":6027},{"id":"minecraft:orange_shulker_box","blockRuntimeId":10777},{"id":"minecraft:yellow_shulker_box","blockRuntimeId":286},{"id":"minecraft:lime_shulker_box","blockRuntimeId":1556},{"id":"minecraft:green_shulker_box","blockRuntimeId":11075},{"id":"minecraft:cyan_shulker_box","blockRuntimeId":11798},{"id":"minecraft:light_blue_shulker_box","blockRuntimeId":11567},{"id":"minecraft:blue_shulker_box","blockRuntimeId":10830},{"id":"minecraft:purple_shulker_box","blockRuntimeId":12794},{"id":"minecraft:magenta_shulker_box","blockRuntimeId":1243},{"id":"minecraft:pink_shulker_box","blockRuntimeId":5964},{"id":"minecraft:armor_stand"},{"id":"minecraft:noteblock","blockRuntimeId":1606},{"id":"minecraft:jukebox","blockRuntimeId":7387},{"id":"minecraft:music_disc_13"},{"id":"minecraft:music_disc_cat"},{"id":"minecraft:music_disc_blocks"},{"id":"minecraft:music_disc_chirp"},{"id":"minecraft:music_disc_far"},{"id":"minecraft:music_disc_mall"},{"id":"minecraft:music_disc_mellohi"},{"id":"minecraft:music_disc_stal"},{"id":"minecraft:music_disc_strad"},{"id":"minecraft:music_disc_ward"},{"id":"minecraft:music_disc_11"},{"id":"minecraft:music_disc_wait"},{"id":"minecraft:music_disc_otherside"},{"id":"minecraft:music_disc_5"},{"id":"minecraft:music_disc_pigstep"},{"id":"minecraft:music_disc_relic"},{"id":"minecraft:music_disc_creator"},{"id":"minecraft:music_disc_creator_music_box"},{"id":"minecraft:music_disc_precipice"},{"id":"minecraft:disc_fragment_5"},{"id":"minecraft:glowstone_dust"},{"id":"minecraft:glowstone","blockRuntimeId":5424},{"id":"minecraft:redstone_lamp","blockRuntimeId":1181},{"id":"minecraft:sea_lantern","blockRuntimeId":12963},{"id":"minecraft:oak_sign"},{"id":"minecraft:spruce_sign"},{"id":"minecraft:birch_sign"},{"id":"minecraft:jungle_sign"},{"id":"minecraft:acacia_sign"},{"id":"minecraft:dark_oak_sign"},{"id":"minecraft:mangrove_sign"},{"id":"minecraft:cherry_sign"},{"id":"minecraft:bamboo_sign"},{"id":"minecraft:crimson_sign"},{"id":"minecraft:warped_sign"},{"id":"minecraft:oak_hanging_sign"},{"id":"minecraft:spruce_hanging_sign"},{"id":"minecraft:birch_hanging_sign"},{"id":"minecraft:jungle_hanging_sign"},{"id":"minecraft:acacia_hanging_sign"},{"id":"minecraft:dark_oak_hanging_sign"},{"id":"minecraft:mangrove_hanging_sign"},{"id":"minecraft:cherry_hanging_sign"},{"id":"minecraft:bamboo_hanging_sign"},{"id":"minecraft:crimson_hanging_sign"},{"id":"minecraft:warped_hanging_sign"},{"id":"minecraft:painting"},{"id":"minecraft:frame"},{"id":"minecraft:glow_frame"},{"id":"minecraft:honey_bottle"},{"id":"minecraft:flower_pot"},{"id":"minecraft:bowl"},{"id":"minecraft:bucket"},{"id":"minecraft:milk_bucket"},{"id":"minecraft:water_bucket"},{"id":"minecraft:lava_bucket"},{"id":"minecraft:cod_bucket"},{"id":"minecraft:salmon_bucket"},{"id":"minecraft:tropical_fish_bucket"},{"id":"minecraft:pufferfish_bucket"},{"id":"minecraft:powder_snow_bucket"},{"id":"minecraft:axolotl_bucket"},{"id":"minecraft:tadpole_bucket"},{"id":"minecraft:player_head","blockRuntimeId":4855},{"id":"minecraft:zombie_head","blockRuntimeId":33},{"id":"minecraft:creeper_head","blockRuntimeId":9400},{"id":"minecraft:dragon_head","blockRuntimeId":9424},{"id":"minecraft:skeleton_skull","blockRuntimeId":8109},{"id":"minecraft:wither_skeleton_skull","blockRuntimeId":12469},{"id":"minecraft:piglin_head","blockRuntimeId":11812},{"id":"minecraft:beacon","blockRuntimeId":727},{"id":"minecraft:bell","blockRuntimeId":11508},{"id":"minecraft:conduit","blockRuntimeId":5767},{"id":"minecraft:stonecutter_block","blockRuntimeId":12987},{"id":"minecraft:coal"},{"id":"minecraft:charcoal"},{"id":"minecraft:diamond"},{"id":"minecraft:iron_nugget"},{"id":"minecraft:raw_iron"},{"id":"minecraft:raw_gold"},{"id":"minecraft:raw_copper"},{"id":"minecraft:copper_ingot"},{"id":"minecraft:iron_ingot"},{"id":"minecraft:netherite_scrap"},{"id":"minecraft:netherite_ingot"},{"id":"minecraft:gold_nugget"},{"id":"minecraft:gold_ingot"},{"id":"minecraft:emerald"},{"id":"minecraft:quartz"},{"id":"minecraft:clay_ball"},{"id":"minecraft:brick"},{"id":"minecraft:netherbrick"},{"id":"minecraft:prismarine_shard"},{"id":"minecraft:amethyst_shard"},{"id":"minecraft:prismarine_crystals"},{"id":"minecraft:nautilus_shell"},{"id":"minecraft:heart_of_the_sea"},{"id":"minecraft:turtle_scute"},{"id":"minecraft:armadillo_scute"},{"id":"minecraft:phantom_membrane"},{"id":"minecraft:string"},{"id":"minecraft:feather"},{"id":"minecraft:flint"},{"id":"minecraft:gunpowder"},{"id":"minecraft:leather"},{"id":"minecraft:rabbit_hide"},{"id":"minecraft:rabbit_foot"},{"id":"minecraft:fire_charge"},{"id":"minecraft:blaze_rod"},{"id":"minecraft:breeze_rod"},{"id":"minecraft:heavy_core","blockRuntimeId":12560},{"id":"minecraft:blaze_powder"},{"id":"minecraft:magma_cream"},{"id":"minecraft:fermented_spider_eye"},{"id":"minecraft:echo_shard"},{"id":"minecraft:dragon_breath"},{"id":"minecraft:shulker_shell"},{"id":"minecraft:ghast_tear"},{"id":"minecraft:slime_ball"},{"id":"minecraft:ender_pearl"},{"id":"minecraft:ender_eye"},{"id":"minecraft:nether_star"},{"id":"minecraft:end_rod","blockRuntimeId":9908},{"id":"minecraft:lightning_rod","blockRuntimeId":3507},{"id":"minecraft:end_crystal"},{"id":"minecraft:paper"},{"id":"minecraft:book"},{"id":"minecraft:writable_book"},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQAAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQBAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQCAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQDAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQEAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQFAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQGAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQHAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQIAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQJAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQKAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQLAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQMAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQNAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQOAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQPAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQQAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQRAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQSAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQTAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQUAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQVAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQWAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQXAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQYAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQZAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQaAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQbAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQcAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQdAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQeAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQfAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQgAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQhAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQiAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQjAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQkAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQlAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQmAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAQAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQnAAIDAGx2bAUAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAEAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAIAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAMAAAA="},{"id":"minecraft:enchanted_book","nbt_b64":"CgAACQQAZW5jaAoBAAAAAgIAaWQoAAIDAGx2bAQAAAA="},{"id":"minecraft:oak_boat"},{"id":"minecraft:spruce_boat"},{"id":"minecraft:birch_boat"},{"id":"minecraft:jungle_boat"},{"id":"minecraft:acacia_boat"},{"id":"minecraft:dark_oak_boat"},{"id":"minecraft:mangrove_boat"},{"id":"minecraft:cherry_boat"},{"id":"minecraft:bamboo_raft"},{"id":"minecraft:oak_chest_boat"},{"id":"minecraft:spruce_chest_boat"},{"id":"minecraft:birch_chest_boat"},{"id":"minecraft:jungle_chest_boat"},{"id":"minecraft:acacia_chest_boat"},{"id":"minecraft:dark_oak_chest_boat"},{"id":"minecraft:mangrove_chest_boat"},{"id":"minecraft:cherry_chest_boat"},{"id":"minecraft:bamboo_chest_raft"},{"id":"minecraft:rail","blockRuntimeId":5465},{"id":"minecraft:golden_rail","blockRuntimeId":8004},{"id":"minecraft:detector_rail","blockRuntimeId":5647},{"id":"minecraft:activator_rail","blockRuntimeId":1564},{"id":"minecraft:minecart"},{"id":"minecraft:chest_minecart"},{"id":"minecraft:hopper_minecart"},{"id":"minecraft:tnt_minecart"},{"id":"minecraft:redstone"},{"id":"minecraft:redstone_block","blockRuntimeId":4926},{"id":"minecraft:redstone_torch","blockRuntimeId":4256},{"id":"minecraft:lever","blockRuntimeId":11093},{"id":"minecraft:wooden_button","blockRuntimeId":10959},{"id":"minecraft:spruce_button","blockRuntimeId":5901},{"id":"minecraft:birch_button","blockRuntimeId":13228},{"id":"minecraft:jungle_button","blockRuntimeId":291},{"id":"minecraft:acacia_button","blockRuntimeId":12478},{"id":"minecraft:dark_oak_button","blockRuntimeId":264},{"id":"minecraft:mangrove_button","blockRuntimeId":11845},{"id":"minecraft:cherry_button","blockRuntimeId":6243},{"id":"minecraft:bamboo_button","blockRuntimeId":11041},{"id":"minecraft:stone_button","blockRuntimeId":1881},{"id":"minecraft:crimson_button","blockRuntimeId":6031},{"id":"minecraft:warped_button","blockRuntimeId":12496},{"id":"minecraft:polished_blackstone_button","blockRuntimeId":13254},{"id":"minecraft:tripwire_hook","blockRuntimeId":9982},{"id":"minecraft:wooden_pressure_plate","blockRuntimeId":13555},{"id":"minecraft:spruce_pressure_plate","blockRuntimeId":4908},{"id":"minecraft:birch_pressure_plate","blockRuntimeId":4693},{"id":"minecraft:jungle_pressure_plate","blockRuntimeId":4771},{"id":"minecraft:acacia_pressure_plate","blockRuntimeId":7934},{"id":"minecraft:dark_oak_pressure_plate","blockRuntimeId":10024},{"id":"minecraft:mangrove_pressure_plate","blockRuntimeId":5405},{"id":"minecraft:cherry_pressure_plate","blockRuntimeId":317},{"id":"minecraft:bamboo_pressure_plate","blockRuntimeId":10592},{"id":"minecraft:crimson_pressure_plate","blockRuntimeId":14138},{"id":"minecraft:warped_pressure_plate","blockRuntimeId":1207},{"id":"minecraft:stone_pressure_plate","blockRuntimeId":5425},{"id":"minecraft:light_weighted_pressure_plate","blockRuntimeId":4799},{"id":"minecraft:heavy_weighted_pressure_plate","blockRuntimeId":3490},{"id":"minecraft:polished_blackstone_pressure_plate","blockRuntimeId":10786},{"id":"minecraft:observer","blockRuntimeId":4244},{"id":"minecraft:daylight_detector","blockRuntimeId":5732},{"id":"minecraft:repeater"},{"id":"minecraft:comparator"},{"id":"minecraft:hopper"},{"id":"minecraft:dropper","blockRuntimeId":12802},{"id":"minecraft:dispenser","blockRuntimeId":13516},{"id":"minecraft:crafter","blockRuntimeId":13266},{"id":"minecraft:piston","blockRuntimeId":2942},{"id":"minecraft:sticky_piston","blockRuntimeId":5949},{"id":"minecraft:tnt","blockRuntimeId":11279},{"id":"minecraft:name_tag"},{"id":"minecraft:loom","blockRuntimeId":5365},{"id":"minecraft:banner","nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":8,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":7,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":12,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":14,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":1,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":4,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":5,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":13,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":9,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":3,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":11,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":10,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":2,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":6,"nbt_b64":"CgAAAwQAVHlwZQAAAAAA"},{"id":"minecraft:banner","damage":15,"nbt_b64":"CgAAAwQAVHlwZQEAAAAA"},{"id":"minecraft:creeper_banner_pattern"},{"id":"minecraft:skull_banner_pattern"},{"id":"minecraft:flower_banner_pattern"},{"id":"minecraft:mojang_banner_pattern"},{"id":"minecraft:field_masoned_banner_pattern"},{"id":"minecraft:bordure_indented_banner_pattern"},{"id":"minecraft:piglin_banner_pattern"},{"id":"minecraft:globe_banner_pattern"},{"id":"minecraft:flow_banner_pattern"},{"id":"minecraft:guster_banner_pattern"},{"id":"minecraft:angler_pottery_sherd"},{"id":"minecraft:archer_pottery_sherd"},{"id":"minecraft:arms_up_pottery_sherd"},{"id":"minecraft:blade_pottery_sherd"},{"id":"minecraft:brewer_pottery_sherd"},{"id":"minecraft:burn_pottery_sherd"},{"id":"minecraft:danger_pottery_sherd"},{"id":"minecraft:explorer_pottery_sherd"},{"id":"minecraft:flow_pottery_sherd"},{"id":"minecraft:friend_pottery_sherd"},{"id":"minecraft:guster_pottery_sherd"},{"id":"minecraft:heart_pottery_sherd"},{"id":"minecraft:heartbreak_pottery_sherd"},{"id":"minecraft:howl_pottery_sherd"},{"id":"minecraft:miner_pottery_sherd"},{"id":"minecraft:mourner_pottery_sherd"},{"id":"minecraft:plenty_pottery_sherd"},{"id":"minecraft:prize_pottery_sherd"},{"id":"minecraft:scrape_pottery_sherd"},{"id":"minecraft:sheaf_pottery_sherd"},{"id":"minecraft:shelter_pottery_sherd"},{"id":"minecraft:skull_pottery_sherd"},{"id":"minecraft:snort_pottery_sherd"},{"id":"minecraft:netherite_upgrade_smithing_template"},{"id":"minecraft:sentry_armor_trim_smithing_template"},{"id":"minecraft:vex_armor_trim_smithing_template"},{"id":"minecraft:wild_armor_trim_smithing_template"},{"id":"minecraft:coast_armor_trim_smithing_template"},{"id":"minecraft:dune_armor_trim_smithing_template"},{"id":"minecraft:wayfinder_armor_trim_smithing_template"},{"id":"minecraft:shaper_armor_trim_smithing_template"},{"id":"minecraft:raiser_armor_trim_smithing_template"},{"id":"minecraft:host_armor_trim_smithing_template"},{"id":"minecraft:ward_armor_trim_smithing_template"},{"id":"minecraft:silence_armor_trim_smithing_template"},{"id":"minecraft:tide_armor_trim_smithing_template"},{"id":"minecraft:snout_armor_trim_smithing_template"},{"id":"minecraft:rib_armor_trim_smithing_template"},{"id":"minecraft:eye_armor_trim_smithing_template"},{"id":"minecraft:spire_armor_trim_smithing_template"},{"id":"minecraft:flow_armor_trim_smithing_template"},{"id":"minecraft:bolt_armor_trim_smithing_template"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwAAAAAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAABwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAIBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAHBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAPBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAMBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAOBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAABBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAEBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAFBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAANBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAJBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAADBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAALBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAKBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAACBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_rocket","nbt_b64":"CgAACgkARmlyZXdvcmtzCQoARXhwbG9zaW9ucwoBAAAABw0ARmlyZXdvcmtDb2xvcgEAAAAGBwwARmlyZXdvcmtGYWRlAAAAAAEPAEZpcmV3b3JrRmxpY2tlcgABDQBGaXJld29ya1RyYWlsAAEMAEZpcmV3b3JrVHlwZQAAAQYARmxpZ2h0AQAA"},{"id":"minecraft:firework_star","nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yIR0d/wA="},{"id":"minecraft:firework_star","damage":8,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yUk9H/wA="},{"id":"minecraft:firework_star","damage":7,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yl52d/wA="},{"id":"minecraft:firework_star","damage":15,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y8PDw/wA="},{"id":"minecraft:firework_star","damage":12,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9y2rM6/wA="},{"id":"minecraft:firework_star","damage":14,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yHYD5/wA="},{"id":"minecraft:firework_star","damage":1,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yJi6w/wA="},{"id":"minecraft:firework_star","damage":4,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABAcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqkQ8/wA="},{"id":"minecraft:firework_star","damage":5,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yuDKJ/wA="},{"id":"minecraft:firework_star","damage":13,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAADQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yvU7H/wA="},{"id":"minecraft:firework_star","damage":9,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACQcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yqovz/wA="},{"id":"minecraft:firework_star","damage":3,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yMlSD/wA="},{"id":"minecraft:firework_star","damage":11,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACwcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yPdj+/wA="},{"id":"minecraft:firework_star","damage":10,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAACgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yH8eA/wA="},{"id":"minecraft:firework_star","damage":2,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAAAgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9yFnxe/wA="},{"id":"minecraft:firework_star","damage":6,"nbt_b64":"CgAACg0ARmlyZXdvcmtzSXRlbQcNAEZpcmV3b3JrQ29sb3IBAAAABgcMAEZpcmV3b3JrRmFkZQAAAAABDwBGaXJld29ya0ZsaWNrZXIAAQ0ARmlyZXdvcmtUcmFpbAABDABGaXJld29ya1R5cGUAAAMLAGN1c3RvbUNvbG9ynJwW/wA="},{"id":"minecraft:chain"},{"id":"minecraft:target","blockRuntimeId":10958},{"id":"minecraft:decorated_pot","blockRuntimeId":11300},{"id":"minecraft:trial_key"},{"id":"minecraft:ominous_trial_key"},{"id":"minecraft:lodestone_compass"},{"id":"minecraft:wither_spawn_egg"},{"id":"minecraft:ender_dragon_spawn_egg"}]} \ No newline at end of file diff --git a/src/main/resources/item_mappings.json b/src/main/resources/item_mappings.json index 5687599564b..96275e0ab01 100644 --- a/src/main/resources/item_mappings.json +++ b/src/main/resources/item_mappings.json @@ -1 +1 @@ -{"minecraft:cobblestone_wall":{"0":"minecraft:cobblestone_wall","1":"minecraft:mossy_cobblestone_wall","2":"minecraft:granite_wall","3":"minecraft:diorite_wall","4":"minecraft:andesite_wall","5":"minecraft:sandstone_wall","6":"minecraft:brick_wall","7":"minecraft:stone_brick_wall","8":"minecraft:mossy_stone_brick_wall","9":"minecraft:nether_brick_wall","10":"minecraft:end_stone_brick_wall","11":"minecraft:prismarine_wall","12":"minecraft:red_sandstone_wall","13":"minecraft:red_nether_brick_wall"},"minecraft:sponge":{"0":"minecraft:sponge","1":"minecraft:wet_sponge"},"minecraft:purpur_block":{"0":"minecraft:purpur_block","1":"minecraft:deprecated_purpur_block_1","2":"minecraft:purpur_pillar","3":"minecraft:deprecated_purpur_block_2"},"minecraft:dirt":{"0":"minecraft:dirt","1":"minecraft:coarse_dirt"},"minecraft:yellow_flower":{"0":"minecraft:dandelion"},"minecraft:sand":{"0":"minecraft:sand","1":"minecraft:red_sand"},"minecraft:prismarine":{"0":"minecraft:prismarine","1":"minecraft:dark_prismarine","2":"minecraft:prismarine_bricks"},"minecraft:sandstone":{"0":"minecraft:sandstone","1":"minecraft:chiseled_sandstone","2":"minecraft:cut_sandstone","3":"minecraft:smooth_sandstone"},"minecraft:red_sandstone":{"0":"minecraft:red_sandstone","1":"minecraft:chiseled_red_sandstone","2":"minecraft:cut_red_sandstone","3":"minecraft:smooth_red_sandstone"},"minecraft:quartz_block":{"0":"minecraft:quartz_block","1":"minecraft:chiseled_quartz_block","2":"minecraft:quartz_pillar","3":"minecraft:smooth_quartz"},"minecraft:stone_block_slab2":{"0":"minecraft:red_sandstone_slab","1":"minecraft:purpur_slab","2":"minecraft:prismarine_slab","3":"minecraft:dark_prismarine_slab","4":"minecraft:prismarine_brick_slab","5":"minecraft:mossy_cobblestone_slab","6":"minecraft:smooth_sandstone_slab","7":"minecraft:red_nether_brick_slab"},"minecraft:stone_block_slab3":{"0":"minecraft:end_stone_brick_slab","1":"minecraft:smooth_red_sandstone_slab","2":"minecraft:polished_andesite_slab","3":"minecraft:andesite_slab","4":"minecraft:diorite_slab","5":"minecraft:polished_diorite_slab","6":"minecraft:granite_slab","7":"minecraft:polished_granite_slab"},"minecraft:stone_block_slab4":{"0":"minecraft:mossy_stone_brick_slab","1":"minecraft:smooth_quartz_slab","2":"minecraft:normal_stone_slab","3":"minecraft:cut_sandstone_slab","4":"minecraft:cut_red_sandstone_slab"},"minecraft:monster_egg":{"0":"minecraft:infested_stone","1":"minecraft:infested_cobblestone","2":"minecraft:infested_stone_bricks","3":"minecraft:infested_mossy_stone_bricks","4":"minecraft:infested_cracked_stone_bricks","5":"minecraft:infested_chiseled_stone_bricks"},"minecraft:stonebrick":{"0":"minecraft:stone_bricks","1":"minecraft:mossy_stone_bricks","2":"minecraft:cracked_stone_bricks","3":"minecraft:chiseled_stone_bricks"},"minecraft:anvil":{"0":"minecraft:anvil","4":"minecraft:chipped_anvil","8":"minecraft:damaged_anvil"},"minecraft:stone_block_slab":{"0":"minecraft:smooth_stone_slab","1":"minecraft:sandstone_slab","2":"minecraft:petrified_oak_slab","3":"minecraft:cobblestone_slab","4":"minecraft:brick_slab","5":"minecraft:stone_brick_slab","6":"minecraft:quartz_slab","7":"minecraft:nether_brick_slab"},"minecraft:coral_block":{"0":"minecraft:tube_coral_block","1":"minecraft:brain_coral_block","2":"minecraft:bubble_coral_block","3":"minecraft:fire_coral_block","4":"minecraft:horn_coral_block","8":"minecraft:dead_tube_coral_block","9":"minecraft:dead_brain_coral_block","10":"minecraft:dead_bubble_coral_block","11":"minecraft:dead_fire_coral_block","12":"minecraft:dead_horn_coral_block"},"minecraft:double_plant":{"0":"minecraft:sunflower","1":"minecraft:lilac","2":"minecraft:tall_grass","3":"minecraft:large_fern","4":"minecraft:rose_bush","5":"minecraft:peony"},"minecraft:tallgrass":{"1":"minecraft:short_grass","2":"minecraft:fern"},"minecraft:coral_fan_dead":{"0":"minecraft:dead_tube_coral_fan","1":"minecraft:dead_brain_coral_fan","2":"minecraft:dead_bubble_coral_fan","3":"minecraft:dead_fire_coral_fan","4":"minecraft:dead_horn_coral_fan"},"minecraft:coral_fan":{"0":"minecraft:tube_coral_fan","1":"minecraft:brain_coral_fan","2":"minecraft:bubble_coral_fan","3":"minecraft:fire_coral_fan","4":"minecraft:horn_coral_fan"},"minecraft:sapling":{"0":"minecraft:oak_sapling","1":"minecraft:spruce_sapling","2":"minecraft:birch_sapling","3":"minecraft:jungle_sapling","4":"minecraft:acacia_sapling","5":"minecraft:dark_oak_sapling"},"minecraft:red_flower":{"0":"minecraft:poppy","1":"minecraft:blue_orchid","2":"minecraft:allium","3":"minecraft:azure_bluet","4":"minecraft:red_tulip","5":"minecraft:orange_tulip","6":"minecraft:white_tulip","7":"minecraft:pink_tulip","8":"minecraft:oxeye_daisy","9":"minecraft:cornflower","10":"minecraft:lily_of_the_valley"},"minecraft:wood":{"0":"minecraft:oak_wood","1":"minecraft:spruce_wood","2":"minecraft:birch_wood","3":"minecraft:jungle_wood","4":"minecraft:acacia_wood","5":"minecraft:dark_oak_wood","8":"minecraft:stripped_oak_wood","9":"minecraft:stripped_spruce_wood","10":"minecraft:stripped_birch_wood","11":"minecraft:stripped_jungle_wood","12":"minecraft:stripped_acacia_wood","13":"minecraft:stripped_dark_oak_wood"},"minecraft:double_wooden_slab":{"0":"minecraft:oak_double_slab","1":"minecraft:spruce_double_slab","2":"minecraft:birch_double_slab","3":"minecraft:jungle_double_slab","4":"minecraft:acacia_double_slab","5":"minecraft:dark_oak_double_slab"},"minecraft:wooden_slab":{"0":"minecraft:oak_slab","1":"minecraft:spruce_slab","2":"minecraft:birch_slab","3":"minecraft:jungle_slab","4":"minecraft:acacia_slab","5":"minecraft:dark_oak_slab"},"minecraft:leaves":{"0":"minecraft:oak_leaves","1":"minecraft:spruce_leaves","2":"minecraft:birch_leaves","3":"minecraft:jungle_leaves"},"minecraft:leaves2":{"0":"minecraft:acacia_leaves","1":"minecraft:dark_oak_leaves"},"minecraft:grass":{"0":"minecraft:grass_block"},"minecraft:scute":{"0":"minecraft:turtle_scute"},"minecraft:stone":{"0":"minecraft:stone","1":"minecraft:granite","2":"minecraft:polished_granite","3":"minecraft:diorite","4":"minecraft:polished_diorite","5":"minecraft:andesite","6":"minecraft:polished_andesite"},"minecraft:planks":{"0":"minecraft:oak_planks","1":"minecraft:spruce_planks","2":"minecraft:birch_planks","3":"minecraft:jungle_planks","4":"minecraft:acacia_planks","5":"minecraft:dark_oak_planks"},"minecraft:stained_hardened_clay":{"0":"minecraft:white_terracotta","1":"minecraft:orange_terracotta","2":"minecraft:magenta_terracotta","3":"minecraft:light_blue_terracotta","4":"minecraft:yellow_terracotta","5":"minecraft:lime_terracotta","6":"minecraft:pink_terracotta","7":"minecraft:gray_terracotta","8":"minecraft:light_gray_terracotta","9":"minecraft:cyan_terracotta","10":"minecraft:purple_terracotta","11":"minecraft:blue_terracotta","12":"minecraft:brown_terracotta","13":"minecraft:green_terracotta","14":"minecraft:red_terracotta","15":"minecraft:black_terracotta"},"minecraft:stained_glass":{"0":"minecraft:white_stained_glass","1":"minecraft:orange_stained_glass","2":"minecraft:magenta_stained_glass","3":"minecraft:light_blue_stained_glass","4":"minecraft:yellow_stained_glass","5":"minecraft:lime_stained_glass","6":"minecraft:pink_stained_glass","7":"minecraft:gray_stained_glass","8":"minecraft:light_gray_stained_glass","9":"minecraft:cyan_stained_glass","10":"minecraft:purple_stained_glass","11":"minecraft:blue_stained_glass","12":"minecraft:brown_stained_glass","13":"minecraft:green_stained_glass","14":"minecraft:red_stained_glass","15":"minecraft:black_stained_glass"},"minecraft:stained_glass_pane":{"0":"minecraft:white_stained_glass_pane","1":"minecraft:orange_stained_glass_pane","2":"minecraft:magenta_stained_glass_pane","3":"minecraft:light_blue_stained_glass_pane","4":"minecraft:yellow_stained_glass_pane","5":"minecraft:lime_stained_glass_pane","6":"minecraft:pink_stained_glass_pane","7":"minecraft:gray_stained_glass_pane","8":"minecraft:light_gray_stained_glass_pane","9":"minecraft:cyan_stained_glass_pane","10":"minecraft:purple_stained_glass_pane","11":"minecraft:blue_stained_glass_pane","12":"minecraft:brown_stained_glass_pane","13":"minecraft:green_stained_glass_pane","14":"minecraft:red_stained_glass_pane","15":"minecraft:black_stained_glass_pane"},"minecraft:concrete_powder":{"0":"minecraft:white_concrete_powder","1":"minecraft:orange_concrete_powder","2":"minecraft:magenta_concrete_powder","3":"minecraft:light_blue_concrete_powder","4":"minecraft:yellow_concrete_powder","5":"minecraft:lime_concrete_powder","6":"minecraft:pink_concrete_powder","7":"minecraft:gray_concrete_powder","8":"minecraft:light_gray_concrete_powder","9":"minecraft:cyan_concrete_powder","10":"minecraft:purple_concrete_powder","11":"minecraft:blue_concrete_powder","12":"minecraft:brown_concrete_powder","13":"minecraft:green_concrete_powder","14":"minecraft:red_concrete_powder","15":"minecraft:black_concrete_powder"},"minecraft:concrete":{"0":"minecraft:white_concrete","1":"minecraft:orange_concrete","2":"minecraft:magenta_concrete","3":"minecraft:light_blue_concrete","4":"minecraft:yellow_concrete","5":"minecraft:lime_concrete","6":"minecraft:pink_concrete","7":"minecraft:gray_concrete","8":"minecraft:light_gray_concrete","9":"minecraft:cyan_concrete","10":"minecraft:purple_concrete","11":"minecraft:blue_concrete","12":"minecraft:brown_concrete","13":"minecraft:green_concrete","14":"minecraft:red_concrete","15":"minecraft:black_concrete"},"minecraft:shulker_box":{"0":"minecraft:white_shulker_box","1":"minecraft:orange_shulker_box","2":"minecraft:magenta_shulker_box","3":"minecraft:light_blue_shulker_box","4":"minecraft:yellow_shulker_box","5":"minecraft:lime_shulker_box","6":"minecraft:pink_shulker_box","7":"minecraft:gray_shulker_box","8":"minecraft:light_gray_shulker_box","9":"minecraft:cyan_shulker_box","10":"minecraft:purple_shulker_box","11":"minecraft:blue_shulker_box","12":"minecraft:brown_shulker_box","13":"minecraft:green_shulker_box","14":"minecraft:red_shulker_box","15":"minecraft:black_shulker_box"},"minecraft:coral":{"0":"minecraft:tube_coral","1":"minecraft:brain_coral","2":"minecraft:bubble_coral","3":"minecraft:fire_coral","4":"minecraft:horn_coral","8":"minecraft:dead_tube_coral","9":"minecraft:dead_brain_coral","10":"minecraft:dead_bubble_coral","11":"minecraft:dead_fire_coral","12":"minecraft:dead_horn_coral"},"minecraft:carpet":{"0":"minecraft:white_carpet","1":"minecraft:orange_carpet","2":"minecraft:magenta_carpet","3":"minecraft:light_blue_carpet","4":"minecraft:yellow_carpet","5":"minecraft:lime_carpet","6":"minecraft:pink_carpet","7":"minecraft:gray_carpet","8":"minecraft:light_gray_carpet","9":"minecraft:cyan_carpet","10":"minecraft:purple_carpet","11":"minecraft:blue_carpet","12":"minecraft:brown_carpet","13":"minecraft:green_carpet","14":"minecraft:red_carpet","15":"minecraft:black_carpet"},"minecraft:fence":{"0":"minecraft:oak_fence","1":"minecraft:spruce_fence","2":"minecraft:birch_fence","3":"minecraft:jungle_fence","4":"minecraft:acacia_fence","5":"minecraft:dark_oak_fence"},"minecraft:log":{"0":"minecraft:oak_log","1":"minecraft:spruce_log","2":"minecraft:birch_log","3":"minecraft:jungle_log"},"minecraft:log2":{"0":"minecraft:acacia_log","1":"minecraft:dark_oak_log"},"minecraft:wool":{"0":"minecraft:white_wool","1":"minecraft:orange_wool","2":"minecraft:magenta_wool","3":"minecraft:light_blue_wool","4":"minecraft:yellow_wool","5":"minecraft:lime_wool","6":"minecraft:pink_wool","7":"minecraft:gray_wool","8":"minecraft:light_gray_wool","9":"minecraft:cyan_wool","10":"minecraft:purple_wool","11":"minecraft:blue_wool","12":"minecraft:brown_wool","13":"minecraft:green_wool","14":"minecraft:red_wool","15":"minecraft:black_wool"},"minecraft:banner_pattern":{"0":"minecraft:creeper_banner_pattern","1":"minecraft:skull_banner_pattern","2":"minecraft:flower_banner_pattern","3":"minecraft:mojang_banner_pattern","4":"minecraft:field_masoned_banner_pattern","5":"minecraft:bordure_indented_banner_pattern","6":"minecraft:piglin_banner_pattern","7":"minecraft:globe_banner_pattern"},"minecraft:boat":{"0":"minecraft:oak_boat","1":"minecraft:spruce_boat","2":"minecraft:birch_boat","3":"minecraft:jungle_boat","4":"minecraft:acacia_boat","5":"minecraft:dark_oak_boat","6":"minecraft:mangrove_boat"},"minecraft:bucket":{"0":"minecraft:bucket","1":"minecraft:milk_bucket","2":"minecraft:cod_bucket","3":"minecraft:salmon_bucket","4":"minecraft:tropical_fish_bucket","5":"minecraft:pufferfish_bucket","8":"minecraft:water_bucket","10":"minecraft:lava_bucket","11":"minecraft:powder_snow_bucket","12":"minecraft:axolotl_bucket","13":"minecraft:tadpole_bucket"},"minecraft:coal":{"0":"minecraft:coal","1":"minecraft:charcoal"},"minecraft:dye":{"0":"minecraft:ink_sac","1":"minecraft:red_dye","2":"minecraft:green_dye","3":"minecraft:cocoa_beans","4":"minecraft:lapis_lazuli","5":"minecraft:purple_dye","6":"minecraft:cyan_dye","7":"minecraft:light_gray_dye","8":"minecraft:gray_dye","9":"minecraft:pink_dye","10":"minecraft:lime_dye","11":"minecraft:yellow_dye","12":"minecraft:light_blue_dye","13":"minecraft:magenta_dye","14":"minecraft:orange_dye","15":"minecraft:bone_meal","16":"minecraft:black_dye","17":"minecraft:brown_dye","18":"minecraft:blue_dye","19":"minecraft:white_dye","20":"minecraft:glow_ink_sac"},"minecraft:spawn_egg":{"0":"","10":"minecraft:chicken_spawn_egg","11":"minecraft:cow_spawn_egg","12":"minecraft:pig_spawn_egg","13":"minecraft:sheep_spawn_egg","14":"minecraft:wolf_spawn_egg","15":"minecraft:villager_spawn_egg","16":"minecraft:mooshroom_spawn_egg","17":"minecraft:squid_spawn_egg","18":"minecraft:rabbit_spawn_egg","19":"minecraft:bat_spawn_egg","20":"minecraft:iron_golem_spawn_egg","21":"minecraft:snow_golem_spawn_egg","22":"minecraft:ocelot_spawn_egg","23":"minecraft:horse_spawn_egg","24":"minecraft:donkey_spawn_egg","25":"minecraft:mule_spawn_egg","26":"minecraft:skeleton_horse_spawn_egg","27":"minecraft:zombie_horse_spawn_egg","28":"minecraft:polar_bear_spawn_egg","29":"minecraft:llama_spawn_egg","30":"minecraft:parrot_spawn_egg","31":"minecraft:dolphin_spawn_egg","32":"minecraft:zombie_spawn_egg","33":"minecraft:creeper_spawn_egg","34":"minecraft:skeleton_spawn_egg","35":"minecraft:spider_spawn_egg","36":"minecraft:zombie_pigman_spawn_egg","37":"minecraft:slime_spawn_egg","38":"minecraft:enderman_spawn_egg","39":"minecraft:silverfish_spawn_egg","40":"minecraft:cave_spider_spawn_egg","41":"minecraft:ghast_spawn_egg","42":"minecraft:magma_cube_spawn_egg","43":"minecraft:blaze_spawn_egg","44":"minecraft:zombie_villager_spawn_egg","45":"minecraft:witch_spawn_egg","46":"minecraft:stray_spawn_egg","47":"minecraft:husk_spawn_egg","48":"minecraft:wither_skeleton_spawn_egg","49":"minecraft:guardian_spawn_egg","50":"minecraft:elder_guardian_spawn_egg","51":"minecraft:npc_spawn_egg","54":"minecraft:shulker_spawn_egg","55":"minecraft:endermite_spawn_egg","56":"minecraft:agent_spawn_egg","57":"minecraft:vindicator_spawn_egg","58":"minecraft:phantom_spawn_egg","59":"minecraft:ravager_spawn_egg","74":"minecraft:turtle_spawn_egg","75":"minecraft:cat_spawn_egg","104":"minecraft:evoker_spawn_egg","105":"minecraft:vex_spawn_egg","108":"minecraft:pufferfish_spawn_egg","109":"minecraft:salmon_spawn_egg","110":"minecraft:drowned_spawn_egg","111":"minecraft:tropical_fish_spawn_egg","112":"minecraft:cod_spawn_egg","113":"minecraft:panda_spawn_egg","114":"minecraft:pillager_spawn_egg","115":"minecraft:villager_spawn_egg","116":"minecraft:zombie_villager_spawn_egg","118":"minecraft:wandering_trader_spawn_egg","121":"minecraft:fox_spawn_egg","122":"minecraft:bee_spawn_egg","123":"minecraft:piglin_spawn_egg","124":"minecraft:hoglin_spawn_egg","125":"minecraft:strider_spawn_egg","126":"minecraft:zoglin_spawn_egg","127":"minecraft:piglin_brute_spawn_egg","128":"minecraft:goat_spawn_egg","129":"minecraft:glow_squid_spawn_egg","130":"minecraft:axolotl_spawn_egg","131":"minecraft:warden_spawn_egg","132":"minecraft:frog_spawn_egg","133":"minecraft:tadpole_spawn_egg","134":"minecraft:allay_spawn_egg","138":"minecraft:camel_spawn_egg","139":"minecraft:sniffer_spawn_egg","140":"minecraft:breeze_spawn_egg","142":"minecraft:armadillo_spawn_egg","144":"minecraft:bogged_spawn_egg"}} \ No newline at end of file +{"minecraft:skull":{"0":"minecraft:skeleton_skull","1":"minecraft:wither_skeleton_skull","2":"minecraft:zombie_head","3":"minecraft:player_head","4":"minecraft:creeper_head","5":"minecraft:dragon_head","6":"minecraft:piglin_head"},"minecraft:cobblestone_wall":{"0":"minecraft:cobblestone_wall","1":"minecraft:mossy_cobblestone_wall","2":"minecraft:granite_wall","3":"minecraft:diorite_wall","4":"minecraft:andesite_wall","5":"minecraft:sandstone_wall","6":"minecraft:brick_wall","7":"minecraft:stone_brick_wall","8":"minecraft:mossy_stone_brick_wall","9":"minecraft:nether_brick_wall","10":"minecraft:end_stone_brick_wall","11":"minecraft:prismarine_wall","12":"minecraft:red_sandstone_wall","13":"minecraft:red_nether_brick_wall"},"minecraft:sponge":{"0":"minecraft:sponge","1":"minecraft:wet_sponge"},"minecraft:purpur_block":{"0":"minecraft:purpur_block","1":"minecraft:deprecated_purpur_block_1","2":"minecraft:purpur_pillar","3":"minecraft:deprecated_purpur_block_2"},"minecraft:dirt":{"0":"minecraft:dirt","1":"minecraft:coarse_dirt"},"minecraft:yellow_flower":{"0":"minecraft:dandelion"},"minecraft:sand":{"0":"minecraft:sand","1":"minecraft:red_sand"},"minecraft:prismarine":{"0":"minecraft:prismarine","1":"minecraft:dark_prismarine","2":"minecraft:prismarine_bricks"},"minecraft:sandstone":{"0":"minecraft:sandstone","1":"minecraft:chiseled_sandstone","2":"minecraft:cut_sandstone","3":"minecraft:smooth_sandstone"},"minecraft:red_sandstone":{"0":"minecraft:red_sandstone","1":"minecraft:chiseled_red_sandstone","2":"minecraft:cut_red_sandstone","3":"minecraft:smooth_red_sandstone"},"minecraft:quartz_block":{"0":"minecraft:quartz_block","1":"minecraft:chiseled_quartz_block","2":"minecraft:quartz_pillar","3":"minecraft:smooth_quartz"},"minecraft:stone_block_slab2":{"0":"minecraft:red_sandstone_slab","1":"minecraft:purpur_slab","2":"minecraft:prismarine_slab","3":"minecraft:dark_prismarine_slab","4":"minecraft:prismarine_brick_slab","5":"minecraft:mossy_cobblestone_slab","6":"minecraft:smooth_sandstone_slab","7":"minecraft:red_nether_brick_slab"},"minecraft:stone_block_slab3":{"0":"minecraft:end_stone_brick_slab","1":"minecraft:smooth_red_sandstone_slab","2":"minecraft:polished_andesite_slab","3":"minecraft:andesite_slab","4":"minecraft:diorite_slab","5":"minecraft:polished_diorite_slab","6":"minecraft:granite_slab","7":"minecraft:polished_granite_slab"},"minecraft:stone_block_slab4":{"0":"minecraft:mossy_stone_brick_slab","1":"minecraft:smooth_quartz_slab","2":"minecraft:normal_stone_slab","3":"minecraft:cut_sandstone_slab","4":"minecraft:cut_red_sandstone_slab"},"minecraft:monster_egg":{"0":"minecraft:infested_stone","1":"minecraft:infested_cobblestone","2":"minecraft:infested_stone_bricks","3":"minecraft:infested_mossy_stone_bricks","4":"minecraft:infested_cracked_stone_bricks","5":"minecraft:infested_chiseled_stone_bricks"},"minecraft:stonebrick":{"0":"minecraft:stone_bricks","1":"minecraft:mossy_stone_bricks","2":"minecraft:cracked_stone_bricks","3":"minecraft:chiseled_stone_bricks"},"minecraft:anvil":{"0":"minecraft:anvil","4":"minecraft:chipped_anvil","8":"minecraft:damaged_anvil"},"minecraft:stone_block_slab":{"0":"minecraft:smooth_stone_slab","1":"minecraft:sandstone_slab","2":"minecraft:petrified_oak_slab","3":"minecraft:cobblestone_slab","4":"minecraft:brick_slab","5":"minecraft:stone_brick_slab","6":"minecraft:quartz_slab","7":"minecraft:nether_brick_slab"},"minecraft:coral_block":{"0":"minecraft:tube_coral_block","1":"minecraft:brain_coral_block","2":"minecraft:bubble_coral_block","3":"minecraft:fire_coral_block","4":"minecraft:horn_coral_block","8":"minecraft:dead_tube_coral_block","9":"minecraft:dead_brain_coral_block","10":"minecraft:dead_bubble_coral_block","11":"minecraft:dead_fire_coral_block","12":"minecraft:dead_horn_coral_block"},"minecraft:double_plant":{"0":"minecraft:sunflower","1":"minecraft:lilac","2":"minecraft:tall_grass","3":"minecraft:large_fern","4":"minecraft:rose_bush","5":"minecraft:peony"},"minecraft:tallgrass":{"1":"minecraft:short_grass","2":"minecraft:fern"},"minecraft:coral_fan_dead":{"0":"minecraft:dead_tube_coral_fan","1":"minecraft:dead_brain_coral_fan","2":"minecraft:dead_bubble_coral_fan","3":"minecraft:dead_fire_coral_fan","4":"minecraft:dead_horn_coral_fan"},"minecraft:coral_fan":{"0":"minecraft:tube_coral_fan","1":"minecraft:brain_coral_fan","2":"minecraft:bubble_coral_fan","3":"minecraft:fire_coral_fan","4":"minecraft:horn_coral_fan"},"minecraft:sapling":{"0":"minecraft:oak_sapling","1":"minecraft:spruce_sapling","2":"minecraft:birch_sapling","3":"minecraft:jungle_sapling","4":"minecraft:acacia_sapling","5":"minecraft:dark_oak_sapling"},"minecraft:red_flower":{"0":"minecraft:poppy","1":"minecraft:blue_orchid","2":"minecraft:allium","3":"minecraft:azure_bluet","4":"minecraft:red_tulip","5":"minecraft:orange_tulip","6":"minecraft:white_tulip","7":"minecraft:pink_tulip","8":"minecraft:oxeye_daisy","9":"minecraft:cornflower","10":"minecraft:lily_of_the_valley"},"minecraft:wood":{"0":"minecraft:oak_wood","1":"minecraft:spruce_wood","2":"minecraft:birch_wood","3":"minecraft:jungle_wood","4":"minecraft:acacia_wood","5":"minecraft:dark_oak_wood","8":"minecraft:stripped_oak_wood","9":"minecraft:stripped_spruce_wood","10":"minecraft:stripped_birch_wood","11":"minecraft:stripped_jungle_wood","12":"minecraft:stripped_acacia_wood","13":"minecraft:stripped_dark_oak_wood"},"minecraft:double_wooden_slab":{"0":"minecraft:oak_double_slab","1":"minecraft:spruce_double_slab","2":"minecraft:birch_double_slab","3":"minecraft:jungle_double_slab","4":"minecraft:acacia_double_slab","5":"minecraft:dark_oak_double_slab"},"minecraft:wooden_slab":{"0":"minecraft:oak_slab","1":"minecraft:spruce_slab","2":"minecraft:birch_slab","3":"minecraft:jungle_slab","4":"minecraft:acacia_slab","5":"minecraft:dark_oak_slab"},"minecraft:leaves":{"0":"minecraft:oak_leaves","1":"minecraft:spruce_leaves","2":"minecraft:birch_leaves","3":"minecraft:jungle_leaves"},"minecraft:leaves2":{"0":"minecraft:acacia_leaves","1":"minecraft:dark_oak_leaves"},"minecraft:grass":{"0":"minecraft:grass_block"},"minecraft:scute":{"0":"minecraft:turtle_scute"},"minecraft:stone":{"0":"minecraft:stone","1":"minecraft:granite","2":"minecraft:polished_granite","3":"minecraft:diorite","4":"minecraft:polished_diorite","5":"minecraft:andesite","6":"minecraft:polished_andesite"},"minecraft:planks":{"0":"minecraft:oak_planks","1":"minecraft:spruce_planks","2":"minecraft:birch_planks","3":"minecraft:jungle_planks","4":"minecraft:acacia_planks","5":"minecraft:dark_oak_planks"},"minecraft:stained_hardened_clay":{"0":"minecraft:white_terracotta","1":"minecraft:orange_terracotta","2":"minecraft:magenta_terracotta","3":"minecraft:light_blue_terracotta","4":"minecraft:yellow_terracotta","5":"minecraft:lime_terracotta","6":"minecraft:pink_terracotta","7":"minecraft:gray_terracotta","8":"minecraft:light_gray_terracotta","9":"minecraft:cyan_terracotta","10":"minecraft:purple_terracotta","11":"minecraft:blue_terracotta","12":"minecraft:brown_terracotta","13":"minecraft:green_terracotta","14":"minecraft:red_terracotta","15":"minecraft:black_terracotta"},"minecraft:stained_glass":{"0":"minecraft:white_stained_glass","1":"minecraft:orange_stained_glass","2":"minecraft:magenta_stained_glass","3":"minecraft:light_blue_stained_glass","4":"minecraft:yellow_stained_glass","5":"minecraft:lime_stained_glass","6":"minecraft:pink_stained_glass","7":"minecraft:gray_stained_glass","8":"minecraft:light_gray_stained_glass","9":"minecraft:cyan_stained_glass","10":"minecraft:purple_stained_glass","11":"minecraft:blue_stained_glass","12":"minecraft:brown_stained_glass","13":"minecraft:green_stained_glass","14":"minecraft:red_stained_glass","15":"minecraft:black_stained_glass"},"minecraft:stained_glass_pane":{"0":"minecraft:white_stained_glass_pane","1":"minecraft:orange_stained_glass_pane","2":"minecraft:magenta_stained_glass_pane","3":"minecraft:light_blue_stained_glass_pane","4":"minecraft:yellow_stained_glass_pane","5":"minecraft:lime_stained_glass_pane","6":"minecraft:pink_stained_glass_pane","7":"minecraft:gray_stained_glass_pane","8":"minecraft:light_gray_stained_glass_pane","9":"minecraft:cyan_stained_glass_pane","10":"minecraft:purple_stained_glass_pane","11":"minecraft:blue_stained_glass_pane","12":"minecraft:brown_stained_glass_pane","13":"minecraft:green_stained_glass_pane","14":"minecraft:red_stained_glass_pane","15":"minecraft:black_stained_glass_pane"},"minecraft:concrete_powder":{"0":"minecraft:white_concrete_powder","1":"minecraft:orange_concrete_powder","2":"minecraft:magenta_concrete_powder","3":"minecraft:light_blue_concrete_powder","4":"minecraft:yellow_concrete_powder","5":"minecraft:lime_concrete_powder","6":"minecraft:pink_concrete_powder","7":"minecraft:gray_concrete_powder","8":"minecraft:light_gray_concrete_powder","9":"minecraft:cyan_concrete_powder","10":"minecraft:purple_concrete_powder","11":"minecraft:blue_concrete_powder","12":"minecraft:brown_concrete_powder","13":"minecraft:green_concrete_powder","14":"minecraft:red_concrete_powder","15":"minecraft:black_concrete_powder"},"minecraft:concrete":{"0":"minecraft:white_concrete","1":"minecraft:orange_concrete","2":"minecraft:magenta_concrete","3":"minecraft:light_blue_concrete","4":"minecraft:yellow_concrete","5":"minecraft:lime_concrete","6":"minecraft:pink_concrete","7":"minecraft:gray_concrete","8":"minecraft:light_gray_concrete","9":"minecraft:cyan_concrete","10":"minecraft:purple_concrete","11":"minecraft:blue_concrete","12":"minecraft:brown_concrete","13":"minecraft:green_concrete","14":"minecraft:red_concrete","15":"minecraft:black_concrete"},"minecraft:shulker_box":{"0":"minecraft:white_shulker_box","1":"minecraft:orange_shulker_box","2":"minecraft:magenta_shulker_box","3":"minecraft:light_blue_shulker_box","4":"minecraft:yellow_shulker_box","5":"minecraft:lime_shulker_box","6":"minecraft:pink_shulker_box","7":"minecraft:gray_shulker_box","8":"minecraft:light_gray_shulker_box","9":"minecraft:cyan_shulker_box","10":"minecraft:purple_shulker_box","11":"minecraft:blue_shulker_box","12":"minecraft:brown_shulker_box","13":"minecraft:green_shulker_box","14":"minecraft:red_shulker_box","15":"minecraft:black_shulker_box"},"minecraft:coral":{"0":"minecraft:tube_coral","1":"minecraft:brain_coral","2":"minecraft:bubble_coral","3":"minecraft:fire_coral","4":"minecraft:horn_coral","8":"minecraft:dead_tube_coral","9":"minecraft:dead_brain_coral","10":"minecraft:dead_bubble_coral","11":"minecraft:dead_fire_coral","12":"minecraft:dead_horn_coral"},"minecraft:carpet":{"0":"minecraft:white_carpet","1":"minecraft:orange_carpet","2":"minecraft:magenta_carpet","3":"minecraft:light_blue_carpet","4":"minecraft:yellow_carpet","5":"minecraft:lime_carpet","6":"minecraft:pink_carpet","7":"minecraft:gray_carpet","8":"minecraft:light_gray_carpet","9":"minecraft:cyan_carpet","10":"minecraft:purple_carpet","11":"minecraft:blue_carpet","12":"minecraft:brown_carpet","13":"minecraft:green_carpet","14":"minecraft:red_carpet","15":"minecraft:black_carpet"},"minecraft:fence":{"0":"minecraft:oak_fence","1":"minecraft:spruce_fence","2":"minecraft:birch_fence","3":"minecraft:jungle_fence","4":"minecraft:acacia_fence","5":"minecraft:dark_oak_fence"},"minecraft:log":{"0":"minecraft:oak_log","1":"minecraft:spruce_log","2":"minecraft:birch_log","3":"minecraft:jungle_log"},"minecraft:log2":{"0":"minecraft:acacia_log","1":"minecraft:dark_oak_log"},"minecraft:wool":{"0":"minecraft:white_wool","1":"minecraft:orange_wool","2":"minecraft:magenta_wool","3":"minecraft:light_blue_wool","4":"minecraft:yellow_wool","5":"minecraft:lime_wool","6":"minecraft:pink_wool","7":"minecraft:gray_wool","8":"minecraft:light_gray_wool","9":"minecraft:cyan_wool","10":"minecraft:purple_wool","11":"minecraft:blue_wool","12":"minecraft:brown_wool","13":"minecraft:green_wool","14":"minecraft:red_wool","15":"minecraft:black_wool"},"minecraft:banner_pattern":{"0":"minecraft:creeper_banner_pattern","1":"minecraft:skull_banner_pattern","2":"minecraft:flower_banner_pattern","3":"minecraft:mojang_banner_pattern","4":"minecraft:field_masoned_banner_pattern","5":"minecraft:bordure_indented_banner_pattern","6":"minecraft:piglin_banner_pattern","7":"minecraft:globe_banner_pattern"},"minecraft:boat":{"0":"minecraft:oak_boat","1":"minecraft:spruce_boat","2":"minecraft:birch_boat","3":"minecraft:jungle_boat","4":"minecraft:acacia_boat","5":"minecraft:dark_oak_boat","6":"minecraft:mangrove_boat"},"minecraft:bucket":{"0":"minecraft:bucket","1":"minecraft:milk_bucket","2":"minecraft:cod_bucket","3":"minecraft:salmon_bucket","4":"minecraft:tropical_fish_bucket","5":"minecraft:pufferfish_bucket","8":"minecraft:water_bucket","10":"minecraft:lava_bucket","11":"minecraft:powder_snow_bucket","12":"minecraft:axolotl_bucket","13":"minecraft:tadpole_bucket"},"minecraft:coal":{"0":"minecraft:coal","1":"minecraft:charcoal"},"minecraft:dye":{"0":"minecraft:ink_sac","1":"minecraft:red_dye","2":"minecraft:green_dye","3":"minecraft:cocoa_beans","4":"minecraft:lapis_lazuli","5":"minecraft:purple_dye","6":"minecraft:cyan_dye","7":"minecraft:light_gray_dye","8":"minecraft:gray_dye","9":"minecraft:pink_dye","10":"minecraft:lime_dye","11":"minecraft:yellow_dye","12":"minecraft:light_blue_dye","13":"minecraft:magenta_dye","14":"minecraft:orange_dye","15":"minecraft:bone_meal","16":"minecraft:black_dye","17":"minecraft:brown_dye","18":"minecraft:blue_dye","19":"minecraft:white_dye","20":"minecraft:glow_ink_sac"},"minecraft:spawn_egg":{"0":"","10":"minecraft:chicken_spawn_egg","11":"minecraft:cow_spawn_egg","12":"minecraft:pig_spawn_egg","13":"minecraft:sheep_spawn_egg","14":"minecraft:wolf_spawn_egg","15":"minecraft:villager_spawn_egg","16":"minecraft:mooshroom_spawn_egg","17":"minecraft:squid_spawn_egg","18":"minecraft:rabbit_spawn_egg","19":"minecraft:bat_spawn_egg","20":"minecraft:iron_golem_spawn_egg","21":"minecraft:snow_golem_spawn_egg","22":"minecraft:ocelot_spawn_egg","23":"minecraft:horse_spawn_egg","24":"minecraft:donkey_spawn_egg","25":"minecraft:mule_spawn_egg","26":"minecraft:skeleton_horse_spawn_egg","27":"minecraft:zombie_horse_spawn_egg","28":"minecraft:polar_bear_spawn_egg","29":"minecraft:llama_spawn_egg","30":"minecraft:parrot_spawn_egg","31":"minecraft:dolphin_spawn_egg","32":"minecraft:zombie_spawn_egg","33":"minecraft:creeper_spawn_egg","34":"minecraft:skeleton_spawn_egg","35":"minecraft:spider_spawn_egg","36":"minecraft:zombie_pigman_spawn_egg","37":"minecraft:slime_spawn_egg","38":"minecraft:enderman_spawn_egg","39":"minecraft:silverfish_spawn_egg","40":"minecraft:cave_spider_spawn_egg","41":"minecraft:ghast_spawn_egg","42":"minecraft:magma_cube_spawn_egg","43":"minecraft:blaze_spawn_egg","44":"minecraft:zombie_villager_spawn_egg","45":"minecraft:witch_spawn_egg","46":"minecraft:stray_spawn_egg","47":"minecraft:husk_spawn_egg","48":"minecraft:wither_skeleton_spawn_egg","49":"minecraft:guardian_spawn_egg","50":"minecraft:elder_guardian_spawn_egg","51":"minecraft:npc_spawn_egg","54":"minecraft:shulker_spawn_egg","55":"minecraft:endermite_spawn_egg","56":"minecraft:agent_spawn_egg","57":"minecraft:vindicator_spawn_egg","58":"minecraft:phantom_spawn_egg","59":"minecraft:ravager_spawn_egg","74":"minecraft:turtle_spawn_egg","75":"minecraft:cat_spawn_egg","104":"minecraft:evoker_spawn_egg","105":"minecraft:vex_spawn_egg","108":"minecraft:pufferfish_spawn_egg","109":"minecraft:salmon_spawn_egg","110":"minecraft:drowned_spawn_egg","111":"minecraft:tropical_fish_spawn_egg","112":"minecraft:cod_spawn_egg","113":"minecraft:panda_spawn_egg","114":"minecraft:pillager_spawn_egg","115":"minecraft:villager_spawn_egg","116":"minecraft:zombie_villager_spawn_egg","118":"minecraft:wandering_trader_spawn_egg","121":"minecraft:fox_spawn_egg","122":"minecraft:bee_spawn_egg","123":"minecraft:piglin_spawn_egg","124":"minecraft:hoglin_spawn_egg","125":"minecraft:strider_spawn_egg","126":"minecraft:zoglin_spawn_egg","127":"minecraft:piglin_brute_spawn_egg","128":"minecraft:goat_spawn_egg","129":"minecraft:glow_squid_spawn_egg","130":"minecraft:axolotl_spawn_egg","131":"minecraft:warden_spawn_egg","132":"minecraft:frog_spawn_egg","133":"minecraft:tadpole_spawn_egg","134":"minecraft:allay_spawn_egg","138":"minecraft:camel_spawn_egg","139":"minecraft:sniffer_spawn_egg","140":"minecraft:breeze_spawn_egg","142":"minecraft:armadillo_spawn_egg","144":"minecraft:bogged_spawn_egg"}} \ No newline at end of file diff --git a/src/main/resources/lang/ara/lang.ini b/src/main/resources/lang/ara/lang.ini index 1765758e2f4..80db7ed4895 100644 --- a/src/main/resources/lang/ara/lang.ini +++ b/src/main/resources/lang/ara/lang.ini @@ -143,9 +143,7 @@ commands.unbanip.invalid=لقد ادخلت IP غير معروف commands.unbanip.success=فك حظر IP ADDRESS {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=حافظ العالم الاوتوماتيكي شغل commands.save.disabled=حافظ العالم الاوتوماتيكي اطفئ commands.save.start=قيد الحفظ... @@ -280,8 +278,7 @@ nukkit.command.op.description=Gives the specified player operator status nukkit.command.unban.player.description=Allows the specified player to use this server nukkit.command.unban.ip.description=Allows the specified IP address to use this server nukkit.command.save.description=Saves the server to disk -nukkit.command.saveoff.description=Disables server autosaving -nukkit.command.saveon.description=Enables server autosaving + nukkit.command.say.description=Broadcasts the given message as the sender nukkit.command.seed.description=Shows the world seed nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. diff --git a/src/main/resources/lang/bra/lang.ini b/src/main/resources/lang/bra/lang.ini index 81d91137446..be8d745cc0a 100644 --- a/src/main/resources/lang/bra/lang.ini +++ b/src/main/resources/lang/bra/lang.ini @@ -152,9 +152,7 @@ commands.unbanip.invalid=Você digitou um IP inválido commands.unbanip.success=IP desbanido: {%0} commands.unbanip.usage=/pardon-ip <ip> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Salvamento automático ligado commands.save.disabled=Salvamento automático desligado commands.save.start=Salvando... diff --git a/src/main/resources/lang/chs/lang.ini b/src/main/resources/lang/chs/lang.ini index f5a5628f875..f05fce1a4d5 100644 --- a/src/main/resources/lang/chs/lang.ini +++ b/src/main/resources/lang/chs/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=无效的 IP 地址 commands.unbanip.success=已解封 IP 地址 {%0} commands.unbanip.usage=/pardon-ip <IP 地址> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=自动保存已启用 commands.save.disabled=自动保存已禁用 commands.save.start=正在保存游戏 diff --git a/src/main/resources/lang/cht/lang.ini b/src/main/resources/lang/cht/lang.ini index 381c29989fe..e2a2bee0258 100644 --- a/src/main/resources/lang/cht/lang.ini +++ b/src/main/resources/lang/cht/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=無效的 IP 位址 commands.unbanip.success=已解除封鎖 IP {%0} commands.unbanip.usage=/pardon-ip <IP 位址> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=自動存檔已啟用 commands.save.disabled=自動存檔已停用 commands.save.start=儲存遊戲中 diff --git a/src/main/resources/lang/cze/lang.ini b/src/main/resources/lang/cze/lang.ini index bd36b8945ed..8f2ace44c0b 100644 --- a/src/main/resources/lang/cze/lang.ini +++ b/src/main/resources/lang/cze/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Zadal jsi nespravnoj IP adresu commands.unbanip.success=IP adresa odbanovana {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Zapinam autosave sveta commands.save.disabled=Vypinam autosavd sveta commands.save.start=Ukladam... diff --git a/src/main/resources/lang/deu/lang.ini b/src/main/resources/lang/deu/lang.ini index 42550b11411..8ed69cfb2b9 100644 --- a/src/main/resources/lang/deu/lang.ini +++ b/src/main/resources/lang/deu/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Ungültige IP-Adresse! commands.unbanip.success=Die IP-Adresse {%0} wurde entbannt! commands.unbanip.usage=/pardon-ip <IP> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Automatisches Welt-Speichern wurde aktiviert! commands.save.disabled=Automatisches Welt-Speichern wurde deaktiviert! commands.save.start=Speichere... diff --git a/src/main/resources/lang/eng/lang.ini b/src/main/resources/lang/eng/lang.ini index bdb21aded5a..dd2b6b62c53 100644 --- a/src/main/resources/lang/eng/lang.ini +++ b/src/main/resources/lang/eng/lang.ini @@ -153,9 +153,7 @@ commands.unbanip.invalid=You have entered an invalid IP address commands.unbanip.success=Unbanned IP address {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Turned on world auto-saving commands.save.disabled=Turned off world auto-saving commands.save.start=Saving... @@ -311,8 +309,7 @@ nukkit.command.op.description=Gives the specified player operator status nukkit.command.unban.player.description=Allows the specified player to use this server nukkit.command.unban.ip.description=Allows the specified IP address to use this server nukkit.command.save.description=Saves the server to disk -nukkit.command.saveoff.description=Disables server autosaving -nukkit.command.saveon.description=Enables server autosaving + nukkit.command.say.description=Broadcasts the given message as the sender nukkit.command.seed.description=Shows the world seed nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. diff --git a/src/main/resources/lang/fin/lang.ini b/src/main/resources/lang/fin/lang.ini index 62701107dc6..aabc20af6e5 100644 --- a/src/main/resources/lang/fin/lang.ini +++ b/src/main/resources/lang/fin/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Virheellinen IP osoite commands.unbanip.success=Poistettiin esto osoitteelta {%0} commands.unbanip.usage=/pardon-ip <osoite> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Otettiin maailman käyttöön automaattinen tallennus commands.save.disabled=Poistettiin maailman automaattinen tallennus käytöstä commands.save.start=Tallennetaan... diff --git a/src/main/resources/lang/idn/lang.ini b/src/main/resources/lang/idn/lang.ini index 893815fe0ad..4921419474f 100644 --- a/src/main/resources/lang/idn/lang.ini +++ b/src/main/resources/lang/idn/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Kamu memasukan alamat IP yang salah commands.unbanip.success=Unbanned IP address {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Turned on world auto-saving commands.save.disabled=Turned off world auto-saving commands.save.start=Saving... @@ -307,8 +305,7 @@ nukkit.command.op.description=Gives the specified player operator status nukkit.command.unban.player.description=Allows the specified player to use this server nukkit.command.unban.ip.description=Allows the specified IP address to use this server nukkit.command.save.description=Saves the server to disk -nukkit.command.saveoff.description=Disables server autosaving -nukkit.command.saveon.description=Enables server autosaving + nukkit.command.say.description=Broadcasts the given message as the sender nukkit.command.seed.description=Shows the world seed nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. diff --git a/src/main/resources/lang/jpn/lang.ini b/src/main/resources/lang/jpn/lang.ini index 101bc136080..5d2719b115a 100644 --- a/src/main/resources/lang/jpn/lang.ini +++ b/src/main/resources/lang/jpn/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=無効なIPアドレスです commands.unbanip.success=IPアドレス {%0} のBANが解除されました commands.unbanip.usage=/pardon-ip <IPアドレス> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=ワールドの自動保存を有効にしました commands.save.disabled=ワールドの自動保存を無効にしました commands.save.start=保存中… diff --git a/src/main/resources/lang/kor/lang.ini b/src/main/resources/lang/kor/lang.ini index 54d37a2f3f6..0e51de8e7da 100644 --- a/src/main/resources/lang/kor/lang.ini +++ b/src/main/resources/lang/kor/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=입력된 IP 주소가 유효하지 않습니다 commands.unbanip.success=IP 주소 {%0}의 차단을 해제했습니다 commands.unbanip.usage=/pardon-ip <아이피> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=월드 자동 저장을 켰습니다 commands.save.disabled=월드 자동 저장을 껐습니다 commands.save.start=저장 중... diff --git a/src/main/resources/lang/ltu/lang.ini b/src/main/resources/lang/ltu/lang.ini index f8a23121eb8..7e9600628f1 100644 --- a/src/main/resources/lang/ltu/lang.ini +++ b/src/main/resources/lang/ltu/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Įvedėte blogą IP adresą commands.unbanip.success=Atblokuotas IP adresas {%0} commands.unbanip.usage=/pardon-ip <IP> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Įjungtas pasaulio auto išsaugojimas commands.save.disabled=Išjungtas pasaulio auto išsaugojimas commands.save.start=Išsaugoma... diff --git a/src/main/resources/lang/pol/lang.ini b/src/main/resources/lang/pol/lang.ini index 159c8c15a3c..91d50e7e83a 100644 --- a/src/main/resources/lang/pol/lang.ini +++ b/src/main/resources/lang/pol/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Wprowadziles niepoprawny adres IP commands.unbanip.success=Odbanowano adres IP {%0} commands.unbanip.usage=/pardon-ip <adres> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Wlaczono automatyczne zapisywanie swiata commands.save.disabled=Wylaczono automatyczne zapisywania swiata commands.save.start=Zapisywanie... diff --git a/src/main/resources/lang/rus/lang.ini b/src/main/resources/lang/rus/lang.ini index 86704e4e878..57d8717e47e 100644 --- a/src/main/resources/lang/rus/lang.ini +++ b/src/main/resources/lang/rus/lang.ini @@ -153,9 +153,7 @@ commands.unbanip.invalid=Вы ввели неверный IP адрес commands.unbanip.success=Разблокирован IP адрес {%0} commands.unbanip.usage=/pardon-ip <IP-адрес> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Автосохранение включено commands.save.disabled=Автосохранение выключено commands.save.start=Сохранение... diff --git a/src/main/resources/lang/spa/lang.ini b/src/main/resources/lang/spa/lang.ini index fbb23885bb8..22bf9d71428 100644 --- a/src/main/resources/lang/spa/lang.ini +++ b/src/main/resources/lang/spa/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=Has introducido una IP no válida commands.unbanip.success=IP {%0} desbloqueada commands.unbanip.usage=/pardon-ip <IP> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Auto-guardar mundo activado commands.save.disabled=Auto-guardado de mundo desactivado commands.save.start=Guardando... diff --git a/src/main/resources/lang/tur/lang.ini b/src/main/resources/lang/tur/lang.ini index ea7d5ae4b43..dae0ec95068 100644 --- a/src/main/resources/lang/tur/lang.ini +++ b/src/main/resources/lang/tur/lang.ini @@ -150,9 +150,7 @@ commands.unbanip.invalid=You have entered an invalid IP address commands.unbanip.success=Unbanned IP address {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Turned on world auto-saving commands.save.disabled=Turned off world auto-saving commands.save.start=Saving... @@ -307,8 +305,7 @@ nukkit.command.op.description=Gives the specified player operator status nukkit.command.unban.player.description=Allows the specified player to use this server nukkit.command.unban.ip.description=Allows the specified IP address to use this server nukkit.command.save.description=Saves the server to disk -nukkit.command.saveoff.description=Disables server autosaving -nukkit.command.saveon.description=Enables server autosaving + nukkit.command.say.description=Broadcasts the given message as the sender nukkit.command.seed.description=Shows the world seed nukkit.command.setworldspawn.description=Sets a worlds's spawn point. If no coordinates are specified, the player's coordinates will be used. diff --git a/src/main/resources/lang/ukr/lang.ini b/src/main/resources/lang/ukr/lang.ini index 4a669877127..fdc98cdfc5f 100644 --- a/src/main/resources/lang/ukr/lang.ini +++ b/src/main/resources/lang/ukr/lang.ini @@ -154,9 +154,7 @@ commands.unbanip.invalid=Ви ввели неправильну IP адресу commands.unbanip.success=Розбанено IP адресу {%0} commands.unbanip.usage=/pardon-ip <address> -commands.save.usage=/save-all -commands.save-on.usage=/save-on -commands.save-off.usage=/save-off +commands.save.usage=/save [on|off|hold|resume] commands.save.enabled=Автоматичне збереження ввімкнено commands.save.disabled=Автоматичне збереження вимкнено commands.save.start=Збереження... diff --git a/src/main/resources/legacy_item_ids.json b/src/main/resources/legacy_item_ids.json index 19884dd0911..28c3118780b 100644 --- a/src/main/resources/legacy_item_ids.json +++ b/src/main/resources/legacy_item_ids.json @@ -1 +1 @@ -{"minecraft:mangrove_double_slab":-499,"minecraft:stripped_mangrove_wood":-498,"minecraft:mangrove_wood":-497,"minecraft:mangrove_trapdoor":-496,"minecraft:mangrove_wall_sign":-495,"minecraft:mangrove_standing_sign":-494,"minecraft:mangrove_fence_gate":-492,"minecraft:mangrove_fence":-491,"minecraft:mangrove_pressure_plate":-490,"minecraft:mangrove_slab":-489,"minecraft:mangrove_stairs":-488,"minecraft:mangrove_button":-487,"minecraft:mangrove_planks":-486,"minecraft:stripped_mangrove_log":-485,"minecraft:mangrove_log":-484,"minecraft:muddy_mangrove_roots":-483,"minecraft:mangrove_roots":-482,"minecraft:mud_brick_wall":-481,"minecraft:mud_brick_stairs":-480,"minecraft:mud_brick_double_slab":-479,"minecraft:mud_brick_slab":-478,"minecraft:packed_mud":-477,"minecraft:mud_bricks":-475,"minecraft:mangrove_propagule":-474,"minecraft:mud":-473,"minecraft:mangrove_leaves":-472,"minecraft:ochre_froglight":-471,"minecraft:verdant_froglight":-470,"minecraft:pearlescent_froglight":-469,"minecraft:frog_spawn":-468,"minecraft:reinforced_deepslate":-466,"minecraft:client_request_placeholder_block":-465,"minecraft:sculk_shrieker":-461,"minecraft:sculk_catalyst":-460,"minecraft:sculk_vein":-459,"minecraft:sculk":-458,"minecraft:infested_deepslate":-454,"minecraft:raw_gold_block":-453,"minecraft:raw_copper_block":-452,"minecraft:raw_iron_block":-451,"minecraft:waxed_oxidized_double_cut_copper_slab":-450,"minecraft:waxed_oxidized_cut_copper_slab":-449,"minecraft:waxed_oxidized_cut_copper_stairs":-448,"minecraft:waxed_oxidized_cut_copper":-447,"minecraft:waxed_oxidized_copper":-446,"minecraft:black_candle_cake":-445,"minecraft:red_candle_cake":-444,"minecraft:green_candle_cake":-443,"minecraft:brown_candle_cake":-442,"minecraft:blue_candle_cake":-441,"minecraft:purple_candle_cake":-440,"minecraft:cyan_candle_cake":-439,"minecraft:light_gray_candle_cake":-438,"minecraft:gray_candle_cake":-437,"minecraft:pink_candle_cake":-436,"minecraft:lime_candle_cake":-435,"minecraft:yellow_candle_cake":-434,"minecraft:light_blue_candle_cake":-433,"minecraft:magenta_candle_cake":-432,"minecraft:orange_candle_cake":-431,"minecraft:white_candle_cake":-430,"minecraft:candle_cake":-429,"minecraft:black_candle":-428,"minecraft:red_candle":-427,"minecraft:green_candle":-426,"minecraft:brown_candle":-425,"minecraft:blue_candle":-424,"minecraft:purple_candle":-423,"minecraft:cyan_candle":-422,"minecraft:light_gray_candle":-421,"minecraft:gray_candle":-420,"minecraft:pink_candle":-419,"minecraft:lime_candle":-418,"minecraft:yellow_candle":-417,"minecraft:light_blue_candle":-416,"minecraft:magenta_candle":-415,"minecraft:orange_candle":-414,"minecraft:white_candle":-413,"minecraft:candle":-412,"minecraft:glow_lichen":-411,"minecraft:cracked_deepslate_bricks":-410,"minecraft:cracked_deepslate_tiles":-409,"minecraft:deepslate_copper_ore":-408,"minecraft:deepslate_emerald_ore":-407,"minecraft:deepslate_coal_ore":-406,"minecraft:deepslate_diamond_ore":-405,"minecraft:lit_deepslate_redstone_ore":-404,"minecraft:deepslate_redstone_ore":-403,"minecraft:deepslate_gold_ore":-402,"minecraft:deepslate_iron_ore":-401,"minecraft:deepslate_lapis_ore":-400,"minecraft:deepslate_brick_double_slab":-399,"minecraft:deepslate_tile_double_slab":-398,"minecraft:polished_deepslate_double_slab":-397,"minecraft:cobbled_deepslate_double_slab":-396,"minecraft:chiseled_deepslate":-395,"minecraft:deepslate_brick_wall":-394,"minecraft:deepslate_brick_stairs":-393,"minecraft:deepslate_brick_slab":-392,"minecraft:deepslate_bricks":-391,"minecraft:deepslate_tile_wall":-390,"minecraft:deepslate_tile_stairs":-389,"minecraft:deepslate_tile_slab":-388,"minecraft:deepslate_tiles":-387,"minecraft:polished_deepslate_wall":-386,"minecraft:polished_deepslate_stairs":-385,"minecraft:polished_deepslate_slab":-384,"minecraft:polished_deepslate":-383,"minecraft:cobbled_deepslate_wall":-382,"minecraft:cobbled_deepslate_stairs":-381,"minecraft:cobbled_deepslate_slab":-380,"minecraft:cobbled_deepslate":-379,"minecraft:deepslate":-378,"minecraft:smooth_basalt":-377,"minecraft:cave_vines_head_with_berries":-376,"minecraft:cave_vines_body_with_berries":-375,"minecraft:waxed_weathered_double_cut_copper_slab":-374,"minecraft:waxed_exposed_double_cut_copper_slab":-373,"minecraft:waxed_double_cut_copper_slab":-372,"minecraft:oxidized_double_cut_copper_slab":-371,"minecraft:weathered_double_cut_copper_slab":-370,"minecraft:exposed_double_cut_copper_slab":-369,"minecraft:double_cut_copper_slab":-368,"minecraft:waxed_weathered_cut_copper_slab":-367,"minecraft:waxed_exposed_cut_copper_slab":-366,"minecraft:waxed_cut_copper_slab":-365,"minecraft:oxidized_cut_copper_slab":-364,"minecraft:weathered_cut_copper_slab":-363,"minecraft:exposed_cut_copper_slab":-362,"minecraft:cut_copper_slab":-361,"minecraft:waxed_weathered_cut_copper_stairs":-360,"minecraft:waxed_exposed_cut_copper_stairs":-359,"minecraft:waxed_cut_copper_stairs":-358,"minecraft:oxidized_cut_copper_stairs":-357,"minecraft:weathered_cut_copper_stairs":-356,"minecraft:exposed_cut_copper_stairs":-355,"minecraft:cut_copper_stairs":-354,"minecraft:waxed_weathered_cut_copper":-353,"minecraft:waxed_exposed_cut_copper":-352,"minecraft:waxed_cut_copper":-351,"minecraft:oxidized_cut_copper":-350,"minecraft:weathered_cut_copper":-349,"minecraft:exposed_cut_copper":-348,"minecraft:cut_copper":-347,"minecraft:waxed_weathered_copper":-346,"minecraft:waxed_exposed_copper":-345,"minecraft:waxed_copper":-344,"minecraft:oxidized_copper":-343,"minecraft:weathered_copper":-342,"minecraft:exposed_copper":-341,"minecraft:copper_block":-340,"minecraft:item.glow_frame":-339,"minecraft:flowering_azalea":-338,"minecraft:azalea":-337,"minecraft:small_dripleaf_block":-336,"minecraft:moss_carpet":-335,"minecraft:tinted_glass":-334,"minecraft:tuff":-333,"minecraft:small_amethyst_bud":-332,"minecraft:medium_amethyst_bud":-331,"minecraft:large_amethyst_bud":-330,"minecraft:amethyst_cluster":-329,"minecraft:budding_amethyst":-328,"minecraft:amethyst_block":-327,"minecraft:calcite":-326,"minecraft:azalea_leaves_flowered":-325,"minecraft:azalea_leaves":-324,"minecraft:big_dripleaf":-323,"minecraft:cave_vines":-322,"minecraft:spore_blossom":-321,"minecraft:moss_block":-320,"minecraft:hanging_roots":-319,"minecraft:dirt_with_roots":-318,"minecraft:dripstone_block":-317,"minecraft:lightning_rod":-312,"minecraft:copper_ore":-311,"minecraft:pointed_dripstone":-308,"minecraft:sculk_sensor":-307,"minecraft:powder_snow":-306,"minecraft:quartz_bricks":-304,"minecraft:cracked_nether_bricks":-303,"minecraft:chiseled_nether_bricks":-302,"minecraft:stripped_warped_hyphae":-301,"minecraft:stripped_crimson_hyphae":-300,"minecraft:crimson_hyphae":-299,"minecraft:warped_hyphae":-298,"minecraft:polished_blackstone_wall":-297,"minecraft:polished_blackstone_button":-296,"minecraft:polished_blackstone_pressure_plate":-295,"minecraft:polished_blackstone_double_slab":-294,"minecraft:polished_blackstone_slab":-293,"minecraft:polished_blackstone_stairs":-292,"minecraft:polished_blackstone":-291,"minecraft:item.soul_campfire":-290,"minecraft:crying_obsidian":-289,"minecraft:nether_gold_ore":-288,"minecraft:twisting_vines":-287,"minecraft:item.chain":-286,"minecraft:polished_blackstone_brick_double_slab":-285,"minecraft:polished_blackstone_brick_slab":-284,"minecraft:blackstone_double_slab":-283,"minecraft:blackstone_slab":-282,"minecraft:gilded_blackstone":-281,"minecraft:cracked_polished_blackstone_bricks":-280,"minecraft:chiseled_polished_blackstone":-279,"minecraft:polished_blackstone_brick_wall":-278,"minecraft:blackstone_wall":-277,"minecraft:blackstone_stairs":-276,"minecraft:polished_blackstone_brick_stairs":-275,"minecraft:polished_blackstone_bricks":-274,"minecraft:blackstone":-273,"minecraft:respawn_anchor":-272,"minecraft:ancient_debris":-271,"minecraft:netherite_block":-270,"minecraft:soul_lantern":-269,"minecraft:soul_torch":-268,"minecraft:warped_double_slab":-267,"minecraft:crimson_double_slab":-266,"minecraft:warped_slab":-265,"minecraft:crimson_slab":-264,"minecraft:warped_pressure_plate":-263,"minecraft:crimson_pressure_plate":-262,"minecraft:warped_button":-261,"minecraft:crimson_button":-260,"minecraft:warped_fence_gate":-259,"minecraft:crimson_fence_gate":-258,"minecraft:warped_fence":-257,"minecraft:crimson_fence":-256,"minecraft:warped_stairs":-255,"minecraft:crimson_stairs":-254,"minecraft:warped_wall_sign":-253,"minecraft:crimson_wall_sign":-252,"minecraft:warped_standing_sign":-251,"minecraft:crimson_standing_sign":-250,"minecraft:warped_trapdoor":-247,"minecraft:crimson_trapdoor":-246,"minecraft:item.warped_door":-245,"minecraft:item.crimson_door":-244,"minecraft:warped_planks":-243,"minecraft:crimson_planks":-242,"minecraft:stripped_warped_stem":-241,"minecraft:stripped_crimson_stem":-240,"minecraft:target":-239,"minecraft:item.nether_sprouts":-238,"minecraft:soul_fire":-237,"minecraft:soul_soil":-236,"minecraft:polished_basalt":-235,"minecraft:basalt":-234,"minecraft:warped_nylium":-233,"minecraft:crimson_nylium":-232,"minecraft:weeping_vines":-231,"minecraft:shroomlight":-230,"minecraft:warped_fungus":-229,"minecraft:crimson_fungus":-228,"minecraft:warped_wart_block":-227,"minecraft:warped_stem":-226,"minecraft:crimson_stem":-225,"minecraft:warped_roots":-224,"minecraft:crimson_roots":-223,"minecraft:lodestone":-222,"minecraft:honeycomb_block":-221,"minecraft:honey_block":-220,"minecraft:beehive":-219,"minecraft:bee_nest":-218,"minecraft:stickypistonarmcollision":-217,"minecraft:sticky_piston_arm_collision":-217,"minecraft:wither_rose":-216,"minecraft:light_block":-215,"minecraft:lit_blast_furnace":-214,"minecraft:composter":-213,"minecraft:wood":-212,"minecraft:jigsaw":-211,"minecraft:lava_cauldron":-210,"minecraft:item.campfire":-209,"minecraft:lantern":-208,"minecraft:sweet_berry_bush":-207,"minecraft:bell":-206,"minecraft:loom":-204,"minecraft:barrel":-203,"minecraft:smithing_table":-202,"minecraft:fletching_table":-201,"minecraft:cartography_table":-200,"minecraft:lit_smoker":-199,"minecraft:smoker":-198,"minecraft:stonecutter_block":-197,"minecraft:blast_furnace":-196,"minecraft:grindstone":-195,"minecraft:lectern":-194,"minecraft:darkoak_wall_sign":-193,"minecraft:darkoak_standing_sign":-192,"minecraft:acacia_wall_sign":-191,"minecraft:acacia_standing_sign":-190,"minecraft:jungle_wall_sign":-189,"minecraft:jungle_standing_sign":-188,"minecraft:birch_wall_sign":-187,"minecraft:birch_standing_sign":-186,"minecraft:smooth_quartz_stairs":-185,"minecraft:red_nether_brick_stairs":-184,"minecraft:smooth_stone":-183,"minecraft:spruce_wall_sign":-182,"minecraft:spruce_standing_sign":-181,"minecraft:normal_stone_stairs":-180,"minecraft:mossy_cobblestone_stairs":-179,"minecraft:end_brick_stairs":-178,"minecraft:smooth_sandstone_stairs":-177,"minecraft:smooth_red_sandstone_stairs":-176,"minecraft:mossy_stone_brick_stairs":-175,"minecraft:polished_andesite_stairs":-174,"minecraft:polished_diorite_stairs":-173,"minecraft:polished_granite_stairs":-172,"minecraft:andesite_stairs":-171,"minecraft:diorite_stairs":-170,"minecraft:granite_stairs":-169,"minecraft:real_double_stone_slab4":-168,"minecraft:double_stone_block_slab4":-168,"minecraft:real_double_stone_slab3":-167,"minecraft:double_stone_block_slab3":-167,"minecraft:double_stone_slab4":-166,"minecraft:stone_block_slab4":-166,"minecraft:scaffolding":-165,"minecraft:bamboo_sapling":-164,"minecraft:bamboo":-163,"minecraft:double_stone_slab3":-162,"minecraft:stone_block_slab3":-162,"minecraft:barrier":-161,"minecraft:bubble_column":-160,"minecraft:turtle_egg":-159,"minecraft:air":-158,"minecraft:conduit":-157,"minecraft:sea_pickle":-156,"minecraft:carved_pumpkin":-155,"minecraft:spruce_pressure_plate":-154,"minecraft:jungle_pressure_plate":-153,"minecraft:dark_oak_pressure_plate":-152,"minecraft:birch_pressure_plate":-151,"minecraft:acacia_pressure_plate":-150,"minecraft:spruce_trapdoor":-149,"minecraft:jungle_trapdoor":-148,"minecraft:dark_oak_trapdoor":-147,"minecraft:birch_trapdoor":-146,"minecraft:acacia_trapdoor":-145,"minecraft:spruce_button":-144,"minecraft:jungle_button":-143,"minecraft:dark_oak_button":-142,"minecraft:birch_button":-141,"minecraft:acacia_button":-140,"minecraft:dried_kelp_block":-139,"minecraft:item.kelp":-138,"minecraft:coral_fan_hang3":-137,"minecraft:coral_fan_hang2":-136,"minecraft:coral_fan_hang":-135,"minecraft:coral_fan_dead":-134,"minecraft:coral_fan":-133,"minecraft:coral_block":-132,"minecraft:coral":-131,"minecraft:seagrass":-130,"minecraft:element_118":-129,"minecraft:element_117":-128,"minecraft:element_116":-127,"minecraft:element_115":-126,"minecraft:element_114":-125,"minecraft:element_113":-124,"minecraft:element_112":-123,"minecraft:element_111":-122,"minecraft:element_110":-121,"minecraft:element_109":-120,"minecraft:element_108":-119,"minecraft:element_107":-118,"minecraft:element_106":-117,"minecraft:element_105":-116,"minecraft:element_104":-115,"minecraft:element_103":-114,"minecraft:element_102":-113,"minecraft:element_101":-112,"minecraft:element_100":-111,"minecraft:element_99":-110,"minecraft:element_98":-109,"minecraft:element_97":-108,"minecraft:element_96":-107,"minecraft:element_95":-106,"minecraft:element_94":-105,"minecraft:element_93":-104,"minecraft:element_92":-103,"minecraft:element_91":-102,"minecraft:element_90":-101,"minecraft:element_89":-100,"minecraft:element_88":-99,"minecraft:element_87":-98,"minecraft:element_86":-97,"minecraft:element_85":-96,"minecraft:element_84":-95,"minecraft:element_83":-94,"minecraft:element_82":-93,"minecraft:element_81":-92,"minecraft:element_80":-91,"minecraft:element_79":-90,"minecraft:element_78":-89,"minecraft:element_77":-88,"minecraft:element_76":-87,"minecraft:element_75":-86,"minecraft:element_74":-85,"minecraft:element_73":-84,"minecraft:element_72":-83,"minecraft:element_71":-82,"minecraft:element_70":-81,"minecraft:element_69":-80,"minecraft:element_68":-79,"minecraft:element_67":-78,"minecraft:element_66":-77,"minecraft:element_65":-76,"minecraft:element_64":-75,"minecraft:element_63":-74,"minecraft:element_62":-73,"minecraft:element_61":-72,"minecraft:element_60":-71,"minecraft:element_59":-70,"minecraft:element_58":-69,"minecraft:element_57":-68,"minecraft:element_56":-67,"minecraft:element_55":-66,"minecraft:element_54":-65,"minecraft:element_53":-64,"minecraft:element_52":-63,"minecraft:element_51":-62,"minecraft:element_50":-61,"minecraft:element_49":-60,"minecraft:element_48":-59,"minecraft:element_47":-58,"minecraft:element_46":-57,"minecraft:element_45":-56,"minecraft:element_44":-55,"minecraft:element_43":-54,"minecraft:element_42":-53,"minecraft:element_41":-52,"minecraft:element_40":-51,"minecraft:element_39":-50,"minecraft:element_38":-49,"minecraft:element_37":-48,"minecraft:element_36":-47,"minecraft:element_35":-46,"minecraft:element_34":-45,"minecraft:element_33":-44,"minecraft:element_32":-43,"minecraft:element_31":-42,"minecraft:element_30":-41,"minecraft:element_29":-40,"minecraft:element_28":-39,"minecraft:element_27":-38,"minecraft:element_26":-37,"minecraft:element_25":-36,"minecraft:element_24":-35,"minecraft:element_23":-34,"minecraft:element_22":-33,"minecraft:element_21":-32,"minecraft:element_20":-31,"minecraft:element_19":-30,"minecraft:element_18":-29,"minecraft:element_17":-28,"minecraft:element_16":-27,"minecraft:element_15":-26,"minecraft:element_14":-25,"minecraft:element_13":-24,"minecraft:element_12":-23,"minecraft:element_11":-22,"minecraft:element_10":-21,"minecraft:element_9":-20,"minecraft:element_8":-19,"minecraft:element_7":-18,"minecraft:element_6":-17,"minecraft:element_5":-16,"minecraft:element_4":-15,"minecraft:element_3":-14,"minecraft:element_2":-13,"minecraft:element_1":-12,"minecraft:blue_ice":-11,"minecraft:stripped_oak_log":-10,"minecraft:stripped_dark_oak_log":-9,"minecraft:stripped_acacia_log":-8,"minecraft:stripped_jungle_log":-7,"minecraft:stripped_birch_log":-6,"minecraft:stripped_spruce_log":-5,"minecraft:prismarine_bricks_stairs":-4,"minecraft:dark_prismarine_stairs":-3,"minecraft:prismarine_stairs":-2,"minecraft:stone":1,"minecraft:grass":2,"minecraft:grass_block":2,"minecraft:dirt":3,"minecraft:cobblestone":4,"minecraft:planks":5,"minecraft:sapling":6,"minecraft:bedrock":7,"minecraft:flowing_water":8,"minecraft:water":9,"minecraft:flowing_lava":10,"minecraft:lava":11,"minecraft:sand":12,"minecraft:gravel":13,"minecraft:gold_ore":14,"minecraft:iron_ore":15,"minecraft:coal_ore":16,"minecraft:log":17,"minecraft:leaves":18,"minecraft:sponge":19,"minecraft:glass":20,"minecraft:lapis_ore":21,"minecraft:lapis_block":22,"minecraft:dispenser":23,"minecraft:sandstone":24,"minecraft:noteblock":25,"minecraft:item.bed":26,"minecraft:golden_rail":27,"minecraft:detector_rail":28,"minecraft:sticky_piston":29,"minecraft:web":30,"minecraft:tallgrass":31,"minecraft:deadbush":32,"minecraft:piston":33,"minecraft:pistonarmcollision":34,"minecraft:piston_arm_collision":34,"minecraft:wool":35,"minecraft:element_0":36,"minecraft:yellow_flower":37,"minecraft:red_flower":38,"minecraft:brown_mushroom":39,"minecraft:red_mushroom":40,"minecraft:gold_block":41,"minecraft:iron_block":42,"minecraft:real_double_stone_slab":43,"minecraft:double_stone_block_slab":43,"minecraft:double_stone_slab":44,"minecraft:stone_block_slab":44,"minecraft:brick_block":45,"minecraft:tnt":46,"minecraft:bookshelf":47,"minecraft:mossy_cobblestone":48,"minecraft:obsidian":49,"minecraft:torch":50,"minecraft:fire":51,"minecraft:mob_spawner":52,"minecraft:oak_stairs":53,"minecraft:chest":54,"minecraft:redstone_wire":55,"minecraft:diamond_ore":56,"minecraft:diamond_block":57,"minecraft:crafting_table":58,"minecraft:item.wheat":59,"minecraft:farmland":60,"minecraft:furnace":61,"minecraft:lit_furnace":62,"minecraft:standing_sign":63,"minecraft:item.wooden_door":64,"minecraft:ladder":65,"minecraft:rail":66,"minecraft:stone_stairs":67,"minecraft:wall_sign":68,"minecraft:lever":69,"minecraft:stone_pressure_plate":70,"minecraft:item.iron_door":71,"minecraft:wooden_pressure_plate":72,"minecraft:redstone_ore":73,"minecraft:lit_redstone_ore":74,"minecraft:unlit_redstone_torch":75,"minecraft:redstone_torch":76,"minecraft:stone_button":77,"minecraft:snow_layer":78,"minecraft:ice":79,"minecraft:snow":80,"minecraft:cactus":81,"minecraft:clay":82,"minecraft:item.reeds":83,"minecraft:jukebox":84,"minecraft:fence":85,"minecraft:pumpkin":86,"minecraft:netherrack":87,"minecraft:soul_sand":88,"minecraft:glowstone":89,"minecraft:portal":90,"minecraft:lit_pumpkin":91,"minecraft:item.cake":92,"minecraft:unpowered_repeater":93,"minecraft:powered_repeater":94,"minecraft:invisiblebedrock":95,"minecraft:invisible_bedrock":95,"minecraft:trapdoor":96,"minecraft:monster_egg":97,"minecraft:stonebrick":98,"minecraft:brown_mushroom_block":99,"minecraft:red_mushroom_block":100,"minecraft:iron_bars":101,"minecraft:glass_pane":102,"minecraft:melon_block":103,"minecraft:pumpkin_stem":104,"minecraft:melon_stem":105,"minecraft:vine":106,"minecraft:fence_gate":107,"minecraft:brick_stairs":108,"minecraft:stone_brick_stairs":109,"minecraft:mycelium":110,"minecraft:waterlily":111,"minecraft:nether_brick":112,"minecraft:nether_brick_fence":113,"minecraft:nether_brick_stairs":114,"minecraft:item.nether_wart":115,"minecraft:enchanting_table":116,"minecraft:brewingstandblock":117,"minecraft:item.cauldron":118,"minecraft:end_portal":119,"minecraft:end_portal_frame":120,"minecraft:end_stone":121,"minecraft:dragon_egg":122,"minecraft:redstone_lamp":123,"minecraft:lit_redstone_lamp":124,"minecraft:dropper":125,"minecraft:activator_rail":126,"minecraft:cocoa":127,"minecraft:sandstone_stairs":128,"minecraft:emerald_ore":129,"minecraft:ender_chest":130,"minecraft:tripwire_hook":131,"minecraft:tripwire":132,"minecraft:trip_wire":132,"minecraft:emerald_block":133,"minecraft:spruce_stairs":134,"minecraft:birch_stairs":135,"minecraft:jungle_stairs":136,"minecraft:command_block":137,"minecraft:beacon":138,"minecraft:cobblestone_wall":139,"minecraft:item.flower_pot":140,"minecraft:carrots":141,"minecraft:potatoes":142,"minecraft:wooden_button":143,"minecraft:item.skull":144,"minecraft:anvil":145,"minecraft:trapped_chest":146,"minecraft:light_weighted_pressure_plate":147,"minecraft:heavy_weighted_pressure_plate":148,"minecraft:unpowered_comparator":149,"minecraft:powered_comparator":150,"minecraft:daylight_detector":151,"minecraft:redstone_block":152,"minecraft:quartz_ore":153,"minecraft:item.hopper":154,"minecraft:quartz_block":155,"minecraft:quartz_stairs":156,"minecraft:double_wooden_slab":157,"minecraft:wooden_slab":158,"minecraft:stained_hardened_clay":159,"minecraft:stained_glass_pane":160,"minecraft:leaves2":161,"minecraft:log2":162,"minecraft:acacia_stairs":163,"minecraft:dark_oak_stairs":164,"minecraft:slime":165,"minecraft:glow_stick":166,"minecraft:iron_trapdoor":167,"minecraft:prismarine":168,"minecraft:sealantern":169,"minecraft:sea_lantern":169,"minecraft:hay_block":170,"minecraft:carpet":171,"minecraft:hardened_clay":172,"minecraft:coal_block":173,"minecraft:packed_ice":174,"minecraft:double_plant":175,"minecraft:standing_banner":176,"minecraft:wall_banner":177,"minecraft:daylight_detector_inverted":178,"minecraft:red_sandstone":179,"minecraft:red_sandstone_stairs":180,"minecraft:real_double_stone_slab2":181,"minecraft:double_stone_block_slab2":181,"minecraft:double_stone_slab2":182,"minecraft:stone_block_slab2":182,"minecraft:spruce_fence_gate":183,"minecraft:birch_fence_gate":184,"minecraft:jungle_fence_gate":185,"minecraft:dark_oak_fence_gate":186,"minecraft:acacia_fence_gate":187,"minecraft:repeating_command_block":188,"minecraft:chain_command_block":189,"minecraft:hard_glass_pane":190,"minecraft:hard_stained_glass_pane":191,"minecraft:chemical_heat":192,"minecraft:item.spruce_door":193,"minecraft:item.birch_door":194,"minecraft:item.jungle_door":195,"minecraft:item.acacia_door":196,"minecraft:item.dark_oak_door":197,"minecraft:grass_path":198,"minecraft:item.frame":199,"minecraft:chorus_flower":200,"minecraft:purpur_block":201,"minecraft:colored_torch_rg":202,"minecraft:purpur_stairs":203,"minecraft:colored_torch_bp":204,"minecraft:undyed_shulker_box":205,"minecraft:end_bricks":206,"minecraft:frosted_ice":207,"minecraft:end_rod":208,"minecraft:end_gateway":209,"minecraft:allow":210,"minecraft:deny":211,"minecraft:border_block":212,"minecraft:magma":213,"minecraft:nether_wart_block":214,"minecraft:red_nether_brick":215,"minecraft:bone_block":216,"minecraft:structure_void":217,"minecraft:shulker_box":218,"minecraft:purple_glazed_terracotta":219,"minecraft:white_glazed_terracotta":220,"minecraft:orange_glazed_terracotta":221,"minecraft:magenta_glazed_terracotta":222,"minecraft:light_blue_glazed_terracotta":223,"minecraft:yellow_glazed_terracotta":224,"minecraft:lime_glazed_terracotta":225,"minecraft:pink_glazed_terracotta":226,"minecraft:gray_glazed_terracotta":227,"minecraft:silver_glazed_terracotta":228,"minecraft:cyan_glazed_terracotta":229,"minecraft:blue_glazed_terracotta":231,"minecraft:brown_glazed_terracotta":232,"minecraft:green_glazed_terracotta":233,"minecraft:red_glazed_terracotta":234,"minecraft:black_glazed_terracotta":235,"minecraft:concrete":236,"minecraft:concrete_powder":237,"minecraft:chemistry_table":238,"minecraft:underwater_torch":239,"minecraft:chorus_plant":240,"minecraft:stained_glass":241,"minecraft:item.camera":242,"minecraft:podzol":243,"minecraft:item.beetroot":244,"minecraft:stonecutter":245,"minecraft:glowingobsidian":246,"minecraft:netherreactor":247,"minecraft:info_update":248,"minecraft:info_update2":249,"minecraft:movingblock":250,"minecraft:moving_block":250,"minecraft:observer":251,"minecraft:structure_block":252,"minecraft:hard_glass":253,"minecraft:hard_stained_glass":254,"minecraft:reserved6":255,"minecraft:iron_shovel":256,"minecraft:iron_pickaxe":257,"minecraft:iron_axe":258,"minecraft:flint_and_steel":259,"minecraft:apple":260,"minecraft:bow":261,"minecraft:arrow":262,"minecraft:coal":263,"minecraft:diamond":264,"minecraft:iron_ingot":265,"minecraft:gold_ingot":266,"minecraft:iron_sword":267,"minecraft:wooden_sword":268,"minecraft:wooden_shovel":269,"minecraft:wooden_pickaxe":270,"minecraft:wooden_axe":271,"minecraft:stone_sword":272,"minecraft:stone_shovel":273,"minecraft:stone_pickaxe":274,"minecraft:stone_axe":275,"minecraft:diamond_sword":276,"minecraft:diamond_shovel":277,"minecraft:diamond_pickaxe":278,"minecraft:diamond_axe":279,"minecraft:stick":280,"minecraft:bowl":281,"minecraft:mushroom_stew":282,"minecraft:golden_sword":283,"minecraft:golden_shovel":284,"minecraft:golden_pickaxe":285,"minecraft:golden_axe":286,"minecraft:string":287,"minecraft:feather":288,"minecraft:gunpowder":289,"minecraft:wooden_hoe":290,"minecraft:stone_hoe":291,"minecraft:iron_hoe":292,"minecraft:diamond_hoe":293,"minecraft:golden_hoe":294,"minecraft:wheat_seeds":295,"minecraft:wheat":296,"minecraft:bread":297,"minecraft:leather_helmet":298,"minecraft:leather_chestplate":299,"minecraft:leather_leggings":300,"minecraft:leather_boots":301,"minecraft:chainmail_helmet":302,"minecraft:chainmail_chestplate":303,"minecraft:chainmail_leggings":304,"minecraft:chainmail_boots":305,"minecraft:iron_helmet":306,"minecraft:iron_chestplate":307,"minecraft:iron_leggings":308,"minecraft:iron_boots":309,"minecraft:diamond_helmet":310,"minecraft:diamond_chestplate":311,"minecraft:diamond_leggings":312,"minecraft:diamond_boots":313,"minecraft:golden_helmet":314,"minecraft:golden_chestplate":315,"minecraft:golden_leggings":316,"minecraft:golden_boots":317,"minecraft:flint":318,"minecraft:porkchop":319,"minecraft:cooked_porkchop":320,"minecraft:painting":321,"minecraft:golden_apple":322,"minecraft:oak_sign":323,"minecraft:wooden_door":324,"minecraft:bucket":325,"minecraft:minecart":328,"minecraft:saddle":329,"minecraft:iron_door":330,"minecraft:redstone":331,"minecraft:snowball":332,"minecraft:boat":333,"minecraft:leather":334,"minecraft:kelp":335,"minecraft:brick":336,"minecraft:clay_ball":337,"minecraft:sugar_cane":338,"minecraft:paper":339,"minecraft:book":340,"minecraft:slime_ball":341,"minecraft:chest_minecart":342,"minecraft:egg":344,"minecraft:compass":345,"minecraft:fishing_rod":346,"minecraft:clock":347,"minecraft:glowstone_dust":348,"minecraft:cod":349,"minecraft:cooked_cod":350,"minecraft:dye":351,"minecraft:bone":352,"minecraft:sugar":353,"minecraft:cake":354,"minecraft:bed":355,"minecraft:repeater":356,"minecraft:cookie":357,"minecraft:filled_map":358,"minecraft:shears":359,"minecraft:melon_slice":360,"minecraft:pumpkin_seeds":361,"minecraft:melon_seeds":362,"minecraft:beef":363,"minecraft:cooked_beef":364,"minecraft:chicken":365,"minecraft:cooked_chicken":366,"minecraft:rotten_flesh":367,"minecraft:ender_pearl":368,"minecraft:blaze_rod":369,"minecraft:ghast_tear":370,"minecraft:gold_nugget":371,"minecraft:nether_wart":372,"minecraft:potion":373,"minecraft:glass_bottle":374,"minecraft:spider_eye":375,"minecraft:fermented_spider_eye":376,"minecraft:blaze_powder":377,"minecraft:magma_cream":378,"minecraft:brewing_stand":379,"minecraft:cauldron":380,"minecraft:ender_eye":381,"minecraft:glistering_melon_slice":382,"minecraft:spawn_egg":383,"minecraft:experience_bottle":384,"minecraft:fire_charge":385,"minecraft:writable_book":386,"minecraft:written_book":387,"minecraft:emerald":388,"minecraft:frame":389,"minecraft:flower_pot":390,"minecraft:carrot":391,"minecraft:potato":392,"minecraft:baked_potato":393,"minecraft:poisonous_potato":394,"minecraft:empty_map":395,"minecraft:golden_carrot":396,"minecraft:skull":397,"minecraft:carrot_on_a_stick":398,"minecraft:nether_star":399,"minecraft:pumpkin_pie":400,"minecraft:firework_rocket":401,"minecraft:firework_star":402,"minecraft:enchanted_book":403,"minecraft:comparator":404,"minecraft:netherbrick":405,"minecraft:quartz":406,"minecraft:tnt_minecart":407,"minecraft:hopper_minecart":408,"minecraft:prismarine_shard":409,"minecraft:hopper":410,"minecraft:rabbit":411,"minecraft:cooked_rabbit":412,"minecraft:rabbit_stew":413,"minecraft:rabbit_foot":414,"minecraft:rabbit_hide":415,"minecraft:leather_horse_armor":416,"minecraft:iron_horse_armor":417,"minecraft:golden_horse_armor":418,"minecraft:diamond_horse_armor":419,"minecraft:lead":420,"minecraft:name_tag":421,"minecraft:prismarine_crystals":422,"minecraft:mutton":423,"minecraft:cooked_mutton":424,"minecraft:armor_stand":425,"minecraft:end_crystal":426,"minecraft:spruce_door":427,"minecraft:birch_door":428,"minecraft:jungle_door":429,"minecraft:acacia_door":430,"minecraft:dark_oak_door":431,"minecraft:chorus_fruit":432,"minecraft:popped_chorus_fruit":433,"minecraft:banner_pattern":434,"minecraft:dragon_breath":437,"minecraft:splash_potion":438,"minecraft:lingering_potion":441,"minecraft:sparkler":442,"minecraft:command_block_minecart":443,"minecraft:elytra":444,"minecraft:shulker_shell":445,"minecraft:banner":446,"minecraft:medicine":447,"minecraft:balloon":448,"minecraft:rapid_fertilizer":449,"minecraft:totem_of_undying":450,"minecraft:bleach":451,"minecraft:iron_nugget":452,"minecraft:ice_bomb":453,"minecraft:trident":455,"minecraft:beetroot":457,"minecraft:beetroot_seeds":458,"minecraft:beetroot_soup":459,"minecraft:salmon":460,"minecraft:tropical_fish":461,"minecraft:pufferfish":462,"minecraft:cooked_salmon":463,"minecraft:dried_kelp":464,"minecraft:nautilus_shell":465,"minecraft:enchanted_golden_apple":466,"minecraft:heart_of_the_sea":467,"minecraft:scute":468,"minecraft:turtle_scute":468,"minecraft:turtle_helmet":469,"minecraft:phantom_membrane":470,"minecraft:crossbow":471,"minecraft:spruce_sign":472,"minecraft:birch_sign":473,"minecraft:jungle_sign":474,"minecraft:acacia_sign":475,"minecraft:dark_oak_sign":476,"minecraft:sweet_berries":477,"minecraft:camera":498,"minecraft:compound":499,"minecraft:music_disc_13":500,"minecraft:music_disc_cat":501,"minecraft:music_disc_blocks":502,"minecraft:music_disc_chirp":503,"minecraft:music_disc_far":504,"minecraft:music_disc_mall":505,"minecraft:music_disc_mellohi":506,"minecraft:music_disc_stal":507,"minecraft:music_disc_strad":508,"minecraft:music_disc_ward":509,"minecraft:music_disc_11":510,"minecraft:music_disc_wait":511,"minecraft:shield":513,"minecraft:raw_iron":520,"minecraft:raw_gold":521,"minecraft:raw_copper":522,"minecraft:glow_berries":654,"minecraft:campfire":720,"minecraft:suspicious_stew":734,"minecraft:honeycomb":736,"minecraft:honey_bottle":737,"minecraft:lodestone_compass":741,"minecraft:netherite_ingot":742,"minecraft:netherite_sword":743,"minecraft:netherite_shovel":744,"minecraft:netherite_pickaxe":745,"minecraft:netherite_axe":746,"minecraft:netherite_hoe":747,"minecraft:netherite_helmet":748,"minecraft:netherite_chestplate":749,"minecraft:netherite_leggings":750,"minecraft:netherite_boots":751,"minecraft:netherite_scrap":752,"minecraft:crimson_sign":753,"minecraft:warped_sign":754,"minecraft:crimson_door":755,"minecraft:warped_door":756,"minecraft:warped_fungus_on_a_stick":757,"minecraft:chain":758,"minecraft:nether_sprouts":760,"minecraft:soul_campfire":801,"minecraft:music_disc_pigstep":759,"minecraft:spyglass":772,"minecraft:music_disc_otherside":773,"minecraft:glow_frame":850,"minecraft:acacia_chest_boat":642,"minecraft:birch_chest_boat":639,"minecraft:chest_boat":645,"minecraft:dark_oak_chest_boat":643,"minecraft:disc_fragment_5":637,"minecraft:jungle_chest_boat":640,"minecraft:mangrove_chest_boat":644,"minecraft:music_disc_5":636,"minecraft:oak_chest_boat":638,"minecraft:spruce_chest_boat":641,"minecraft:amethyst_shard":771,"minecraft:echo_shard":647,"minecraft:goat_horn":761,"minecraft:music_disc_relic":701,"minecraft:copper_ingot":519,"minecraft:recovery_compass":648} \ No newline at end of file +{"minecraft:mangrove_double_slab":-499,"minecraft:stripped_mangrove_wood":-498,"minecraft:mangrove_wood":-497,"minecraft:mangrove_trapdoor":-496,"minecraft:mangrove_wall_sign":-495,"minecraft:mangrove_standing_sign":-494,"minecraft:mangrove_fence_gate":-492,"minecraft:mangrove_fence":-491,"minecraft:mangrove_pressure_plate":-490,"minecraft:mangrove_slab":-489,"minecraft:mangrove_stairs":-488,"minecraft:mangrove_button":-487,"minecraft:mangrove_planks":-486,"minecraft:stripped_mangrove_log":-485,"minecraft:mangrove_log":-484,"minecraft:muddy_mangrove_roots":-483,"minecraft:mangrove_roots":-482,"minecraft:mud_brick_wall":-481,"minecraft:mud_brick_stairs":-480,"minecraft:mud_brick_double_slab":-479,"minecraft:mud_brick_slab":-478,"minecraft:packed_mud":-477,"minecraft:mud_bricks":-475,"minecraft:mangrove_propagule":-474,"minecraft:mud":-473,"minecraft:mangrove_leaves":-472,"minecraft:ochre_froglight":-471,"minecraft:verdant_froglight":-470,"minecraft:pearlescent_froglight":-469,"minecraft:frog_spawn":-468,"minecraft:reinforced_deepslate":-466,"minecraft:client_request_placeholder_block":-465,"minecraft:sculk_shrieker":-461,"minecraft:sculk_catalyst":-460,"minecraft:sculk_vein":-459,"minecraft:sculk":-458,"minecraft:infested_deepslate":-454,"minecraft:raw_gold_block":-453,"minecraft:raw_copper_block":-452,"minecraft:raw_iron_block":-451,"minecraft:waxed_oxidized_double_cut_copper_slab":-450,"minecraft:waxed_oxidized_cut_copper_slab":-449,"minecraft:waxed_oxidized_cut_copper_stairs":-448,"minecraft:waxed_oxidized_cut_copper":-447,"minecraft:waxed_oxidized_copper":-446,"minecraft:black_candle_cake":-445,"minecraft:red_candle_cake":-444,"minecraft:green_candle_cake":-443,"minecraft:brown_candle_cake":-442,"minecraft:blue_candle_cake":-441,"minecraft:purple_candle_cake":-440,"minecraft:cyan_candle_cake":-439,"minecraft:light_gray_candle_cake":-438,"minecraft:gray_candle_cake":-437,"minecraft:pink_candle_cake":-436,"minecraft:lime_candle_cake":-435,"minecraft:yellow_candle_cake":-434,"minecraft:light_blue_candle_cake":-433,"minecraft:magenta_candle_cake":-432,"minecraft:orange_candle_cake":-431,"minecraft:white_candle_cake":-430,"minecraft:candle_cake":-429,"minecraft:black_candle":-428,"minecraft:red_candle":-427,"minecraft:green_candle":-426,"minecraft:brown_candle":-425,"minecraft:blue_candle":-424,"minecraft:purple_candle":-423,"minecraft:cyan_candle":-422,"minecraft:light_gray_candle":-421,"minecraft:gray_candle":-420,"minecraft:pink_candle":-419,"minecraft:lime_candle":-418,"minecraft:yellow_candle":-417,"minecraft:light_blue_candle":-416,"minecraft:magenta_candle":-415,"minecraft:orange_candle":-414,"minecraft:white_candle":-413,"minecraft:candle":-412,"minecraft:glow_lichen":-411,"minecraft:cracked_deepslate_bricks":-410,"minecraft:cracked_deepslate_tiles":-409,"minecraft:deepslate_copper_ore":-408,"minecraft:deepslate_emerald_ore":-407,"minecraft:deepslate_coal_ore":-406,"minecraft:deepslate_diamond_ore":-405,"minecraft:lit_deepslate_redstone_ore":-404,"minecraft:deepslate_redstone_ore":-403,"minecraft:deepslate_gold_ore":-402,"minecraft:deepslate_iron_ore":-401,"minecraft:deepslate_lapis_ore":-400,"minecraft:deepslate_brick_double_slab":-399,"minecraft:deepslate_tile_double_slab":-398,"minecraft:polished_deepslate_double_slab":-397,"minecraft:cobbled_deepslate_double_slab":-396,"minecraft:chiseled_deepslate":-395,"minecraft:deepslate_brick_wall":-394,"minecraft:deepslate_brick_stairs":-393,"minecraft:deepslate_brick_slab":-392,"minecraft:deepslate_bricks":-391,"minecraft:deepslate_tile_wall":-390,"minecraft:deepslate_tile_stairs":-389,"minecraft:deepslate_tile_slab":-388,"minecraft:deepslate_tiles":-387,"minecraft:polished_deepslate_wall":-386,"minecraft:polished_deepslate_stairs":-385,"minecraft:polished_deepslate_slab":-384,"minecraft:polished_deepslate":-383,"minecraft:cobbled_deepslate_wall":-382,"minecraft:cobbled_deepslate_stairs":-381,"minecraft:cobbled_deepslate_slab":-380,"minecraft:cobbled_deepslate":-379,"minecraft:deepslate":-378,"minecraft:smooth_basalt":-377,"minecraft:cave_vines_head_with_berries":-376,"minecraft:cave_vines_body_with_berries":-375,"minecraft:waxed_weathered_double_cut_copper_slab":-374,"minecraft:waxed_exposed_double_cut_copper_slab":-373,"minecraft:waxed_double_cut_copper_slab":-372,"minecraft:oxidized_double_cut_copper_slab":-371,"minecraft:weathered_double_cut_copper_slab":-370,"minecraft:exposed_double_cut_copper_slab":-369,"minecraft:double_cut_copper_slab":-368,"minecraft:waxed_weathered_cut_copper_slab":-367,"minecraft:waxed_exposed_cut_copper_slab":-366,"minecraft:waxed_cut_copper_slab":-365,"minecraft:oxidized_cut_copper_slab":-364,"minecraft:weathered_cut_copper_slab":-363,"minecraft:exposed_cut_copper_slab":-362,"minecraft:cut_copper_slab":-361,"minecraft:waxed_weathered_cut_copper_stairs":-360,"minecraft:waxed_exposed_cut_copper_stairs":-359,"minecraft:waxed_cut_copper_stairs":-358,"minecraft:oxidized_cut_copper_stairs":-357,"minecraft:weathered_cut_copper_stairs":-356,"minecraft:exposed_cut_copper_stairs":-355,"minecraft:cut_copper_stairs":-354,"minecraft:waxed_weathered_cut_copper":-353,"minecraft:waxed_exposed_cut_copper":-352,"minecraft:waxed_cut_copper":-351,"minecraft:oxidized_cut_copper":-350,"minecraft:weathered_cut_copper":-349,"minecraft:exposed_cut_copper":-348,"minecraft:cut_copper":-347,"minecraft:waxed_weathered_copper":-346,"minecraft:waxed_exposed_copper":-345,"minecraft:waxed_copper":-344,"minecraft:oxidized_copper":-343,"minecraft:weathered_copper":-342,"minecraft:exposed_copper":-341,"minecraft:copper_block":-340,"minecraft:item.glow_frame":-339,"minecraft:flowering_azalea":-338,"minecraft:azalea":-337,"minecraft:small_dripleaf_block":-336,"minecraft:moss_carpet":-335,"minecraft:tinted_glass":-334,"minecraft:tuff":-333,"minecraft:small_amethyst_bud":-332,"minecraft:medium_amethyst_bud":-331,"minecraft:large_amethyst_bud":-330,"minecraft:amethyst_cluster":-329,"minecraft:budding_amethyst":-328,"minecraft:amethyst_block":-327,"minecraft:calcite":-326,"minecraft:azalea_leaves_flowered":-325,"minecraft:azalea_leaves":-324,"minecraft:big_dripleaf":-323,"minecraft:cave_vines":-322,"minecraft:spore_blossom":-321,"minecraft:moss_block":-320,"minecraft:hanging_roots":-319,"minecraft:dirt_with_roots":-318,"minecraft:dripstone_block":-317,"minecraft:lightning_rod":-312,"minecraft:copper_ore":-311,"minecraft:pointed_dripstone":-308,"minecraft:sculk_sensor":-307,"minecraft:powder_snow":-306,"minecraft:quartz_bricks":-304,"minecraft:cracked_nether_bricks":-303,"minecraft:chiseled_nether_bricks":-302,"minecraft:stripped_warped_hyphae":-301,"minecraft:stripped_crimson_hyphae":-300,"minecraft:crimson_hyphae":-299,"minecraft:warped_hyphae":-298,"minecraft:polished_blackstone_wall":-297,"minecraft:polished_blackstone_button":-296,"minecraft:polished_blackstone_pressure_plate":-295,"minecraft:polished_blackstone_double_slab":-294,"minecraft:polished_blackstone_slab":-293,"minecraft:polished_blackstone_stairs":-292,"minecraft:polished_blackstone":-291,"minecraft:item.soul_campfire":-290,"minecraft:crying_obsidian":-289,"minecraft:nether_gold_ore":-288,"minecraft:twisting_vines":-287,"minecraft:item.chain":-286,"minecraft:polished_blackstone_brick_double_slab":-285,"minecraft:polished_blackstone_brick_slab":-284,"minecraft:blackstone_double_slab":-283,"minecraft:blackstone_slab":-282,"minecraft:gilded_blackstone":-281,"minecraft:cracked_polished_blackstone_bricks":-280,"minecraft:chiseled_polished_blackstone":-279,"minecraft:polished_blackstone_brick_wall":-278,"minecraft:blackstone_wall":-277,"minecraft:blackstone_stairs":-276,"minecraft:polished_blackstone_brick_stairs":-275,"minecraft:polished_blackstone_bricks":-274,"minecraft:blackstone":-273,"minecraft:respawn_anchor":-272,"minecraft:ancient_debris":-271,"minecraft:netherite_block":-270,"minecraft:soul_lantern":-269,"minecraft:soul_torch":-268,"minecraft:warped_double_slab":-267,"minecraft:crimson_double_slab":-266,"minecraft:warped_slab":-265,"minecraft:crimson_slab":-264,"minecraft:warped_pressure_plate":-263,"minecraft:crimson_pressure_plate":-262,"minecraft:warped_button":-261,"minecraft:crimson_button":-260,"minecraft:warped_fence_gate":-259,"minecraft:crimson_fence_gate":-258,"minecraft:warped_fence":-257,"minecraft:crimson_fence":-256,"minecraft:warped_stairs":-255,"minecraft:crimson_stairs":-254,"minecraft:warped_wall_sign":-253,"minecraft:crimson_wall_sign":-252,"minecraft:warped_standing_sign":-251,"minecraft:crimson_standing_sign":-250,"minecraft:warped_trapdoor":-247,"minecraft:crimson_trapdoor":-246,"minecraft:item.warped_door":-245,"minecraft:item.crimson_door":-244,"minecraft:warped_planks":-243,"minecraft:crimson_planks":-242,"minecraft:stripped_warped_stem":-241,"minecraft:stripped_crimson_stem":-240,"minecraft:target":-239,"minecraft:item.nether_sprouts":-238,"minecraft:soul_fire":-237,"minecraft:soul_soil":-236,"minecraft:polished_basalt":-235,"minecraft:basalt":-234,"minecraft:warped_nylium":-233,"minecraft:crimson_nylium":-232,"minecraft:weeping_vines":-231,"minecraft:shroomlight":-230,"minecraft:warped_fungus":-229,"minecraft:crimson_fungus":-228,"minecraft:warped_wart_block":-227,"minecraft:warped_stem":-226,"minecraft:crimson_stem":-225,"minecraft:warped_roots":-224,"minecraft:crimson_roots":-223,"minecraft:lodestone":-222,"minecraft:honeycomb_block":-221,"minecraft:honey_block":-220,"minecraft:beehive":-219,"minecraft:bee_nest":-218,"minecraft:stickypistonarmcollision":-217,"minecraft:sticky_piston_arm_collision":-217,"minecraft:wither_rose":-216,"minecraft:light_block":-215,"minecraft:lit_blast_furnace":-214,"minecraft:composter":-213,"minecraft:wood":-212,"minecraft:jigsaw":-211,"minecraft:lava_cauldron":-210,"minecraft:item.campfire":-209,"minecraft:lantern":-208,"minecraft:sweet_berry_bush":-207,"minecraft:bell":-206,"minecraft:loom":-204,"minecraft:barrel":-203,"minecraft:smithing_table":-202,"minecraft:fletching_table":-201,"minecraft:cartography_table":-200,"minecraft:lit_smoker":-199,"minecraft:smoker":-198,"minecraft:stonecutter_block":-197,"minecraft:blast_furnace":-196,"minecraft:grindstone":-195,"minecraft:lectern":-194,"minecraft:darkoak_wall_sign":-193,"minecraft:darkoak_standing_sign":-192,"minecraft:acacia_wall_sign":-191,"minecraft:acacia_standing_sign":-190,"minecraft:jungle_wall_sign":-189,"minecraft:jungle_standing_sign":-188,"minecraft:birch_wall_sign":-187,"minecraft:birch_standing_sign":-186,"minecraft:smooth_quartz_stairs":-185,"minecraft:red_nether_brick_stairs":-184,"minecraft:smooth_stone":-183,"minecraft:spruce_wall_sign":-182,"minecraft:spruce_standing_sign":-181,"minecraft:normal_stone_stairs":-180,"minecraft:mossy_cobblestone_stairs":-179,"minecraft:end_brick_stairs":-178,"minecraft:smooth_sandstone_stairs":-177,"minecraft:smooth_red_sandstone_stairs":-176,"minecraft:mossy_stone_brick_stairs":-175,"minecraft:polished_andesite_stairs":-174,"minecraft:polished_diorite_stairs":-173,"minecraft:polished_granite_stairs":-172,"minecraft:andesite_stairs":-171,"minecraft:diorite_stairs":-170,"minecraft:granite_stairs":-169,"minecraft:real_double_stone_slab4":-168,"minecraft:double_stone_block_slab4":-168,"minecraft:real_double_stone_slab3":-167,"minecraft:double_stone_block_slab3":-167,"minecraft:double_stone_slab4":-166,"minecraft:stone_block_slab4":-166,"minecraft:scaffolding":-165,"minecraft:bamboo_sapling":-164,"minecraft:bamboo":-163,"minecraft:double_stone_slab3":-162,"minecraft:stone_block_slab3":-162,"minecraft:barrier":-161,"minecraft:bubble_column":-160,"minecraft:turtle_egg":-159,"minecraft:air":-158,"minecraft:conduit":-157,"minecraft:sea_pickle":-156,"minecraft:carved_pumpkin":-155,"minecraft:spruce_pressure_plate":-154,"minecraft:jungle_pressure_plate":-153,"minecraft:dark_oak_pressure_plate":-152,"minecraft:birch_pressure_plate":-151,"minecraft:acacia_pressure_plate":-150,"minecraft:spruce_trapdoor":-149,"minecraft:jungle_trapdoor":-148,"minecraft:dark_oak_trapdoor":-147,"minecraft:birch_trapdoor":-146,"minecraft:acacia_trapdoor":-145,"minecraft:spruce_button":-144,"minecraft:jungle_button":-143,"minecraft:dark_oak_button":-142,"minecraft:birch_button":-141,"minecraft:acacia_button":-140,"minecraft:dried_kelp_block":-139,"minecraft:item.kelp":-138,"minecraft:coral_fan_hang3":-137,"minecraft:coral_fan_hang2":-136,"minecraft:coral_fan_hang":-135,"minecraft:coral_fan_dead":-134,"minecraft:coral_fan":-133,"minecraft:coral_block":-132,"minecraft:coral":-131,"minecraft:seagrass":-130,"minecraft:element_118":-129,"minecraft:element_117":-128,"minecraft:element_116":-127,"minecraft:element_115":-126,"minecraft:element_114":-125,"minecraft:element_113":-124,"minecraft:element_112":-123,"minecraft:element_111":-122,"minecraft:element_110":-121,"minecraft:element_109":-120,"minecraft:element_108":-119,"minecraft:element_107":-118,"minecraft:element_106":-117,"minecraft:element_105":-116,"minecraft:element_104":-115,"minecraft:element_103":-114,"minecraft:element_102":-113,"minecraft:element_101":-112,"minecraft:element_100":-111,"minecraft:element_99":-110,"minecraft:element_98":-109,"minecraft:element_97":-108,"minecraft:element_96":-107,"minecraft:element_95":-106,"minecraft:element_94":-105,"minecraft:element_93":-104,"minecraft:element_92":-103,"minecraft:element_91":-102,"minecraft:element_90":-101,"minecraft:element_89":-100,"minecraft:element_88":-99,"minecraft:element_87":-98,"minecraft:element_86":-97,"minecraft:element_85":-96,"minecraft:element_84":-95,"minecraft:element_83":-94,"minecraft:element_82":-93,"minecraft:element_81":-92,"minecraft:element_80":-91,"minecraft:element_79":-90,"minecraft:element_78":-89,"minecraft:element_77":-88,"minecraft:element_76":-87,"minecraft:element_75":-86,"minecraft:element_74":-85,"minecraft:element_73":-84,"minecraft:element_72":-83,"minecraft:element_71":-82,"minecraft:element_70":-81,"minecraft:element_69":-80,"minecraft:element_68":-79,"minecraft:element_67":-78,"minecraft:element_66":-77,"minecraft:element_65":-76,"minecraft:element_64":-75,"minecraft:element_63":-74,"minecraft:element_62":-73,"minecraft:element_61":-72,"minecraft:element_60":-71,"minecraft:element_59":-70,"minecraft:element_58":-69,"minecraft:element_57":-68,"minecraft:element_56":-67,"minecraft:element_55":-66,"minecraft:element_54":-65,"minecraft:element_53":-64,"minecraft:element_52":-63,"minecraft:element_51":-62,"minecraft:element_50":-61,"minecraft:element_49":-60,"minecraft:element_48":-59,"minecraft:element_47":-58,"minecraft:element_46":-57,"minecraft:element_45":-56,"minecraft:element_44":-55,"minecraft:element_43":-54,"minecraft:element_42":-53,"minecraft:element_41":-52,"minecraft:element_40":-51,"minecraft:element_39":-50,"minecraft:element_38":-49,"minecraft:element_37":-48,"minecraft:element_36":-47,"minecraft:element_35":-46,"minecraft:element_34":-45,"minecraft:element_33":-44,"minecraft:element_32":-43,"minecraft:element_31":-42,"minecraft:element_30":-41,"minecraft:element_29":-40,"minecraft:element_28":-39,"minecraft:element_27":-38,"minecraft:element_26":-37,"minecraft:element_25":-36,"minecraft:element_24":-35,"minecraft:element_23":-34,"minecraft:element_22":-33,"minecraft:element_21":-32,"minecraft:element_20":-31,"minecraft:element_19":-30,"minecraft:element_18":-29,"minecraft:element_17":-28,"minecraft:element_16":-27,"minecraft:element_15":-26,"minecraft:element_14":-25,"minecraft:element_13":-24,"minecraft:element_12":-23,"minecraft:element_11":-22,"minecraft:element_10":-21,"minecraft:element_9":-20,"minecraft:element_8":-19,"minecraft:element_7":-18,"minecraft:element_6":-17,"minecraft:element_5":-16,"minecraft:element_4":-15,"minecraft:element_3":-14,"minecraft:element_2":-13,"minecraft:element_1":-12,"minecraft:blue_ice":-11,"minecraft:stripped_oak_log":-10,"minecraft:stripped_dark_oak_log":-9,"minecraft:stripped_acacia_log":-8,"minecraft:stripped_jungle_log":-7,"minecraft:stripped_birch_log":-6,"minecraft:stripped_spruce_log":-5,"minecraft:prismarine_bricks_stairs":-4,"minecraft:dark_prismarine_stairs":-3,"minecraft:prismarine_stairs":-2,"minecraft:stone":1,"minecraft:grass":2,"minecraft:grass_block":2,"minecraft:dirt":3,"minecraft:cobblestone":4,"minecraft:planks":5,"minecraft:sapling":6,"minecraft:bedrock":7,"minecraft:flowing_water":8,"minecraft:water":9,"minecraft:flowing_lava":10,"minecraft:lava":11,"minecraft:sand":12,"minecraft:gravel":13,"minecraft:gold_ore":14,"minecraft:iron_ore":15,"minecraft:coal_ore":16,"minecraft:log":17,"minecraft:leaves":18,"minecraft:sponge":19,"minecraft:glass":20,"minecraft:lapis_ore":21,"minecraft:lapis_block":22,"minecraft:dispenser":23,"minecraft:sandstone":24,"minecraft:noteblock":25,"minecraft:item.bed":26,"minecraft:golden_rail":27,"minecraft:detector_rail":28,"minecraft:sticky_piston":29,"minecraft:web":30,"minecraft:tallgrass":31,"minecraft:deadbush":32,"minecraft:piston":33,"minecraft:pistonarmcollision":34,"minecraft:piston_arm_collision":34,"minecraft:wool":35,"minecraft:element_0":36,"minecraft:yellow_flower":37,"minecraft:red_flower":38,"minecraft:brown_mushroom":39,"minecraft:red_mushroom":40,"minecraft:gold_block":41,"minecraft:iron_block":42,"minecraft:real_double_stone_slab":43,"minecraft:double_stone_block_slab":43,"minecraft:double_stone_slab":44,"minecraft:stone_block_slab":44,"minecraft:brick_block":45,"minecraft:tnt":46,"minecraft:bookshelf":47,"minecraft:mossy_cobblestone":48,"minecraft:obsidian":49,"minecraft:torch":50,"minecraft:fire":51,"minecraft:mob_spawner":52,"minecraft:oak_stairs":53,"minecraft:chest":54,"minecraft:redstone_wire":55,"minecraft:diamond_ore":56,"minecraft:diamond_block":57,"minecraft:crafting_table":58,"minecraft:item.wheat":59,"minecraft:farmland":60,"minecraft:furnace":61,"minecraft:lit_furnace":62,"minecraft:standing_sign":63,"minecraft:item.wooden_door":64,"minecraft:ladder":65,"minecraft:rail":66,"minecraft:stone_stairs":67,"minecraft:wall_sign":68,"minecraft:lever":69,"minecraft:stone_pressure_plate":70,"minecraft:item.iron_door":71,"minecraft:wooden_pressure_plate":72,"minecraft:redstone_ore":73,"minecraft:lit_redstone_ore":74,"minecraft:unlit_redstone_torch":75,"minecraft:redstone_torch":76,"minecraft:stone_button":77,"minecraft:snow_layer":78,"minecraft:ice":79,"minecraft:snow":80,"minecraft:cactus":81,"minecraft:clay":82,"minecraft:item.reeds":83,"minecraft:jukebox":84,"minecraft:fence":85,"minecraft:pumpkin":86,"minecraft:netherrack":87,"minecraft:soul_sand":88,"minecraft:glowstone":89,"minecraft:portal":90,"minecraft:lit_pumpkin":91,"minecraft:item.cake":92,"minecraft:unpowered_repeater":93,"minecraft:powered_repeater":94,"minecraft:invisiblebedrock":95,"minecraft:invisible_bedrock":95,"minecraft:trapdoor":96,"minecraft:monster_egg":97,"minecraft:stonebrick":98,"minecraft:brown_mushroom_block":99,"minecraft:red_mushroom_block":100,"minecraft:iron_bars":101,"minecraft:glass_pane":102,"minecraft:melon_block":103,"minecraft:pumpkin_stem":104,"minecraft:melon_stem":105,"minecraft:vine":106,"minecraft:fence_gate":107,"minecraft:brick_stairs":108,"minecraft:stone_brick_stairs":109,"minecraft:mycelium":110,"minecraft:waterlily":111,"minecraft:nether_brick":112,"minecraft:nether_brick_fence":113,"minecraft:nether_brick_stairs":114,"minecraft:item.nether_wart":115,"minecraft:enchanting_table":116,"minecraft:brewingstandblock":117,"minecraft:item.cauldron":118,"minecraft:end_portal":119,"minecraft:end_portal_frame":120,"minecraft:end_stone":121,"minecraft:dragon_egg":122,"minecraft:redstone_lamp":123,"minecraft:lit_redstone_lamp":124,"minecraft:dropper":125,"minecraft:activator_rail":126,"minecraft:cocoa":127,"minecraft:sandstone_stairs":128,"minecraft:emerald_ore":129,"minecraft:ender_chest":130,"minecraft:tripwire_hook":131,"minecraft:tripwire":132,"minecraft:trip_wire":132,"minecraft:emerald_block":133,"minecraft:spruce_stairs":134,"minecraft:birch_stairs":135,"minecraft:jungle_stairs":136,"minecraft:command_block":137,"minecraft:beacon":138,"minecraft:cobblestone_wall":139,"minecraft:item.flower_pot":140,"minecraft:carrots":141,"minecraft:potatoes":142,"minecraft:wooden_button":143,"minecraft:item.skull":144,"minecraft:anvil":145,"minecraft:trapped_chest":146,"minecraft:light_weighted_pressure_plate":147,"minecraft:heavy_weighted_pressure_plate":148,"minecraft:unpowered_comparator":149,"minecraft:powered_comparator":150,"minecraft:daylight_detector":151,"minecraft:redstone_block":152,"minecraft:quartz_ore":153,"minecraft:item.hopper":154,"minecraft:quartz_block":155,"minecraft:quartz_stairs":156,"minecraft:double_wooden_slab":157,"minecraft:wooden_slab":158,"minecraft:stained_hardened_clay":159,"minecraft:stained_glass_pane":160,"minecraft:leaves2":161,"minecraft:log2":162,"minecraft:acacia_stairs":163,"minecraft:dark_oak_stairs":164,"minecraft:slime":165,"minecraft:glow_stick":166,"minecraft:iron_trapdoor":167,"minecraft:prismarine":168,"minecraft:sealantern":169,"minecraft:sea_lantern":169,"minecraft:hay_block":170,"minecraft:carpet":171,"minecraft:hardened_clay":172,"minecraft:coal_block":173,"minecraft:packed_ice":174,"minecraft:double_plant":175,"minecraft:standing_banner":176,"minecraft:wall_banner":177,"minecraft:daylight_detector_inverted":178,"minecraft:red_sandstone":179,"minecraft:red_sandstone_stairs":180,"minecraft:real_double_stone_slab2":181,"minecraft:double_stone_block_slab2":181,"minecraft:double_stone_slab2":182,"minecraft:stone_block_slab2":182,"minecraft:spruce_fence_gate":183,"minecraft:birch_fence_gate":184,"minecraft:jungle_fence_gate":185,"minecraft:dark_oak_fence_gate":186,"minecraft:acacia_fence_gate":187,"minecraft:repeating_command_block":188,"minecraft:chain_command_block":189,"minecraft:hard_glass_pane":190,"minecraft:hard_stained_glass_pane":191,"minecraft:chemical_heat":192,"minecraft:item.spruce_door":193,"minecraft:item.birch_door":194,"minecraft:item.jungle_door":195,"minecraft:item.acacia_door":196,"minecraft:item.dark_oak_door":197,"minecraft:grass_path":198,"minecraft:item.frame":199,"minecraft:chorus_flower":200,"minecraft:purpur_block":201,"minecraft:colored_torch_rg":202,"minecraft:purpur_stairs":203,"minecraft:colored_torch_bp":204,"minecraft:undyed_shulker_box":205,"minecraft:end_bricks":206,"minecraft:frosted_ice":207,"minecraft:end_rod":208,"minecraft:end_gateway":209,"minecraft:allow":210,"minecraft:deny":211,"minecraft:border_block":212,"minecraft:magma":213,"minecraft:nether_wart_block":214,"minecraft:red_nether_brick":215,"minecraft:bone_block":216,"minecraft:structure_void":217,"minecraft:shulker_box":218,"minecraft:purple_glazed_terracotta":219,"minecraft:white_glazed_terracotta":220,"minecraft:orange_glazed_terracotta":221,"minecraft:magenta_glazed_terracotta":222,"minecraft:light_blue_glazed_terracotta":223,"minecraft:yellow_glazed_terracotta":224,"minecraft:lime_glazed_terracotta":225,"minecraft:pink_glazed_terracotta":226,"minecraft:gray_glazed_terracotta":227,"minecraft:silver_glazed_terracotta":228,"minecraft:cyan_glazed_terracotta":229,"minecraft:blue_glazed_terracotta":231,"minecraft:brown_glazed_terracotta":232,"minecraft:green_glazed_terracotta":233,"minecraft:red_glazed_terracotta":234,"minecraft:black_glazed_terracotta":235,"minecraft:concrete":236,"minecraft:concrete_powder":237,"minecraft:chemistry_table":238,"minecraft:underwater_torch":239,"minecraft:chorus_plant":240,"minecraft:stained_glass":241,"minecraft:item.camera":242,"minecraft:podzol":243,"minecraft:item.beetroot":244,"minecraft:stonecutter":245,"minecraft:glowingobsidian":246,"minecraft:netherreactor":247,"minecraft:info_update":248,"minecraft:info_update2":249,"minecraft:movingblock":250,"minecraft:moving_block":250,"minecraft:observer":251,"minecraft:structure_block":252,"minecraft:hard_glass":253,"minecraft:hard_stained_glass":254,"minecraft:reserved6":255,"minecraft:iron_shovel":256,"minecraft:iron_pickaxe":257,"minecraft:iron_axe":258,"minecraft:flint_and_steel":259,"minecraft:apple":260,"minecraft:bow":261,"minecraft:arrow":262,"minecraft:coal":263,"minecraft:diamond":264,"minecraft:iron_ingot":265,"minecraft:gold_ingot":266,"minecraft:iron_sword":267,"minecraft:wooden_sword":268,"minecraft:wooden_shovel":269,"minecraft:wooden_pickaxe":270,"minecraft:wooden_axe":271,"minecraft:stone_sword":272,"minecraft:stone_shovel":273,"minecraft:stone_pickaxe":274,"minecraft:stone_axe":275,"minecraft:diamond_sword":276,"minecraft:diamond_shovel":277,"minecraft:diamond_pickaxe":278,"minecraft:diamond_axe":279,"minecraft:stick":280,"minecraft:bowl":281,"minecraft:mushroom_stew":282,"minecraft:golden_sword":283,"minecraft:golden_shovel":284,"minecraft:golden_pickaxe":285,"minecraft:golden_axe":286,"minecraft:string":287,"minecraft:feather":288,"minecraft:gunpowder":289,"minecraft:wooden_hoe":290,"minecraft:stone_hoe":291,"minecraft:iron_hoe":292,"minecraft:diamond_hoe":293,"minecraft:golden_hoe":294,"minecraft:wheat_seeds":295,"minecraft:wheat":296,"minecraft:bread":297,"minecraft:leather_helmet":298,"minecraft:leather_chestplate":299,"minecraft:leather_leggings":300,"minecraft:leather_boots":301,"minecraft:chainmail_helmet":302,"minecraft:chainmail_chestplate":303,"minecraft:chainmail_leggings":304,"minecraft:chainmail_boots":305,"minecraft:iron_helmet":306,"minecraft:iron_chestplate":307,"minecraft:iron_leggings":308,"minecraft:iron_boots":309,"minecraft:diamond_helmet":310,"minecraft:diamond_chestplate":311,"minecraft:diamond_leggings":312,"minecraft:diamond_boots":313,"minecraft:golden_helmet":314,"minecraft:golden_chestplate":315,"minecraft:golden_leggings":316,"minecraft:golden_boots":317,"minecraft:flint":318,"minecraft:porkchop":319,"minecraft:cooked_porkchop":320,"minecraft:painting":321,"minecraft:golden_apple":322,"minecraft:oak_sign":323,"minecraft:wooden_door":324,"minecraft:bucket":325,"minecraft:minecart":328,"minecraft:saddle":329,"minecraft:iron_door":330,"minecraft:redstone":331,"minecraft:snowball":332,"minecraft:boat":333,"minecraft:leather":334,"minecraft:kelp":335,"minecraft:brick":336,"minecraft:clay_ball":337,"minecraft:sugar_cane":338,"minecraft:paper":339,"minecraft:book":340,"minecraft:slime_ball":341,"minecraft:chest_minecart":342,"minecraft:egg":344,"minecraft:compass":345,"minecraft:fishing_rod":346,"minecraft:clock":347,"minecraft:glowstone_dust":348,"minecraft:cod":349,"minecraft:cooked_cod":350,"minecraft:dye":351,"minecraft:bone":352,"minecraft:sugar":353,"minecraft:cake":354,"minecraft:bed":355,"minecraft:repeater":356,"minecraft:cookie":357,"minecraft:filled_map":358,"minecraft:shears":359,"minecraft:melon_slice":360,"minecraft:pumpkin_seeds":361,"minecraft:melon_seeds":362,"minecraft:beef":363,"minecraft:cooked_beef":364,"minecraft:chicken":365,"minecraft:cooked_chicken":366,"minecraft:rotten_flesh":367,"minecraft:ender_pearl":368,"minecraft:blaze_rod":369,"minecraft:ghast_tear":370,"minecraft:gold_nugget":371,"minecraft:nether_wart":372,"minecraft:potion":373,"minecraft:glass_bottle":374,"minecraft:spider_eye":375,"minecraft:fermented_spider_eye":376,"minecraft:blaze_powder":377,"minecraft:magma_cream":378,"minecraft:brewing_stand":379,"minecraft:cauldron":380,"minecraft:ender_eye":381,"minecraft:glistering_melon_slice":382,"minecraft:spawn_egg":383,"minecraft:experience_bottle":384,"minecraft:fire_charge":385,"minecraft:writable_book":386,"minecraft:written_book":387,"minecraft:emerald":388,"minecraft:frame":389,"minecraft:flower_pot":390,"minecraft:carrot":391,"minecraft:potato":392,"minecraft:baked_potato":393,"minecraft:poisonous_potato":394,"minecraft:empty_map":395,"minecraft:golden_carrot":396,"minecraft:skull":397,"minecraft:carrot_on_a_stick":398,"minecraft:nether_star":399,"minecraft:pumpkin_pie":400,"minecraft:firework_rocket":401,"minecraft:firework_star":402,"minecraft:enchanted_book":403,"minecraft:comparator":404,"minecraft:netherbrick":405,"minecraft:quartz":406,"minecraft:tnt_minecart":407,"minecraft:hopper_minecart":408,"minecraft:prismarine_shard":409,"minecraft:hopper":410,"minecraft:rabbit":411,"minecraft:cooked_rabbit":412,"minecraft:rabbit_stew":413,"minecraft:rabbit_foot":414,"minecraft:rabbit_hide":415,"minecraft:leather_horse_armor":416,"minecraft:iron_horse_armor":417,"minecraft:golden_horse_armor":418,"minecraft:diamond_horse_armor":419,"minecraft:lead":420,"minecraft:name_tag":421,"minecraft:prismarine_crystals":422,"minecraft:mutton":423,"minecraft:cooked_mutton":424,"minecraft:armor_stand":425,"minecraft:end_crystal":426,"minecraft:spruce_door":427,"minecraft:birch_door":428,"minecraft:jungle_door":429,"minecraft:acacia_door":430,"minecraft:dark_oak_door":431,"minecraft:chorus_fruit":432,"minecraft:popped_chorus_fruit":433,"minecraft:banner_pattern":434,"minecraft:dragon_breath":437,"minecraft:splash_potion":438,"minecraft:lingering_potion":441,"minecraft:sparkler":442,"minecraft:command_block_minecart":443,"minecraft:elytra":444,"minecraft:shulker_shell":445,"minecraft:banner":446,"minecraft:medicine":447,"minecraft:balloon":448,"minecraft:rapid_fertilizer":449,"minecraft:totem_of_undying":450,"minecraft:bleach":451,"minecraft:iron_nugget":452,"minecraft:ice_bomb":453,"minecraft:trident":455,"minecraft:beetroot":457,"minecraft:beetroot_seeds":458,"minecraft:beetroot_soup":459,"minecraft:salmon":460,"minecraft:tropical_fish":461,"minecraft:pufferfish":462,"minecraft:cooked_salmon":463,"minecraft:dried_kelp":464,"minecraft:nautilus_shell":465,"minecraft:enchanted_golden_apple":466,"minecraft:heart_of_the_sea":467,"minecraft:scute":468,"minecraft:turtle_scute":468,"minecraft:turtle_helmet":469,"minecraft:phantom_membrane":470,"minecraft:crossbow":471,"minecraft:spruce_sign":472,"minecraft:birch_sign":473,"minecraft:jungle_sign":474,"minecraft:acacia_sign":475,"minecraft:dark_oak_sign":476,"minecraft:sweet_berries":477,"minecraft:camera":498,"minecraft:compound":499,"minecraft:music_disc_13":500,"minecraft:music_disc_cat":501,"minecraft:music_disc_blocks":502,"minecraft:music_disc_chirp":503,"minecraft:music_disc_far":504,"minecraft:music_disc_mall":505,"minecraft:music_disc_mellohi":506,"minecraft:music_disc_stal":507,"minecraft:music_disc_strad":508,"minecraft:music_disc_ward":509,"minecraft:music_disc_11":510,"minecraft:music_disc_wait":511,"minecraft:shield":513,"minecraft:raw_iron":520,"minecraft:raw_gold":521,"minecraft:raw_copper":522,"minecraft:glow_berries":654,"minecraft:campfire":720,"minecraft:suspicious_stew":734,"minecraft:honeycomb":736,"minecraft:honey_bottle":737,"minecraft:lodestone_compass":741,"minecraft:netherite_ingot":742,"minecraft:netherite_sword":743,"minecraft:netherite_shovel":744,"minecraft:netherite_pickaxe":745,"minecraft:netherite_axe":746,"minecraft:netherite_hoe":747,"minecraft:netherite_helmet":748,"minecraft:netherite_chestplate":749,"minecraft:netherite_leggings":750,"minecraft:netherite_boots":751,"minecraft:netherite_scrap":752,"minecraft:crimson_sign":753,"minecraft:warped_sign":754,"minecraft:crimson_door":755,"minecraft:warped_door":756,"minecraft:warped_fungus_on_a_stick":757,"minecraft:chain":758,"minecraft:nether_sprouts":760,"minecraft:soul_campfire":801,"minecraft:music_disc_pigstep":759,"minecraft:spyglass":772,"minecraft:music_disc_otherside":773,"minecraft:glow_frame":850,"minecraft:acacia_chest_boat":642,"minecraft:birch_chest_boat":639,"minecraft:chest_boat":645,"minecraft:dark_oak_chest_boat":643,"minecraft:disc_fragment_5":637,"minecraft:jungle_chest_boat":640,"minecraft:mangrove_chest_boat":644,"minecraft:music_disc_5":636,"minecraft:oak_chest_boat":638,"minecraft:spruce_chest_boat":641,"minecraft:amethyst_shard":771,"minecraft:echo_shard":647,"minecraft:goat_horn":761,"minecraft:music_disc_relic":701,"minecraft:copper_ingot":519,"minecraft:recovery_compass":648,"minecraft:mangrove_door":-493} \ No newline at end of file diff --git a/src/main/resources/runtime_block_states.dat b/src/main/resources/runtime_block_states.dat index 4a7e10179626df61df797749ecd0e7b4496bcfcb..be7c3b1df3f3ec6d89fa919953c6fd578e0b76a0 100644 GIT binary patch literal 54984 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 literal 54234 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 diff --git a/src/main/resources/runtime_block_states_662.dat b/src/main/resources/runtime_block_states_662.dat index 74e5a477a94d135c0128b89b0178fb239f605645..30c5b775de382cb8baebf259aec36b936c4bf3d4 100644 GIT binary patch literal 54898 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 literal 54297 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 diff --git a/src/main/resources/runtime_item_states.json b/src/main/resources/runtime_item_states.json index 27f54b2969f..2f2e94d0572 100644 --- a/src/main/resources/runtime_item_states.json +++ b/src/main/resources/runtime_item_states.json @@ -1 +1 @@ -[{"name":"minecraft:acacia_boat","id":388},{"name":"minecraft:acacia_button","id":-140},{"name":"minecraft:acacia_chest_boat","id":662},{"name":"minecraft:acacia_door","id":573},{"name":"minecraft:acacia_double_slab","id":-812},{"name":"minecraft:acacia_fence","id":-575},{"name":"minecraft:acacia_fence_gate","id":187},{"name":"minecraft:acacia_hanging_sign","id":-504},{"name":"minecraft:acacia_leaves","id":161},{"name":"minecraft:acacia_log","id":162},{"name":"minecraft:acacia_planks","id":-742},{"name":"minecraft:acacia_pressure_plate","id":-150},{"name":"minecraft:acacia_sapling","id":-828},{"name":"minecraft:acacia_sign","id":596},{"name":"minecraft:acacia_slab","id":-807},{"name":"minecraft:acacia_stairs","id":163},{"name":"minecraft:acacia_standing_sign","id":-190},{"name":"minecraft:acacia_trapdoor","id":-145},{"name":"minecraft:acacia_wall_sign","id":-191},{"name":"minecraft:acacia_wood","id":-817},{"name":"minecraft:activator_rail","id":126},{"name":"minecraft:agent_spawn_egg","id":498},{"name":"minecraft:air","id":-158},{"name":"minecraft:allay_spawn_egg","id":651},{"name":"minecraft:allium","id":-831},{"name":"minecraft:allow","id":210},{"name":"minecraft:amethyst_block","id":-327},{"name":"minecraft:amethyst_cluster","id":-329},{"name":"minecraft:amethyst_shard","id":644},{"name":"minecraft:ancient_debris","id":-271},{"name":"minecraft:andesite","id":-594},{"name":"minecraft:andesite_double_slab","id":-920},{"name":"minecraft:andesite_slab","id":-893},{"name":"minecraft:andesite_stairs","id":-171},{"name":"minecraft:andesite_wall","id":-974},{"name":"minecraft:angler_pottery_sherd","id":676},{"name":"minecraft:anvil","id":145},{"name":"minecraft:apple","id":261},{"name":"minecraft:archer_pottery_sherd","id":677},{"name":"minecraft:armadillo_scute","id":722},{"name":"minecraft:armadillo_spawn_egg","id":721},{"name":"minecraft:armor_stand","id":569},{"name":"minecraft:arms_up_pottery_sherd","id":678},{"name":"minecraft:arrow","id":308},{"name":"minecraft:axolotl_bucket","id":377},{"name":"minecraft:axolotl_spawn_egg","id":513},{"name":"minecraft:azalea","id":-337},{"name":"minecraft:azalea_leaves","id":-324},{"name":"minecraft:azalea_leaves_flowered","id":-325},{"name":"minecraft:azure_bluet","id":-832},{"name":"minecraft:baked_potato","id":286},{"name":"minecraft:balloon","id":618},{"name":"minecraft:bamboo","id":-163},{"name":"minecraft:bamboo_block","id":-527},{"name":"minecraft:bamboo_button","id":-511},{"name":"minecraft:bamboo_chest_raft","id":674},{"name":"minecraft:bamboo_door","id":-517},{"name":"minecraft:bamboo_double_slab","id":-521},{"name":"minecraft:bamboo_fence","id":-515},{"name":"minecraft:bamboo_fence_gate","id":-516},{"name":"minecraft:bamboo_hanging_sign","id":-522},{"name":"minecraft:bamboo_mosaic","id":-509},{"name":"minecraft:bamboo_mosaic_double_slab","id":-525},{"name":"minecraft:bamboo_mosaic_slab","id":-524},{"name":"minecraft:bamboo_mosaic_stairs","id":-523},{"name":"minecraft:bamboo_planks","id":-510},{"name":"minecraft:bamboo_pressure_plate","id":-514},{"name":"minecraft:bamboo_raft","id":673},{"name":"minecraft:bamboo_sapling","id":-164},{"name":"minecraft:bamboo_sign","id":672},{"name":"minecraft:bamboo_slab","id":-513},{"name":"minecraft:bamboo_stairs","id":-512},{"name":"minecraft:bamboo_standing_sign","id":-518},{"name":"minecraft:bamboo_trapdoor","id":-520},{"name":"minecraft:bamboo_wall_sign","id":-519},{"name":"minecraft:banner","id":584},{"name":"minecraft:banner_pattern","id":770},{"name":"minecraft:barrel","id":-203},{"name":"minecraft:barrier","id":-161},{"name":"minecraft:basalt","id":-234},{"name":"minecraft:bat_spawn_egg","id":463},{"name":"minecraft:beacon","id":138},{"name":"minecraft:bed","id":427},{"name":"minecraft:bedrock","id":7},{"name":"minecraft:bee_nest","id":-218},{"name":"minecraft:bee_spawn_egg","id":505},{"name":"minecraft:beef","id":278},{"name":"minecraft:beehive","id":-219},{"name":"minecraft:beetroot","id":290},{"name":"minecraft:beetroot_seeds","id":300},{"name":"minecraft:beetroot_soup","id":291},{"name":"minecraft:bell","id":-206},{"name":"minecraft:big_dripleaf","id":-323},{"name":"minecraft:birch_boat","id":385},{"name":"minecraft:birch_button","id":-141},{"name":"minecraft:birch_chest_boat","id":659},{"name":"minecraft:birch_door","id":571},{"name":"minecraft:birch_double_slab","id":-810},{"name":"minecraft:birch_fence","id":-576},{"name":"minecraft:birch_fence_gate","id":184},{"name":"minecraft:birch_hanging_sign","id":-502},{"name":"minecraft:birch_leaves","id":-801},{"name":"minecraft:birch_log","id":-570},{"name":"minecraft:birch_planks","id":-740},{"name":"minecraft:birch_pressure_plate","id":-151},{"name":"minecraft:birch_sapling","id":-826},{"name":"minecraft:birch_sign","id":594},{"name":"minecraft:birch_slab","id":-805},{"name":"minecraft:birch_stairs","id":135},{"name":"minecraft:birch_standing_sign","id":-186},{"name":"minecraft:birch_trapdoor","id":-146},{"name":"minecraft:birch_wall_sign","id":-187},{"name":"minecraft:birch_wood","id":-815},{"name":"minecraft:black_candle","id":-428},{"name":"minecraft:black_candle_cake","id":-445},{"name":"minecraft:black_carpet","id":-611},{"name":"minecraft:black_concrete","id":-642},{"name":"minecraft:black_concrete_powder","id":-723},{"name":"minecraft:black_dye","id":404},{"name":"minecraft:black_glazed_terracotta","id":235},{"name":"minecraft:black_shulker_box","id":-627},{"name":"minecraft:black_stained_glass","id":-687},{"name":"minecraft:black_stained_glass_pane","id":-657},{"name":"minecraft:black_terracotta","id":-738},{"name":"minecraft:black_wool","id":-554},{"name":"minecraft:blackstone","id":-273},{"name":"minecraft:blackstone_double_slab","id":-283},{"name":"minecraft:blackstone_slab","id":-282},{"name":"minecraft:blackstone_stairs","id":-276},{"name":"minecraft:blackstone_wall","id":-277},{"name":"minecraft:blade_pottery_sherd","id":679},{"name":"minecraft:blast_furnace","id":-196},{"name":"minecraft:blaze_powder","id":439},{"name":"minecraft:blaze_rod","id":432},{"name":"minecraft:blaze_spawn_egg","id":466},{"name":"minecraft:bleach","id":616},{"name":"minecraft:blue_candle","id":-424},{"name":"minecraft:blue_candle_cake","id":-441},{"name":"minecraft:blue_carpet","id":-607},{"name":"minecraft:blue_concrete","id":-638},{"name":"minecraft:blue_concrete_powder","id":-719},{"name":"minecraft:blue_dye","id":408},{"name":"minecraft:blue_glazed_terracotta","id":231},{"name":"minecraft:blue_ice","id":-11},{"name":"minecraft:blue_orchid","id":-830},{"name":"minecraft:blue_shulker_box","id":-623},{"name":"minecraft:blue_stained_glass","id":-683},{"name":"minecraft:blue_stained_glass_pane","id":-653},{"name":"minecraft:blue_terracotta","id":-734},{"name":"minecraft:blue_wool","id":-563},{"name":"minecraft:boat","id":768},{"name":"minecraft:bogged_spawn_egg","id":473},{"name":"minecraft:bolt_armor_trim_smithing_template","id":718},{"name":"minecraft:bone","id":424},{"name":"minecraft:bone_block","id":216},{"name":"minecraft:bone_meal","id":420},{"name":"minecraft:book","id":396},{"name":"minecraft:bookshelf","id":47},{"name":"minecraft:border_block","id":212},{"name":"minecraft:bordure_indented_banner_pattern","id":603},{"name":"minecraft:bow","id":307},{"name":"minecraft:bowl","id":329},{"name":"minecraft:brain_coral","id":-581},{"name":"minecraft:brain_coral_block","id":-849},{"name":"minecraft:brain_coral_fan","id":-840},{"name":"minecraft:brain_coral_wall_fan","id":-904},{"name":"minecraft:bread","id":266},{"name":"minecraft:breeze_rod","id":257},{"name":"minecraft:breeze_spawn_egg","id":512},{"name":"minecraft:brewer_pottery_sherd","id":680},{"name":"minecraft:brewing_stand","id":441},{"name":"minecraft:brick","id":392},{"name":"minecraft:brick_block","id":45},{"name":"minecraft:brick_double_slab","id":-880},{"name":"minecraft:brick_slab","id":-874},{"name":"minecraft:brick_stairs","id":108},{"name":"minecraft:brick_wall","id":-976},{"name":"minecraft:brown_candle","id":-425},{"name":"minecraft:brown_candle_cake","id":-442},{"name":"minecraft:brown_carpet","id":-608},{"name":"minecraft:brown_concrete","id":-639},{"name":"minecraft:brown_concrete_powder","id":-720},{"name":"minecraft:brown_dye","id":407},{"name":"minecraft:brown_glazed_terracotta","id":232},{"name":"minecraft:brown_mushroom","id":39},{"name":"minecraft:brown_mushroom_block","id":99},{"name":"minecraft:brown_shulker_box","id":-624},{"name":"minecraft:brown_stained_glass","id":-684},{"name":"minecraft:brown_stained_glass_pane","id":-654},{"name":"minecraft:brown_terracotta","id":-735},{"name":"minecraft:brown_wool","id":-555},{"name":"minecraft:brush","id":699},{"name":"minecraft:bubble_column","id":-160},{"name":"minecraft:bubble_coral","id":-582},{"name":"minecraft:bubble_coral_block","id":-850},{"name":"minecraft:bubble_coral_fan","id":-841},{"name":"minecraft:bubble_coral_wall_fan","id":-136},{"name":"minecraft:bucket","id":368},{"name":"minecraft:budding_amethyst","id":-328},{"name":"minecraft:burn_pottery_sherd","id":681},{"name":"minecraft:cactus","id":81},{"name":"minecraft:cake","id":426},{"name":"minecraft:calcite","id":-326},{"name":"minecraft:calibrated_sculk_sensor","id":-580},{"name":"minecraft:camel_spawn_egg","id":675},{"name":"minecraft:camera","id":613},{"name":"minecraft:campfire","id":608},{"name":"minecraft:candle","id":-412},{"name":"minecraft:candle_cake","id":-429},{"name":"minecraft:carpet","id":727},{"name":"minecraft:carrot","id":284},{"name":"minecraft:carrot_on_a_stick","id":534},{"name":"minecraft:carrots","id":141},{"name":"minecraft:cartography_table","id":-200},{"name":"minecraft:carved_pumpkin","id":-155},{"name":"minecraft:cat_spawn_egg","id":499},{"name":"minecraft:cauldron","id":442},{"name":"minecraft:cave_spider_spawn_egg","id":467},{"name":"minecraft:cave_vines","id":-322},{"name":"minecraft:cave_vines_body_with_berries","id":-375},{"name":"minecraft:cave_vines_head_with_berries","id":-376},{"name":"minecraft:chain","id":639},{"name":"minecraft:chain_command_block","id":189},{"name":"minecraft:chainmail_boots","id":350},{"name":"minecraft:chainmail_chestplate","id":348},{"name":"minecraft:chainmail_helmet","id":347},{"name":"minecraft:chainmail_leggings","id":349},{"name":"minecraft:charcoal","id":310},{"name":"minecraft:chemical_heat","id":192},{"name":"minecraft:chemistry_table","id":762},{"name":"minecraft:cherry_boat","id":669},{"name":"minecraft:cherry_button","id":-530},{"name":"minecraft:cherry_chest_boat","id":670},{"name":"minecraft:cherry_door","id":-531},{"name":"minecraft:cherry_double_slab","id":-540},{"name":"minecraft:cherry_fence","id":-532},{"name":"minecraft:cherry_fence_gate","id":-533},{"name":"minecraft:cherry_hanging_sign","id":-534},{"name":"minecraft:cherry_leaves","id":-548},{"name":"minecraft:cherry_log","id":-536},{"name":"minecraft:cherry_planks","id":-537},{"name":"minecraft:cherry_pressure_plate","id":-538},{"name":"minecraft:cherry_sapling","id":-547},{"name":"minecraft:cherry_sign","id":671},{"name":"minecraft:cherry_slab","id":-539},{"name":"minecraft:cherry_stairs","id":-541},{"name":"minecraft:cherry_standing_sign","id":-542},{"name":"minecraft:cherry_trapdoor","id":-543},{"name":"minecraft:cherry_wall_sign","id":-544},{"name":"minecraft:cherry_wood","id":-546},{"name":"minecraft:chest","id":54},{"name":"minecraft:chest_boat","id":665},{"name":"minecraft:chest_minecart","id":398},{"name":"minecraft:chicken","id":280},{"name":"minecraft:chicken_spawn_egg","id":445},{"name":"minecraft:chipped_anvil","id":-959},{"name":"minecraft:chiseled_bookshelf","id":-526},{"name":"minecraft:chiseled_copper","id":-760},{"name":"minecraft:chiseled_deepslate","id":-395},{"name":"minecraft:chiseled_nether_bricks","id":-302},{"name":"minecraft:chiseled_polished_blackstone","id":-279},{"name":"minecraft:chiseled_quartz_block","id":-953},{"name":"minecraft:chiseled_red_sandstone","id":-956},{"name":"minecraft:chiseled_sandstone","id":-944},{"name":"minecraft:chiseled_stone_bricks","id":-870},{"name":"minecraft:chiseled_tuff","id":-753},{"name":"minecraft:chiseled_tuff_bricks","id":-759},{"name":"minecraft:chorus_flower","id":200},{"name":"minecraft:chorus_fruit","id":575},{"name":"minecraft:chorus_plant","id":240},{"name":"minecraft:clay","id":82},{"name":"minecraft:clay_ball","id":393},{"name":"minecraft:client_request_placeholder_block","id":-465},{"name":"minecraft:clock","id":402},{"name":"minecraft:coal","id":309},{"name":"minecraft:coal_block","id":173},{"name":"minecraft:coal_ore","id":16},{"name":"minecraft:coarse_dirt","id":-962},{"name":"minecraft:coast_armor_trim_smithing_template","id":703},{"name":"minecraft:cobbled_deepslate","id":-379},{"name":"minecraft:cobbled_deepslate_double_slab","id":-396},{"name":"minecraft:cobbled_deepslate_slab","id":-380},{"name":"minecraft:cobbled_deepslate_stairs","id":-381},{"name":"minecraft:cobbled_deepslate_wall","id":-382},{"name":"minecraft:cobblestone","id":4},{"name":"minecraft:cobblestone_double_slab","id":-879},{"name":"minecraft:cobblestone_slab","id":-873},{"name":"minecraft:cobblestone_wall","id":139},{"name":"minecraft:cocoa","id":127},{"name":"minecraft:cocoa_beans","id":421},{"name":"minecraft:cod","id":269},{"name":"minecraft:cod_bucket","id":372},{"name":"minecraft:cod_spawn_egg","id":491},{"name":"minecraft:colored_torch_blue","id":204},{"name":"minecraft:colored_torch_bp","id":766},{"name":"minecraft:colored_torch_green","id":-963},{"name":"minecraft:colored_torch_purple","id":-964},{"name":"minecraft:colored_torch_red","id":202},{"name":"minecraft:colored_torch_rg","id":765},{"name":"minecraft:command_block","id":137},{"name":"minecraft:command_block_minecart","id":580},{"name":"minecraft:comparator","id":539},{"name":"minecraft:compass","id":400},{"name":"minecraft:composter","id":-213},{"name":"minecraft:compound","id":614},{"name":"minecraft:compound_creator","id":238},{"name":"minecraft:concrete","id":753},{"name":"minecraft:concrete_powder","id":754},{"name":"minecraft:conduit","id":-157},{"name":"minecraft:cooked_beef","id":279},{"name":"minecraft:cooked_chicken","id":281},{"name":"minecraft:cooked_cod","id":273},{"name":"minecraft:cooked_mutton","id":568},{"name":"minecraft:cooked_porkchop","id":268},{"name":"minecraft:cooked_rabbit","id":294},{"name":"minecraft:cooked_salmon","id":274},{"name":"minecraft:cookie","id":276},{"name":"minecraft:copper_block","id":-340},{"name":"minecraft:copper_bulb","id":-776},{"name":"minecraft:copper_door","id":-784},{"name":"minecraft:copper_grate","id":-768},{"name":"minecraft:copper_ingot","id":521},{"name":"minecraft:copper_ore","id":-311},{"name":"minecraft:copper_trapdoor","id":-792},{"name":"minecraft:coral","id":749},{"name":"minecraft:coral_block","id":731},{"name":"minecraft:coral_fan","id":740},{"name":"minecraft:coral_fan_dead","id":741},{"name":"minecraft:cornflower","id":-838},{"name":"minecraft:cow_spawn_egg","id":446},{"name":"minecraft:cracked_deepslate_bricks","id":-410},{"name":"minecraft:cracked_deepslate_tiles","id":-409},{"name":"minecraft:cracked_nether_bricks","id":-303},{"name":"minecraft:cracked_polished_blackstone_bricks","id":-280},{"name":"minecraft:cracked_stone_bricks","id":-869},{"name":"minecraft:crafter","id":-313},{"name":"minecraft:crafting_table","id":58},{"name":"minecraft:creeper_banner_pattern","id":599},{"name":"minecraft:creeper_spawn_egg","id":451},{"name":"minecraft:crimson_button","id":-260},{"name":"minecraft:crimson_door","id":636},{"name":"minecraft:crimson_double_slab","id":-266},{"name":"minecraft:crimson_fence","id":-256},{"name":"minecraft:crimson_fence_gate","id":-258},{"name":"minecraft:crimson_fungus","id":-228},{"name":"minecraft:crimson_hanging_sign","id":-506},{"name":"minecraft:crimson_hyphae","id":-299},{"name":"minecraft:crimson_nylium","id":-232},{"name":"minecraft:crimson_planks","id":-242},{"name":"minecraft:crimson_pressure_plate","id":-262},{"name":"minecraft:crimson_roots","id":-223},{"name":"minecraft:crimson_sign","id":634},{"name":"minecraft:crimson_slab","id":-264},{"name":"minecraft:crimson_stairs","id":-254},{"name":"minecraft:crimson_standing_sign","id":-250},{"name":"minecraft:crimson_stem","id":-225},{"name":"minecraft:crimson_trapdoor","id":-246},{"name":"minecraft:crimson_wall_sign","id":-252},{"name":"minecraft:crossbow","id":592},{"name":"minecraft:crying_obsidian","id":-289},{"name":"minecraft:cut_copper","id":-347},{"name":"minecraft:cut_copper_slab","id":-361},{"name":"minecraft:cut_copper_stairs","id":-354},{"name":"minecraft:cut_red_sandstone","id":-957},{"name":"minecraft:cut_red_sandstone_double_slab","id":-928},{"name":"minecraft:cut_red_sandstone_slab","id":-901},{"name":"minecraft:cut_sandstone","id":-945},{"name":"minecraft:cut_sandstone_double_slab","id":-927},{"name":"minecraft:cut_sandstone_slab","id":-900},{"name":"minecraft:cyan_candle","id":-422},{"name":"minecraft:cyan_candle_cake","id":-439},{"name":"minecraft:cyan_carpet","id":-605},{"name":"minecraft:cyan_concrete","id":-636},{"name":"minecraft:cyan_concrete_powder","id":-717},{"name":"minecraft:cyan_dye","id":410},{"name":"minecraft:cyan_glazed_terracotta","id":229},{"name":"minecraft:cyan_shulker_box","id":-621},{"name":"minecraft:cyan_stained_glass","id":-681},{"name":"minecraft:cyan_stained_glass_pane","id":-651},{"name":"minecraft:cyan_terracotta","id":-732},{"name":"minecraft:cyan_wool","id":-561},{"name":"minecraft:damaged_anvil","id":-960},{"name":"minecraft:dandelion","id":37},{"name":"minecraft:danger_pottery_sherd","id":682},{"name":"minecraft:dark_oak_boat","id":389},{"name":"minecraft:dark_oak_button","id":-142},{"name":"minecraft:dark_oak_chest_boat","id":663},{"name":"minecraft:dark_oak_door","id":574},{"name":"minecraft:dark_oak_double_slab","id":-813},{"name":"minecraft:dark_oak_fence","id":-577},{"name":"minecraft:dark_oak_fence_gate","id":186},{"name":"minecraft:dark_oak_hanging_sign","id":-505},{"name":"minecraft:dark_oak_leaves","id":-803},{"name":"minecraft:dark_oak_log","id":-572},{"name":"minecraft:dark_oak_planks","id":-743},{"name":"minecraft:dark_oak_pressure_plate","id":-152},{"name":"minecraft:dark_oak_sapling","id":-829},{"name":"minecraft:dark_oak_sign","id":597},{"name":"minecraft:dark_oak_slab","id":-808},{"name":"minecraft:dark_oak_stairs","id":164},{"name":"minecraft:dark_oak_trapdoor","id":-147},{"name":"minecraft:dark_oak_wood","id":-818},{"name":"minecraft:dark_prismarine","id":-947},{"name":"minecraft:dark_prismarine_double_slab","id":-913},{"name":"minecraft:dark_prismarine_slab","id":-886},{"name":"minecraft:dark_prismarine_stairs","id":-3},{"name":"minecraft:darkoak_standing_sign","id":-192},{"name":"minecraft:darkoak_wall_sign","id":-193},{"name":"minecraft:daylight_detector","id":151},{"name":"minecraft:daylight_detector_inverted","id":178},{"name":"minecraft:dead_brain_coral","id":-586},{"name":"minecraft:dead_brain_coral_block","id":-854},{"name":"minecraft:dead_brain_coral_fan","id":-844},{"name":"minecraft:dead_brain_coral_wall_fan","id":-906},{"name":"minecraft:dead_bubble_coral","id":-587},{"name":"minecraft:dead_bubble_coral_block","id":-855},{"name":"minecraft:dead_bubble_coral_fan","id":-845},{"name":"minecraft:dead_bubble_coral_wall_fan","id":-908},{"name":"minecraft:dead_fire_coral","id":-588},{"name":"minecraft:dead_fire_coral_block","id":-856},{"name":"minecraft:dead_fire_coral_fan","id":-846},{"name":"minecraft:dead_fire_coral_wall_fan","id":-909},{"name":"minecraft:dead_horn_coral","id":-589},{"name":"minecraft:dead_horn_coral_block","id":-857},{"name":"minecraft:dead_horn_coral_fan","id":-847},{"name":"minecraft:dead_horn_coral_wall_fan","id":-910},{"name":"minecraft:dead_tube_coral","id":-585},{"name":"minecraft:dead_tube_coral_block","id":-853},{"name":"minecraft:dead_tube_coral_fan","id":-134},{"name":"minecraft:dead_tube_coral_wall_fan","id":-905},{"name":"minecraft:deadbush","id":32},{"name":"minecraft:decorated_pot","id":-551},{"name":"minecraft:deepslate","id":-378},{"name":"minecraft:deepslate_brick_double_slab","id":-399},{"name":"minecraft:deepslate_brick_slab","id":-392},{"name":"minecraft:deepslate_brick_stairs","id":-393},{"name":"minecraft:deepslate_brick_wall","id":-394},{"name":"minecraft:deepslate_bricks","id":-391},{"name":"minecraft:deepslate_coal_ore","id":-406},{"name":"minecraft:deepslate_copper_ore","id":-408},{"name":"minecraft:deepslate_diamond_ore","id":-405},{"name":"minecraft:deepslate_emerald_ore","id":-407},{"name":"minecraft:deepslate_gold_ore","id":-402},{"name":"minecraft:deepslate_iron_ore","id":-401},{"name":"minecraft:deepslate_lapis_ore","id":-400},{"name":"minecraft:deepslate_redstone_ore","id":-403},{"name":"minecraft:deepslate_tile_double_slab","id":-398},{"name":"minecraft:deepslate_tile_slab","id":-388},{"name":"minecraft:deepslate_tile_stairs","id":-389},{"name":"minecraft:deepslate_tile_wall","id":-390},{"name":"minecraft:deepslate_tiles","id":-387},{"name":"minecraft:deny","id":211},{"name":"minecraft:deprecated_anvil","id":-961},{"name":"minecraft:deprecated_purpur_block_1","id":-950},{"name":"minecraft:deprecated_purpur_block_2","id":-952},{"name":"minecraft:detector_rail","id":28},{"name":"minecraft:diamond","id":311},{"name":"minecraft:diamond_axe","id":326},{"name":"minecraft:diamond_block","id":57},{"name":"minecraft:diamond_boots","id":358},{"name":"minecraft:diamond_chestplate","id":356},{"name":"minecraft:diamond_helmet","id":355},{"name":"minecraft:diamond_hoe","id":340},{"name":"minecraft:diamond_horse_armor","id":550},{"name":"minecraft:diamond_leggings","id":357},{"name":"minecraft:diamond_ore","id":56},{"name":"minecraft:diamond_pickaxe","id":325},{"name":"minecraft:diamond_shovel","id":324},{"name":"minecraft:diamond_sword","id":323},{"name":"minecraft:diorite","id":-592},{"name":"minecraft:diorite_double_slab","id":-921},{"name":"minecraft:diorite_slab","id":-894},{"name":"minecraft:diorite_stairs","id":-170},{"name":"minecraft:diorite_wall","id":-973},{"name":"minecraft:dirt","id":3},{"name":"minecraft:dirt_with_roots","id":-318},{"name":"minecraft:disc_fragment_5","id":657},{"name":"minecraft:dispenser","id":23},{"name":"minecraft:dolphin_spawn_egg","id":495},{"name":"minecraft:donkey_spawn_egg","id":476},{"name":"minecraft:double_cut_copper_slab","id":-368},{"name":"minecraft:double_plant","id":747},{"name":"minecraft:double_stone_block_slab","id":736},{"name":"minecraft:double_stone_block_slab2","id":737},{"name":"minecraft:double_stone_block_slab3","id":738},{"name":"minecraft:double_stone_block_slab4","id":739},{"name":"minecraft:dragon_breath","id":577},{"name":"minecraft:dragon_egg","id":122},{"name":"minecraft:dried_kelp","id":275},{"name":"minecraft:dried_kelp_block","id":-139},{"name":"minecraft:dripstone_block","id":-317},{"name":"minecraft:dropper","id":125},{"name":"minecraft:drowned_spawn_egg","id":494},{"name":"minecraft:dune_armor_trim_smithing_template","id":702},{"name":"minecraft:dye","id":769},{"name":"minecraft:echo_shard","id":667},{"name":"minecraft:egg","id":399},{"name":"minecraft:elder_guardian_spawn_egg","id":482},{"name":"minecraft:element_0","id":36},{"name":"minecraft:element_1","id":-12},{"name":"minecraft:element_10","id":-21},{"name":"minecraft:element_100","id":-111},{"name":"minecraft:element_101","id":-112},{"name":"minecraft:element_102","id":-113},{"name":"minecraft:element_103","id":-114},{"name":"minecraft:element_104","id":-115},{"name":"minecraft:element_105","id":-116},{"name":"minecraft:element_106","id":-117},{"name":"minecraft:element_107","id":-118},{"name":"minecraft:element_108","id":-119},{"name":"minecraft:element_109","id":-120},{"name":"minecraft:element_11","id":-22},{"name":"minecraft:element_110","id":-121},{"name":"minecraft:element_111","id":-122},{"name":"minecraft:element_112","id":-123},{"name":"minecraft:element_113","id":-124},{"name":"minecraft:element_114","id":-125},{"name":"minecraft:element_115","id":-126},{"name":"minecraft:element_116","id":-127},{"name":"minecraft:element_117","id":-128},{"name":"minecraft:element_118","id":-129},{"name":"minecraft:element_12","id":-23},{"name":"minecraft:element_13","id":-24},{"name":"minecraft:element_14","id":-25},{"name":"minecraft:element_15","id":-26},{"name":"minecraft:element_16","id":-27},{"name":"minecraft:element_17","id":-28},{"name":"minecraft:element_18","id":-29},{"name":"minecraft:element_19","id":-30},{"name":"minecraft:element_2","id":-13},{"name":"minecraft:element_20","id":-31},{"name":"minecraft:element_21","id":-32},{"name":"minecraft:element_22","id":-33},{"name":"minecraft:element_23","id":-34},{"name":"minecraft:element_24","id":-35},{"name":"minecraft:element_25","id":-36},{"name":"minecraft:element_26","id":-37},{"name":"minecraft:element_27","id":-38},{"name":"minecraft:element_28","id":-39},{"name":"minecraft:element_29","id":-40},{"name":"minecraft:element_3","id":-14},{"name":"minecraft:element_30","id":-41},{"name":"minecraft:element_31","id":-42},{"name":"minecraft:element_32","id":-43},{"name":"minecraft:element_33","id":-44},{"name":"minecraft:element_34","id":-45},{"name":"minecraft:element_35","id":-46},{"name":"minecraft:element_36","id":-47},{"name":"minecraft:element_37","id":-48},{"name":"minecraft:element_38","id":-49},{"name":"minecraft:element_39","id":-50},{"name":"minecraft:element_4","id":-15},{"name":"minecraft:element_40","id":-51},{"name":"minecraft:element_41","id":-52},{"name":"minecraft:element_42","id":-53},{"name":"minecraft:element_43","id":-54},{"name":"minecraft:element_44","id":-55},{"name":"minecraft:element_45","id":-56},{"name":"minecraft:element_46","id":-57},{"name":"minecraft:element_47","id":-58},{"name":"minecraft:element_48","id":-59},{"name":"minecraft:element_49","id":-60},{"name":"minecraft:element_5","id":-16},{"name":"minecraft:element_50","id":-61},{"name":"minecraft:element_51","id":-62},{"name":"minecraft:element_52","id":-63},{"name":"minecraft:element_53","id":-64},{"name":"minecraft:element_54","id":-65},{"name":"minecraft:element_55","id":-66},{"name":"minecraft:element_56","id":-67},{"name":"minecraft:element_57","id":-68},{"name":"minecraft:element_58","id":-69},{"name":"minecraft:element_59","id":-70},{"name":"minecraft:element_6","id":-17},{"name":"minecraft:element_60","id":-71},{"name":"minecraft:element_61","id":-72},{"name":"minecraft:element_62","id":-73},{"name":"minecraft:element_63","id":-74},{"name":"minecraft:element_64","id":-75},{"name":"minecraft:element_65","id":-76},{"name":"minecraft:element_66","id":-77},{"name":"minecraft:element_67","id":-78},{"name":"minecraft:element_68","id":-79},{"name":"minecraft:element_69","id":-80},{"name":"minecraft:element_7","id":-18},{"name":"minecraft:element_70","id":-81},{"name":"minecraft:element_71","id":-82},{"name":"minecraft:element_72","id":-83},{"name":"minecraft:element_73","id":-84},{"name":"minecraft:element_74","id":-85},{"name":"minecraft:element_75","id":-86},{"name":"minecraft:element_76","id":-87},{"name":"minecraft:element_77","id":-88},{"name":"minecraft:element_78","id":-89},{"name":"minecraft:element_79","id":-90},{"name":"minecraft:element_8","id":-19},{"name":"minecraft:element_80","id":-91},{"name":"minecraft:element_81","id":-92},{"name":"minecraft:element_82","id":-93},{"name":"minecraft:element_83","id":-94},{"name":"minecraft:element_84","id":-95},{"name":"minecraft:element_85","id":-96},{"name":"minecraft:element_86","id":-97},{"name":"minecraft:element_87","id":-98},{"name":"minecraft:element_88","id":-99},{"name":"minecraft:element_89","id":-100},{"name":"minecraft:element_9","id":-20},{"name":"minecraft:element_90","id":-101},{"name":"minecraft:element_91","id":-102},{"name":"minecraft:element_92","id":-103},{"name":"minecraft:element_93","id":-104},{"name":"minecraft:element_94","id":-105},{"name":"minecraft:element_95","id":-106},{"name":"minecraft:element_96","id":-107},{"name":"minecraft:element_97","id":-108},{"name":"minecraft:element_98","id":-109},{"name":"minecraft:element_99","id":-110},{"name":"minecraft:element_constructor","id":-987},{"name":"minecraft:elytra","id":581},{"name":"minecraft:emerald","id":529},{"name":"minecraft:emerald_block","id":133},{"name":"minecraft:emerald_ore","id":129},{"name":"minecraft:empty_map","id":532},{"name":"minecraft:enchanted_book","id":538},{"name":"minecraft:enchanted_golden_apple","id":264},{"name":"minecraft:enchanting_table","id":116},{"name":"minecraft:end_brick_stairs","id":-178},{"name":"minecraft:end_bricks","id":206},{"name":"minecraft:end_crystal","id":772},{"name":"minecraft:end_gateway","id":209},{"name":"minecraft:end_portal","id":119},{"name":"minecraft:end_portal_frame","id":120},{"name":"minecraft:end_rod","id":208},{"name":"minecraft:end_stone","id":121},{"name":"minecraft:end_stone_brick_double_slab","id":-167},{"name":"minecraft:end_stone_brick_slab","id":-162},{"name":"minecraft:end_stone_brick_wall","id":-980},{"name":"minecraft:ender_chest","id":130},{"name":"minecraft:ender_dragon_spawn_egg","id":518},{"name":"minecraft:ender_eye","id":443},{"name":"minecraft:ender_pearl","id":431},{"name":"minecraft:enderman_spawn_egg","id":452},{"name":"minecraft:endermite_spawn_egg","id":470},{"name":"minecraft:evoker_spawn_egg","id":486},{"name":"minecraft:experience_bottle","id":525},{"name":"minecraft:explorer_pottery_sherd","id":683},{"name":"minecraft:exposed_chiseled_copper","id":-761},{"name":"minecraft:exposed_copper","id":-341},{"name":"minecraft:exposed_copper_bulb","id":-777},{"name":"minecraft:exposed_copper_door","id":-785},{"name":"minecraft:exposed_copper_grate","id":-769},{"name":"minecraft:exposed_copper_trapdoor","id":-793},{"name":"minecraft:exposed_cut_copper","id":-348},{"name":"minecraft:exposed_cut_copper_slab","id":-362},{"name":"minecraft:exposed_cut_copper_stairs","id":-355},{"name":"minecraft:exposed_double_cut_copper_slab","id":-369},{"name":"minecraft:eye_armor_trim_smithing_template","id":706},{"name":"minecraft:farmland","id":60},{"name":"minecraft:feather","id":335},{"name":"minecraft:fence","id":729},{"name":"minecraft:fence_gate","id":107},{"name":"minecraft:fermented_spider_eye","id":438},{"name":"minecraft:fern","id":-848},{"name":"minecraft:field_masoned_banner_pattern","id":602},{"name":"minecraft:filled_map","id":429},{"name":"minecraft:fire","id":51},{"name":"minecraft:fire_charge","id":526},{"name":"minecraft:fire_coral","id":-583},{"name":"minecraft:fire_coral_block","id":-851},{"name":"minecraft:fire_coral_fan","id":-842},{"name":"minecraft:fire_coral_wall_fan","id":-907},{"name":"minecraft:firework_rocket","id":536},{"name":"minecraft:firework_star","id":537},{"name":"minecraft:fishing_rod","id":401},{"name":"minecraft:fletching_table","id":-201},{"name":"minecraft:flint","id":364},{"name":"minecraft:flint_and_steel","id":306},{"name":"minecraft:flow_armor_trim_smithing_template","id":717},{"name":"minecraft:flow_banner_pattern","id":606},{"name":"minecraft:flow_pottery_sherd","id":684},{"name":"minecraft:flower_banner_pattern","id":598},{"name":"minecraft:flower_pot","id":531},{"name":"minecraft:flowering_azalea","id":-338},{"name":"minecraft:flowing_lava","id":10},{"name":"minecraft:flowing_water","id":8},{"name":"minecraft:fox_spawn_egg","id":501},{"name":"minecraft:frame","id":530},{"name":"minecraft:friend_pottery_sherd","id":685},{"name":"minecraft:frog_spawn","id":-468},{"name":"minecraft:frog_spawn_egg","id":648},{"name":"minecraft:frosted_ice","id":207},{"name":"minecraft:furnace","id":61},{"name":"minecraft:ghast_spawn_egg","id":464},{"name":"minecraft:ghast_tear","id":434},{"name":"minecraft:gilded_blackstone","id":-281},{"name":"minecraft:glass","id":20},{"name":"minecraft:glass_bottle","id":437},{"name":"minecraft:glass_pane","id":102},{"name":"minecraft:glistering_melon_slice","id":444},{"name":"minecraft:globe_banner_pattern","id":605},{"name":"minecraft:glow_berries","id":773},{"name":"minecraft:glow_frame","id":643},{"name":"minecraft:glow_ink_sac","id":520},{"name":"minecraft:glow_lichen","id":-411},{"name":"minecraft:glow_squid_spawn_egg","id":515},{"name":"minecraft:glow_stick","id":621},{"name":"minecraft:glowingobsidian","id":246},{"name":"minecraft:glowstone","id":89},{"name":"minecraft:glowstone_dust","id":403},{"name":"minecraft:goat_horn","id":647},{"name":"minecraft:goat_spawn_egg","id":514},{"name":"minecraft:gold_block","id":41},{"name":"minecraft:gold_ingot","id":313},{"name":"minecraft:gold_nugget","id":435},{"name":"minecraft:gold_ore","id":14},{"name":"minecraft:golden_apple","id":263},{"name":"minecraft:golden_axe","id":333},{"name":"minecraft:golden_boots","id":362},{"name":"minecraft:golden_carrot","id":288},{"name":"minecraft:golden_chestplate","id":360},{"name":"minecraft:golden_helmet","id":359},{"name":"minecraft:golden_hoe","id":341},{"name":"minecraft:golden_horse_armor","id":549},{"name":"minecraft:golden_leggings","id":361},{"name":"minecraft:golden_pickaxe","id":332},{"name":"minecraft:golden_rail","id":27},{"name":"minecraft:golden_shovel","id":331},{"name":"minecraft:golden_sword","id":330},{"name":"minecraft:granite","id":-590},{"name":"minecraft:granite_double_slab","id":-923},{"name":"minecraft:granite_slab","id":-896},{"name":"minecraft:granite_stairs","id":-169},{"name":"minecraft:granite_wall","id":-972},{"name":"minecraft:grass_block","id":2},{"name":"minecraft:grass_path","id":198},{"name":"minecraft:gravel","id":13},{"name":"minecraft:gray_candle","id":-420},{"name":"minecraft:gray_candle_cake","id":-437},{"name":"minecraft:gray_carpet","id":-603},{"name":"minecraft:gray_concrete","id":-634},{"name":"minecraft:gray_concrete_powder","id":-715},{"name":"minecraft:gray_dye","id":412},{"name":"minecraft:gray_glazed_terracotta","id":227},{"name":"minecraft:gray_shulker_box","id":-619},{"name":"minecraft:gray_stained_glass","id":-679},{"name":"minecraft:gray_stained_glass_pane","id":-649},{"name":"minecraft:gray_terracotta","id":-730},{"name":"minecraft:gray_wool","id":-553},{"name":"minecraft:green_candle","id":-426},{"name":"minecraft:green_candle_cake","id":-443},{"name":"minecraft:green_carpet","id":-609},{"name":"minecraft:green_concrete","id":-640},{"name":"minecraft:green_concrete_powder","id":-721},{"name":"minecraft:green_dye","id":406},{"name":"minecraft:green_glazed_terracotta","id":233},{"name":"minecraft:green_shulker_box","id":-625},{"name":"minecraft:green_stained_glass","id":-685},{"name":"minecraft:green_stained_glass_pane","id":-655},{"name":"minecraft:green_terracotta","id":-736},{"name":"minecraft:green_wool","id":-560},{"name":"minecraft:grindstone","id":-195},{"name":"minecraft:guardian_spawn_egg","id":471},{"name":"minecraft:gunpowder","id":336},{"name":"minecraft:guster_banner_pattern","id":607},{"name":"minecraft:guster_pottery_sherd","id":686},{"name":"minecraft:hanging_roots","id":-319},{"name":"minecraft:hard_black_stained_glass","id":-702},{"name":"minecraft:hard_black_stained_glass_pane","id":-672},{"name":"minecraft:hard_blue_stained_glass","id":-698},{"name":"minecraft:hard_blue_stained_glass_pane","id":-668},{"name":"minecraft:hard_brown_stained_glass","id":-699},{"name":"minecraft:hard_brown_stained_glass_pane","id":-669},{"name":"minecraft:hard_cyan_stained_glass","id":-696},{"name":"minecraft:hard_cyan_stained_glass_pane","id":-666},{"name":"minecraft:hard_glass","id":253},{"name":"minecraft:hard_glass_pane","id":190},{"name":"minecraft:hard_gray_stained_glass","id":-694},{"name":"minecraft:hard_gray_stained_glass_pane","id":-664},{"name":"minecraft:hard_green_stained_glass","id":-700},{"name":"minecraft:hard_green_stained_glass_pane","id":-670},{"name":"minecraft:hard_light_blue_stained_glass","id":-690},{"name":"minecraft:hard_light_blue_stained_glass_pane","id":-660},{"name":"minecraft:hard_light_gray_stained_glass","id":-695},{"name":"minecraft:hard_light_gray_stained_glass_pane","id":-665},{"name":"minecraft:hard_lime_stained_glass","id":-692},{"name":"minecraft:hard_lime_stained_glass_pane","id":-662},{"name":"minecraft:hard_magenta_stained_glass","id":-689},{"name":"minecraft:hard_magenta_stained_glass_pane","id":-659},{"name":"minecraft:hard_orange_stained_glass","id":-688},{"name":"minecraft:hard_orange_stained_glass_pane","id":-658},{"name":"minecraft:hard_pink_stained_glass","id":-693},{"name":"minecraft:hard_pink_stained_glass_pane","id":-663},{"name":"minecraft:hard_purple_stained_glass","id":-697},{"name":"minecraft:hard_purple_stained_glass_pane","id":-667},{"name":"minecraft:hard_red_stained_glass","id":-701},{"name":"minecraft:hard_red_stained_glass_pane","id":-671},{"name":"minecraft:hard_stained_glass","id":763},{"name":"minecraft:hard_stained_glass_pane","id":764},{"name":"minecraft:hard_white_stained_glass","id":254},{"name":"minecraft:hard_white_stained_glass_pane","id":191},{"name":"minecraft:hard_yellow_stained_glass","id":-691},{"name":"minecraft:hard_yellow_stained_glass_pane","id":-661},{"name":"minecraft:hardened_clay","id":172},{"name":"minecraft:hay_block","id":170},{"name":"minecraft:heart_of_the_sea","id":588},{"name":"minecraft:heart_pottery_sherd","id":687},{"name":"minecraft:heartbreak_pottery_sherd","id":688},{"name":"minecraft:heavy_core","id":-316},{"name":"minecraft:heavy_weighted_pressure_plate","id":148},{"name":"minecraft:hoglin_spawn_egg","id":507},{"name":"minecraft:honey_block","id":-220},{"name":"minecraft:honey_bottle","id":611},{"name":"minecraft:honeycomb","id":610},{"name":"minecraft:honeycomb_block","id":-221},{"name":"minecraft:hopper","id":544},{"name":"minecraft:hopper_minecart","id":543},{"name":"minecraft:horn_coral","id":-584},{"name":"minecraft:horn_coral_block","id":-852},{"name":"minecraft:horn_coral_fan","id":-843},{"name":"minecraft:horn_coral_wall_fan","id":-137},{"name":"minecraft:horse_spawn_egg","id":468},{"name":"minecraft:host_armor_trim_smithing_template","id":716},{"name":"minecraft:howl_pottery_sherd","id":689},{"name":"minecraft:husk_spawn_egg","id":474},{"name":"minecraft:ice","id":79},{"name":"minecraft:ice_bomb","id":615},{"name":"minecraft:infested_chiseled_stone_bricks","id":-862},{"name":"minecraft:infested_cobblestone","id":-858},{"name":"minecraft:infested_cracked_stone_bricks","id":-861},{"name":"minecraft:infested_deepslate","id":-454},{"name":"minecraft:infested_mossy_stone_bricks","id":-860},{"name":"minecraft:infested_stone","id":97},{"name":"minecraft:infested_stone_bricks","id":-859},{"name":"minecraft:info_update","id":248},{"name":"minecraft:info_update2","id":249},{"name":"minecraft:ink_sac","id":422},{"name":"minecraft:invisible_bedrock","id":95},{"name":"minecraft:iron_axe","id":305},{"name":"minecraft:iron_bars","id":101},{"name":"minecraft:iron_block","id":42},{"name":"minecraft:iron_boots","id":354},{"name":"minecraft:iron_chestplate","id":352},{"name":"minecraft:iron_door","id":380},{"name":"minecraft:iron_golem_spawn_egg","id":516},{"name":"minecraft:iron_helmet","id":351},{"name":"minecraft:iron_hoe","id":339},{"name":"minecraft:iron_horse_armor","id":548},{"name":"minecraft:iron_ingot","id":312},{"name":"minecraft:iron_leggings","id":353},{"name":"minecraft:iron_nugget","id":586},{"name":"minecraft:iron_ore","id":15},{"name":"minecraft:iron_pickaxe","id":304},{"name":"minecraft:iron_shovel","id":303},{"name":"minecraft:iron_sword","id":314},{"name":"minecraft:iron_trapdoor","id":167},{"name":"minecraft:item.acacia_door","id":196},{"name":"minecraft:item.bed","id":26},{"name":"minecraft:item.beetroot","id":244},{"name":"minecraft:item.birch_door","id":194},{"name":"minecraft:item.brewing_stand","id":117},{"name":"minecraft:item.cake","id":92},{"name":"minecraft:item.camera","id":242},{"name":"minecraft:item.campfire","id":-209},{"name":"minecraft:item.cauldron","id":118},{"name":"minecraft:item.chain","id":-286},{"name":"minecraft:item.crimson_door","id":-244},{"name":"minecraft:item.dark_oak_door","id":197},{"name":"minecraft:item.flower_pot","id":140},{"name":"minecraft:item.frame","id":199},{"name":"minecraft:item.glow_frame","id":-339},{"name":"minecraft:item.hopper","id":154},{"name":"minecraft:item.iron_door","id":71},{"name":"minecraft:item.jungle_door","id":195},{"name":"minecraft:item.kelp","id":-138},{"name":"minecraft:item.mangrove_door","id":-493},{"name":"minecraft:item.nether_sprouts","id":-238},{"name":"minecraft:item.nether_wart","id":115},{"name":"minecraft:item.reeds","id":83},{"name":"minecraft:item.skull","id":144},{"name":"minecraft:item.soul_campfire","id":-290},{"name":"minecraft:item.spruce_door","id":193},{"name":"minecraft:item.warped_door","id":-245},{"name":"minecraft:item.wheat","id":59},{"name":"minecraft:item.wooden_door","id":64},{"name":"minecraft:jigsaw","id":-211},{"name":"minecraft:jukebox","id":84},{"name":"minecraft:jungle_boat","id":386},{"name":"minecraft:jungle_button","id":-143},{"name":"minecraft:jungle_chest_boat","id":660},{"name":"minecraft:jungle_door","id":572},{"name":"minecraft:jungle_double_slab","id":-811},{"name":"minecraft:jungle_fence","id":-578},{"name":"minecraft:jungle_fence_gate","id":185},{"name":"minecraft:jungle_hanging_sign","id":-503},{"name":"minecraft:jungle_leaves","id":-802},{"name":"minecraft:jungle_log","id":-571},{"name":"minecraft:jungle_planks","id":-741},{"name":"minecraft:jungle_pressure_plate","id":-153},{"name":"minecraft:jungle_sapling","id":-827},{"name":"minecraft:jungle_sign","id":595},{"name":"minecraft:jungle_slab","id":-806},{"name":"minecraft:jungle_stairs","id":136},{"name":"minecraft:jungle_standing_sign","id":-188},{"name":"minecraft:jungle_trapdoor","id":-148},{"name":"minecraft:jungle_wall_sign","id":-189},{"name":"minecraft:jungle_wood","id":-816},{"name":"minecraft:kelp","id":391},{"name":"minecraft:lab_table","id":-988},{"name":"minecraft:ladder","id":65},{"name":"minecraft:lantern","id":-208},{"name":"minecraft:lapis_block","id":22},{"name":"minecraft:lapis_lazuli","id":423},{"name":"minecraft:lapis_ore","id":21},{"name":"minecraft:large_amethyst_bud","id":-330},{"name":"minecraft:large_fern","id":-865},{"name":"minecraft:lava","id":11},{"name":"minecraft:lava_bucket","id":371},{"name":"minecraft:lead","id":564},{"name":"minecraft:leather","id":390},{"name":"minecraft:leather_boots","id":346},{"name":"minecraft:leather_chestplate","id":344},{"name":"minecraft:leather_helmet","id":343},{"name":"minecraft:leather_horse_armor","id":547},{"name":"minecraft:leather_leggings","id":345},{"name":"minecraft:leaves","id":743},{"name":"minecraft:leaves2","id":744},{"name":"minecraft:lectern","id":-194},{"name":"minecraft:lever","id":69},{"name":"minecraft:light_block","id":767},{"name":"minecraft:light_block_0","id":-215},{"name":"minecraft:light_block_1","id":-929},{"name":"minecraft:light_block_10","id":-938},{"name":"minecraft:light_block_11","id":-939},{"name":"minecraft:light_block_12","id":-940},{"name":"minecraft:light_block_13","id":-941},{"name":"minecraft:light_block_14","id":-942},{"name":"minecraft:light_block_15","id":-943},{"name":"minecraft:light_block_2","id":-930},{"name":"minecraft:light_block_3","id":-931},{"name":"minecraft:light_block_4","id":-932},{"name":"minecraft:light_block_5","id":-933},{"name":"minecraft:light_block_6","id":-934},{"name":"minecraft:light_block_7","id":-935},{"name":"minecraft:light_block_8","id":-936},{"name":"minecraft:light_block_9","id":-937},{"name":"minecraft:light_blue_candle","id":-416},{"name":"minecraft:light_blue_candle_cake","id":-433},{"name":"minecraft:light_blue_carpet","id":-599},{"name":"minecraft:light_blue_concrete","id":-630},{"name":"minecraft:light_blue_concrete_powder","id":-711},{"name":"minecraft:light_blue_dye","id":416},{"name":"minecraft:light_blue_glazed_terracotta","id":223},{"name":"minecraft:light_blue_shulker_box","id":-615},{"name":"minecraft:light_blue_stained_glass","id":-675},{"name":"minecraft:light_blue_stained_glass_pane","id":-645},{"name":"minecraft:light_blue_terracotta","id":-726},{"name":"minecraft:light_blue_wool","id":-562},{"name":"minecraft:light_gray_candle","id":-421},{"name":"minecraft:light_gray_candle_cake","id":-438},{"name":"minecraft:light_gray_carpet","id":-604},{"name":"minecraft:light_gray_concrete","id":-635},{"name":"minecraft:light_gray_concrete_powder","id":-716},{"name":"minecraft:light_gray_dye","id":411},{"name":"minecraft:light_gray_shulker_box","id":-620},{"name":"minecraft:light_gray_stained_glass","id":-680},{"name":"minecraft:light_gray_stained_glass_pane","id":-650},{"name":"minecraft:light_gray_terracotta","id":-731},{"name":"minecraft:light_gray_wool","id":-552},{"name":"minecraft:light_weighted_pressure_plate","id":147},{"name":"minecraft:lightning_rod","id":-312},{"name":"minecraft:lilac","id":-863},{"name":"minecraft:lily_of_the_valley","id":-839},{"name":"minecraft:lime_candle","id":-418},{"name":"minecraft:lime_candle_cake","id":-435},{"name":"minecraft:lime_carpet","id":-601},{"name":"minecraft:lime_concrete","id":-632},{"name":"minecraft:lime_concrete_powder","id":-713},{"name":"minecraft:lime_dye","id":414},{"name":"minecraft:lime_glazed_terracotta","id":225},{"name":"minecraft:lime_shulker_box","id":-617},{"name":"minecraft:lime_stained_glass","id":-677},{"name":"minecraft:lime_stained_glass_pane","id":-647},{"name":"minecraft:lime_terracotta","id":-728},{"name":"minecraft:lime_wool","id":-559},{"name":"minecraft:lingering_potion","id":579},{"name":"minecraft:lit_blast_furnace","id":-214},{"name":"minecraft:lit_deepslate_redstone_ore","id":-404},{"name":"minecraft:lit_furnace","id":62},{"name":"minecraft:lit_pumpkin","id":91},{"name":"minecraft:lit_redstone_lamp","id":124},{"name":"minecraft:lit_redstone_ore","id":74},{"name":"minecraft:lit_smoker","id":-199},{"name":"minecraft:llama_spawn_egg","id":484},{"name":"minecraft:lodestone","id":-222},{"name":"minecraft:lodestone_compass","id":622},{"name":"minecraft:log","id":728},{"name":"minecraft:log2","id":751},{"name":"minecraft:loom","id":-204},{"name":"minecraft:mace","id":327},{"name":"minecraft:magenta_candle","id":-415},{"name":"minecraft:magenta_candle_cake","id":-432},{"name":"minecraft:magenta_carpet","id":-598},{"name":"minecraft:magenta_concrete","id":-629},{"name":"minecraft:magenta_concrete_powder","id":-710},{"name":"minecraft:magenta_dye","id":417},{"name":"minecraft:magenta_glazed_terracotta","id":222},{"name":"minecraft:magenta_shulker_box","id":-614},{"name":"minecraft:magenta_stained_glass","id":-674},{"name":"minecraft:magenta_stained_glass_pane","id":-644},{"name":"minecraft:magenta_terracotta","id":-725},{"name":"minecraft:magenta_wool","id":-565},{"name":"minecraft:magma","id":213},{"name":"minecraft:magma_cream","id":440},{"name":"minecraft:magma_cube_spawn_egg","id":465},{"name":"minecraft:mangrove_boat","id":655},{"name":"minecraft:mangrove_button","id":-487},{"name":"minecraft:mangrove_chest_boat","id":664},{"name":"minecraft:mangrove_door","id":653},{"name":"minecraft:mangrove_double_slab","id":-499},{"name":"minecraft:mangrove_fence","id":-491},{"name":"minecraft:mangrove_fence_gate","id":-492},{"name":"minecraft:mangrove_hanging_sign","id":-508},{"name":"minecraft:mangrove_leaves","id":-472},{"name":"minecraft:mangrove_log","id":-484},{"name":"minecraft:mangrove_planks","id":-486},{"name":"minecraft:mangrove_pressure_plate","id":-490},{"name":"minecraft:mangrove_propagule","id":-474},{"name":"minecraft:mangrove_roots","id":-482},{"name":"minecraft:mangrove_sign","id":654},{"name":"minecraft:mangrove_slab","id":-489},{"name":"minecraft:mangrove_stairs","id":-488},{"name":"minecraft:mangrove_standing_sign","id":-494},{"name":"minecraft:mangrove_trapdoor","id":-496},{"name":"minecraft:mangrove_wall_sign","id":-495},{"name":"minecraft:mangrove_wood","id":-497},{"name":"minecraft:material_reducer","id":-986},{"name":"minecraft:medicine","id":619},{"name":"minecraft:medium_amethyst_bud","id":-331},{"name":"minecraft:melon_block","id":103},{"name":"minecraft:melon_seeds","id":298},{"name":"minecraft:melon_slice","id":277},{"name":"minecraft:melon_stem","id":105},{"name":"minecraft:milk_bucket","id":369},{"name":"minecraft:minecart","id":378},{"name":"minecraft:miner_pottery_sherd","id":690},{"name":"minecraft:mob_spawner","id":52},{"name":"minecraft:mojang_banner_pattern","id":601},{"name":"minecraft:monster_egg","id":752},{"name":"minecraft:mooshroom_spawn_egg","id":450},{"name":"minecraft:moss_block","id":-320},{"name":"minecraft:moss_carpet","id":-335},{"name":"minecraft:mossy_cobblestone","id":48},{"name":"minecraft:mossy_cobblestone_double_slab","id":-915},{"name":"minecraft:mossy_cobblestone_slab","id":-888},{"name":"minecraft:mossy_cobblestone_stairs","id":-179},{"name":"minecraft:mossy_cobblestone_wall","id":-971},{"name":"minecraft:mossy_stone_brick_double_slab","id":-168},{"name":"minecraft:mossy_stone_brick_slab","id":-166},{"name":"minecraft:mossy_stone_brick_stairs","id":-175},{"name":"minecraft:mossy_stone_brick_wall","id":-978},{"name":"minecraft:mossy_stone_bricks","id":-868},{"name":"minecraft:mourner_pottery_sherd","id":691},{"name":"minecraft:moving_block","id":250},{"name":"minecraft:mud","id":-473},{"name":"minecraft:mud_brick_double_slab","id":-479},{"name":"minecraft:mud_brick_slab","id":-478},{"name":"minecraft:mud_brick_stairs","id":-480},{"name":"minecraft:mud_brick_wall","id":-481},{"name":"minecraft:mud_bricks","id":-475},{"name":"minecraft:muddy_mangrove_roots","id":-483},{"name":"minecraft:mule_spawn_egg","id":477},{"name":"minecraft:mushroom_stew","id":265},{"name":"minecraft:music_disc_11","id":561},{"name":"minecraft:music_disc_13","id":551},{"name":"minecraft:music_disc_5","id":656},{"name":"minecraft:music_disc_blocks","id":553},{"name":"minecraft:music_disc_cat","id":552},{"name":"minecraft:music_disc_chirp","id":554},{"name":"minecraft:music_disc_creator","id":759},{"name":"minecraft:music_disc_creator_music_box","id":760},{"name":"minecraft:music_disc_far","id":555},{"name":"minecraft:music_disc_mall","id":556},{"name":"minecraft:music_disc_mellohi","id":557},{"name":"minecraft:music_disc_otherside","id":646},{"name":"minecraft:music_disc_pigstep","id":640},{"name":"minecraft:music_disc_precipice","id":761},{"name":"minecraft:music_disc_relic","id":719},{"name":"minecraft:music_disc_stal","id":558},{"name":"minecraft:music_disc_strad","id":559},{"name":"minecraft:music_disc_wait","id":562},{"name":"minecraft:music_disc_ward","id":560},{"name":"minecraft:mutton","id":567},{"name":"minecraft:mycelium","id":110},{"name":"minecraft:name_tag","id":565},{"name":"minecraft:nautilus_shell","id":587},{"name":"minecraft:nether_brick","id":112},{"name":"minecraft:nether_brick_double_slab","id":-883},{"name":"minecraft:nether_brick_fence","id":113},{"name":"minecraft:nether_brick_slab","id":-877},{"name":"minecraft:nether_brick_stairs","id":114},{"name":"minecraft:nether_brick_wall","id":-979},{"name":"minecraft:nether_gold_ore","id":-288},{"name":"minecraft:nether_sprouts","id":641},{"name":"minecraft:nether_star","id":535},{"name":"minecraft:nether_wart","id":299},{"name":"minecraft:nether_wart_block","id":214},{"name":"minecraft:netherbrick","id":540},{"name":"minecraft:netherite_axe","id":626},{"name":"minecraft:netherite_block","id":-270},{"name":"minecraft:netherite_boots","id":632},{"name":"minecraft:netherite_chestplate","id":630},{"name":"minecraft:netherite_helmet","id":629},{"name":"minecraft:netherite_hoe","id":627},{"name":"minecraft:netherite_ingot","id":628},{"name":"minecraft:netherite_leggings","id":631},{"name":"minecraft:netherite_pickaxe","id":625},{"name":"minecraft:netherite_scrap","id":633},{"name":"minecraft:netherite_shovel","id":624},{"name":"minecraft:netherite_sword","id":623},{"name":"minecraft:netherite_upgrade_smithing_template","id":700},{"name":"minecraft:netherrack","id":87},{"name":"minecraft:netherreactor","id":247},{"name":"minecraft:normal_stone_double_slab","id":-926},{"name":"minecraft:normal_stone_slab","id":-899},{"name":"minecraft:normal_stone_stairs","id":-180},{"name":"minecraft:noteblock","id":25},{"name":"minecraft:npc_spawn_egg","id":481},{"name":"minecraft:oak_boat","id":384},{"name":"minecraft:oak_chest_boat","id":658},{"name":"minecraft:oak_double_slab","id":157},{"name":"minecraft:oak_fence","id":85},{"name":"minecraft:oak_hanging_sign","id":-500},{"name":"minecraft:oak_leaves","id":18},{"name":"minecraft:oak_log","id":17},{"name":"minecraft:oak_planks","id":5},{"name":"minecraft:oak_sapling","id":6},{"name":"minecraft:oak_sign","id":366},{"name":"minecraft:oak_slab","id":158},{"name":"minecraft:oak_stairs","id":53},{"name":"minecraft:oak_wood","id":-212},{"name":"minecraft:observer","id":251},{"name":"minecraft:obsidian","id":49},{"name":"minecraft:ocelot_spawn_egg","id":461},{"name":"minecraft:ochre_froglight","id":-471},{"name":"minecraft:ominous_bottle","id":612},{"name":"minecraft:ominous_trial_key","id":258},{"name":"minecraft:orange_candle","id":-414},{"name":"minecraft:orange_candle_cake","id":-431},{"name":"minecraft:orange_carpet","id":-597},{"name":"minecraft:orange_concrete","id":-628},{"name":"minecraft:orange_concrete_powder","id":-709},{"name":"minecraft:orange_dye","id":418},{"name":"minecraft:orange_glazed_terracotta","id":221},{"name":"minecraft:orange_shulker_box","id":-613},{"name":"minecraft:orange_stained_glass","id":-673},{"name":"minecraft:orange_stained_glass_pane","id":-643},{"name":"minecraft:orange_terracotta","id":-724},{"name":"minecraft:orange_tulip","id":-834},{"name":"minecraft:orange_wool","id":-557},{"name":"minecraft:oxeye_daisy","id":-837},{"name":"minecraft:oxidized_chiseled_copper","id":-763},{"name":"minecraft:oxidized_copper","id":-343},{"name":"minecraft:oxidized_copper_bulb","id":-779},{"name":"minecraft:oxidized_copper_door","id":-787},{"name":"minecraft:oxidized_copper_grate","id":-771},{"name":"minecraft:oxidized_copper_trapdoor","id":-795},{"name":"minecraft:oxidized_cut_copper","id":-350},{"name":"minecraft:oxidized_cut_copper_slab","id":-364},{"name":"minecraft:oxidized_cut_copper_stairs","id":-357},{"name":"minecraft:oxidized_double_cut_copper_slab","id":-371},{"name":"minecraft:packed_ice","id":174},{"name":"minecraft:packed_mud","id":-477},{"name":"minecraft:painting","id":365},{"name":"minecraft:panda_spawn_egg","id":500},{"name":"minecraft:paper","id":395},{"name":"minecraft:parrot_spawn_egg","id":489},{"name":"minecraft:pearlescent_froglight","id":-469},{"name":"minecraft:peony","id":-867},{"name":"minecraft:petrified_oak_double_slab","id":-903},{"name":"minecraft:petrified_oak_slab","id":-902},{"name":"minecraft:phantom_membrane","id":591},{"name":"minecraft:phantom_spawn_egg","id":497},{"name":"minecraft:pig_spawn_egg","id":447},{"name":"minecraft:piglin_banner_pattern","id":604},{"name":"minecraft:piglin_brute_spawn_egg","id":510},{"name":"minecraft:piglin_spawn_egg","id":508},{"name":"minecraft:pillager_spawn_egg","id":502},{"name":"minecraft:pink_candle","id":-419},{"name":"minecraft:pink_candle_cake","id":-436},{"name":"minecraft:pink_carpet","id":-602},{"name":"minecraft:pink_concrete","id":-633},{"name":"minecraft:pink_concrete_powder","id":-714},{"name":"minecraft:pink_dye","id":413},{"name":"minecraft:pink_glazed_terracotta","id":226},{"name":"minecraft:pink_petals","id":-549},{"name":"minecraft:pink_shulker_box","id":-618},{"name":"minecraft:pink_stained_glass","id":-678},{"name":"minecraft:pink_stained_glass_pane","id":-648},{"name":"minecraft:pink_terracotta","id":-729},{"name":"minecraft:pink_tulip","id":-836},{"name":"minecraft:pink_wool","id":-566},{"name":"minecraft:piston","id":33},{"name":"minecraft:piston_arm_collision","id":34},{"name":"minecraft:pitcher_crop","id":-574},{"name":"minecraft:pitcher_plant","id":-612},{"name":"minecraft:pitcher_pod","id":302},{"name":"minecraft:planks","id":748},{"name":"minecraft:plenty_pottery_sherd","id":692},{"name":"minecraft:podzol","id":243},{"name":"minecraft:pointed_dripstone","id":-308},{"name":"minecraft:poisonous_potato","id":287},{"name":"minecraft:polar_bear_spawn_egg","id":483},{"name":"minecraft:polished_andesite","id":-595},{"name":"minecraft:polished_andesite_double_slab","id":-919},{"name":"minecraft:polished_andesite_slab","id":-892},{"name":"minecraft:polished_andesite_stairs","id":-174},{"name":"minecraft:polished_basalt","id":-235},{"name":"minecraft:polished_blackstone","id":-291},{"name":"minecraft:polished_blackstone_brick_double_slab","id":-285},{"name":"minecraft:polished_blackstone_brick_slab","id":-284},{"name":"minecraft:polished_blackstone_brick_stairs","id":-275},{"name":"minecraft:polished_blackstone_brick_wall","id":-278},{"name":"minecraft:polished_blackstone_bricks","id":-274},{"name":"minecraft:polished_blackstone_button","id":-296},{"name":"minecraft:polished_blackstone_double_slab","id":-294},{"name":"minecraft:polished_blackstone_pressure_plate","id":-295},{"name":"minecraft:polished_blackstone_slab","id":-293},{"name":"minecraft:polished_blackstone_stairs","id":-292},{"name":"minecraft:polished_blackstone_wall","id":-297},{"name":"minecraft:polished_deepslate","id":-383},{"name":"minecraft:polished_deepslate_double_slab","id":-397},{"name":"minecraft:polished_deepslate_slab","id":-384},{"name":"minecraft:polished_deepslate_stairs","id":-385},{"name":"minecraft:polished_deepslate_wall","id":-386},{"name":"minecraft:polished_diorite","id":-593},{"name":"minecraft:polished_diorite_double_slab","id":-922},{"name":"minecraft:polished_diorite_slab","id":-895},{"name":"minecraft:polished_diorite_stairs","id":-173},{"name":"minecraft:polished_granite","id":-591},{"name":"minecraft:polished_granite_double_slab","id":-924},{"name":"minecraft:polished_granite_slab","id":-897},{"name":"minecraft:polished_granite_stairs","id":-172},{"name":"minecraft:polished_tuff","id":-748},{"name":"minecraft:polished_tuff_double_slab","id":-750},{"name":"minecraft:polished_tuff_slab","id":-749},{"name":"minecraft:polished_tuff_stairs","id":-751},{"name":"minecraft:polished_tuff_wall","id":-752},{"name":"minecraft:popped_chorus_fruit","id":576},{"name":"minecraft:poppy","id":38},{"name":"minecraft:porkchop","id":267},{"name":"minecraft:portal","id":90},{"name":"minecraft:potato","id":285},{"name":"minecraft:potatoes","id":142},{"name":"minecraft:potion","id":436},{"name":"minecraft:powder_snow","id":-306},{"name":"minecraft:powder_snow_bucket","id":376},{"name":"minecraft:powered_comparator","id":150},{"name":"minecraft:powered_repeater","id":94},{"name":"minecraft:prismarine","id":168},{"name":"minecraft:prismarine_brick_double_slab","id":-914},{"name":"minecraft:prismarine_brick_slab","id":-887},{"name":"minecraft:prismarine_bricks","id":-948},{"name":"minecraft:prismarine_bricks_stairs","id":-4},{"name":"minecraft:prismarine_crystals","id":566},{"name":"minecraft:prismarine_double_slab","id":-912},{"name":"minecraft:prismarine_shard","id":582},{"name":"minecraft:prismarine_slab","id":-885},{"name":"minecraft:prismarine_stairs","id":-2},{"name":"minecraft:prismarine_wall","id":-981},{"name":"minecraft:prize_pottery_sherd","id":693},{"name":"minecraft:pufferfish","id":272},{"name":"minecraft:pufferfish_bucket","id":375},{"name":"minecraft:pufferfish_spawn_egg","id":492},{"name":"minecraft:pumpkin","id":86},{"name":"minecraft:pumpkin_pie","id":289},{"name":"minecraft:pumpkin_seeds","id":297},{"name":"minecraft:pumpkin_stem","id":104},{"name":"minecraft:purple_candle","id":-423},{"name":"minecraft:purple_candle_cake","id":-440},{"name":"minecraft:purple_carpet","id":-606},{"name":"minecraft:purple_concrete","id":-637},{"name":"minecraft:purple_concrete_powder","id":-718},{"name":"minecraft:purple_dye","id":409},{"name":"minecraft:purple_glazed_terracotta","id":219},{"name":"minecraft:purple_shulker_box","id":-622},{"name":"minecraft:purple_stained_glass","id":-682},{"name":"minecraft:purple_stained_glass_pane","id":-652},{"name":"minecraft:purple_terracotta","id":-733},{"name":"minecraft:purple_wool","id":-564},{"name":"minecraft:purpur_block","id":201},{"name":"minecraft:purpur_double_slab","id":-911},{"name":"minecraft:purpur_pillar","id":-951},{"name":"minecraft:purpur_slab","id":-884},{"name":"minecraft:purpur_stairs","id":203},{"name":"minecraft:quartz","id":541},{"name":"minecraft:quartz_block","id":155},{"name":"minecraft:quartz_bricks","id":-304},{"name":"minecraft:quartz_double_slab","id":-882},{"name":"minecraft:quartz_ore","id":153},{"name":"minecraft:quartz_pillar","id":-954},{"name":"minecraft:quartz_slab","id":-876},{"name":"minecraft:quartz_stairs","id":156},{"name":"minecraft:rabbit","id":293},{"name":"minecraft:rabbit_foot","id":545},{"name":"minecraft:rabbit_hide","id":546},{"name":"minecraft:rabbit_spawn_egg","id":469},{"name":"minecraft:rabbit_stew","id":295},{"name":"minecraft:rail","id":66},{"name":"minecraft:raiser_armor_trim_smithing_template","id":714},{"name":"minecraft:rapid_fertilizer","id":617},{"name":"minecraft:ravager_spawn_egg","id":504},{"name":"minecraft:raw_copper","id":524},{"name":"minecraft:raw_copper_block","id":-452},{"name":"minecraft:raw_gold","id":523},{"name":"minecraft:raw_gold_block","id":-453},{"name":"minecraft:raw_iron","id":522},{"name":"minecraft:raw_iron_block","id":-451},{"name":"minecraft:recovery_compass","id":666},{"name":"minecraft:red_candle","id":-427},{"name":"minecraft:red_candle_cake","id":-444},{"name":"minecraft:red_carpet","id":-610},{"name":"minecraft:red_concrete","id":-641},{"name":"minecraft:red_concrete_powder","id":-722},{"name":"minecraft:red_dye","id":405},{"name":"minecraft:red_flower","id":746},{"name":"minecraft:red_glazed_terracotta","id":234},{"name":"minecraft:red_mushroom","id":40},{"name":"minecraft:red_mushroom_block","id":100},{"name":"minecraft:red_nether_brick","id":215},{"name":"minecraft:red_nether_brick_double_slab","id":-917},{"name":"minecraft:red_nether_brick_slab","id":-890},{"name":"minecraft:red_nether_brick_stairs","id":-184},{"name":"minecraft:red_nether_brick_wall","id":-983},{"name":"minecraft:red_sand","id":-949},{"name":"minecraft:red_sandstone","id":179},{"name":"minecraft:red_sandstone_double_slab","id":181},{"name":"minecraft:red_sandstone_slab","id":182},{"name":"minecraft:red_sandstone_stairs","id":180},{"name":"minecraft:red_sandstone_wall","id":-982},{"name":"minecraft:red_shulker_box","id":-626},{"name":"minecraft:red_stained_glass","id":-686},{"name":"minecraft:red_stained_glass_pane","id":-656},{"name":"minecraft:red_terracotta","id":-737},{"name":"minecraft:red_tulip","id":-833},{"name":"minecraft:red_wool","id":-556},{"name":"minecraft:redstone","id":381},{"name":"minecraft:redstone_block","id":152},{"name":"minecraft:redstone_lamp","id":123},{"name":"minecraft:redstone_ore","id":73},{"name":"minecraft:redstone_torch","id":76},{"name":"minecraft:redstone_wire","id":55},{"name":"minecraft:reinforced_deepslate","id":-466},{"name":"minecraft:repeater","id":428},{"name":"minecraft:repeating_command_block","id":188},{"name":"minecraft:reserved6","id":255},{"name":"minecraft:respawn_anchor","id":-272},{"name":"minecraft:rib_armor_trim_smithing_template","id":710},{"name":"minecraft:rose_bush","id":-866},{"name":"minecraft:rotten_flesh","id":282},{"name":"minecraft:saddle","id":379},{"name":"minecraft:salmon","id":270},{"name":"minecraft:salmon_bucket","id":373},{"name":"minecraft:salmon_spawn_egg","id":493},{"name":"minecraft:sand","id":12},{"name":"minecraft:sandstone","id":24},{"name":"minecraft:sandstone_double_slab","id":-878},{"name":"minecraft:sandstone_slab","id":-872},{"name":"minecraft:sandstone_stairs","id":128},{"name":"minecraft:sandstone_wall","id":-975},{"name":"minecraft:sapling","id":742},{"name":"minecraft:scaffolding","id":-165},{"name":"minecraft:scrape_pottery_sherd","id":694},{"name":"minecraft:sculk","id":-458},{"name":"minecraft:sculk_catalyst","id":-460},{"name":"minecraft:sculk_sensor","id":-307},{"name":"minecraft:sculk_shrieker","id":-461},{"name":"minecraft:sculk_vein","id":-459},{"name":"minecraft:sea_lantern","id":169},{"name":"minecraft:sea_pickle","id":-156},{"name":"minecraft:seagrass","id":-130},{"name":"minecraft:sentry_armor_trim_smithing_template","id":701},{"name":"minecraft:shaper_armor_trim_smithing_template","id":715},{"name":"minecraft:sheaf_pottery_sherd","id":695},{"name":"minecraft:shears","id":430},{"name":"minecraft:sheep_spawn_egg","id":448},{"name":"minecraft:shelter_pottery_sherd","id":696},{"name":"minecraft:shield","id":363},{"name":"minecraft:short_grass","id":31},{"name":"minecraft:shroomlight","id":-230},{"name":"minecraft:shulker_box","id":757},{"name":"minecraft:shulker_shell","id":583},{"name":"minecraft:shulker_spawn_egg","id":480},{"name":"minecraft:silence_armor_trim_smithing_template","id":712},{"name":"minecraft:silver_glazed_terracotta","id":228},{"name":"minecraft:silverfish_spawn_egg","id":453},{"name":"minecraft:skeleton_horse_spawn_egg","id":478},{"name":"minecraft:skeleton_spawn_egg","id":454},{"name":"minecraft:skull","id":533},{"name":"minecraft:skull_banner_pattern","id":600},{"name":"minecraft:skull_pottery_sherd","id":697},{"name":"minecraft:slime","id":165},{"name":"minecraft:slime_ball","id":397},{"name":"minecraft:slime_spawn_egg","id":455},{"name":"minecraft:small_amethyst_bud","id":-332},{"name":"minecraft:small_dripleaf_block","id":-336},{"name":"minecraft:smithing_table","id":-202},{"name":"minecraft:smoker","id":-198},{"name":"minecraft:smooth_basalt","id":-377},{"name":"minecraft:smooth_quartz","id":-955},{"name":"minecraft:smooth_quartz_double_slab","id":-925},{"name":"minecraft:smooth_quartz_slab","id":-898},{"name":"minecraft:smooth_quartz_stairs","id":-185},{"name":"minecraft:smooth_red_sandstone","id":-958},{"name":"minecraft:smooth_red_sandstone_double_slab","id":-918},{"name":"minecraft:smooth_red_sandstone_slab","id":-891},{"name":"minecraft:smooth_red_sandstone_stairs","id":-176},{"name":"minecraft:smooth_sandstone","id":-946},{"name":"minecraft:smooth_sandstone_double_slab","id":-916},{"name":"minecraft:smooth_sandstone_slab","id":-889},{"name":"minecraft:smooth_sandstone_stairs","id":-177},{"name":"minecraft:smooth_stone","id":-183},{"name":"minecraft:smooth_stone_double_slab","id":43},{"name":"minecraft:smooth_stone_slab","id":44},{"name":"minecraft:sniffer_egg","id":-596},{"name":"minecraft:sniffer_spawn_egg","id":511},{"name":"minecraft:snort_pottery_sherd","id":698},{"name":"minecraft:snout_armor_trim_smithing_template","id":709},{"name":"minecraft:snow","id":80},{"name":"minecraft:snow_golem_spawn_egg","id":517},{"name":"minecraft:snow_layer","id":78},{"name":"minecraft:snowball","id":382},{"name":"minecraft:soul_campfire","id":642},{"name":"minecraft:soul_fire","id":-237},{"name":"minecraft:soul_lantern","id":-269},{"name":"minecraft:soul_sand","id":88},{"name":"minecraft:soul_soil","id":-236},{"name":"minecraft:soul_torch","id":-268},{"name":"minecraft:sparkler","id":620},{"name":"minecraft:spawn_egg","id":771},{"name":"minecraft:spider_eye","id":283},{"name":"minecraft:spider_spawn_egg","id":456},{"name":"minecraft:spire_armor_trim_smithing_template","id":711},{"name":"minecraft:splash_potion","id":578},{"name":"minecraft:sponge","id":19},{"name":"minecraft:spore_blossom","id":-321},{"name":"minecraft:spruce_boat","id":387},{"name":"minecraft:spruce_button","id":-144},{"name":"minecraft:spruce_chest_boat","id":661},{"name":"minecraft:spruce_door","id":570},{"name":"minecraft:spruce_double_slab","id":-809},{"name":"minecraft:spruce_fence","id":-579},{"name":"minecraft:spruce_fence_gate","id":183},{"name":"minecraft:spruce_hanging_sign","id":-501},{"name":"minecraft:spruce_leaves","id":-800},{"name":"minecraft:spruce_log","id":-569},{"name":"minecraft:spruce_planks","id":-739},{"name":"minecraft:spruce_pressure_plate","id":-154},{"name":"minecraft:spruce_sapling","id":-825},{"name":"minecraft:spruce_sign","id":593},{"name":"minecraft:spruce_slab","id":-804},{"name":"minecraft:spruce_stairs","id":134},{"name":"minecraft:spruce_standing_sign","id":-181},{"name":"minecraft:spruce_trapdoor","id":-149},{"name":"minecraft:spruce_wall_sign","id":-182},{"name":"minecraft:spruce_wood","id":-814},{"name":"minecraft:spyglass","id":645},{"name":"minecraft:squid_spawn_egg","id":460},{"name":"minecraft:stained_glass","id":755},{"name":"minecraft:stained_glass_pane","id":756},{"name":"minecraft:stained_hardened_clay","id":720},{"name":"minecraft:standing_banner","id":176},{"name":"minecraft:standing_sign","id":63},{"name":"minecraft:stick","id":328},{"name":"minecraft:sticky_piston","id":29},{"name":"minecraft:sticky_piston_arm_collision","id":-217},{"name":"minecraft:stone","id":1},{"name":"minecraft:stone_axe","id":322},{"name":"minecraft:stone_block_slab","id":732},{"name":"minecraft:stone_block_slab2","id":733},{"name":"minecraft:stone_block_slab3","id":734},{"name":"minecraft:stone_block_slab4","id":735},{"name":"minecraft:stone_brick_double_slab","id":-881},{"name":"minecraft:stone_brick_slab","id":-875},{"name":"minecraft:stone_brick_stairs","id":109},{"name":"minecraft:stone_brick_wall","id":-977},{"name":"minecraft:stone_bricks","id":98},{"name":"minecraft:stone_button","id":77},{"name":"minecraft:stone_hoe","id":338},{"name":"minecraft:stone_pickaxe","id":321},{"name":"minecraft:stone_pressure_plate","id":70},{"name":"minecraft:stone_shovel","id":320},{"name":"minecraft:stone_stairs","id":67},{"name":"minecraft:stone_sword","id":319},{"name":"minecraft:stonebrick","id":730},{"name":"minecraft:stonecutter","id":245},{"name":"minecraft:stonecutter_block","id":-197},{"name":"minecraft:stray_spawn_egg","id":472},{"name":"minecraft:strider_spawn_egg","id":506},{"name":"minecraft:string","id":334},{"name":"minecraft:stripped_acacia_log","id":-8},{"name":"minecraft:stripped_acacia_wood","id":-823},{"name":"minecraft:stripped_bamboo_block","id":-528},{"name":"minecraft:stripped_birch_log","id":-6},{"name":"minecraft:stripped_birch_wood","id":-821},{"name":"minecraft:stripped_cherry_log","id":-535},{"name":"minecraft:stripped_cherry_wood","id":-545},{"name":"minecraft:stripped_crimson_hyphae","id":-300},{"name":"minecraft:stripped_crimson_stem","id":-240},{"name":"minecraft:stripped_dark_oak_log","id":-9},{"name":"minecraft:stripped_dark_oak_wood","id":-824},{"name":"minecraft:stripped_jungle_log","id":-7},{"name":"minecraft:stripped_jungle_wood","id":-822},{"name":"minecraft:stripped_mangrove_log","id":-485},{"name":"minecraft:stripped_mangrove_wood","id":-498},{"name":"minecraft:stripped_oak_log","id":-10},{"name":"minecraft:stripped_oak_wood","id":-819},{"name":"minecraft:stripped_spruce_log","id":-5},{"name":"minecraft:stripped_spruce_wood","id":-820},{"name":"minecraft:stripped_warped_hyphae","id":-301},{"name":"minecraft:stripped_warped_stem","id":-241},{"name":"minecraft:structure_block","id":252},{"name":"minecraft:structure_void","id":217},{"name":"minecraft:sugar","id":425},{"name":"minecraft:sugar_cane","id":394},{"name":"minecraft:sunflower","id":175},{"name":"minecraft:suspicious_gravel","id":-573},{"name":"minecraft:suspicious_sand","id":-529},{"name":"minecraft:suspicious_stew","id":609},{"name":"minecraft:sweet_berries","id":292},{"name":"minecraft:sweet_berry_bush","id":-207},{"name":"minecraft:tadpole_bucket","id":650},{"name":"minecraft:tadpole_spawn_egg","id":649},{"name":"minecraft:tall_grass","id":-864},{"name":"minecraft:tallgrass","id":750},{"name":"minecraft:target","id":-239},{"name":"minecraft:tide_armor_trim_smithing_template","id":708},{"name":"minecraft:tinted_glass","id":-334},{"name":"minecraft:tnt","id":46},{"name":"minecraft:tnt_minecart","id":542},{"name":"minecraft:torch","id":50},{"name":"minecraft:torchflower","id":-568},{"name":"minecraft:torchflower_crop","id":-567},{"name":"minecraft:torchflower_seeds","id":301},{"name":"minecraft:totem_of_undying","id":585},{"name":"minecraft:trader_llama_spawn_egg","id":668},{"name":"minecraft:trapdoor","id":96},{"name":"minecraft:trapped_chest","id":146},{"name":"minecraft:trial_key","id":259},{"name":"minecraft:trial_spawner","id":-315},{"name":"minecraft:trident","id":563},{"name":"minecraft:trip_wire","id":132},{"name":"minecraft:tripwire_hook","id":131},{"name":"minecraft:tropical_fish","id":271},{"name":"minecraft:tropical_fish_bucket","id":374},{"name":"minecraft:tropical_fish_spawn_egg","id":490},{"name":"minecraft:tube_coral","id":-131},{"name":"minecraft:tube_coral_block","id":-132},{"name":"minecraft:tube_coral_fan","id":-133},{"name":"minecraft:tube_coral_wall_fan","id":-135},{"name":"minecraft:tuff","id":-333},{"name":"minecraft:tuff_brick_double_slab","id":-756},{"name":"minecraft:tuff_brick_slab","id":-755},{"name":"minecraft:tuff_brick_stairs","id":-757},{"name":"minecraft:tuff_brick_wall","id":-758},{"name":"minecraft:tuff_bricks","id":-754},{"name":"minecraft:tuff_double_slab","id":-745},{"name":"minecraft:tuff_slab","id":-744},{"name":"minecraft:tuff_stairs","id":-746},{"name":"minecraft:tuff_wall","id":-747},{"name":"minecraft:turtle_egg","id":-159},{"name":"minecraft:turtle_helmet","id":590},{"name":"minecraft:turtle_scute","id":589},{"name":"minecraft:turtle_spawn_egg","id":496},{"name":"minecraft:twisting_vines","id":-287},{"name":"minecraft:underwater_tnt","id":-985},{"name":"minecraft:underwater_torch","id":239},{"name":"minecraft:undyed_shulker_box","id":205},{"name":"minecraft:unknown","id":-305},{"name":"minecraft:unlit_redstone_torch","id":75},{"name":"minecraft:unpowered_comparator","id":149},{"name":"minecraft:unpowered_repeater","id":93},{"name":"minecraft:vault","id":-314},{"name":"minecraft:verdant_froglight","id":-470},{"name":"minecraft:vex_armor_trim_smithing_template","id":707},{"name":"minecraft:vex_spawn_egg","id":487},{"name":"minecraft:villager_spawn_egg","id":459},{"name":"minecraft:vindicator_spawn_egg","id":485},{"name":"minecraft:vine","id":106},{"name":"minecraft:wall_banner","id":177},{"name":"minecraft:wall_sign","id":68},{"name":"minecraft:wandering_trader_spawn_egg","id":503},{"name":"minecraft:ward_armor_trim_smithing_template","id":705},{"name":"minecraft:warden_spawn_egg","id":652},{"name":"minecraft:warped_button","id":-261},{"name":"minecraft:warped_door","id":637},{"name":"minecraft:warped_double_slab","id":-267},{"name":"minecraft:warped_fence","id":-257},{"name":"minecraft:warped_fence_gate","id":-259},{"name":"minecraft:warped_fungus","id":-229},{"name":"minecraft:warped_fungus_on_a_stick","id":638},{"name":"minecraft:warped_hanging_sign","id":-507},{"name":"minecraft:warped_hyphae","id":-298},{"name":"minecraft:warped_nylium","id":-233},{"name":"minecraft:warped_planks","id":-243},{"name":"minecraft:warped_pressure_plate","id":-263},{"name":"minecraft:warped_roots","id":-224},{"name":"minecraft:warped_sign","id":635},{"name":"minecraft:warped_slab","id":-265},{"name":"minecraft:warped_stairs","id":-255},{"name":"minecraft:warped_standing_sign","id":-251},{"name":"minecraft:warped_stem","id":-226},{"name":"minecraft:warped_trapdoor","id":-247},{"name":"minecraft:warped_wall_sign","id":-253},{"name":"minecraft:warped_wart_block","id":-227},{"name":"minecraft:water","id":9},{"name":"minecraft:water_bucket","id":370},{"name":"minecraft:waterlily","id":111},{"name":"minecraft:waxed_chiseled_copper","id":-764},{"name":"minecraft:waxed_copper","id":-344},{"name":"minecraft:waxed_copper_bulb","id":-780},{"name":"minecraft:waxed_copper_door","id":-788},{"name":"minecraft:waxed_copper_grate","id":-772},{"name":"minecraft:waxed_copper_trapdoor","id":-796},{"name":"minecraft:waxed_cut_copper","id":-351},{"name":"minecraft:waxed_cut_copper_slab","id":-365},{"name":"minecraft:waxed_cut_copper_stairs","id":-358},{"name":"minecraft:waxed_double_cut_copper_slab","id":-372},{"name":"minecraft:waxed_exposed_chiseled_copper","id":-765},{"name":"minecraft:waxed_exposed_copper","id":-345},{"name":"minecraft:waxed_exposed_copper_bulb","id":-781},{"name":"minecraft:waxed_exposed_copper_door","id":-789},{"name":"minecraft:waxed_exposed_copper_grate","id":-773},{"name":"minecraft:waxed_exposed_copper_trapdoor","id":-797},{"name":"minecraft:waxed_exposed_cut_copper","id":-352},{"name":"minecraft:waxed_exposed_cut_copper_slab","id":-366},{"name":"minecraft:waxed_exposed_cut_copper_stairs","id":-359},{"name":"minecraft:waxed_exposed_double_cut_copper_slab","id":-373},{"name":"minecraft:waxed_oxidized_chiseled_copper","id":-766},{"name":"minecraft:waxed_oxidized_copper","id":-446},{"name":"minecraft:waxed_oxidized_copper_bulb","id":-783},{"name":"minecraft:waxed_oxidized_copper_door","id":-791},{"name":"minecraft:waxed_oxidized_copper_grate","id":-775},{"name":"minecraft:waxed_oxidized_copper_trapdoor","id":-799},{"name":"minecraft:waxed_oxidized_cut_copper","id":-447},{"name":"minecraft:waxed_oxidized_cut_copper_slab","id":-449},{"name":"minecraft:waxed_oxidized_cut_copper_stairs","id":-448},{"name":"minecraft:waxed_oxidized_double_cut_copper_slab","id":-450},{"name":"minecraft:waxed_weathered_chiseled_copper","id":-767},{"name":"minecraft:waxed_weathered_copper","id":-346},{"name":"minecraft:waxed_weathered_copper_bulb","id":-782},{"name":"minecraft:waxed_weathered_copper_door","id":-790},{"name":"minecraft:waxed_weathered_copper_grate","id":-774},{"name":"minecraft:waxed_weathered_copper_trapdoor","id":-798},{"name":"minecraft:waxed_weathered_cut_copper","id":-353},{"name":"minecraft:waxed_weathered_cut_copper_slab","id":-367},{"name":"minecraft:waxed_weathered_cut_copper_stairs","id":-360},{"name":"minecraft:waxed_weathered_double_cut_copper_slab","id":-374},{"name":"minecraft:wayfinder_armor_trim_smithing_template","id":713},{"name":"minecraft:weathered_chiseled_copper","id":-762},{"name":"minecraft:weathered_copper","id":-342},{"name":"minecraft:weathered_copper_bulb","id":-778},{"name":"minecraft:weathered_copper_door","id":-786},{"name":"minecraft:weathered_copper_grate","id":-770},{"name":"minecraft:weathered_copper_trapdoor","id":-794},{"name":"minecraft:weathered_cut_copper","id":-349},{"name":"minecraft:weathered_cut_copper_slab","id":-363},{"name":"minecraft:weathered_cut_copper_stairs","id":-356},{"name":"minecraft:weathered_double_cut_copper_slab","id":-370},{"name":"minecraft:web","id":30},{"name":"minecraft:weeping_vines","id":-231},{"name":"minecraft:wet_sponge","id":-984},{"name":"minecraft:wheat","id":342},{"name":"minecraft:wheat_seeds","id":296},{"name":"minecraft:white_candle","id":-413},{"name":"minecraft:white_candle_cake","id":-430},{"name":"minecraft:white_carpet","id":171},{"name":"minecraft:white_concrete","id":236},{"name":"minecraft:white_concrete_powder","id":237},{"name":"minecraft:white_dye","id":419},{"name":"minecraft:white_glazed_terracotta","id":220},{"name":"minecraft:white_shulker_box","id":218},{"name":"minecraft:white_stained_glass","id":241},{"name":"minecraft:white_stained_glass_pane","id":160},{"name":"minecraft:white_terracotta","id":159},{"name":"minecraft:white_tulip","id":-835},{"name":"minecraft:white_wool","id":35},{"name":"minecraft:wild_armor_trim_smithing_template","id":704},{"name":"minecraft:wind_charge","id":260},{"name":"minecraft:witch_spawn_egg","id":462},{"name":"minecraft:wither_rose","id":-216},{"name":"minecraft:wither_skeleton_spawn_egg","id":475},{"name":"minecraft:wither_spawn_egg","id":519},{"name":"minecraft:wolf_armor","id":723},{"name":"minecraft:wolf_spawn_egg","id":449},{"name":"minecraft:wood","id":758},{"name":"minecraft:wooden_axe","id":318},{"name":"minecraft:wooden_button","id":143},{"name":"minecraft:wooden_door","id":367},{"name":"minecraft:wooden_hoe","id":337},{"name":"minecraft:wooden_pickaxe","id":317},{"name":"minecraft:wooden_pressure_plate","id":72},{"name":"minecraft:wooden_shovel","id":316},{"name":"minecraft:wooden_slab","id":745},{"name":"minecraft:wooden_sword","id":315},{"name":"minecraft:wool","id":726},{"name":"minecraft:writable_book","id":527},{"name":"minecraft:written_book","id":528},{"name":"minecraft:yellow_candle","id":-417},{"name":"minecraft:yellow_candle_cake","id":-434},{"name":"minecraft:yellow_carpet","id":-600},{"name":"minecraft:yellow_concrete","id":-631},{"name":"minecraft:yellow_concrete_powder","id":-712},{"name":"minecraft:yellow_dye","id":415},{"name":"minecraft:yellow_glazed_terracotta","id":224},{"name":"minecraft:yellow_shulker_box","id":-616},{"name":"minecraft:yellow_stained_glass","id":-676},{"name":"minecraft:yellow_stained_glass_pane","id":-646},{"name":"minecraft:yellow_terracotta","id":-727},{"name":"minecraft:yellow_wool","id":-558},{"name":"minecraft:zoglin_spawn_egg","id":509},{"name":"minecraft:zombie_horse_spawn_egg","id":479},{"name":"minecraft:zombie_pigman_spawn_egg","id":458},{"name":"minecraft:zombie_spawn_egg","id":457},{"name":"minecraft:zombie_villager_spawn_egg","id":488}] \ No newline at end of file +[{"name":"minecraft:acacia_boat","id":405},{"name":"minecraft:acacia_button","id":-140},{"name":"minecraft:acacia_chest_boat","id":678},{"name":"minecraft:acacia_door","id":589},{"name":"minecraft:acacia_double_slab","id":-812},{"name":"minecraft:acacia_fence","id":-575},{"name":"minecraft:acacia_fence_gate","id":187},{"name":"minecraft:acacia_hanging_sign","id":-504},{"name":"minecraft:acacia_leaves","id":161},{"name":"minecraft:acacia_log","id":162},{"name":"minecraft:acacia_planks","id":-742},{"name":"minecraft:acacia_pressure_plate","id":-150},{"name":"minecraft:acacia_sapling","id":-828},{"name":"minecraft:acacia_sign","id":612},{"name":"minecraft:acacia_slab","id":-807},{"name":"minecraft:acacia_stairs","id":163},{"name":"minecraft:acacia_standing_sign","id":-190},{"name":"minecraft:acacia_trapdoor","id":-145},{"name":"minecraft:acacia_wall_sign","id":-191},{"name":"minecraft:acacia_wood","id":-817},{"name":"minecraft:activator_rail","id":126},{"name":"minecraft:agent_spawn_egg","id":515},{"name":"minecraft:air","id":-158},{"name":"minecraft:allay_spawn_egg","id":667},{"name":"minecraft:allium","id":-831},{"name":"minecraft:allow","id":210},{"name":"minecraft:amethyst_block","id":-327},{"name":"minecraft:amethyst_cluster","id":-329},{"name":"minecraft:amethyst_shard","id":660},{"name":"minecraft:ancient_debris","id":-271},{"name":"minecraft:andesite","id":-594},{"name":"minecraft:andesite_double_slab","id":-920},{"name":"minecraft:andesite_slab","id":-893},{"name":"minecraft:andesite_stairs","id":-171},{"name":"minecraft:andesite_wall","id":-974},{"name":"minecraft:angler_pottery_sherd","id":692},{"name":"minecraft:anvil","id":145},{"name":"minecraft:apple","id":278},{"name":"minecraft:archer_pottery_sherd","id":693},{"name":"minecraft:armadillo_scute","id":739},{"name":"minecraft:armadillo_spawn_egg","id":738},{"name":"minecraft:armor_stand","id":585},{"name":"minecraft:arms_up_pottery_sherd","id":694},{"name":"minecraft:arrow","id":325},{"name":"minecraft:axolotl_bucket","id":394},{"name":"minecraft:axolotl_spawn_egg","id":530},{"name":"minecraft:azalea","id":-337},{"name":"minecraft:azalea_leaves","id":-324},{"name":"minecraft:azalea_leaves_flowered","id":-325},{"name":"minecraft:azure_bluet","id":-832},{"name":"minecraft:baked_potato","id":303},{"name":"minecraft:balloon","id":634},{"name":"minecraft:bamboo","id":-163},{"name":"minecraft:bamboo_block","id":-527},{"name":"minecraft:bamboo_button","id":-511},{"name":"minecraft:bamboo_chest_raft","id":690},{"name":"minecraft:bamboo_door","id":-517},{"name":"minecraft:bamboo_double_slab","id":-521},{"name":"minecraft:bamboo_fence","id":-515},{"name":"minecraft:bamboo_fence_gate","id":-516},{"name":"minecraft:bamboo_hanging_sign","id":-522},{"name":"minecraft:bamboo_mosaic","id":-509},{"name":"minecraft:bamboo_mosaic_double_slab","id":-525},{"name":"minecraft:bamboo_mosaic_slab","id":-524},{"name":"minecraft:bamboo_mosaic_stairs","id":-523},{"name":"minecraft:bamboo_planks","id":-510},{"name":"minecraft:bamboo_pressure_plate","id":-514},{"name":"minecraft:bamboo_raft","id":689},{"name":"minecraft:bamboo_sapling","id":-164},{"name":"minecraft:bamboo_sign","id":688},{"name":"minecraft:bamboo_slab","id":-513},{"name":"minecraft:bamboo_stairs","id":-512},{"name":"minecraft:bamboo_standing_sign","id":-518},{"name":"minecraft:bamboo_trapdoor","id":-520},{"name":"minecraft:bamboo_wall_sign","id":-519},{"name":"minecraft:banner","id":600},{"name":"minecraft:banner_pattern","id":787},{"name":"minecraft:barrel","id":-203},{"name":"minecraft:barrier","id":-161},{"name":"minecraft:basalt","id":-234},{"name":"minecraft:bat_spawn_egg","id":480},{"name":"minecraft:beacon","id":138},{"name":"minecraft:bed","id":444},{"name":"minecraft:bedrock","id":7},{"name":"minecraft:bee_nest","id":-218},{"name":"minecraft:bee_spawn_egg","id":522},{"name":"minecraft:beef","id":295},{"name":"minecraft:beehive","id":-219},{"name":"minecraft:beetroot","id":307},{"name":"minecraft:beetroot_seeds","id":317},{"name":"minecraft:beetroot_soup","id":308},{"name":"minecraft:bell","id":-206},{"name":"minecraft:big_dripleaf","id":-323},{"name":"minecraft:birch_boat","id":402},{"name":"minecraft:birch_button","id":-141},{"name":"minecraft:birch_chest_boat","id":675},{"name":"minecraft:birch_door","id":587},{"name":"minecraft:birch_double_slab","id":-810},{"name":"minecraft:birch_fence","id":-576},{"name":"minecraft:birch_fence_gate","id":184},{"name":"minecraft:birch_hanging_sign","id":-502},{"name":"minecraft:birch_leaves","id":-801},{"name":"minecraft:birch_log","id":-570},{"name":"minecraft:birch_planks","id":-740},{"name":"minecraft:birch_pressure_plate","id":-151},{"name":"minecraft:birch_sapling","id":-826},{"name":"minecraft:birch_sign","id":610},{"name":"minecraft:birch_slab","id":-805},{"name":"minecraft:birch_stairs","id":135},{"name":"minecraft:birch_standing_sign","id":-186},{"name":"minecraft:birch_trapdoor","id":-146},{"name":"minecraft:birch_wall_sign","id":-187},{"name":"minecraft:birch_wood","id":-815},{"name":"minecraft:black_bundle","id":257},{"name":"minecraft:black_candle","id":-428},{"name":"minecraft:black_candle_cake","id":-445},{"name":"minecraft:black_carpet","id":-611},{"name":"minecraft:black_concrete","id":-642},{"name":"minecraft:black_concrete_powder","id":-723},{"name":"minecraft:black_dye","id":421},{"name":"minecraft:black_glazed_terracotta","id":235},{"name":"minecraft:black_shulker_box","id":-627},{"name":"minecraft:black_stained_glass","id":-687},{"name":"minecraft:black_stained_glass_pane","id":-657},{"name":"minecraft:black_terracotta","id":-738},{"name":"minecraft:black_wool","id":-554},{"name":"minecraft:blackstone","id":-273},{"name":"minecraft:blackstone_double_slab","id":-283},{"name":"minecraft:blackstone_slab","id":-282},{"name":"minecraft:blackstone_stairs","id":-276},{"name":"minecraft:blackstone_wall","id":-277},{"name":"minecraft:blade_pottery_sherd","id":695},{"name":"minecraft:blast_furnace","id":-196},{"name":"minecraft:blaze_powder","id":456},{"name":"minecraft:blaze_rod","id":449},{"name":"minecraft:blaze_spawn_egg","id":483},{"name":"minecraft:bleach","id":632},{"name":"minecraft:blue_bundle","id":258},{"name":"minecraft:blue_candle","id":-424},{"name":"minecraft:blue_candle_cake","id":-441},{"name":"minecraft:blue_carpet","id":-607},{"name":"minecraft:blue_concrete","id":-638},{"name":"minecraft:blue_concrete_powder","id":-719},{"name":"minecraft:blue_dye","id":425},{"name":"minecraft:blue_glazed_terracotta","id":231},{"name":"minecraft:blue_ice","id":-11},{"name":"minecraft:blue_orchid","id":-830},{"name":"minecraft:blue_shulker_box","id":-623},{"name":"minecraft:blue_stained_glass","id":-683},{"name":"minecraft:blue_stained_glass_pane","id":-653},{"name":"minecraft:blue_terracotta","id":-734},{"name":"minecraft:blue_wool","id":-563},{"name":"minecraft:boat","id":785},{"name":"minecraft:bogged_spawn_egg","id":490},{"name":"minecraft:bolt_armor_trim_smithing_template","id":734},{"name":"minecraft:bone","id":441},{"name":"minecraft:bone_block","id":216},{"name":"minecraft:bone_meal","id":437},{"name":"minecraft:book","id":413},{"name":"minecraft:bookshelf","id":47},{"name":"minecraft:border_block","id":212},{"name":"minecraft:bordure_indented_banner_pattern","id":619},{"name":"minecraft:bow","id":324},{"name":"minecraft:bowl","id":346},{"name":"minecraft:brain_coral","id":-581},{"name":"minecraft:brain_coral_block","id":-849},{"name":"minecraft:brain_coral_fan","id":-840},{"name":"minecraft:brain_coral_wall_fan","id":-904},{"name":"minecraft:bread","id":283},{"name":"minecraft:breeze_rod","id":274},{"name":"minecraft:breeze_spawn_egg","id":529},{"name":"minecraft:brewer_pottery_sherd","id":696},{"name":"minecraft:brewing_stand","id":458},{"name":"minecraft:brick","id":409},{"name":"minecraft:brick_block","id":45},{"name":"minecraft:brick_double_slab","id":-880},{"name":"minecraft:brick_slab","id":-874},{"name":"minecraft:brick_stairs","id":108},{"name":"minecraft:brick_wall","id":-976},{"name":"minecraft:brown_bundle","id":259},{"name":"minecraft:brown_candle","id":-425},{"name":"minecraft:brown_candle_cake","id":-442},{"name":"minecraft:brown_carpet","id":-608},{"name":"minecraft:brown_concrete","id":-639},{"name":"minecraft:brown_concrete_powder","id":-720},{"name":"minecraft:brown_dye","id":424},{"name":"minecraft:brown_glazed_terracotta","id":232},{"name":"minecraft:brown_mushroom","id":39},{"name":"minecraft:brown_mushroom_block","id":99},{"name":"minecraft:brown_shulker_box","id":-624},{"name":"minecraft:brown_stained_glass","id":-684},{"name":"minecraft:brown_stained_glass_pane","id":-654},{"name":"minecraft:brown_terracotta","id":-735},{"name":"minecraft:brown_wool","id":-555},{"name":"minecraft:brush","id":715},{"name":"minecraft:bubble_column","id":-160},{"name":"minecraft:bubble_coral","id":-582},{"name":"minecraft:bubble_coral_block","id":-850},{"name":"minecraft:bubble_coral_fan","id":-841},{"name":"minecraft:bubble_coral_wall_fan","id":-136},{"name":"minecraft:bucket","id":385},{"name":"minecraft:budding_amethyst","id":-328},{"name":"minecraft:bundle","id":260},{"name":"minecraft:burn_pottery_sherd","id":697},{"name":"minecraft:cactus","id":81},{"name":"minecraft:cake","id":443},{"name":"minecraft:calcite","id":-326},{"name":"minecraft:calibrated_sculk_sensor","id":-580},{"name":"minecraft:camel_spawn_egg","id":691},{"name":"minecraft:camera","id":629},{"name":"minecraft:campfire","id":624},{"name":"minecraft:candle","id":-412},{"name":"minecraft:candle_cake","id":-429},{"name":"minecraft:carpet","id":744},{"name":"minecraft:carrot","id":301},{"name":"minecraft:carrot_on_a_stick","id":550},{"name":"minecraft:carrots","id":141},{"name":"minecraft:cartography_table","id":-200},{"name":"minecraft:carved_pumpkin","id":-155},{"name":"minecraft:cat_spawn_egg","id":516},{"name":"minecraft:cauldron","id":459},{"name":"minecraft:cave_spider_spawn_egg","id":484},{"name":"minecraft:cave_vines","id":-322},{"name":"minecraft:cave_vines_body_with_berries","id":-375},{"name":"minecraft:cave_vines_head_with_berries","id":-376},{"name":"minecraft:chain","id":655},{"name":"minecraft:chain_command_block","id":189},{"name":"minecraft:chainmail_boots","id":367},{"name":"minecraft:chainmail_chestplate","id":365},{"name":"minecraft:chainmail_helmet","id":364},{"name":"minecraft:chainmail_leggings","id":366},{"name":"minecraft:charcoal","id":327},{"name":"minecraft:chemical_heat","id":192},{"name":"minecraft:chemistry_table","id":779},{"name":"minecraft:cherry_boat","id":685},{"name":"minecraft:cherry_button","id":-530},{"name":"minecraft:cherry_chest_boat","id":686},{"name":"minecraft:cherry_door","id":-531},{"name":"minecraft:cherry_double_slab","id":-540},{"name":"minecraft:cherry_fence","id":-532},{"name":"minecraft:cherry_fence_gate","id":-533},{"name":"minecraft:cherry_hanging_sign","id":-534},{"name":"minecraft:cherry_leaves","id":-548},{"name":"minecraft:cherry_log","id":-536},{"name":"minecraft:cherry_planks","id":-537},{"name":"minecraft:cherry_pressure_plate","id":-538},{"name":"minecraft:cherry_sapling","id":-547},{"name":"minecraft:cherry_sign","id":687},{"name":"minecraft:cherry_slab","id":-539},{"name":"minecraft:cherry_stairs","id":-541},{"name":"minecraft:cherry_standing_sign","id":-542},{"name":"minecraft:cherry_trapdoor","id":-543},{"name":"minecraft:cherry_wall_sign","id":-544},{"name":"minecraft:cherry_wood","id":-546},{"name":"minecraft:chest","id":54},{"name":"minecraft:chest_boat","id":681},{"name":"minecraft:chest_minecart","id":415},{"name":"minecraft:chicken","id":297},{"name":"minecraft:chicken_spawn_egg","id":462},{"name":"minecraft:chipped_anvil","id":-959},{"name":"minecraft:chiseled_bookshelf","id":-526},{"name":"minecraft:chiseled_copper","id":-760},{"name":"minecraft:chiseled_deepslate","id":-395},{"name":"minecraft:chiseled_nether_bricks","id":-302},{"name":"minecraft:chiseled_polished_blackstone","id":-279},{"name":"minecraft:chiseled_quartz_block","id":-953},{"name":"minecraft:chiseled_red_sandstone","id":-956},{"name":"minecraft:chiseled_sandstone","id":-944},{"name":"minecraft:chiseled_stone_bricks","id":-870},{"name":"minecraft:chiseled_tuff","id":-753},{"name":"minecraft:chiseled_tuff_bricks","id":-759},{"name":"minecraft:chorus_flower","id":200},{"name":"minecraft:chorus_fruit","id":591},{"name":"minecraft:chorus_plant","id":240},{"name":"minecraft:clay","id":82},{"name":"minecraft:clay_ball","id":410},{"name":"minecraft:client_request_placeholder_block","id":-465},{"name":"minecraft:clock","id":419},{"name":"minecraft:coal","id":326},{"name":"minecraft:coal_block","id":173},{"name":"minecraft:coal_ore","id":16},{"name":"minecraft:coarse_dirt","id":-962},{"name":"minecraft:coast_armor_trim_smithing_template","id":719},{"name":"minecraft:cobbled_deepslate","id":-379},{"name":"minecraft:cobbled_deepslate_double_slab","id":-396},{"name":"minecraft:cobbled_deepslate_slab","id":-380},{"name":"minecraft:cobbled_deepslate_stairs","id":-381},{"name":"minecraft:cobbled_deepslate_wall","id":-382},{"name":"minecraft:cobblestone","id":4},{"name":"minecraft:cobblestone_double_slab","id":-879},{"name":"minecraft:cobblestone_slab","id":-873},{"name":"minecraft:cobblestone_wall","id":139},{"name":"minecraft:cocoa","id":127},{"name":"minecraft:cocoa_beans","id":438},{"name":"minecraft:cod","id":286},{"name":"minecraft:cod_bucket","id":389},{"name":"minecraft:cod_spawn_egg","id":508},{"name":"minecraft:colored_torch_blue","id":204},{"name":"minecraft:colored_torch_bp","id":783},{"name":"minecraft:colored_torch_green","id":-963},{"name":"minecraft:colored_torch_purple","id":-964},{"name":"minecraft:colored_torch_red","id":202},{"name":"minecraft:colored_torch_rg","id":782},{"name":"minecraft:command_block","id":137},{"name":"minecraft:command_block_minecart","id":596},{"name":"minecraft:comparator","id":555},{"name":"minecraft:compass","id":417},{"name":"minecraft:composter","id":-213},{"name":"minecraft:compound","id":630},{"name":"minecraft:compound_creator","id":238},{"name":"minecraft:concrete","id":770},{"name":"minecraft:concrete_powder","id":771},{"name":"minecraft:conduit","id":-157},{"name":"minecraft:cooked_beef","id":296},{"name":"minecraft:cooked_chicken","id":298},{"name":"minecraft:cooked_cod","id":290},{"name":"minecraft:cooked_mutton","id":584},{"name":"minecraft:cooked_porkchop","id":285},{"name":"minecraft:cooked_rabbit","id":311},{"name":"minecraft:cooked_salmon","id":291},{"name":"minecraft:cookie","id":293},{"name":"minecraft:copper_block","id":-340},{"name":"minecraft:copper_bulb","id":-776},{"name":"minecraft:copper_door","id":-784},{"name":"minecraft:copper_grate","id":-768},{"name":"minecraft:copper_ingot","id":538},{"name":"minecraft:copper_ore","id":-311},{"name":"minecraft:copper_trapdoor","id":-792},{"name":"minecraft:coral","id":766},{"name":"minecraft:coral_block","id":748},{"name":"minecraft:coral_fan","id":757},{"name":"minecraft:coral_fan_dead","id":758},{"name":"minecraft:cornflower","id":-838},{"name":"minecraft:cow_spawn_egg","id":463},{"name":"minecraft:cracked_deepslate_bricks","id":-410},{"name":"minecraft:cracked_deepslate_tiles","id":-409},{"name":"minecraft:cracked_nether_bricks","id":-303},{"name":"minecraft:cracked_polished_blackstone_bricks","id":-280},{"name":"minecraft:cracked_stone_bricks","id":-869},{"name":"minecraft:crafter","id":-313},{"name":"minecraft:crafting_table","id":58},{"name":"minecraft:creeper_banner_pattern","id":615},{"name":"minecraft:creeper_head","id":-968},{"name":"minecraft:creeper_spawn_egg","id":468},{"name":"minecraft:crimson_button","id":-260},{"name":"minecraft:crimson_door","id":652},{"name":"minecraft:crimson_double_slab","id":-266},{"name":"minecraft:crimson_fence","id":-256},{"name":"minecraft:crimson_fence_gate","id":-258},{"name":"minecraft:crimson_fungus","id":-228},{"name":"minecraft:crimson_hanging_sign","id":-506},{"name":"minecraft:crimson_hyphae","id":-299},{"name":"minecraft:crimson_nylium","id":-232},{"name":"minecraft:crimson_planks","id":-242},{"name":"minecraft:crimson_pressure_plate","id":-262},{"name":"minecraft:crimson_roots","id":-223},{"name":"minecraft:crimson_sign","id":650},{"name":"minecraft:crimson_slab","id":-264},{"name":"minecraft:crimson_stairs","id":-254},{"name":"minecraft:crimson_standing_sign","id":-250},{"name":"minecraft:crimson_stem","id":-225},{"name":"minecraft:crimson_trapdoor","id":-246},{"name":"minecraft:crimson_wall_sign","id":-252},{"name":"minecraft:crossbow","id":608},{"name":"minecraft:crying_obsidian","id":-289},{"name":"minecraft:cut_copper","id":-347},{"name":"minecraft:cut_copper_slab","id":-361},{"name":"minecraft:cut_copper_stairs","id":-354},{"name":"minecraft:cut_red_sandstone","id":-957},{"name":"minecraft:cut_red_sandstone_double_slab","id":-928},{"name":"minecraft:cut_red_sandstone_slab","id":-901},{"name":"minecraft:cut_sandstone","id":-945},{"name":"minecraft:cut_sandstone_double_slab","id":-927},{"name":"minecraft:cut_sandstone_slab","id":-900},{"name":"minecraft:cyan_bundle","id":261},{"name":"minecraft:cyan_candle","id":-422},{"name":"minecraft:cyan_candle_cake","id":-439},{"name":"minecraft:cyan_carpet","id":-605},{"name":"minecraft:cyan_concrete","id":-636},{"name":"minecraft:cyan_concrete_powder","id":-717},{"name":"minecraft:cyan_dye","id":427},{"name":"minecraft:cyan_glazed_terracotta","id":229},{"name":"minecraft:cyan_shulker_box","id":-621},{"name":"minecraft:cyan_stained_glass","id":-681},{"name":"minecraft:cyan_stained_glass_pane","id":-651},{"name":"minecraft:cyan_terracotta","id":-732},{"name":"minecraft:cyan_wool","id":-561},{"name":"minecraft:damaged_anvil","id":-960},{"name":"minecraft:dandelion","id":37},{"name":"minecraft:danger_pottery_sherd","id":698},{"name":"minecraft:dark_oak_boat","id":406},{"name":"minecraft:dark_oak_button","id":-142},{"name":"minecraft:dark_oak_chest_boat","id":679},{"name":"minecraft:dark_oak_door","id":590},{"name":"minecraft:dark_oak_double_slab","id":-813},{"name":"minecraft:dark_oak_fence","id":-577},{"name":"minecraft:dark_oak_fence_gate","id":186},{"name":"minecraft:dark_oak_hanging_sign","id":-505},{"name":"minecraft:dark_oak_leaves","id":-803},{"name":"minecraft:dark_oak_log","id":-572},{"name":"minecraft:dark_oak_planks","id":-743},{"name":"minecraft:dark_oak_pressure_plate","id":-152},{"name":"minecraft:dark_oak_sapling","id":-829},{"name":"minecraft:dark_oak_sign","id":613},{"name":"minecraft:dark_oak_slab","id":-808},{"name":"minecraft:dark_oak_stairs","id":164},{"name":"minecraft:dark_oak_trapdoor","id":-147},{"name":"minecraft:dark_oak_wood","id":-818},{"name":"minecraft:dark_prismarine","id":-947},{"name":"minecraft:dark_prismarine_double_slab","id":-913},{"name":"minecraft:dark_prismarine_slab","id":-886},{"name":"minecraft:dark_prismarine_stairs","id":-3},{"name":"minecraft:darkoak_standing_sign","id":-192},{"name":"minecraft:darkoak_wall_sign","id":-193},{"name":"minecraft:daylight_detector","id":151},{"name":"minecraft:daylight_detector_inverted","id":178},{"name":"minecraft:dead_brain_coral","id":-586},{"name":"minecraft:dead_brain_coral_block","id":-854},{"name":"minecraft:dead_brain_coral_fan","id":-844},{"name":"minecraft:dead_brain_coral_wall_fan","id":-906},{"name":"minecraft:dead_bubble_coral","id":-587},{"name":"minecraft:dead_bubble_coral_block","id":-855},{"name":"minecraft:dead_bubble_coral_fan","id":-845},{"name":"minecraft:dead_bubble_coral_wall_fan","id":-908},{"name":"minecraft:dead_fire_coral","id":-588},{"name":"minecraft:dead_fire_coral_block","id":-856},{"name":"minecraft:dead_fire_coral_fan","id":-846},{"name":"minecraft:dead_fire_coral_wall_fan","id":-909},{"name":"minecraft:dead_horn_coral","id":-589},{"name":"minecraft:dead_horn_coral_block","id":-857},{"name":"minecraft:dead_horn_coral_fan","id":-847},{"name":"minecraft:dead_horn_coral_wall_fan","id":-910},{"name":"minecraft:dead_tube_coral","id":-585},{"name":"minecraft:dead_tube_coral_block","id":-853},{"name":"minecraft:dead_tube_coral_fan","id":-134},{"name":"minecraft:dead_tube_coral_wall_fan","id":-905},{"name":"minecraft:deadbush","id":32},{"name":"minecraft:decorated_pot","id":-551},{"name":"minecraft:deepslate","id":-378},{"name":"minecraft:deepslate_brick_double_slab","id":-399},{"name":"minecraft:deepslate_brick_slab","id":-392},{"name":"minecraft:deepslate_brick_stairs","id":-393},{"name":"minecraft:deepslate_brick_wall","id":-394},{"name":"minecraft:deepslate_bricks","id":-391},{"name":"minecraft:deepslate_coal_ore","id":-406},{"name":"minecraft:deepslate_copper_ore","id":-408},{"name":"minecraft:deepslate_diamond_ore","id":-405},{"name":"minecraft:deepslate_emerald_ore","id":-407},{"name":"minecraft:deepslate_gold_ore","id":-402},{"name":"minecraft:deepslate_iron_ore","id":-401},{"name":"minecraft:deepslate_lapis_ore","id":-400},{"name":"minecraft:deepslate_redstone_ore","id":-403},{"name":"minecraft:deepslate_tile_double_slab","id":-398},{"name":"minecraft:deepslate_tile_slab","id":-388},{"name":"minecraft:deepslate_tile_stairs","id":-389},{"name":"minecraft:deepslate_tile_wall","id":-390},{"name":"minecraft:deepslate_tiles","id":-387},{"name":"minecraft:deny","id":211},{"name":"minecraft:deprecated_anvil","id":-961},{"name":"minecraft:deprecated_purpur_block_1","id":-950},{"name":"minecraft:deprecated_purpur_block_2","id":-952},{"name":"minecraft:detector_rail","id":28},{"name":"minecraft:diamond","id":328},{"name":"minecraft:diamond_axe","id":343},{"name":"minecraft:diamond_block","id":57},{"name":"minecraft:diamond_boots","id":375},{"name":"minecraft:diamond_chestplate","id":373},{"name":"minecraft:diamond_helmet","id":372},{"name":"minecraft:diamond_hoe","id":357},{"name":"minecraft:diamond_horse_armor","id":566},{"name":"minecraft:diamond_leggings","id":374},{"name":"minecraft:diamond_ore","id":56},{"name":"minecraft:diamond_pickaxe","id":342},{"name":"minecraft:diamond_shovel","id":341},{"name":"minecraft:diamond_sword","id":340},{"name":"minecraft:diorite","id":-592},{"name":"minecraft:diorite_double_slab","id":-921},{"name":"minecraft:diorite_slab","id":-894},{"name":"minecraft:diorite_stairs","id":-170},{"name":"minecraft:diorite_wall","id":-973},{"name":"minecraft:dirt","id":3},{"name":"minecraft:dirt_with_roots","id":-318},{"name":"minecraft:disc_fragment_5","id":673},{"name":"minecraft:dispenser","id":23},{"name":"minecraft:dolphin_spawn_egg","id":512},{"name":"minecraft:donkey_spawn_egg","id":493},{"name":"minecraft:double_cut_copper_slab","id":-368},{"name":"minecraft:double_plant","id":764},{"name":"minecraft:double_stone_block_slab","id":753},{"name":"minecraft:double_stone_block_slab2","id":754},{"name":"minecraft:double_stone_block_slab3","id":755},{"name":"minecraft:double_stone_block_slab4","id":756},{"name":"minecraft:dragon_breath","id":593},{"name":"minecraft:dragon_egg","id":122},{"name":"minecraft:dragon_head","id":-969},{"name":"minecraft:dried_kelp","id":292},{"name":"minecraft:dried_kelp_block","id":-139},{"name":"minecraft:dripstone_block","id":-317},{"name":"minecraft:dropper","id":125},{"name":"minecraft:drowned_spawn_egg","id":511},{"name":"minecraft:dune_armor_trim_smithing_template","id":718},{"name":"minecraft:dye","id":786},{"name":"minecraft:echo_shard","id":683},{"name":"minecraft:egg","id":416},{"name":"minecraft:elder_guardian_spawn_egg","id":499},{"name":"minecraft:element_0","id":36},{"name":"minecraft:element_1","id":-12},{"name":"minecraft:element_10","id":-21},{"name":"minecraft:element_100","id":-111},{"name":"minecraft:element_101","id":-112},{"name":"minecraft:element_102","id":-113},{"name":"minecraft:element_103","id":-114},{"name":"minecraft:element_104","id":-115},{"name":"minecraft:element_105","id":-116},{"name":"minecraft:element_106","id":-117},{"name":"minecraft:element_107","id":-118},{"name":"minecraft:element_108","id":-119},{"name":"minecraft:element_109","id":-120},{"name":"minecraft:element_11","id":-22},{"name":"minecraft:element_110","id":-121},{"name":"minecraft:element_111","id":-122},{"name":"minecraft:element_112","id":-123},{"name":"minecraft:element_113","id":-124},{"name":"minecraft:element_114","id":-125},{"name":"minecraft:element_115","id":-126},{"name":"minecraft:element_116","id":-127},{"name":"minecraft:element_117","id":-128},{"name":"minecraft:element_118","id":-129},{"name":"minecraft:element_12","id":-23},{"name":"minecraft:element_13","id":-24},{"name":"minecraft:element_14","id":-25},{"name":"minecraft:element_15","id":-26},{"name":"minecraft:element_16","id":-27},{"name":"minecraft:element_17","id":-28},{"name":"minecraft:element_18","id":-29},{"name":"minecraft:element_19","id":-30},{"name":"minecraft:element_2","id":-13},{"name":"minecraft:element_20","id":-31},{"name":"minecraft:element_21","id":-32},{"name":"minecraft:element_22","id":-33},{"name":"minecraft:element_23","id":-34},{"name":"minecraft:element_24","id":-35},{"name":"minecraft:element_25","id":-36},{"name":"minecraft:element_26","id":-37},{"name":"minecraft:element_27","id":-38},{"name":"minecraft:element_28","id":-39},{"name":"minecraft:element_29","id":-40},{"name":"minecraft:element_3","id":-14},{"name":"minecraft:element_30","id":-41},{"name":"minecraft:element_31","id":-42},{"name":"minecraft:element_32","id":-43},{"name":"minecraft:element_33","id":-44},{"name":"minecraft:element_34","id":-45},{"name":"minecraft:element_35","id":-46},{"name":"minecraft:element_36","id":-47},{"name":"minecraft:element_37","id":-48},{"name":"minecraft:element_38","id":-49},{"name":"minecraft:element_39","id":-50},{"name":"minecraft:element_4","id":-15},{"name":"minecraft:element_40","id":-51},{"name":"minecraft:element_41","id":-52},{"name":"minecraft:element_42","id":-53},{"name":"minecraft:element_43","id":-54},{"name":"minecraft:element_44","id":-55},{"name":"minecraft:element_45","id":-56},{"name":"minecraft:element_46","id":-57},{"name":"minecraft:element_47","id":-58},{"name":"minecraft:element_48","id":-59},{"name":"minecraft:element_49","id":-60},{"name":"minecraft:element_5","id":-16},{"name":"minecraft:element_50","id":-61},{"name":"minecraft:element_51","id":-62},{"name":"minecraft:element_52","id":-63},{"name":"minecraft:element_53","id":-64},{"name":"minecraft:element_54","id":-65},{"name":"minecraft:element_55","id":-66},{"name":"minecraft:element_56","id":-67},{"name":"minecraft:element_57","id":-68},{"name":"minecraft:element_58","id":-69},{"name":"minecraft:element_59","id":-70},{"name":"minecraft:element_6","id":-17},{"name":"minecraft:element_60","id":-71},{"name":"minecraft:element_61","id":-72},{"name":"minecraft:element_62","id":-73},{"name":"minecraft:element_63","id":-74},{"name":"minecraft:element_64","id":-75},{"name":"minecraft:element_65","id":-76},{"name":"minecraft:element_66","id":-77},{"name":"minecraft:element_67","id":-78},{"name":"minecraft:element_68","id":-79},{"name":"minecraft:element_69","id":-80},{"name":"minecraft:element_7","id":-18},{"name":"minecraft:element_70","id":-81},{"name":"minecraft:element_71","id":-82},{"name":"minecraft:element_72","id":-83},{"name":"minecraft:element_73","id":-84},{"name":"minecraft:element_74","id":-85},{"name":"minecraft:element_75","id":-86},{"name":"minecraft:element_76","id":-87},{"name":"minecraft:element_77","id":-88},{"name":"minecraft:element_78","id":-89},{"name":"minecraft:element_79","id":-90},{"name":"minecraft:element_8","id":-19},{"name":"minecraft:element_80","id":-91},{"name":"minecraft:element_81","id":-92},{"name":"minecraft:element_82","id":-93},{"name":"minecraft:element_83","id":-94},{"name":"minecraft:element_84","id":-95},{"name":"minecraft:element_85","id":-96},{"name":"minecraft:element_86","id":-97},{"name":"minecraft:element_87","id":-98},{"name":"minecraft:element_88","id":-99},{"name":"minecraft:element_89","id":-100},{"name":"minecraft:element_9","id":-20},{"name":"minecraft:element_90","id":-101},{"name":"minecraft:element_91","id":-102},{"name":"minecraft:element_92","id":-103},{"name":"minecraft:element_93","id":-104},{"name":"minecraft:element_94","id":-105},{"name":"minecraft:element_95","id":-106},{"name":"minecraft:element_96","id":-107},{"name":"minecraft:element_97","id":-108},{"name":"minecraft:element_98","id":-109},{"name":"minecraft:element_99","id":-110},{"name":"minecraft:element_constructor","id":-987},{"name":"minecraft:elytra","id":597},{"name":"minecraft:emerald","id":546},{"name":"minecraft:emerald_block","id":133},{"name":"minecraft:emerald_ore","id":129},{"name":"minecraft:empty_map","id":549},{"name":"minecraft:enchanted_book","id":554},{"name":"minecraft:enchanted_golden_apple","id":281},{"name":"minecraft:enchanting_table","id":116},{"name":"minecraft:end_brick_stairs","id":-178},{"name":"minecraft:end_bricks","id":206},{"name":"minecraft:end_crystal","id":789},{"name":"minecraft:end_gateway","id":209},{"name":"minecraft:end_portal","id":119},{"name":"minecraft:end_portal_frame","id":120},{"name":"minecraft:end_rod","id":208},{"name":"minecraft:end_stone","id":121},{"name":"minecraft:end_stone_brick_double_slab","id":-167},{"name":"minecraft:end_stone_brick_slab","id":-162},{"name":"minecraft:end_stone_brick_wall","id":-980},{"name":"minecraft:ender_chest","id":130},{"name":"minecraft:ender_dragon_spawn_egg","id":535},{"name":"minecraft:ender_eye","id":460},{"name":"minecraft:ender_pearl","id":448},{"name":"minecraft:enderman_spawn_egg","id":469},{"name":"minecraft:endermite_spawn_egg","id":487},{"name":"minecraft:evoker_spawn_egg","id":503},{"name":"minecraft:experience_bottle","id":542},{"name":"minecraft:explorer_pottery_sherd","id":699},{"name":"minecraft:exposed_chiseled_copper","id":-761},{"name":"minecraft:exposed_copper","id":-341},{"name":"minecraft:exposed_copper_bulb","id":-777},{"name":"minecraft:exposed_copper_door","id":-785},{"name":"minecraft:exposed_copper_grate","id":-769},{"name":"minecraft:exposed_copper_trapdoor","id":-793},{"name":"minecraft:exposed_cut_copper","id":-348},{"name":"minecraft:exposed_cut_copper_slab","id":-362},{"name":"minecraft:exposed_cut_copper_stairs","id":-355},{"name":"minecraft:exposed_double_cut_copper_slab","id":-369},{"name":"minecraft:eye_armor_trim_smithing_template","id":722},{"name":"minecraft:farmland","id":60},{"name":"minecraft:feather","id":352},{"name":"minecraft:fence","id":746},{"name":"minecraft:fence_gate","id":107},{"name":"minecraft:fermented_spider_eye","id":455},{"name":"minecraft:fern","id":-848},{"name":"minecraft:field_masoned_banner_pattern","id":618},{"name":"minecraft:filled_map","id":446},{"name":"minecraft:fire","id":51},{"name":"minecraft:fire_charge","id":543},{"name":"minecraft:fire_coral","id":-583},{"name":"minecraft:fire_coral_block","id":-851},{"name":"minecraft:fire_coral_fan","id":-842},{"name":"minecraft:fire_coral_wall_fan","id":-907},{"name":"minecraft:firework_rocket","id":552},{"name":"minecraft:firework_star","id":553},{"name":"minecraft:fishing_rod","id":418},{"name":"minecraft:fletching_table","id":-201},{"name":"minecraft:flint","id":381},{"name":"minecraft:flint_and_steel","id":323},{"name":"minecraft:flow_armor_trim_smithing_template","id":733},{"name":"minecraft:flow_banner_pattern","id":622},{"name":"minecraft:flow_pottery_sherd","id":700},{"name":"minecraft:flower_banner_pattern","id":614},{"name":"minecraft:flower_pot","id":548},{"name":"minecraft:flowering_azalea","id":-338},{"name":"minecraft:flowing_lava","id":10},{"name":"minecraft:flowing_water","id":8},{"name":"minecraft:fox_spawn_egg","id":518},{"name":"minecraft:frame","id":547},{"name":"minecraft:friend_pottery_sherd","id":701},{"name":"minecraft:frog_spawn","id":-468},{"name":"minecraft:frog_spawn_egg","id":664},{"name":"minecraft:frosted_ice","id":207},{"name":"minecraft:furnace","id":61},{"name":"minecraft:ghast_spawn_egg","id":481},{"name":"minecraft:ghast_tear","id":451},{"name":"minecraft:gilded_blackstone","id":-281},{"name":"minecraft:glass","id":20},{"name":"minecraft:glass_bottle","id":454},{"name":"minecraft:glass_pane","id":102},{"name":"minecraft:glistering_melon_slice","id":461},{"name":"minecraft:globe_banner_pattern","id":621},{"name":"minecraft:glow_berries","id":790},{"name":"minecraft:glow_frame","id":659},{"name":"minecraft:glow_ink_sac","id":537},{"name":"minecraft:glow_lichen","id":-411},{"name":"minecraft:glow_squid_spawn_egg","id":532},{"name":"minecraft:glow_stick","id":637},{"name":"minecraft:glowingobsidian","id":246},{"name":"minecraft:glowstone","id":89},{"name":"minecraft:glowstone_dust","id":420},{"name":"minecraft:goat_horn","id":663},{"name":"minecraft:goat_spawn_egg","id":531},{"name":"minecraft:gold_block","id":41},{"name":"minecraft:gold_ingot","id":330},{"name":"minecraft:gold_nugget","id":452},{"name":"minecraft:gold_ore","id":14},{"name":"minecraft:golden_apple","id":280},{"name":"minecraft:golden_axe","id":350},{"name":"minecraft:golden_boots","id":379},{"name":"minecraft:golden_carrot","id":305},{"name":"minecraft:golden_chestplate","id":377},{"name":"minecraft:golden_helmet","id":376},{"name":"minecraft:golden_hoe","id":358},{"name":"minecraft:golden_horse_armor","id":565},{"name":"minecraft:golden_leggings","id":378},{"name":"minecraft:golden_pickaxe","id":349},{"name":"minecraft:golden_rail","id":27},{"name":"minecraft:golden_shovel","id":348},{"name":"minecraft:golden_sword","id":347},{"name":"minecraft:granite","id":-590},{"name":"minecraft:granite_double_slab","id":-923},{"name":"minecraft:granite_slab","id":-896},{"name":"minecraft:granite_stairs","id":-169},{"name":"minecraft:granite_wall","id":-972},{"name":"minecraft:grass_block","id":2},{"name":"minecraft:grass_path","id":198},{"name":"minecraft:gravel","id":13},{"name":"minecraft:gray_bundle","id":262},{"name":"minecraft:gray_candle","id":-420},{"name":"minecraft:gray_candle_cake","id":-437},{"name":"minecraft:gray_carpet","id":-603},{"name":"minecraft:gray_concrete","id":-634},{"name":"minecraft:gray_concrete_powder","id":-715},{"name":"minecraft:gray_dye","id":429},{"name":"minecraft:gray_glazed_terracotta","id":227},{"name":"minecraft:gray_shulker_box","id":-619},{"name":"minecraft:gray_stained_glass","id":-679},{"name":"minecraft:gray_stained_glass_pane","id":-649},{"name":"minecraft:gray_terracotta","id":-730},{"name":"minecraft:gray_wool","id":-553},{"name":"minecraft:green_bundle","id":263},{"name":"minecraft:green_candle","id":-426},{"name":"minecraft:green_candle_cake","id":-443},{"name":"minecraft:green_carpet","id":-609},{"name":"minecraft:green_concrete","id":-640},{"name":"minecraft:green_concrete_powder","id":-721},{"name":"minecraft:green_dye","id":423},{"name":"minecraft:green_glazed_terracotta","id":233},{"name":"minecraft:green_shulker_box","id":-625},{"name":"minecraft:green_stained_glass","id":-685},{"name":"minecraft:green_stained_glass_pane","id":-655},{"name":"minecraft:green_terracotta","id":-736},{"name":"minecraft:green_wool","id":-560},{"name":"minecraft:grindstone","id":-195},{"name":"minecraft:guardian_spawn_egg","id":488},{"name":"minecraft:gunpowder","id":353},{"name":"minecraft:guster_banner_pattern","id":623},{"name":"minecraft:guster_pottery_sherd","id":702},{"name":"minecraft:hanging_roots","id":-319},{"name":"minecraft:hard_black_stained_glass","id":-702},{"name":"minecraft:hard_black_stained_glass_pane","id":-672},{"name":"minecraft:hard_blue_stained_glass","id":-698},{"name":"minecraft:hard_blue_stained_glass_pane","id":-668},{"name":"minecraft:hard_brown_stained_glass","id":-699},{"name":"minecraft:hard_brown_stained_glass_pane","id":-669},{"name":"minecraft:hard_cyan_stained_glass","id":-696},{"name":"minecraft:hard_cyan_stained_glass_pane","id":-666},{"name":"minecraft:hard_glass","id":253},{"name":"minecraft:hard_glass_pane","id":190},{"name":"minecraft:hard_gray_stained_glass","id":-694},{"name":"minecraft:hard_gray_stained_glass_pane","id":-664},{"name":"minecraft:hard_green_stained_glass","id":-700},{"name":"minecraft:hard_green_stained_glass_pane","id":-670},{"name":"minecraft:hard_light_blue_stained_glass","id":-690},{"name":"minecraft:hard_light_blue_stained_glass_pane","id":-660},{"name":"minecraft:hard_light_gray_stained_glass","id":-695},{"name":"minecraft:hard_light_gray_stained_glass_pane","id":-665},{"name":"minecraft:hard_lime_stained_glass","id":-692},{"name":"minecraft:hard_lime_stained_glass_pane","id":-662},{"name":"minecraft:hard_magenta_stained_glass","id":-689},{"name":"minecraft:hard_magenta_stained_glass_pane","id":-659},{"name":"minecraft:hard_orange_stained_glass","id":-688},{"name":"minecraft:hard_orange_stained_glass_pane","id":-658},{"name":"minecraft:hard_pink_stained_glass","id":-693},{"name":"minecraft:hard_pink_stained_glass_pane","id":-663},{"name":"minecraft:hard_purple_stained_glass","id":-697},{"name":"minecraft:hard_purple_stained_glass_pane","id":-667},{"name":"minecraft:hard_red_stained_glass","id":-701},{"name":"minecraft:hard_red_stained_glass_pane","id":-671},{"name":"minecraft:hard_stained_glass","id":780},{"name":"minecraft:hard_stained_glass_pane","id":781},{"name":"minecraft:hard_white_stained_glass","id":254},{"name":"minecraft:hard_white_stained_glass_pane","id":191},{"name":"minecraft:hard_yellow_stained_glass","id":-691},{"name":"minecraft:hard_yellow_stained_glass_pane","id":-661},{"name":"minecraft:hardened_clay","id":172},{"name":"minecraft:hay_block","id":170},{"name":"minecraft:heart_of_the_sea","id":604},{"name":"minecraft:heart_pottery_sherd","id":703},{"name":"minecraft:heartbreak_pottery_sherd","id":704},{"name":"minecraft:heavy_core","id":-316},{"name":"minecraft:heavy_weighted_pressure_plate","id":148},{"name":"minecraft:hoglin_spawn_egg","id":524},{"name":"minecraft:honey_block","id":-220},{"name":"minecraft:honey_bottle","id":627},{"name":"minecraft:honeycomb","id":626},{"name":"minecraft:honeycomb_block","id":-221},{"name":"minecraft:hopper","id":560},{"name":"minecraft:hopper_minecart","id":559},{"name":"minecraft:horn_coral","id":-584},{"name":"minecraft:horn_coral_block","id":-852},{"name":"minecraft:horn_coral_fan","id":-843},{"name":"minecraft:horn_coral_wall_fan","id":-137},{"name":"minecraft:horse_spawn_egg","id":485},{"name":"minecraft:host_armor_trim_smithing_template","id":732},{"name":"minecraft:howl_pottery_sherd","id":705},{"name":"minecraft:husk_spawn_egg","id":491},{"name":"minecraft:ice","id":79},{"name":"minecraft:ice_bomb","id":631},{"name":"minecraft:infested_chiseled_stone_bricks","id":-862},{"name":"minecraft:infested_cobblestone","id":-858},{"name":"minecraft:infested_cracked_stone_bricks","id":-861},{"name":"minecraft:infested_deepslate","id":-454},{"name":"minecraft:infested_mossy_stone_bricks","id":-860},{"name":"minecraft:infested_stone","id":97},{"name":"minecraft:infested_stone_bricks","id":-859},{"name":"minecraft:info_update","id":248},{"name":"minecraft:info_update2","id":249},{"name":"minecraft:ink_sac","id":439},{"name":"minecraft:invisible_bedrock","id":95},{"name":"minecraft:iron_axe","id":322},{"name":"minecraft:iron_bars","id":101},{"name":"minecraft:iron_block","id":42},{"name":"minecraft:iron_boots","id":371},{"name":"minecraft:iron_chestplate","id":369},{"name":"minecraft:iron_door","id":397},{"name":"minecraft:iron_golem_spawn_egg","id":533},{"name":"minecraft:iron_helmet","id":368},{"name":"minecraft:iron_hoe","id":356},{"name":"minecraft:iron_horse_armor","id":564},{"name":"minecraft:iron_ingot","id":329},{"name":"minecraft:iron_leggings","id":370},{"name":"minecraft:iron_nugget","id":602},{"name":"minecraft:iron_ore","id":15},{"name":"minecraft:iron_pickaxe","id":321},{"name":"minecraft:iron_shovel","id":320},{"name":"minecraft:iron_sword","id":331},{"name":"minecraft:iron_trapdoor","id":167},{"name":"minecraft:item.acacia_door","id":196},{"name":"minecraft:item.bed","id":26},{"name":"minecraft:item.beetroot","id":244},{"name":"minecraft:item.birch_door","id":194},{"name":"minecraft:item.brewing_stand","id":117},{"name":"minecraft:item.cake","id":92},{"name":"minecraft:item.camera","id":242},{"name":"minecraft:item.campfire","id":-209},{"name":"minecraft:item.cauldron","id":118},{"name":"minecraft:item.chain","id":-286},{"name":"minecraft:item.crimson_door","id":-244},{"name":"minecraft:item.dark_oak_door","id":197},{"name":"minecraft:item.flower_pot","id":140},{"name":"minecraft:item.frame","id":199},{"name":"minecraft:item.glow_frame","id":-339},{"name":"minecraft:item.hopper","id":154},{"name":"minecraft:item.iron_door","id":71},{"name":"minecraft:item.jungle_door","id":195},{"name":"minecraft:item.kelp","id":-138},{"name":"minecraft:item.mangrove_door","id":-493},{"name":"minecraft:item.nether_sprouts","id":-238},{"name":"minecraft:item.nether_wart","id":115},{"name":"minecraft:item.reeds","id":83},{"name":"minecraft:item.soul_campfire","id":-290},{"name":"minecraft:item.spruce_door","id":193},{"name":"minecraft:item.warped_door","id":-245},{"name":"minecraft:item.wheat","id":59},{"name":"minecraft:item.wooden_door","id":64},{"name":"minecraft:jigsaw","id":-211},{"name":"minecraft:jukebox","id":84},{"name":"minecraft:jungle_boat","id":403},{"name":"minecraft:jungle_button","id":-143},{"name":"minecraft:jungle_chest_boat","id":676},{"name":"minecraft:jungle_door","id":588},{"name":"minecraft:jungle_double_slab","id":-811},{"name":"minecraft:jungle_fence","id":-578},{"name":"minecraft:jungle_fence_gate","id":185},{"name":"minecraft:jungle_hanging_sign","id":-503},{"name":"minecraft:jungle_leaves","id":-802},{"name":"minecraft:jungle_log","id":-571},{"name":"minecraft:jungle_planks","id":-741},{"name":"minecraft:jungle_pressure_plate","id":-153},{"name":"minecraft:jungle_sapling","id":-827},{"name":"minecraft:jungle_sign","id":611},{"name":"minecraft:jungle_slab","id":-806},{"name":"minecraft:jungle_stairs","id":136},{"name":"minecraft:jungle_standing_sign","id":-188},{"name":"minecraft:jungle_trapdoor","id":-148},{"name":"minecraft:jungle_wall_sign","id":-189},{"name":"minecraft:jungle_wood","id":-816},{"name":"minecraft:kelp","id":408},{"name":"minecraft:lab_table","id":-988},{"name":"minecraft:ladder","id":65},{"name":"minecraft:lantern","id":-208},{"name":"minecraft:lapis_block","id":22},{"name":"minecraft:lapis_lazuli","id":440},{"name":"minecraft:lapis_ore","id":21},{"name":"minecraft:large_amethyst_bud","id":-330},{"name":"minecraft:large_fern","id":-865},{"name":"minecraft:lava","id":11},{"name":"minecraft:lava_bucket","id":388},{"name":"minecraft:lead","id":580},{"name":"minecraft:leather","id":407},{"name":"minecraft:leather_boots","id":363},{"name":"minecraft:leather_chestplate","id":361},{"name":"minecraft:leather_helmet","id":360},{"name":"minecraft:leather_horse_armor","id":563},{"name":"minecraft:leather_leggings","id":362},{"name":"minecraft:leaves","id":760},{"name":"minecraft:leaves2","id":761},{"name":"minecraft:lectern","id":-194},{"name":"minecraft:lever","id":69},{"name":"minecraft:light_block","id":784},{"name":"minecraft:light_block_0","id":-215},{"name":"minecraft:light_block_1","id":-929},{"name":"minecraft:light_block_10","id":-938},{"name":"minecraft:light_block_11","id":-939},{"name":"minecraft:light_block_12","id":-940},{"name":"minecraft:light_block_13","id":-941},{"name":"minecraft:light_block_14","id":-942},{"name":"minecraft:light_block_15","id":-943},{"name":"minecraft:light_block_2","id":-930},{"name":"minecraft:light_block_3","id":-931},{"name":"minecraft:light_block_4","id":-932},{"name":"minecraft:light_block_5","id":-933},{"name":"minecraft:light_block_6","id":-934},{"name":"minecraft:light_block_7","id":-935},{"name":"minecraft:light_block_8","id":-936},{"name":"minecraft:light_block_9","id":-937},{"name":"minecraft:light_blue_bundle","id":264},{"name":"minecraft:light_blue_candle","id":-416},{"name":"minecraft:light_blue_candle_cake","id":-433},{"name":"minecraft:light_blue_carpet","id":-599},{"name":"minecraft:light_blue_concrete","id":-630},{"name":"minecraft:light_blue_concrete_powder","id":-711},{"name":"minecraft:light_blue_dye","id":433},{"name":"minecraft:light_blue_glazed_terracotta","id":223},{"name":"minecraft:light_blue_shulker_box","id":-615},{"name":"minecraft:light_blue_stained_glass","id":-675},{"name":"minecraft:light_blue_stained_glass_pane","id":-645},{"name":"minecraft:light_blue_terracotta","id":-726},{"name":"minecraft:light_blue_wool","id":-562},{"name":"minecraft:light_gray_bundle","id":265},{"name":"minecraft:light_gray_candle","id":-421},{"name":"minecraft:light_gray_candle_cake","id":-438},{"name":"minecraft:light_gray_carpet","id":-604},{"name":"minecraft:light_gray_concrete","id":-635},{"name":"minecraft:light_gray_concrete_powder","id":-716},{"name":"minecraft:light_gray_dye","id":428},{"name":"minecraft:light_gray_shulker_box","id":-620},{"name":"minecraft:light_gray_stained_glass","id":-680},{"name":"minecraft:light_gray_stained_glass_pane","id":-650},{"name":"minecraft:light_gray_terracotta","id":-731},{"name":"minecraft:light_gray_wool","id":-552},{"name":"minecraft:light_weighted_pressure_plate","id":147},{"name":"minecraft:lightning_rod","id":-312},{"name":"minecraft:lilac","id":-863},{"name":"minecraft:lily_of_the_valley","id":-839},{"name":"minecraft:lime_bundle","id":266},{"name":"minecraft:lime_candle","id":-418},{"name":"minecraft:lime_candle_cake","id":-435},{"name":"minecraft:lime_carpet","id":-601},{"name":"minecraft:lime_concrete","id":-632},{"name":"minecraft:lime_concrete_powder","id":-713},{"name":"minecraft:lime_dye","id":431},{"name":"minecraft:lime_glazed_terracotta","id":225},{"name":"minecraft:lime_shulker_box","id":-617},{"name":"minecraft:lime_stained_glass","id":-677},{"name":"minecraft:lime_stained_glass_pane","id":-647},{"name":"minecraft:lime_terracotta","id":-728},{"name":"minecraft:lime_wool","id":-559},{"name":"minecraft:lingering_potion","id":595},{"name":"minecraft:lit_blast_furnace","id":-214},{"name":"minecraft:lit_deepslate_redstone_ore","id":-404},{"name":"minecraft:lit_furnace","id":62},{"name":"minecraft:lit_pumpkin","id":91},{"name":"minecraft:lit_redstone_lamp","id":124},{"name":"minecraft:lit_redstone_ore","id":74},{"name":"minecraft:lit_smoker","id":-199},{"name":"minecraft:llama_spawn_egg","id":501},{"name":"minecraft:lodestone","id":-222},{"name":"minecraft:lodestone_compass","id":638},{"name":"minecraft:log","id":745},{"name":"minecraft:log2","id":768},{"name":"minecraft:loom","id":-204},{"name":"minecraft:mace","id":344},{"name":"minecraft:magenta_bundle","id":267},{"name":"minecraft:magenta_candle","id":-415},{"name":"minecraft:magenta_candle_cake","id":-432},{"name":"minecraft:magenta_carpet","id":-598},{"name":"minecraft:magenta_concrete","id":-629},{"name":"minecraft:magenta_concrete_powder","id":-710},{"name":"minecraft:magenta_dye","id":434},{"name":"minecraft:magenta_glazed_terracotta","id":222},{"name":"minecraft:magenta_shulker_box","id":-614},{"name":"minecraft:magenta_stained_glass","id":-674},{"name":"minecraft:magenta_stained_glass_pane","id":-644},{"name":"minecraft:magenta_terracotta","id":-725},{"name":"minecraft:magenta_wool","id":-565},{"name":"minecraft:magma","id":213},{"name":"minecraft:magma_cream","id":457},{"name":"minecraft:magma_cube_spawn_egg","id":482},{"name":"minecraft:mangrove_boat","id":671},{"name":"minecraft:mangrove_button","id":-487},{"name":"minecraft:mangrove_chest_boat","id":680},{"name":"minecraft:mangrove_door","id":669},{"name":"minecraft:mangrove_double_slab","id":-499},{"name":"minecraft:mangrove_fence","id":-491},{"name":"minecraft:mangrove_fence_gate","id":-492},{"name":"minecraft:mangrove_hanging_sign","id":-508},{"name":"minecraft:mangrove_leaves","id":-472},{"name":"minecraft:mangrove_log","id":-484},{"name":"minecraft:mangrove_planks","id":-486},{"name":"minecraft:mangrove_pressure_plate","id":-490},{"name":"minecraft:mangrove_propagule","id":-474},{"name":"minecraft:mangrove_roots","id":-482},{"name":"minecraft:mangrove_sign","id":670},{"name":"minecraft:mangrove_slab","id":-489},{"name":"minecraft:mangrove_stairs","id":-488},{"name":"minecraft:mangrove_standing_sign","id":-494},{"name":"minecraft:mangrove_trapdoor","id":-496},{"name":"minecraft:mangrove_wall_sign","id":-495},{"name":"minecraft:mangrove_wood","id":-497},{"name":"minecraft:material_reducer","id":-986},{"name":"minecraft:medicine","id":635},{"name":"minecraft:medium_amethyst_bud","id":-331},{"name":"minecraft:melon_block","id":103},{"name":"minecraft:melon_seeds","id":315},{"name":"minecraft:melon_slice","id":294},{"name":"minecraft:melon_stem","id":105},{"name":"minecraft:milk_bucket","id":386},{"name":"minecraft:minecart","id":395},{"name":"minecraft:miner_pottery_sherd","id":706},{"name":"minecraft:mob_spawner","id":52},{"name":"minecraft:mojang_banner_pattern","id":617},{"name":"minecraft:monster_egg","id":769},{"name":"minecraft:mooshroom_spawn_egg","id":467},{"name":"minecraft:moss_block","id":-320},{"name":"minecraft:moss_carpet","id":-335},{"name":"minecraft:mossy_cobblestone","id":48},{"name":"minecraft:mossy_cobblestone_double_slab","id":-915},{"name":"minecraft:mossy_cobblestone_slab","id":-888},{"name":"minecraft:mossy_cobblestone_stairs","id":-179},{"name":"minecraft:mossy_cobblestone_wall","id":-971},{"name":"minecraft:mossy_stone_brick_double_slab","id":-168},{"name":"minecraft:mossy_stone_brick_slab","id":-166},{"name":"minecraft:mossy_stone_brick_stairs","id":-175},{"name":"minecraft:mossy_stone_brick_wall","id":-978},{"name":"minecraft:mossy_stone_bricks","id":-868},{"name":"minecraft:mourner_pottery_sherd","id":707},{"name":"minecraft:moving_block","id":250},{"name":"minecraft:mud","id":-473},{"name":"minecraft:mud_brick_double_slab","id":-479},{"name":"minecraft:mud_brick_slab","id":-478},{"name":"minecraft:mud_brick_stairs","id":-480},{"name":"minecraft:mud_brick_wall","id":-481},{"name":"minecraft:mud_bricks","id":-475},{"name":"minecraft:muddy_mangrove_roots","id":-483},{"name":"minecraft:mule_spawn_egg","id":494},{"name":"minecraft:mushroom_stem","id":-1008},{"name":"minecraft:mushroom_stew","id":282},{"name":"minecraft:music_disc_11","id":577},{"name":"minecraft:music_disc_13","id":567},{"name":"minecraft:music_disc_5","id":672},{"name":"minecraft:music_disc_blocks","id":569},{"name":"minecraft:music_disc_cat","id":568},{"name":"minecraft:music_disc_chirp","id":570},{"name":"minecraft:music_disc_creator","id":776},{"name":"minecraft:music_disc_creator_music_box","id":777},{"name":"minecraft:music_disc_far","id":571},{"name":"minecraft:music_disc_mall","id":572},{"name":"minecraft:music_disc_mellohi","id":573},{"name":"minecraft:music_disc_otherside","id":662},{"name":"minecraft:music_disc_pigstep","id":656},{"name":"minecraft:music_disc_precipice","id":778},{"name":"minecraft:music_disc_relic","id":735},{"name":"minecraft:music_disc_stal","id":574},{"name":"minecraft:music_disc_strad","id":575},{"name":"minecraft:music_disc_wait","id":578},{"name":"minecraft:music_disc_ward","id":576},{"name":"minecraft:mutton","id":583},{"name":"minecraft:mycelium","id":110},{"name":"minecraft:name_tag","id":581},{"name":"minecraft:nautilus_shell","id":603},{"name":"minecraft:nether_brick","id":112},{"name":"minecraft:nether_brick_double_slab","id":-883},{"name":"minecraft:nether_brick_fence","id":113},{"name":"minecraft:nether_brick_slab","id":-877},{"name":"minecraft:nether_brick_stairs","id":114},{"name":"minecraft:nether_brick_wall","id":-979},{"name":"minecraft:nether_gold_ore","id":-288},{"name":"minecraft:nether_sprouts","id":657},{"name":"minecraft:nether_star","id":551},{"name":"minecraft:nether_wart","id":316},{"name":"minecraft:nether_wart_block","id":214},{"name":"minecraft:netherbrick","id":556},{"name":"minecraft:netherite_axe","id":642},{"name":"minecraft:netherite_block","id":-270},{"name":"minecraft:netherite_boots","id":648},{"name":"minecraft:netherite_chestplate","id":646},{"name":"minecraft:netherite_helmet","id":645},{"name":"minecraft:netherite_hoe","id":643},{"name":"minecraft:netherite_ingot","id":644},{"name":"minecraft:netherite_leggings","id":647},{"name":"minecraft:netherite_pickaxe","id":641},{"name":"minecraft:netherite_scrap","id":649},{"name":"minecraft:netherite_shovel","id":640},{"name":"minecraft:netherite_sword","id":639},{"name":"minecraft:netherite_upgrade_smithing_template","id":716},{"name":"minecraft:netherrack","id":87},{"name":"minecraft:netherreactor","id":247},{"name":"minecraft:normal_stone_double_slab","id":-926},{"name":"minecraft:normal_stone_slab","id":-899},{"name":"minecraft:normal_stone_stairs","id":-180},{"name":"minecraft:noteblock","id":25},{"name":"minecraft:npc_spawn_egg","id":498},{"name":"minecraft:oak_boat","id":401},{"name":"minecraft:oak_chest_boat","id":674},{"name":"minecraft:oak_double_slab","id":157},{"name":"minecraft:oak_fence","id":85},{"name":"minecraft:oak_hanging_sign","id":-500},{"name":"minecraft:oak_leaves","id":18},{"name":"minecraft:oak_log","id":17},{"name":"minecraft:oak_planks","id":5},{"name":"minecraft:oak_sapling","id":6},{"name":"minecraft:oak_sign","id":383},{"name":"minecraft:oak_slab","id":158},{"name":"minecraft:oak_stairs","id":53},{"name":"minecraft:oak_wood","id":-212},{"name":"minecraft:observer","id":251},{"name":"minecraft:obsidian","id":49},{"name":"minecraft:ocelot_spawn_egg","id":478},{"name":"minecraft:ochre_froglight","id":-471},{"name":"minecraft:ominous_bottle","id":628},{"name":"minecraft:ominous_trial_key","id":275},{"name":"minecraft:orange_bundle","id":268},{"name":"minecraft:orange_candle","id":-414},{"name":"minecraft:orange_candle_cake","id":-431},{"name":"minecraft:orange_carpet","id":-597},{"name":"minecraft:orange_concrete","id":-628},{"name":"minecraft:orange_concrete_powder","id":-709},{"name":"minecraft:orange_dye","id":435},{"name":"minecraft:orange_glazed_terracotta","id":221},{"name":"minecraft:orange_shulker_box","id":-613},{"name":"minecraft:orange_stained_glass","id":-673},{"name":"minecraft:orange_stained_glass_pane","id":-643},{"name":"minecraft:orange_terracotta","id":-724},{"name":"minecraft:orange_tulip","id":-834},{"name":"minecraft:orange_wool","id":-557},{"name":"minecraft:oxeye_daisy","id":-837},{"name":"minecraft:oxidized_chiseled_copper","id":-763},{"name":"minecraft:oxidized_copper","id":-343},{"name":"minecraft:oxidized_copper_bulb","id":-779},{"name":"minecraft:oxidized_copper_door","id":-787},{"name":"minecraft:oxidized_copper_grate","id":-771},{"name":"minecraft:oxidized_copper_trapdoor","id":-795},{"name":"minecraft:oxidized_cut_copper","id":-350},{"name":"minecraft:oxidized_cut_copper_slab","id":-364},{"name":"minecraft:oxidized_cut_copper_stairs","id":-357},{"name":"minecraft:oxidized_double_cut_copper_slab","id":-371},{"name":"minecraft:packed_ice","id":174},{"name":"minecraft:packed_mud","id":-477},{"name":"minecraft:painting","id":382},{"name":"minecraft:panda_spawn_egg","id":517},{"name":"minecraft:paper","id":412},{"name":"minecraft:parrot_spawn_egg","id":506},{"name":"minecraft:pearlescent_froglight","id":-469},{"name":"minecraft:peony","id":-867},{"name":"minecraft:petrified_oak_double_slab","id":-903},{"name":"minecraft:petrified_oak_slab","id":-902},{"name":"minecraft:phantom_membrane","id":607},{"name":"minecraft:phantom_spawn_egg","id":514},{"name":"minecraft:pig_spawn_egg","id":464},{"name":"minecraft:piglin_banner_pattern","id":620},{"name":"minecraft:piglin_brute_spawn_egg","id":527},{"name":"minecraft:piglin_head","id":-970},{"name":"minecraft:piglin_spawn_egg","id":525},{"name":"minecraft:pillager_spawn_egg","id":519},{"name":"minecraft:pink_bundle","id":269},{"name":"minecraft:pink_candle","id":-419},{"name":"minecraft:pink_candle_cake","id":-436},{"name":"minecraft:pink_carpet","id":-602},{"name":"minecraft:pink_concrete","id":-633},{"name":"minecraft:pink_concrete_powder","id":-714},{"name":"minecraft:pink_dye","id":430},{"name":"minecraft:pink_glazed_terracotta","id":226},{"name":"minecraft:pink_petals","id":-549},{"name":"minecraft:pink_shulker_box","id":-618},{"name":"minecraft:pink_stained_glass","id":-678},{"name":"minecraft:pink_stained_glass_pane","id":-648},{"name":"minecraft:pink_terracotta","id":-729},{"name":"minecraft:pink_tulip","id":-836},{"name":"minecraft:pink_wool","id":-566},{"name":"minecraft:piston","id":33},{"name":"minecraft:piston_arm_collision","id":34},{"name":"minecraft:pitcher_crop","id":-574},{"name":"minecraft:pitcher_plant","id":-612},{"name":"minecraft:pitcher_pod","id":319},{"name":"minecraft:planks","id":765},{"name":"minecraft:player_head","id":-967},{"name":"minecraft:plenty_pottery_sherd","id":708},{"name":"minecraft:podzol","id":243},{"name":"minecraft:pointed_dripstone","id":-308},{"name":"minecraft:poisonous_potato","id":304},{"name":"minecraft:polar_bear_spawn_egg","id":500},{"name":"minecraft:polished_andesite","id":-595},{"name":"minecraft:polished_andesite_double_slab","id":-919},{"name":"minecraft:polished_andesite_slab","id":-892},{"name":"minecraft:polished_andesite_stairs","id":-174},{"name":"minecraft:polished_basalt","id":-235},{"name":"minecraft:polished_blackstone","id":-291},{"name":"minecraft:polished_blackstone_brick_double_slab","id":-285},{"name":"minecraft:polished_blackstone_brick_slab","id":-284},{"name":"minecraft:polished_blackstone_brick_stairs","id":-275},{"name":"minecraft:polished_blackstone_brick_wall","id":-278},{"name":"minecraft:polished_blackstone_bricks","id":-274},{"name":"minecraft:polished_blackstone_button","id":-296},{"name":"minecraft:polished_blackstone_double_slab","id":-294},{"name":"minecraft:polished_blackstone_pressure_plate","id":-295},{"name":"minecraft:polished_blackstone_slab","id":-293},{"name":"minecraft:polished_blackstone_stairs","id":-292},{"name":"minecraft:polished_blackstone_wall","id":-297},{"name":"minecraft:polished_deepslate","id":-383},{"name":"minecraft:polished_deepslate_double_slab","id":-397},{"name":"minecraft:polished_deepslate_slab","id":-384},{"name":"minecraft:polished_deepslate_stairs","id":-385},{"name":"minecraft:polished_deepslate_wall","id":-386},{"name":"minecraft:polished_diorite","id":-593},{"name":"minecraft:polished_diorite_double_slab","id":-922},{"name":"minecraft:polished_diorite_slab","id":-895},{"name":"minecraft:polished_diorite_stairs","id":-173},{"name":"minecraft:polished_granite","id":-591},{"name":"minecraft:polished_granite_double_slab","id":-924},{"name":"minecraft:polished_granite_slab","id":-897},{"name":"minecraft:polished_granite_stairs","id":-172},{"name":"minecraft:polished_tuff","id":-748},{"name":"minecraft:polished_tuff_double_slab","id":-750},{"name":"minecraft:polished_tuff_slab","id":-749},{"name":"minecraft:polished_tuff_stairs","id":-751},{"name":"minecraft:polished_tuff_wall","id":-752},{"name":"minecraft:popped_chorus_fruit","id":592},{"name":"minecraft:poppy","id":38},{"name":"minecraft:porkchop","id":284},{"name":"minecraft:portal","id":90},{"name":"minecraft:potato","id":302},{"name":"minecraft:potatoes","id":142},{"name":"minecraft:potion","id":453},{"name":"minecraft:powder_snow","id":-306},{"name":"minecraft:powder_snow_bucket","id":393},{"name":"minecraft:powered_comparator","id":150},{"name":"minecraft:powered_repeater","id":94},{"name":"minecraft:prismarine","id":168},{"name":"minecraft:prismarine_brick_double_slab","id":-914},{"name":"minecraft:prismarine_brick_slab","id":-887},{"name":"minecraft:prismarine_bricks","id":-948},{"name":"minecraft:prismarine_bricks_stairs","id":-4},{"name":"minecraft:prismarine_crystals","id":582},{"name":"minecraft:prismarine_double_slab","id":-912},{"name":"minecraft:prismarine_shard","id":598},{"name":"minecraft:prismarine_slab","id":-885},{"name":"minecraft:prismarine_stairs","id":-2},{"name":"minecraft:prismarine_wall","id":-981},{"name":"minecraft:prize_pottery_sherd","id":709},{"name":"minecraft:pufferfish","id":289},{"name":"minecraft:pufferfish_bucket","id":392},{"name":"minecraft:pufferfish_spawn_egg","id":509},{"name":"minecraft:pumpkin","id":86},{"name":"minecraft:pumpkin_pie","id":306},{"name":"minecraft:pumpkin_seeds","id":314},{"name":"minecraft:pumpkin_stem","id":104},{"name":"minecraft:purple_bundle","id":270},{"name":"minecraft:purple_candle","id":-423},{"name":"minecraft:purple_candle_cake","id":-440},{"name":"minecraft:purple_carpet","id":-606},{"name":"minecraft:purple_concrete","id":-637},{"name":"minecraft:purple_concrete_powder","id":-718},{"name":"minecraft:purple_dye","id":426},{"name":"minecraft:purple_glazed_terracotta","id":219},{"name":"minecraft:purple_shulker_box","id":-622},{"name":"minecraft:purple_stained_glass","id":-682},{"name":"minecraft:purple_stained_glass_pane","id":-652},{"name":"minecraft:purple_terracotta","id":-733},{"name":"minecraft:purple_wool","id":-564},{"name":"minecraft:purpur_block","id":201},{"name":"minecraft:purpur_double_slab","id":-911},{"name":"minecraft:purpur_pillar","id":-951},{"name":"minecraft:purpur_slab","id":-884},{"name":"minecraft:purpur_stairs","id":203},{"name":"minecraft:quartz","id":557},{"name":"minecraft:quartz_block","id":155},{"name":"minecraft:quartz_bricks","id":-304},{"name":"minecraft:quartz_double_slab","id":-882},{"name":"minecraft:quartz_ore","id":153},{"name":"minecraft:quartz_pillar","id":-954},{"name":"minecraft:quartz_slab","id":-876},{"name":"minecraft:quartz_stairs","id":156},{"name":"minecraft:rabbit","id":310},{"name":"minecraft:rabbit_foot","id":561},{"name":"minecraft:rabbit_hide","id":562},{"name":"minecraft:rabbit_spawn_egg","id":486},{"name":"minecraft:rabbit_stew","id":312},{"name":"minecraft:rail","id":66},{"name":"minecraft:raiser_armor_trim_smithing_template","id":730},{"name":"minecraft:rapid_fertilizer","id":633},{"name":"minecraft:ravager_spawn_egg","id":521},{"name":"minecraft:raw_copper","id":541},{"name":"minecraft:raw_copper_block","id":-452},{"name":"minecraft:raw_gold","id":540},{"name":"minecraft:raw_gold_block","id":-453},{"name":"minecraft:raw_iron","id":539},{"name":"minecraft:raw_iron_block","id":-451},{"name":"minecraft:recovery_compass","id":682},{"name":"minecraft:red_bundle","id":271},{"name":"minecraft:red_candle","id":-427},{"name":"minecraft:red_candle_cake","id":-444},{"name":"minecraft:red_carpet","id":-610},{"name":"minecraft:red_concrete","id":-641},{"name":"minecraft:red_concrete_powder","id":-722},{"name":"minecraft:red_dye","id":422},{"name":"minecraft:red_flower","id":763},{"name":"minecraft:red_glazed_terracotta","id":234},{"name":"minecraft:red_mushroom","id":40},{"name":"minecraft:red_mushroom_block","id":100},{"name":"minecraft:red_nether_brick","id":215},{"name":"minecraft:red_nether_brick_double_slab","id":-917},{"name":"minecraft:red_nether_brick_slab","id":-890},{"name":"minecraft:red_nether_brick_stairs","id":-184},{"name":"minecraft:red_nether_brick_wall","id":-983},{"name":"minecraft:red_sand","id":-949},{"name":"minecraft:red_sandstone","id":179},{"name":"minecraft:red_sandstone_double_slab","id":181},{"name":"minecraft:red_sandstone_slab","id":182},{"name":"minecraft:red_sandstone_stairs","id":180},{"name":"minecraft:red_sandstone_wall","id":-982},{"name":"minecraft:red_shulker_box","id":-626},{"name":"minecraft:red_stained_glass","id":-686},{"name":"minecraft:red_stained_glass_pane","id":-656},{"name":"minecraft:red_terracotta","id":-737},{"name":"minecraft:red_tulip","id":-833},{"name":"minecraft:red_wool","id":-556},{"name":"minecraft:redstone","id":398},{"name":"minecraft:redstone_block","id":152},{"name":"minecraft:redstone_lamp","id":123},{"name":"minecraft:redstone_ore","id":73},{"name":"minecraft:redstone_torch","id":76},{"name":"minecraft:redstone_wire","id":55},{"name":"minecraft:reinforced_deepslate","id":-466},{"name":"minecraft:repeater","id":445},{"name":"minecraft:repeating_command_block","id":188},{"name":"minecraft:reserved6","id":255},{"name":"minecraft:respawn_anchor","id":-272},{"name":"minecraft:rib_armor_trim_smithing_template","id":726},{"name":"minecraft:rose_bush","id":-866},{"name":"minecraft:rotten_flesh","id":299},{"name":"minecraft:saddle","id":396},{"name":"minecraft:salmon","id":287},{"name":"minecraft:salmon_bucket","id":390},{"name":"minecraft:salmon_spawn_egg","id":510},{"name":"minecraft:sand","id":12},{"name":"minecraft:sandstone","id":24},{"name":"minecraft:sandstone_double_slab","id":-878},{"name":"minecraft:sandstone_slab","id":-872},{"name":"minecraft:sandstone_stairs","id":128},{"name":"minecraft:sandstone_wall","id":-975},{"name":"minecraft:sapling","id":759},{"name":"minecraft:scaffolding","id":-165},{"name":"minecraft:scrape_pottery_sherd","id":710},{"name":"minecraft:sculk","id":-458},{"name":"minecraft:sculk_catalyst","id":-460},{"name":"minecraft:sculk_sensor","id":-307},{"name":"minecraft:sculk_shrieker","id":-461},{"name":"minecraft:sculk_vein","id":-459},{"name":"minecraft:sea_lantern","id":169},{"name":"minecraft:sea_pickle","id":-156},{"name":"minecraft:seagrass","id":-130},{"name":"minecraft:sentry_armor_trim_smithing_template","id":717},{"name":"minecraft:shaper_armor_trim_smithing_template","id":731},{"name":"minecraft:sheaf_pottery_sherd","id":711},{"name":"minecraft:shears","id":447},{"name":"minecraft:sheep_spawn_egg","id":465},{"name":"minecraft:shelter_pottery_sherd","id":712},{"name":"minecraft:shield","id":380},{"name":"minecraft:short_grass","id":31},{"name":"minecraft:shroomlight","id":-230},{"name":"minecraft:shulker_box","id":774},{"name":"minecraft:shulker_shell","id":599},{"name":"minecraft:shulker_spawn_egg","id":497},{"name":"minecraft:silence_armor_trim_smithing_template","id":728},{"name":"minecraft:silver_glazed_terracotta","id":228},{"name":"minecraft:silverfish_spawn_egg","id":470},{"name":"minecraft:skeleton_horse_spawn_egg","id":495},{"name":"minecraft:skeleton_skull","id":144},{"name":"minecraft:skeleton_spawn_egg","id":471},{"name":"minecraft:skull","id":736},{"name":"minecraft:skull_banner_pattern","id":616},{"name":"minecraft:skull_pottery_sherd","id":713},{"name":"minecraft:slime","id":165},{"name":"minecraft:slime_ball","id":414},{"name":"minecraft:slime_spawn_egg","id":472},{"name":"minecraft:small_amethyst_bud","id":-332},{"name":"minecraft:small_dripleaf_block","id":-336},{"name":"minecraft:smithing_table","id":-202},{"name":"minecraft:smoker","id":-198},{"name":"minecraft:smooth_basalt","id":-377},{"name":"minecraft:smooth_quartz","id":-955},{"name":"minecraft:smooth_quartz_double_slab","id":-925},{"name":"minecraft:smooth_quartz_slab","id":-898},{"name":"minecraft:smooth_quartz_stairs","id":-185},{"name":"minecraft:smooth_red_sandstone","id":-958},{"name":"minecraft:smooth_red_sandstone_double_slab","id":-918},{"name":"minecraft:smooth_red_sandstone_slab","id":-891},{"name":"minecraft:smooth_red_sandstone_stairs","id":-176},{"name":"minecraft:smooth_sandstone","id":-946},{"name":"minecraft:smooth_sandstone_double_slab","id":-916},{"name":"minecraft:smooth_sandstone_slab","id":-889},{"name":"minecraft:smooth_sandstone_stairs","id":-177},{"name":"minecraft:smooth_stone","id":-183},{"name":"minecraft:smooth_stone_double_slab","id":43},{"name":"minecraft:smooth_stone_slab","id":44},{"name":"minecraft:sniffer_egg","id":-596},{"name":"minecraft:sniffer_spawn_egg","id":528},{"name":"minecraft:snort_pottery_sherd","id":714},{"name":"minecraft:snout_armor_trim_smithing_template","id":725},{"name":"minecraft:snow","id":80},{"name":"minecraft:snow_golem_spawn_egg","id":534},{"name":"minecraft:snow_layer","id":78},{"name":"minecraft:snowball","id":399},{"name":"minecraft:soul_campfire","id":658},{"name":"minecraft:soul_fire","id":-237},{"name":"minecraft:soul_lantern","id":-269},{"name":"minecraft:soul_sand","id":88},{"name":"minecraft:soul_soil","id":-236},{"name":"minecraft:soul_torch","id":-268},{"name":"minecraft:sparkler","id":636},{"name":"minecraft:spawn_egg","id":788},{"name":"minecraft:spider_eye","id":300},{"name":"minecraft:spider_spawn_egg","id":473},{"name":"minecraft:spire_armor_trim_smithing_template","id":727},{"name":"minecraft:splash_potion","id":594},{"name":"minecraft:sponge","id":19},{"name":"minecraft:spore_blossom","id":-321},{"name":"minecraft:spruce_boat","id":404},{"name":"minecraft:spruce_button","id":-144},{"name":"minecraft:spruce_chest_boat","id":677},{"name":"minecraft:spruce_door","id":586},{"name":"minecraft:spruce_double_slab","id":-809},{"name":"minecraft:spruce_fence","id":-579},{"name":"minecraft:spruce_fence_gate","id":183},{"name":"minecraft:spruce_hanging_sign","id":-501},{"name":"minecraft:spruce_leaves","id":-800},{"name":"minecraft:spruce_log","id":-569},{"name":"minecraft:spruce_planks","id":-739},{"name":"minecraft:spruce_pressure_plate","id":-154},{"name":"minecraft:spruce_sapling","id":-825},{"name":"minecraft:spruce_sign","id":609},{"name":"minecraft:spruce_slab","id":-804},{"name":"minecraft:spruce_stairs","id":134},{"name":"minecraft:spruce_standing_sign","id":-181},{"name":"minecraft:spruce_trapdoor","id":-149},{"name":"minecraft:spruce_wall_sign","id":-182},{"name":"minecraft:spruce_wood","id":-814},{"name":"minecraft:spyglass","id":661},{"name":"minecraft:squid_spawn_egg","id":477},{"name":"minecraft:stained_glass","id":772},{"name":"minecraft:stained_glass_pane","id":773},{"name":"minecraft:stained_hardened_clay","id":737},{"name":"minecraft:standing_banner","id":176},{"name":"minecraft:standing_sign","id":63},{"name":"minecraft:stick","id":345},{"name":"minecraft:sticky_piston","id":29},{"name":"minecraft:sticky_piston_arm_collision","id":-217},{"name":"minecraft:stone","id":1},{"name":"minecraft:stone_axe","id":339},{"name":"minecraft:stone_block_slab","id":749},{"name":"minecraft:stone_block_slab2","id":750},{"name":"minecraft:stone_block_slab3","id":751},{"name":"minecraft:stone_block_slab4","id":752},{"name":"minecraft:stone_brick_double_slab","id":-881},{"name":"minecraft:stone_brick_slab","id":-875},{"name":"minecraft:stone_brick_stairs","id":109},{"name":"minecraft:stone_brick_wall","id":-977},{"name":"minecraft:stone_bricks","id":98},{"name":"minecraft:stone_button","id":77},{"name":"minecraft:stone_hoe","id":355},{"name":"minecraft:stone_pickaxe","id":338},{"name":"minecraft:stone_pressure_plate","id":70},{"name":"minecraft:stone_shovel","id":337},{"name":"minecraft:stone_stairs","id":67},{"name":"minecraft:stone_sword","id":336},{"name":"minecraft:stonebrick","id":747},{"name":"minecraft:stonecutter","id":245},{"name":"minecraft:stonecutter_block","id":-197},{"name":"minecraft:stray_spawn_egg","id":489},{"name":"minecraft:strider_spawn_egg","id":523},{"name":"minecraft:string","id":351},{"name":"minecraft:stripped_acacia_log","id":-8},{"name":"minecraft:stripped_acacia_wood","id":-823},{"name":"minecraft:stripped_bamboo_block","id":-528},{"name":"minecraft:stripped_birch_log","id":-6},{"name":"minecraft:stripped_birch_wood","id":-821},{"name":"minecraft:stripped_cherry_log","id":-535},{"name":"minecraft:stripped_cherry_wood","id":-545},{"name":"minecraft:stripped_crimson_hyphae","id":-300},{"name":"minecraft:stripped_crimson_stem","id":-240},{"name":"minecraft:stripped_dark_oak_log","id":-9},{"name":"minecraft:stripped_dark_oak_wood","id":-824},{"name":"minecraft:stripped_jungle_log","id":-7},{"name":"minecraft:stripped_jungle_wood","id":-822},{"name":"minecraft:stripped_mangrove_log","id":-485},{"name":"minecraft:stripped_mangrove_wood","id":-498},{"name":"minecraft:stripped_oak_log","id":-10},{"name":"minecraft:stripped_oak_wood","id":-819},{"name":"minecraft:stripped_spruce_log","id":-5},{"name":"minecraft:stripped_spruce_wood","id":-820},{"name":"minecraft:stripped_warped_hyphae","id":-301},{"name":"minecraft:stripped_warped_stem","id":-241},{"name":"minecraft:structure_block","id":252},{"name":"minecraft:structure_void","id":217},{"name":"minecraft:sugar","id":442},{"name":"minecraft:sugar_cane","id":411},{"name":"minecraft:sunflower","id":175},{"name":"minecraft:suspicious_gravel","id":-573},{"name":"minecraft:suspicious_sand","id":-529},{"name":"minecraft:suspicious_stew","id":625},{"name":"minecraft:sweet_berries","id":309},{"name":"minecraft:sweet_berry_bush","id":-207},{"name":"minecraft:tadpole_bucket","id":666},{"name":"minecraft:tadpole_spawn_egg","id":665},{"name":"minecraft:tall_grass","id":-864},{"name":"minecraft:tallgrass","id":767},{"name":"minecraft:target","id":-239},{"name":"minecraft:tide_armor_trim_smithing_template","id":724},{"name":"minecraft:tinted_glass","id":-334},{"name":"minecraft:tnt","id":46},{"name":"minecraft:tnt_minecart","id":558},{"name":"minecraft:torch","id":50},{"name":"minecraft:torchflower","id":-568},{"name":"minecraft:torchflower_crop","id":-567},{"name":"minecraft:torchflower_seeds","id":318},{"name":"minecraft:totem_of_undying","id":601},{"name":"minecraft:trader_llama_spawn_egg","id":684},{"name":"minecraft:trapdoor","id":96},{"name":"minecraft:trapped_chest","id":146},{"name":"minecraft:trial_key","id":276},{"name":"minecraft:trial_spawner","id":-315},{"name":"minecraft:trident","id":579},{"name":"minecraft:trip_wire","id":132},{"name":"minecraft:tripwire_hook","id":131},{"name":"minecraft:tropical_fish","id":288},{"name":"minecraft:tropical_fish_bucket","id":391},{"name":"minecraft:tropical_fish_spawn_egg","id":507},{"name":"minecraft:tube_coral","id":-131},{"name":"minecraft:tube_coral_block","id":-132},{"name":"minecraft:tube_coral_fan","id":-133},{"name":"minecraft:tube_coral_wall_fan","id":-135},{"name":"minecraft:tuff","id":-333},{"name":"minecraft:tuff_brick_double_slab","id":-756},{"name":"minecraft:tuff_brick_slab","id":-755},{"name":"minecraft:tuff_brick_stairs","id":-757},{"name":"minecraft:tuff_brick_wall","id":-758},{"name":"minecraft:tuff_bricks","id":-754},{"name":"minecraft:tuff_double_slab","id":-745},{"name":"minecraft:tuff_slab","id":-744},{"name":"minecraft:tuff_stairs","id":-746},{"name":"minecraft:tuff_wall","id":-747},{"name":"minecraft:turtle_egg","id":-159},{"name":"minecraft:turtle_helmet","id":606},{"name":"minecraft:turtle_scute","id":605},{"name":"minecraft:turtle_spawn_egg","id":513},{"name":"minecraft:twisting_vines","id":-287},{"name":"minecraft:underwater_tnt","id":-985},{"name":"minecraft:underwater_torch","id":239},{"name":"minecraft:undyed_shulker_box","id":205},{"name":"minecraft:unknown","id":-305},{"name":"minecraft:unlit_redstone_torch","id":75},{"name":"minecraft:unpowered_comparator","id":149},{"name":"minecraft:unpowered_repeater","id":93},{"name":"minecraft:vault","id":-314},{"name":"minecraft:verdant_froglight","id":-470},{"name":"minecraft:vex_armor_trim_smithing_template","id":723},{"name":"minecraft:vex_spawn_egg","id":504},{"name":"minecraft:villager_spawn_egg","id":476},{"name":"minecraft:vindicator_spawn_egg","id":502},{"name":"minecraft:vine","id":106},{"name":"minecraft:wall_banner","id":177},{"name":"minecraft:wall_sign","id":68},{"name":"minecraft:wandering_trader_spawn_egg","id":520},{"name":"minecraft:ward_armor_trim_smithing_template","id":721},{"name":"minecraft:warden_spawn_egg","id":668},{"name":"minecraft:warped_button","id":-261},{"name":"minecraft:warped_door","id":653},{"name":"minecraft:warped_double_slab","id":-267},{"name":"minecraft:warped_fence","id":-257},{"name":"minecraft:warped_fence_gate","id":-259},{"name":"minecraft:warped_fungus","id":-229},{"name":"minecraft:warped_fungus_on_a_stick","id":654},{"name":"minecraft:warped_hanging_sign","id":-507},{"name":"minecraft:warped_hyphae","id":-298},{"name":"minecraft:warped_nylium","id":-233},{"name":"minecraft:warped_planks","id":-243},{"name":"minecraft:warped_pressure_plate","id":-263},{"name":"minecraft:warped_roots","id":-224},{"name":"minecraft:warped_sign","id":651},{"name":"minecraft:warped_slab","id":-265},{"name":"minecraft:warped_stairs","id":-255},{"name":"minecraft:warped_standing_sign","id":-251},{"name":"minecraft:warped_stem","id":-226},{"name":"minecraft:warped_trapdoor","id":-247},{"name":"minecraft:warped_wall_sign","id":-253},{"name":"minecraft:warped_wart_block","id":-227},{"name":"minecraft:water","id":9},{"name":"minecraft:water_bucket","id":387},{"name":"minecraft:waterlily","id":111},{"name":"minecraft:waxed_chiseled_copper","id":-764},{"name":"minecraft:waxed_copper","id":-344},{"name":"minecraft:waxed_copper_bulb","id":-780},{"name":"minecraft:waxed_copper_door","id":-788},{"name":"minecraft:waxed_copper_grate","id":-772},{"name":"minecraft:waxed_copper_trapdoor","id":-796},{"name":"minecraft:waxed_cut_copper","id":-351},{"name":"minecraft:waxed_cut_copper_slab","id":-365},{"name":"minecraft:waxed_cut_copper_stairs","id":-358},{"name":"minecraft:waxed_double_cut_copper_slab","id":-372},{"name":"minecraft:waxed_exposed_chiseled_copper","id":-765},{"name":"minecraft:waxed_exposed_copper","id":-345},{"name":"minecraft:waxed_exposed_copper_bulb","id":-781},{"name":"minecraft:waxed_exposed_copper_door","id":-789},{"name":"minecraft:waxed_exposed_copper_grate","id":-773},{"name":"minecraft:waxed_exposed_copper_trapdoor","id":-797},{"name":"minecraft:waxed_exposed_cut_copper","id":-352},{"name":"minecraft:waxed_exposed_cut_copper_slab","id":-366},{"name":"minecraft:waxed_exposed_cut_copper_stairs","id":-359},{"name":"minecraft:waxed_exposed_double_cut_copper_slab","id":-373},{"name":"minecraft:waxed_oxidized_chiseled_copper","id":-766},{"name":"minecraft:waxed_oxidized_copper","id":-446},{"name":"minecraft:waxed_oxidized_copper_bulb","id":-783},{"name":"minecraft:waxed_oxidized_copper_door","id":-791},{"name":"minecraft:waxed_oxidized_copper_grate","id":-775},{"name":"minecraft:waxed_oxidized_copper_trapdoor","id":-799},{"name":"minecraft:waxed_oxidized_cut_copper","id":-447},{"name":"minecraft:waxed_oxidized_cut_copper_slab","id":-449},{"name":"minecraft:waxed_oxidized_cut_copper_stairs","id":-448},{"name":"minecraft:waxed_oxidized_double_cut_copper_slab","id":-450},{"name":"minecraft:waxed_weathered_chiseled_copper","id":-767},{"name":"minecraft:waxed_weathered_copper","id":-346},{"name":"minecraft:waxed_weathered_copper_bulb","id":-782},{"name":"minecraft:waxed_weathered_copper_door","id":-790},{"name":"minecraft:waxed_weathered_copper_grate","id":-774},{"name":"minecraft:waxed_weathered_copper_trapdoor","id":-798},{"name":"minecraft:waxed_weathered_cut_copper","id":-353},{"name":"minecraft:waxed_weathered_cut_copper_slab","id":-367},{"name":"minecraft:waxed_weathered_cut_copper_stairs","id":-360},{"name":"minecraft:waxed_weathered_double_cut_copper_slab","id":-374},{"name":"minecraft:wayfinder_armor_trim_smithing_template","id":729},{"name":"minecraft:weathered_chiseled_copper","id":-762},{"name":"minecraft:weathered_copper","id":-342},{"name":"minecraft:weathered_copper_bulb","id":-778},{"name":"minecraft:weathered_copper_door","id":-786},{"name":"minecraft:weathered_copper_grate","id":-770},{"name":"minecraft:weathered_copper_trapdoor","id":-794},{"name":"minecraft:weathered_cut_copper","id":-349},{"name":"minecraft:weathered_cut_copper_slab","id":-363},{"name":"minecraft:weathered_cut_copper_stairs","id":-356},{"name":"minecraft:weathered_double_cut_copper_slab","id":-370},{"name":"minecraft:web","id":30},{"name":"minecraft:weeping_vines","id":-231},{"name":"minecraft:wet_sponge","id":-984},{"name":"minecraft:wheat","id":359},{"name":"minecraft:wheat_seeds","id":313},{"name":"minecraft:white_bundle","id":272},{"name":"minecraft:white_candle","id":-413},{"name":"minecraft:white_candle_cake","id":-430},{"name":"minecraft:white_carpet","id":171},{"name":"minecraft:white_concrete","id":236},{"name":"minecraft:white_concrete_powder","id":237},{"name":"minecraft:white_dye","id":436},{"name":"minecraft:white_glazed_terracotta","id":220},{"name":"minecraft:white_shulker_box","id":218},{"name":"minecraft:white_stained_glass","id":241},{"name":"minecraft:white_stained_glass_pane","id":160},{"name":"minecraft:white_terracotta","id":159},{"name":"minecraft:white_tulip","id":-835},{"name":"minecraft:white_wool","id":35},{"name":"minecraft:wild_armor_trim_smithing_template","id":720},{"name":"minecraft:wind_charge","id":277},{"name":"minecraft:witch_spawn_egg","id":479},{"name":"minecraft:wither_rose","id":-216},{"name":"minecraft:wither_skeleton_skull","id":-965},{"name":"minecraft:wither_skeleton_spawn_egg","id":492},{"name":"minecraft:wither_spawn_egg","id":536},{"name":"minecraft:wolf_armor","id":740},{"name":"minecraft:wolf_spawn_egg","id":466},{"name":"minecraft:wood","id":775},{"name":"minecraft:wooden_axe","id":335},{"name":"minecraft:wooden_button","id":143},{"name":"minecraft:wooden_door","id":384},{"name":"minecraft:wooden_hoe","id":354},{"name":"minecraft:wooden_pickaxe","id":334},{"name":"minecraft:wooden_pressure_plate","id":72},{"name":"minecraft:wooden_shovel","id":333},{"name":"minecraft:wooden_slab","id":762},{"name":"minecraft:wooden_sword","id":332},{"name":"minecraft:wool","id":743},{"name":"minecraft:writable_book","id":544},{"name":"minecraft:written_book","id":545},{"name":"minecraft:yellow_bundle","id":273},{"name":"minecraft:yellow_candle","id":-417},{"name":"minecraft:yellow_candle_cake","id":-434},{"name":"minecraft:yellow_carpet","id":-600},{"name":"minecraft:yellow_concrete","id":-631},{"name":"minecraft:yellow_concrete_powder","id":-712},{"name":"minecraft:yellow_dye","id":432},{"name":"minecraft:yellow_glazed_terracotta","id":224},{"name":"minecraft:yellow_shulker_box","id":-616},{"name":"minecraft:yellow_stained_glass","id":-676},{"name":"minecraft:yellow_stained_glass_pane","id":-646},{"name":"minecraft:yellow_terracotta","id":-727},{"name":"minecraft:yellow_wool","id":-558},{"name":"minecraft:zoglin_spawn_egg","id":526},{"name":"minecraft:zombie_head","id":-966},{"name":"minecraft:zombie_horse_spawn_egg","id":496},{"name":"minecraft:zombie_pigman_spawn_egg","id":475},{"name":"minecraft:zombie_spawn_egg","id":474},{"name":"minecraft:zombie_villager_spawn_egg","id":505}] \ No newline at end of file